Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theming drag drop ghost element #6461

Merged
merged 9 commits into from
Jul 14, 2023
1 change: 1 addition & 0 deletions src/base-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface ISurvey extends ITextProcessor, ISurveyErrorOwner {
cancelPreviewByPage(panel: IPanel): any;
editText: string;
cssNavigationEdit: string;
rootElement?: HTMLElement;

requiredText: string;
beforeSettingQuestionErrors(
Expand Down
18 changes: 9 additions & 9 deletions src/dragdrop/choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class DragDropChoices extends DragDropCore<QuestionSelectBase> {
cursor: grabbing;
position: absolute;
z-index: 10000;
font-family: var(--font-family, $font-family);
font-family: var(--font-family, 'Open Sans');
`;

const isDeepClone = true;
Expand All @@ -34,10 +34,10 @@ export class DragDropChoices extends DragDropCore<QuestionSelectBase> {
);
clone.style.cssText = `
min-width: 100px;
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.1);
background-color: var(--background, white);
border-radius: 36px;
padding-right: 16px;
box-shadow: var(--sjs-shadow-large, 0px 8px 16px 0px rgba(0, 0, 0, 0.1)), var(--sjs-shadow-medium, 0px 2px 6px 0px rgba(0, 0, 0, 0.1));
background-color: var(--sjs-general-backcolor, var(--background, #fff));
border-radius: calc(4.5 * var(--sjs-base-unit, var(--base-unit, 8px)));
padding-right: calc(2* var(--sjs-base-unit, var(--base-unit, 8px)));
margin-left: 0;
`;

Expand Down Expand Up @@ -69,10 +69,10 @@ export class DragDropChoices extends DragDropCore<QuestionSelectBase> {
cursor: grabbing;
position: absolute;
z-index: 10000;
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.1), 0px 2px 6px rgba(0, 0, 0, 0.1);
padding: 4px;
border-radius: 4px;
background: white;
box-shadow: var(--sjs-shadow-large, 0px 8px 16px 0px rgba(0, 0, 0, 0.1)), var(--sjs-shadow-medium, 0px 2px 6px 0px rgba(0, 0, 0, 0.1));
background-color: var(--sjs-general-backcolor, var(--background, #fff));
padding: calc(0.5 * var(--sjs-base-unit, var(--base-unit, 8px)));
border-radius: calc(0.5 * var(--sjs-base-unit, var(--base-unit, 8px)));
`;

const itemValueNode = draggedElementNode.closest("[data-sv-drop-target-item-value]");
Expand Down
1 change: 1 addition & 0 deletions src/dragdrop/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export abstract class DragDropCore<T> implements IDragDropEngine {
}

public startDrag(event: PointerEvent, draggedElement: any, parentElement?: any, draggedElementNode?: HTMLElement, preventSaveTargetNode: boolean = false): void {
this.domAdapter.rootContainer = this.survey?.rootElement;
this.domAdapter.startDrag(event, draggedElement, parentElement, draggedElementNode, preventSaveTargetNode);
}

Expand Down
75 changes: 48 additions & 27 deletions src/dragdrop/dom-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ if(typeof window !== "undefined") {
export interface IDragDropDOMAdapter {
startDrag(event: PointerEvent, draggedElement: any, parentElement: any, draggedElementNode: HTMLElement, preventSaveTargetNode: boolean): void;
draggedElementShortcut: HTMLElement;
rootContainer: HTMLElement;
}

export class DragDropDOMAdapter implements IDragDropDOMAdapter {
Expand All @@ -39,6 +40,13 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {

constructor(private dd: IDragDropEngine, private longTap?: boolean) {
}
private get rootElement() {
if(isShadowDOM(settings.environment.root)) {
return settings.environment.root.host;
} else {
return this.rootContainer || settings.environment.root.documentElement || document.body;
}
}
private stopLongTapIfMoveEnough = (pointerMoveEvent: PointerEvent) => {
pointerMoveEvent.preventDefault();
this.currentX = pointerMoveEvent.pageX;
Expand Down Expand Up @@ -93,7 +101,7 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
`;
settings.environment.rootElement.appendChild(this.savedTargetNode);
this.rootElement.appendChild(this.savedTargetNode);
}

this.stopLongTap();
Expand All @@ -115,6 +123,9 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {
event.stopPropagation();
}
private moveShortcutElement(event: PointerEvent) {
const rootElementX= this.rootElement.getBoundingClientRect().x;
const rootElementY = this.rootElement.getBoundingClientRect().y;

this.doScroll(event.clientY, event.clientX);

const shortcutHeight = this.draggedElementShortcut.offsetHeight;
Expand All @@ -128,55 +139,64 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {
shortcutYOffset = shortcutHeight / 2;
}

const documentBottom = (isShadowDOM(settings.environment.root) ? settings.environment.root.host : settings.environment.root.documentElement).clientHeight;
const documentRight = (isShadowDOM(settings.environment.root) ? settings.environment.root.host : settings.environment.root.documentElement).clientWidth;
const shortcutBottomCoordinate = this.getShortcutBottomCoordinate(event.clientY, shortcutHeight, shortcutYOffset);
const shortcutRightCoordinate = this.getShortcutRightCoordinate(event.clientX, shortcutWidth, shortcutXOffset);
const documentBottom = document.documentElement.clientHeight;
const documentRight = document.documentElement.clientWidth;

const pageX = event.pageX;
const pageY = event.pageY;

const clientX = event.clientX;
const clientY = event.clientY;

const shortcutBottomCoordinate = this.getShortcutBottomCoordinate(clientY, shortcutHeight, shortcutYOffset);
const shortcutRightCoordinate = this.getShortcutRightCoordinate(clientX, shortcutWidth, shortcutXOffset);

if (shortcutRightCoordinate >= documentRight) {
if (shortcutRightCoordinate >= documentRight) { // right boundary
this.draggedElementShortcut.style.left =
event.pageX -
event.clientX +
// pageX -
// clientX +
documentRight -
shortcutWidth +
shortcutWidth -
rootElementX +
"px";
this.draggedElementShortcut.style.top =
event.pageY - shortcutYOffset + "px";
/*pageY*/ clientY - shortcutYOffset - rootElementY + "px";
return;
}

if (event.clientX - shortcutXOffset <= 0) {
if (clientX - shortcutXOffset <= 0) { // left boundary
this.draggedElementShortcut.style.left =
event.pageX - event.clientX + "px";
pageX - clientX - rootElementX + "px";
this.draggedElementShortcut.style.top =
event.pageY - shortcutYOffset + "px";
/*pageY*/ clientY - rootElementY - shortcutYOffset + "px";
return;
}

if (shortcutBottomCoordinate >= documentBottom) {
if (shortcutBottomCoordinate >= documentBottom) { // bottom boundary
this.draggedElementShortcut.style.left =
event.pageX - shortcutXOffset + "px";
/*pageX*/ clientX - shortcutXOffset - rootElementX + "px";
this.draggedElementShortcut.style.top =
event.pageY -
event.clientY +
// pageY -
// clientY +
documentBottom -
shortcutHeight +
shortcutHeight -
rootElementY +
"px";
return;
}

if (event.clientY - shortcutYOffset <= 0) {
if (clientY - shortcutYOffset <= 0) { // top boundary
this.draggedElementShortcut.style.left =
event.pageX - shortcutXOffset + "px";
clientX - shortcutXOffset - rootElementX + "px";
this.draggedElementShortcut.style.top =
event.pageY - event.clientY + "px";
pageY - clientY - rootElementY + "px";
return;
}

this.draggedElementShortcut.style.left =
event.pageX - shortcutXOffset + "px";
clientX - rootElementX - shortcutXOffset + "px";
this.draggedElementShortcut.style.top =
event.pageY - shortcutYOffset + "px";
clientY - rootElementY - shortcutYOffset + "px";
}
private getShortcutBottomCoordinate(currentY: number, shortcutHeight: number, shortcutYOffset: number):number {
return currentY + shortcutHeight - shortcutYOffset;
Expand All @@ -186,7 +206,7 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {
}
private doScroll(clientY: number, clientX: number) {
cancelAnimationFrame(this.scrollIntervalId);
const startScrollBoundary = 50;
const startScrollBoundary = 100;

this.draggedElementShortcut.hidden = true;
let dragOverNode = <HTMLElement>document.elementFromPoint(clientX, clientY);
Expand Down Expand Up @@ -241,14 +261,14 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {
if (IsTouch) {
this.draggedElementShortcut.removeEventListener("contextmenu", this.onContextMenu);
}
settings.environment.rootElement.removeChild(this.draggedElementShortcut);
this.draggedElementShortcut.parentElement.removeChild(this.draggedElementShortcut);

this.dd.clear();
this.draggedElementShortcut = null;
this.scrollIntervalId = null;

if (IsTouch) {
this.savedTargetNode && settings.environment.rootElement.removeChild(this.savedTargetNode);
this.savedTargetNode && this.savedTargetNode.parentElement.removeChild(this.savedTargetNode);
DragDropDOMAdapter.PreventScrolling = false;
}
document.body.style.setProperty("touch-action", "");
Expand All @@ -272,7 +292,7 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {

this.dd.dragInit(event, draggedElement, parentElement, draggedElementNode);

document.body.append(this.draggedElementShortcut);
this.rootElement.append(this.draggedElementShortcut);
this.moveShortcutElement(event);

document.addEventListener("pointermove", this.dragOver);
Expand All @@ -287,6 +307,7 @@ export class DragDropDOMAdapter implements IDragDropDOMAdapter {
}

public draggedElementShortcut: any = null;
public rootContainer: HTMLElement;

public startDrag(event: PointerEvent, draggedElement: any, parentElement?: any, draggedElementNode?: HTMLElement, preventSaveTargetNode: boolean = false): void {
if (IsTouch) {
Expand Down
6 changes: 3 additions & 3 deletions src/dragdrop/matrix-rows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class DragDropMatrixRows extends DragDropCore<QuestionMatrixDynamicModel>
cursor: grabbing;
position: absolute;
z-index: 10000;
font-family: var(--font-family, $font-family);
font-family: var(--font-family, 'Open Sans');
`;

const isDeepClone = true;
Expand All @@ -35,8 +35,8 @@ export class DragDropMatrixRows extends DragDropCore<QuestionMatrixDynamicModel>
const clone = <HTMLElement>(row.cloneNode(isDeepClone));

clone.style.cssText = `
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.1), 0px 2px 6px rgba(0, 0, 0, 0.1);
background-color: white;
box-shadow: var(--sjs-shadow-large, 0px 8px 16px 0px rgba(0, 0, 0, 0.1)), var(--sjs-shadow-medium, 0px 2px 6px 0px rgba(0, 0, 0, 0.1));
background-color: var(--sjs-general-backcolor, var(--background, #fff));
display: flex;
flex-grow: 0;
flex-shrink: 0;
Expand Down
8 changes: 4 additions & 4 deletions src/dragdrop/ranking-choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export class DragDropRankingChoices extends DragDropChoices {
cursor: grabbing;
position: absolute;
z-index: 10000;
border-radius: 36px;
border-radius: calc(12.5 * var(--sjs-base-unit, var(--base-unit, 8px)));
min-width: 100px;
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.1), 0px 2px 6px rgba(0, 0, 0, 0.1);
background-color: var(--background, white);
font-family: var(--font-family, $font-family);
box-shadow: var(--sjs-shadow-medium, 0px 2px 6px 0px rgba(0, 0, 0, 0.1)), var(--sjs-shadow-large, 0px 8px 16px 0px rgba(0, 0, 0, 0.1));
background-color: var(--sjs-general-backcolor, var(--background, #fff));
font-family: var(--font-family, 'Open Sans');
`;

const isDeepClone = true;
Expand Down
2 changes: 2 additions & 0 deletions src/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class SurveyModel extends SurveyElementCore
return SurveyModel.platform;
}
public notifier: Notifier;
public rootElement: HTMLElement;
/**
* A suffix added to the name of the property that stores comments.
*
Expand Down Expand Up @@ -4471,6 +4472,7 @@ export class SurveyModel extends SurveyElementCore
survey: this,
htmlElement: htmlElement,
});
this.rootElement = htmlElement;
}
private processResponsiveness(width: number, mobileWidth: number): boolean {
const isMobile = width < mobileWidth;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions visualRegressionTests/tests/defaultV2/ranking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,45 @@ frameworks.forEach(framework => {
await takeElementScreenshot("question-ranking-select-to-rank-vertical.png", Selector(".sd-question"), t, comparer);
});
});

test("Shortcut position due container layout", async (t) => {
await wrapVisualTest(t, async (t, comparer) => {
await t.resizeWindow(1920, 1080);
await initSurvey(framework, {
showQuestionNumbers: "off",
questions: [
{
type: "ranking",
title: "ranking question",
name: "ranking_question",
choices: ["item1", "item2", "item3", "item4"]
}
]
});

const item1 = Selector("span")
.withText("ranking question")
.parent("[aria-labelledby]")
.find("span")
.withText("item1");

const qustion = Selector("span")
.withText("ranking question")
.parent("[aria-labelledby]");

const patchDragDropToShowGhostElementAfterDrop = ClientFunction(() => {
(<HTMLElement>document.getElementById("surveyElement")).style.margin = "50px";
const question = window["survey"].getAllQuestions()[0];
question.dragDropRankingChoices.removeGhostElementFromSurvey = () => { };
question.dragDropRankingChoices.domAdapter.drop = () => { };
question.dragDropRankingChoices.domAdapter.clear = () => { };
});

await patchDragDropToShowGhostElementAfterDrop();

await t.dragToElement(item1, qustion);

await takeElementScreenshot("question-ranking-shortcut-position-container-layout.png", Selector(".sd-question"), t, comparer);
});
});
});