diff --git a/packages/survey-creator-core/src/components/question.scss b/packages/survey-creator-core/src/components/question.scss index e645daf283..3885d9a391 100644 --- a/packages/survey-creator-core/src/components/question.scss +++ b/packages/survey-creator-core/src/components/question.scss @@ -788,7 +788,15 @@ svc-question, &>sv-ng-file-question { display: none; } - + .svc-question__drop-indicator { + display: block; + } + .svc-question__drag-area { + display: flex; + } + .svc-content-wrapper { + display: flex; + } .sd-element { &>*:not(.sd-element__header) { display: none; diff --git a/packages/survey-creator-core/src/creator-base.ts b/packages/survey-creator-core/src/creator-base.ts index 95a6ccb60e..9e13df2256 100644 --- a/packages/survey-creator-core/src/creator-base.ts +++ b/packages/survey-creator-core/src/creator-base.ts @@ -733,6 +733,8 @@ export class SurveyCreatorModel extends Base */ public onDragDropAllow: EventBase = this.addCreatorEvent(); + public onDragOverLocationCalculating: EventBase = this.addCreatorEvent(); + /** * An event that allows you to create a custom message panel. * @@ -1878,6 +1880,7 @@ export class SurveyCreatorModel extends Base settings.dragDrop.restrictDragQuestionBetweenPages; this.dragDropSurveyElements = new DragDropSurveyElements(null, this); this.dragDropSurveyElements.onGetMaxNestedPanels = (): number => { return this.maxNestedPanels; }; + this.dragDropSurveyElements.onDragOverLocationCalculating = (options) => { this.onDragOverLocationCalculating.fire(this, options); }; let isDraggedFromToolbox = false; this.dragDropSurveyElements.onDragStart.add((sender, options) => { isDraggedFromToolbox = !sender.draggedElement.parent && !sender.draggedElement.isPage; diff --git a/packages/survey-creator-core/src/entries/index.ts b/packages/survey-creator-core/src/entries/index.ts index 971897103b..761b296fed 100644 --- a/packages/survey-creator-core/src/entries/index.ts +++ b/packages/survey-creator-core/src/entries/index.ts @@ -101,6 +101,7 @@ export * from "../presets/presets"; export * from "../presets/presets-properties"; export * from "../presets/presets-tabs"; export * from "../presets/presets-toolbox"; +export * from "../survey-elements"; require("../components/property-panel/property-panel-item.scss"); require("../components/property-panel/property-panel.scss"); diff --git a/packages/survey-creator-core/src/survey-elements.ts b/packages/survey-creator-core/src/survey-elements.ts index 4035220e5c..171df5f2c5 100644 --- a/packages/survey-creator-core/src/survey-elements.ts +++ b/packages/survey-creator-core/src/survey-elements.ts @@ -11,19 +11,26 @@ export function calculateIsSide(dropTargetNode: HTMLElement, clientX: number) { const rect = dropTargetNode.getBoundingClientRect(); return clientX - rect.left <= DragDropSurveyElements.edgeHeight || rect.right - clientX <= DragDropSurveyElements.edgeHeight; } -export function calculateDragOverLocation(clientX: number, clientY: number, dropTargetNode: HTMLElement, dragBeforeOrAfterOnly = false): DragTypeOverMeEnum { - const rect = dropTargetNode.getBoundingClientRect(); + +export function calculateDragOverLocation(clientX: number, clientY: number, rect: DOMRectInit, direction: "top-bottom" | "left-right" = null): DragTypeOverMeEnum { const tg = rect.height / rect.width; const dx = clientX - rect.x; const dy = clientY - rect.y; - if (dragBeforeOrAfterOnly) { + if (direction == "top-bottom") { if (dy >= rect.height / 2) { return DragTypeOverMeEnum.Bottom; } else { return DragTypeOverMeEnum.Top; } } + if (direction == "left-right") { + if (dx >= rect.width / 2) { + return DragTypeOverMeEnum.Right; + } else { + return DragTypeOverMeEnum.Left; + } + } if (dy >= tg * dx) { if (dy >= - tg * dx + rect.height) { @@ -68,6 +75,7 @@ export class DragDropSurveyElements extends DragDropCore { } protected isDraggedElementSelected: boolean = false; public onGetMaxNestedPanels: () => number; + public onDragOverLocationCalculating: (options: any) => void; public get maxNestedPanels(): number { return this.onGetMaxNestedPanels ? this.onGetMaxNestedPanels() : -1; } // private isRight: boolean; @@ -356,12 +364,29 @@ export class DragDropSurveyElements extends DragDropCore { const dropTarget = this.getDropTargetByNode(dropTargetNode, event); if (!!oldInsideContainer != !!this.insideContainer) dropTarget.dragTypeOverMe = null; - let dragOverLocation = calculateDragOverLocation(event.clientX, event.clientY, dropTargetNode, !settings.dragDrop.allowDragToTheSameLine || (!!this.draggedElement && this.draggedElement.isPage)); + const dropTargetRect = dropTargetNode.getBoundingClientRect(); + const calcDirection = !settings.dragDrop.allowDragToTheSameLine || (!!this.draggedElement && this.draggedElement.isPage) ? "top-bottom" : null; + let dragOverLocation = calculateDragOverLocation(event.clientX, event.clientY, dropTargetRect, calcDirection); if (dropTarget && ((dropTarget.isPanel || dropTarget.isPage) && dropTarget.elements.length === 0 || isPanelDynamic(dropTarget) && dropTarget.template.elements.length == 0)) { if (dropTarget.isPage || this.insideContainer) { dragOverLocation = DragTypeOverMeEnum.InsideEmptyPanel; } } + const options = { + survey: this.survey, + draggedSurveyElement: this.draggedElement, + dragOverSurveyElement: dropTarget, + clientX: event.clientX, + clientY: event.clientY, + dragOverRect: dropTargetRect, + insideContainer: this.insideContainer, + dragOverLocation + }; + if (this.onDragOverLocationCalculating) { + this.onDragOverLocationCalculating(options); + dragOverLocation = options.dragOverLocation; + this.insideContainer = options.insideContainer; + } const isDropTargetValid = this.isDropTargetValid( dropTarget, dropTargetNode, diff --git a/packages/survey-creator-core/tests/components.tests.ts b/packages/survey-creator-core/tests/components.tests.ts index f39791dbc3..ca8669922b 100644 --- a/packages/survey-creator-core/tests/components.tests.ts +++ b/packages/survey-creator-core/tests/components.tests.ts @@ -984,15 +984,15 @@ test("QuestionRatingAdornerViewModel allowAdd allowRemove on property readonly", }); test("calculateDragOverLocation", () => { - let location = calculateDragOverLocation(150, 120, { getBoundingClientRect: () => ({ x: 100, y: 100, width: 300, height: 100 }) }); + let location = calculateDragOverLocation(150, 120, { x: 100, y: 100, width: 300, height: 100 }); expect(location).toBe(DragTypeOverMeEnum.Left); // creatorSettings.dragDrop.allowDragToTheSameLine = false; - location = calculateDragOverLocation(150, 120, { getBoundingClientRect: () => ({ x: 100, y: 100, width: 300, height: 100 }) }, true); + location = calculateDragOverLocation(150, 120, { x: 100, y: 100, width: 300, height: 100 }, "top-bottom"); expect(location).toBe(DragTypeOverMeEnum.Top); - location = calculateDragOverLocation(350, 170, { getBoundingClientRect: () => ({ x: 100, y: 100, width: 300, height: 100 }) }, true); + location = calculateDragOverLocation(350, 170, { x: 100, y: 100, width: 300, height: 100 }, "top-bottom"); expect(location).toBe(DragTypeOverMeEnum.Bottom); // creatorSettings.dragDrop.allowDragToTheSameLine = true; - location = calculateDragOverLocation(350, 170, { getBoundingClientRect: () => ({ x: 100, y: 100, width: 300, height: 100 }) }); + location = calculateDragOverLocation(350, 170, { x: 100, y: 100, width: 300, height: 100 }); expect(location).toBe(DragTypeOverMeEnum.Right); }); diff --git a/packages/survey-creator-core/tests/dragdrop-elements.tests.ts b/packages/survey-creator-core/tests/dragdrop-elements.tests.ts index bb5834a69e..50c8cf94d6 100644 --- a/packages/survey-creator-core/tests/dragdrop-elements.tests.ts +++ b/packages/survey-creator-core/tests/dragdrop-elements.tests.ts @@ -1,5 +1,5 @@ import { DragTypeOverMeEnum, Question, QuestionTextModel, SurveyModel } from "survey-core"; -import { DragDropSurveyElements, calculateIsEdge, calculateIsSide } from "../src/survey-elements"; +import { DragDropSurveyElements, calculateDragOverLocation, calculateIsEdge, calculateIsSide } from "../src/survey-elements"; import { CreatorTester } from "./creator-tester"; test("calculateVerticalMiddleOfHTMLElement", () => { @@ -1141,6 +1141,69 @@ test("Support onDragDropAllow, Bug#4572", (): any => { expect(counter).toBe(2); expect(ddHelper["allowDropHere"]).toBeTruthy(); }); +test("Test onDragOverLocationCalculating", (): any => { + const creator = new CreatorTester(); + creator.JSON = { + "logoPosition": "right", + "pages": [ + { + "name": "page1", + "elements": [ + { + "type": "text", + "name": "question1" + } + ] + }, + { + "name": "page2", + "elements": [ + { + "type": "text", + "name": "question2" + } + ] + }, + { + "name": "page2", + "elements": [ + { + "type": "text", + "name": "question3" + } + ] + } + ] + }; + creator.onDragOverLocationCalculating.add((sender, options) => { + if (options.draggedSurveyElement.name == "question2" && options.insideContainer) { + options.insideContainer = false; + options.dragOverLocation = calculateDragOverLocation(options.clientX, options.clientY, options.dragOverRect, "top-bottom"); + } + }); + const ddHelper: any = creator.dragDropSurveyElements; + const q1 = creator.survey.getQuestionByName("question1"); + const q2 = creator.survey.getQuestionByName("question2"); + const q3 = creator.survey.getQuestionByName("question3"); + ddHelper.draggedElement = q2; + ddHelper["allowDropHere"] = true; + ddHelper["findDropTargetNodeFromPoint"] = () => ({ getBoundingClientRect: () => ({ x: 10, y: 10, width: 200, height: 100 }) }); + ddHelper["getDropTargetByNode"] = () => q1; + ddHelper["isDropTargetValid"] = () => true; + ddHelper.dragOver({ clientX: 40, clientY: 50 }); + expect(ddHelper.dragOverLocation).toBe(DragTypeOverMeEnum.Top); + expect(ddHelper.insideContainer).toBeFalsy(); + ddHelper.dragOver({ clientX: 100, clientY: 90 }); + expect(ddHelper.dragOverLocation).toBe(DragTypeOverMeEnum.Bottom); + expect(ddHelper.insideContainer).toBeFalsy(); + ddHelper.draggedElement = q3; + ddHelper.dragOver({ clientX: 40, clientY: 50 }); + expect(ddHelper.dragOverLocation).toBe(DragTypeOverMeEnum.Left); + expect(ddHelper.insideContainer).toBeTruthy(); + ddHelper.dragOver({ clientX: 100, clientY: 90 }); + expect(ddHelper.dragOverLocation).toBe(DragTypeOverMeEnum.Bottom); + expect(ddHelper.insideContainer).toBeTruthy(); +}); test("Support onDragDropAllow&allowDropNextToAnother, #5621", (): any => { const creator = new CreatorTester(); creator.JSON = {