Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</ng-container>
</div>
<div class="svc-tab-designer" [class]="model.getRootCss()" (click)="model.clickDesigner()">
<sv-scroll>
<sv-scroll [onInnerHeightChanged]="model.setHasScroll">
<div class="svc-tab-designer_content">
<ng-container *ngIf="model.showPlaceholder">
<div *ngIf="creator.showHeaderInEmptySurvey && creator.allowEditSurveyTitle" class="svc-designer-header">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ import { DropIndicatorPosition } from "../drag-drop-enums";
import { cleanHtmlElementAfterAnimation, prepareElementForVerticalAnimation } from "survey-core";
import { SurveyElementActionContainer } from "./action-container-view-model";

function getOffsetTop(element: HTMLElement, offsetParent: HTMLElement): number {
let offsetTop = element.offsetTop;
let parent = element.offsetParent as HTMLElement;
while(parent && parent !== offsetParent) {
offsetTop += parent.offsetTop;
parent = parent.offsetParent as HTMLElement;
}
return offsetTop;
}

function debounce(func, delay) {
let timeout;
return function (...args) {
Expand All @@ -36,6 +46,29 @@ const updateRowsVisibility = debounce((target: SurveyElementAdornerBase) => {
}, 50);

export class SurveyElementAdornerBase<T extends SurveyElement = SurveyElement> extends Base {
private initialElementOffsetTop = 0;
private initialContainerScrollTop = 0;
public saveRelativePosition() {
if (!!this.rootElement) {
const container = this.rootElement.closest<HTMLElement>(".sv-scroll__container");
if (!!container) {
this.initialElementOffsetTop = getOffsetTop(this.rootElement, container);
this.initialContainerScrollTop = container.parentElement.scrollTop;
}
}
}
public restoreRelativePosition() {
if (!!this.rootElement) {
const container = this.rootElement.closest<HTMLElement>(".sv-scroll__container");
if (!!container) {
setTimeout(() => {
const currentOffsetTop = getOffsetTop(this.rootElement, container);
const deltaTop = currentOffsetTop - this.initialElementOffsetTop;
container.parentElement.scrollTop = this.initialContainerScrollTop + deltaTop;
}, 10);
}
}
}
public static AdornerValueName = "__sjs_creator_adorner";
protected expandCollapseAction: IAction;
@property({ defaultValue: true }) allowDragging: boolean;
Expand Down Expand Up @@ -217,6 +250,32 @@ export class SurveyElementAdornerBase<T extends SurveyElement = SurveyElement> e
}
}

public dragEnter() {
if (this.hasDragLeft) {
clearTimeout(this.dragCollapsedTimer);
this.hasDragLeft = false;
}
}
private hasDragLeft = false;
public dragLeave() {
if (!this.creator.collapseOnDragLeft) {
return;
}
if (!this.hasDragLeft) {
if (this.canExpandOnDrag && !this.collapsed) {
this.hasDragLeft = true;
this.dragCollapsedTimer = setTimeout(() => {
this.collapseWithDragLeave();
}, this.creator.expandOnDragTimeOut);
}
}
}
protected collapseWithDragLeave() {
this.collapsed = true;
this.dragCollapsedTimer = undefined;
this.hasDragLeft = false;
}

protected allowExpandCollapseByDblClick(element: any) {
return true;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/survey-creator-core/src/components/tabs/designer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,8 @@ export class TabDesignerViewModel extends Base {
rootCss += " svc-tab-designer--" + this.creator.pageEditMode + "-mode";
return rootCss;
}

public setHasScroll = (hasScroll: boolean) => {
this.creator.hasScroll = hasScroll;
};
}
59 changes: 35 additions & 24 deletions packages/survey-creator-core/src/creator-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import { ICreatorOptions } from "./creator-options";
import { Translation } from "../src/components/tabs/translation";
import { StringEditorConnector } from "./components/string-editor";
import { ThemeTabPlugin } from "./components/tabs/theme-plugin";
import { DragDropSurveyElements } from "./dragdrop-survey-elements";
import { DragDropSurveyElements, getPathToRootPageElement } from "./dragdrop-survey-elements";
import { PageAdorner } from "./components/page";
import {
ElementDeletingEvent, PropertyGetReadOnlyEvent, ElementGetDisplayNameEvent, ElementAllowOperationsEvent,
Expand Down Expand Up @@ -2325,9 +2325,12 @@ export class SurveyCreatorModel extends Base
this.dragDropSurveyElements.onDragStart.add((sender, options) => {
const element = sender.draggedElement;
isDraggedFromToolbox = !element.parent && !element.isPage;
if (!!element && (element.isPage || this.collapseOnDrag)) {
if (!!element && !isDraggedFromToolbox && (element.isPage || (this.collapseOnDrag !== false && this.collapseOnDrag !== "none"))) {
this.designerStateManager?.suspend();
this.collapseAllPagesOnDragStart(element);
const adorner = SurveyElementAdornerBase.GetAdorner(element);
adorner?.saveRelativePosition();
this.collapseElementsOnDragStart(element);
adorner?.restoreRelativePosition();
}
this.onDragStart.fire(this, options);
this.startUndoRedoTransaction("drag drop");
Expand All @@ -2347,20 +2350,31 @@ export class SurveyCreatorModel extends Base
}
});
this.dragDropSurveyElements.onDragClear.add((sender, options) => {
isDraggedFromToolbox = false;
this.stopUndoRedoTransaction();
if (!!options.draggedElement && (options.draggedElement.isPage || this.collapseOnDrag)) {
this.designerStateManager?.release();
this.restoreElementsState();
if (!!options.draggedElement && !isDraggedFromToolbox && (options.draggedElement.isPage || (this.collapseOnDrag !== false && this.collapseOnDrag !== "none"))) {
setTimeout(() => {
const adorner = SurveyElementAdornerBase.GetAdorner(options.draggedElement);
adorner?.saveRelativePosition();
this.designerStateManager?.release();
this.restoreElementsStateOnDragEnd();
adorner?.restoreRelativePosition();
}, 0);
}
isDraggedFromToolbox = false;
this.onDragClear.fire(this, options);
});
}
public get designerStateManager() {
return (this.getPlugin("designer") as TabDesignerPlugin)?.designerStateManager;
}
public collapseAllPagesOnDragStart(element: SurveyElement): void {
this.expandCollapseManager.expandCollapseElements("drag-start", true, this.survey.pages.filter(p => !element || element.isPage || p !== (element as any).page));
public hasScroll: boolean = false;
public collapseElementsOnDragStart(element: SurveyElement): void {
if (element.isPage || this.collapseOnDrag === "pages") {
this.expandCollapseManager.expandCollapseElements("drag-start", true, this.survey.pages.filter(p => !element || element.isPage || p !== (element as any).page));
} else if (this.collapseOnDrag === true || this.collapseOnDrag === "all" || this.collapseOnDrag === "adaptive" && this.hasScroll) {
const exeptions = getPathToRootPageElement(element);
this.collapseAll(exeptions, true);
}
}
public getElementExpandCollapseState(element: Question | PageModel | PanelModel, reason: ElementGetExpandCollapseStateEventReason, defaultValue: boolean): boolean {
if (this.expandCollapseButtonVisibility == "never") return false;
Expand Down Expand Up @@ -2388,16 +2402,12 @@ export class SurveyCreatorModel extends Base
}
SurveyElementAdornerBase.RestoreStateFor(element);
}
public restoreElementsState(): void {
this.survey.pages.forEach(element => {
if (element["draggedFrom"] !== undefined) {
const adorner = SurveyElementAdornerBase.GetAdorner(element);
adorner?.blockAnimations();
this.restoreState(element);
adorner?.releaseAnimations();
} else {
this.restoreState(element);
}
public restoreElementsStateOnDragEnd(): void {
this.expandCollapseManager?.getCollapsableElements().forEach(element => {
const adorner = SurveyElementAdornerBase.GetAdorner(element);
adorner?.blockAnimations();
this.restoreState(element);
adorner?.releaseAnimations();
});
}
private initDragDropChoices() {
Expand Down Expand Up @@ -4586,16 +4596,16 @@ export class SurveyCreatorModel extends Base
* @see expandAll
* @see collapseElement
*/
public collapseAll() {
this.expandCollapseManager.expandCollapseElements(null, true);
public collapseAll(exceptions?: SurveyElement[], blockAnimations = false) {
this.expandCollapseManager.expandCollapseElements(null, true, undefined, exceptions, blockAnimations);
}
/**
* Expands all survey elements on the design surface.
* @see collapseAll
* @see expandElement
*/
public expandAll() {
this.expandCollapseManager.expandCollapseElements(null, false);
public expandAll(exceptions?: SurveyElement[], blockAnimations = false) {
this.expandCollapseManager.expandCollapseElements(null, false, undefined, exceptions, blockAnimations);
}
/**
* Collapses an individual survey element on the design surface.
Expand Down Expand Up @@ -4721,7 +4731,8 @@ export class SurveyCreatorModel extends Base
*
* Default value: `false`
*/
public collapseOnDrag: boolean = false;
public collapseOnDrag: true | false | "none" | "pages" | "all" | "adaptive" = false;
public collapseOnDragLeft: boolean = false;
/**
* Specifies whether to clear translations to other languages when a source language translation is changed.
*
Expand Down
61 changes: 61 additions & 0 deletions packages/survey-creator-core/src/dragdrop-survey-elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ export function calculateDragOverLocation(clientX: number, clientY: number, rect
}
}

export function getPathToRootPageElement(element: ISurveyElement) {
let path = [];
let target: any = element;
let lastTarget = element;
while(!!target) {
if (target.isInteractiveDesignElement) {
path.push(target);
lastTarget = target;
}
let parent = target.parent || target.parentQuestion;
if (!parent && target.data && target.data.data && target.data.data.getType && target.data.data.getType() === "matrixdropdown") {
parent = target.data.data;
}
target = parent;
}
if (!lastTarget.isPage) {
path.push((element as any).page || (element as any).__page); // TODO: remove __page
}
return path;
}

export class DragDropSurveyElements extends DragDropCore<any> {
public static newGhostPage: PageModel = null;
public static restrictDragQuestionBetweenPages: boolean = false;
Expand Down Expand Up @@ -343,6 +364,44 @@ export class DragDropSurveyElements extends DragDropCore<any> {
}
return allowOptions.allow;
}
public prevDropTargetPath: ISurveyElement[] = [];
updatePathToDragOver(dropTarget: ISurveyElement, prevDropTarget: ISurveyElement) {
if (!dropTarget) {
this.leaveDragOverElements(this.prevDropTargetPath);
this.prevDropTargetPath = [];
return;
}
if (dropTarget !== prevDropTarget) {
let dropTargetPath = getPathToRootPageElement(dropTarget);
dropTargetPath = dropTargetPath.reverse();
for (let i = 0; i < this.prevDropTargetPath.length && i < dropTargetPath.length; i++) {
if (this.prevDropTargetPath[i] !== dropTargetPath[i]) {
this.leaveDragOverElements(this.prevDropTargetPath.slice(i));
this.enterDragOverElements(dropTargetPath.slice(i));
break;
}
}
this.prevDropTargetPath = dropTargetPath;
}
}
private leaveDragOverElements(elements: ISurveyElement[]) {
elements && elements.forEach((element) => {
const adorner = SurveyElementAdornerBase.GetAdorner(element as any);
if (adorner) {
adorner.dropIndicatorPosition = null;
adorner.dragLeave();
}
});
}
private enterDragOverElements(elements: ISurveyElement[]) {
elements && elements.forEach((element) => {
const adorner = SurveyElementAdornerBase.GetAdorner(element as any);
if (adorner) {
adorner.dragEnter();
}
});
}

public dragOverCore(dropTarget: ISurveyElement, dragOverLocation: DropIndicatorPosition): void {
const oldDragOverIndicatorElement = this.dragOverIndicatorElement;
const oldDropTarget = this.dropTarget;
Expand All @@ -351,6 +410,8 @@ export class DragDropSurveyElements extends DragDropCore<any> {
return;
}
this.dropTarget = dropTarget;
this.updatePathToDragOver(dropTarget, oldDropTarget);
// this.updatePathToDragOver(null, null);
this.dragOverLocation = dragOverLocation;

this.parentElement = this.dropTarget.isPage
Expand Down
11 changes: 7 additions & 4 deletions packages/survey-creator-core/src/expand-collapse-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export class ExpandCollapseManager {
constructor(private creator: CreatorBase) {
}

public expandCollapseElements(reason: ElementGetExpandCollapseStateEventReason, isCollapsed: boolean, elements: SurveyElement[] = null) {
this.updateCollapsed(elements || this.getCollapsableElements(), isCollapsed, reason);
public expandCollapseElements(reason: ElementGetExpandCollapseStateEventReason, isCollapsed: boolean, elements: SurveyElement[] = null, exceptions?: SurveyElement[], blockAnimations = false) {
this.updateCollapsed(elements || this.getCollapsableElements(), isCollapsed, reason, exceptions, blockAnimations);
}
public clearExpandChoicesStates() {
this.expandChoicesStates = {};
Expand Down Expand Up @@ -78,7 +78,7 @@ export class ExpandCollapseManager {
public lockQuestions(locked: boolean) {
this._lockQuestions = locked;
}
private getCollapsableElements() {
public getCollapsableElements() {
return (this.creator.survey.pages as SurveyElement[])
.concat(this.creator.survey.getAllPanels() as unknown as SurveyElement[])
.concat(this.creator.survey.getAllQuestions() as SurveyElement[]);
Expand All @@ -90,15 +90,18 @@ export class ExpandCollapseManager {
return a - b;
});
}
private updateCollapsed(elements: SurveyElement[], value: boolean, reason: ElementGetExpandCollapseStateEventReason) {
private updateCollapsed(elements: SurveyElement[], value: boolean, reason: ElementGetExpandCollapseStateEventReason, exceptions?: SurveyElement[], blockAnimations = false) {
this.sortElements(elements).forEach(element => {
if (element.isQuestion && this._lockQuestions) return;
if (exceptions && exceptions.indexOf(element) > -1) return;
const collapsed = this.creator.getElementExpandCollapseState(element as Question | PageModel | PanelModel, reason, value);
this.creator.designerStateManager?.setElementCollapsed(element, collapsed);
const adorner = SurveyElementAdornerBase.GetAdorner(element);
if (adorner && adorner.allowExpandCollapse) {
const newState = this.creator.getElementExpandCollapseState(element as Question | PageModel | PanelModel, reason, value);
blockAnimations && adorner.blockAnimations();
adorner.collapsed = newState;
blockAnimations && adorner.releaseAnimations();
}
});
}
Expand Down
Loading