Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bcefcbf
refactor!: drop SortableJS dependency
ghiscoding Jun 30, 2026
c0ed485
chore: fix a few issues with Firefox in Linux
ghiscoding Jul 1, 2026
827e2fc
chore: add full test coverage & move all code to custom file
ghiscoding Jul 1, 2026
14c1d27
chore: final AI code review & add Safari fallbacks for safety
ghiscoding Jul 1, 2026
2ee92cb
chore: add touch support & merge event callbacks when possible
ghiscoding Jul 2, 2026
c5d351a
chore: stop click event from bubbling up
ghiscoding Jul 2, 2026
a1ad126
chore: fix header menu with touch event
ghiscoding Jul 2, 2026
e27610f
chore: fix header menu with touch event
ghiscoding Jul 2, 2026
0ebff60
chore: avoid using dragActiveClass too early with header menu
ghiscoding Jul 2, 2026
b2ca91e
chore: add missing ghost element while using touch to drag to dropzone
ghiscoding Jul 2, 2026
1c343d1
chore: add missing ghost element while using touch
ghiscoding Jul 2, 2026
961dc93
chore: add missing ghost element while using touch
ghiscoding Jul 2, 2026
24664ed
chore: fix ghost element not showing with touch
ghiscoding Jul 2, 2026
c5f7403
chore: tweak Firefox on Linux detection
ghiscoding Jul 2, 2026
ebe919b
Merge branch 'master' into refactor/drop-sortablejs
ghiscoding Jul 5, 2026
48ebfd0
chore: fix some more issues with touch & header menu, also firefox linux
ghiscoding Jul 7, 2026
2200e6f
chore: add missing test coverage
ghiscoding Jul 7, 2026
24d5fa0
chore: simplify Draggable Grouping & add curly brace oxlint rule
ghiscoding Jul 7, 2026
3819a46
chore: run Prettier format
ghiscoding Jul 7, 2026
c2ee666
Merge branch 'master' into refactor/drop-sortablejs
ghiscoding Jul 7, 2026
aee5c32
chore: merge branch 'master' into refactor/drop-sortablejs
ghiscoding Jul 7, 2026
2ce018f
chore: cleanup code and remove duplicate code
ghiscoding Jul 9, 2026
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
9 changes: 5 additions & 4 deletions demos/aurelia/test/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,27 @@
// -- This will overwrite an existing command --
import '@4tw/cypress-drag-drop';
import 'cypress-real-events';
import './drag';
import { convertPosition } from './common';

declare global {
// oxlint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
convertPosition(viewport: string): Chainable<HTMLElement | JQuery<HTMLElement> | { x: string; y: string }>;
convertPosition(viewport: string): Chainable<{ x: string; y: string }>;
getCell(
row: number,
col: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
getNthCell(
row: number,
nthCol: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
saveLocalStorage: () => void;
restoreLocalStorage: () => void;
getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable<number>;
Expand Down Expand Up @@ -74,7 +75,7 @@ Cypress.Commands.add('getNthCell', (row, nthCol, viewport = 'topLeft', { parentS
`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="transform: translateY(${row * rowHeight}px);"] > .slick-cell:nth(${nthCol})`
);
});
const LOCAL_STORAGE_MEMORY: any = {};
const LOCAL_STORAGE_MEMORY: Record<string, string> = {};

Cypress.Commands.add('saveLocalStorage', () => {
Object.keys(localStorage).forEach((key) => {
Expand Down
48 changes: 48 additions & 0 deletions demos/aurelia/test/cypress/support/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare global {
namespace Cypress {
interface Chainable {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
drag(target: string | HTMLElement | JQuery<HTMLElement>, options?: any): Chainable<any>;
dragOutside(
viewport?: string,
ms?: number,
Expand All @@ -18,6 +19,53 @@ declare global {
}
}
}

Cypress.Commands.overwrite('drag', (_originalFn: any, subject: any, target: any, _options: any = {}) => {
return cy.wrap(subject as JQuery<HTMLElement>).then(($source) => {
const rawSourceElm = $source?.[0] as HTMLElement | undefined;
const rawTargetElm =
typeof target === 'string'
? (Cypress.$(target).first()?.[0] as HTMLElement | undefined)
: ((target as JQuery<HTMLElement>)?.[0] as HTMLElement | undefined);
const sourceElm =
rawSourceElm?.closest<HTMLElement>('.slick-header-column, .slick-dropped-grouping, [draggable="true"]') ?? rawSourceElm;
const targetElm =
rawTargetElm?.closest<HTMLElement>('.slick-header-column, .slick-dropped-grouping, [draggable="true"]') ?? rawTargetElm;

if (!sourceElm || !targetElm) {
return $source;
}

const sourceRect = sourceElm.getBoundingClientRect();
const targetRect = targetElm.getBoundingClientRect();
const sourceX = Number.isFinite(sourceRect.left + sourceRect.width / 2) ? sourceRect.left + sourceRect.width / 2 : 0;
const sourceY = Number.isFinite(sourceRect.top + sourceRect.height / 2) ? sourceRect.top + sourceRect.height / 2 : 0;
const targetX = Number.isFinite(targetRect.left + targetRect.width / 2) ? targetRect.left + targetRect.width / 2 : 0;
const targetY = Number.isFinite(targetRect.top + targetRect.height / 2) ? targetRect.top + targetRect.height / 2 : 0;

const dataTransfer = new DataTransfer();
const createDragLikeEvent = (eventName: string, x: number, y: number): Event => {
const evt = new Event(eventName, { bubbles: true, cancelable: true });
Object.defineProperty(evt, 'dataTransfer', { value: dataTransfer });
Object.defineProperty(evt, 'clientX', { value: x });
Object.defineProperty(evt, 'clientY', { value: y });
Object.defineProperty(evt, 'pageX', { value: x });
Object.defineProperty(evt, 'pageY', { value: y });
Object.defineProperty(evt, 'screenX', { value: x });
Object.defineProperty(evt, 'screenY', { value: y });
return evt;
};

sourceElm.dispatchEvent(createDragLikeEvent('dragstart', sourceX, sourceY));
targetElm.dispatchEvent(createDragLikeEvent('dragenter', targetX, targetY));
targetElm.dispatchEvent(createDragLikeEvent('dragover', targetX, targetY));
targetElm.dispatchEvent(createDragLikeEvent('drop', targetX, targetY));
sourceElm.dispatchEvent(createDragLikeEvent('dragend', targetX, targetY));

return $source;
});
});

// @ts-ignore
Cypress.Commands.add('dragStart', { prevSubject: true }, (subject: HTMLElement, { cellWidth = 90, cellHeight = 35 } = {}) => {
return cy
Expand Down
9 changes: 5 additions & 4 deletions demos/react/test/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,27 @@
// -- This will overwrite an existing command --
import '@4tw/cypress-drag-drop';
import 'cypress-real-events';
import './drag';
import { convertPosition } from './common';

declare global {
// oxlint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
convertPosition(viewport: string): Chainable<HTMLElement | JQuery<HTMLElement> | { x: string; y: string }>;
convertPosition(viewport: string): Chainable<{ x: string; y: string }>;
getCell(
row: number,
col: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
getNthCell(
row: number,
nthCol: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
saveLocalStorage: () => void;
restoreLocalStorage: () => void;
getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable<number>;
Expand Down Expand Up @@ -74,7 +75,7 @@ Cypress.Commands.add('getNthCell', (row, nthCol, viewport = 'topLeft', { parentS
`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="transform: translateY(${row * rowHeight}px);"] > .slick-cell:nth(${nthCol})`
);
});
const LOCAL_STORAGE_MEMORY: any = {};
const LOCAL_STORAGE_MEMORY: Record<string, string> = {};

Cypress.Commands.add('saveLocalStorage', () => {
Object.keys(localStorage).forEach((key) => {
Expand Down
48 changes: 48 additions & 0 deletions demos/react/test/cypress/support/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare global {
namespace Cypress {
interface Chainable {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
drag(target: string | HTMLElement | JQuery<HTMLElement>, options?: any): Chainable<any>;
dragOutside(
viewport?: string,
ms?: number,
Expand All @@ -18,6 +19,53 @@ declare global {
}
}
}

Cypress.Commands.overwrite('drag', (_originalFn: any, subject: any, target: any, _options: any = {}) => {
return cy.wrap(subject as JQuery<HTMLElement>).then(($source) => {
const rawSourceElm = $source?.[0] as HTMLElement | undefined;
const rawTargetElm =
typeof target === 'string'
? (Cypress.$(target).first()?.[0] as HTMLElement | undefined)
: ((target as JQuery<HTMLElement>)?.[0] as HTMLElement | undefined);
const sourceElm =
rawSourceElm?.closest<HTMLElement>('.slick-header-column, .slick-dropped-grouping, [draggable="true"]') ?? rawSourceElm;
const targetElm =
rawTargetElm?.closest<HTMLElement>('.slick-header-column, .slick-dropped-grouping, [draggable="true"]') ?? rawTargetElm;

if (!sourceElm || !targetElm) {
return $source;
}

const sourceRect = sourceElm.getBoundingClientRect();
const targetRect = targetElm.getBoundingClientRect();
const sourceX = Number.isFinite(sourceRect.left + sourceRect.width / 2) ? sourceRect.left + sourceRect.width / 2 : 0;
const sourceY = Number.isFinite(sourceRect.top + sourceRect.height / 2) ? sourceRect.top + sourceRect.height / 2 : 0;
const targetX = Number.isFinite(targetRect.left + targetRect.width / 2) ? targetRect.left + targetRect.width / 2 : 0;
const targetY = Number.isFinite(targetRect.top + targetRect.height / 2) ? targetRect.top + targetRect.height / 2 : 0;

const dataTransfer = new DataTransfer();
const createDragLikeEvent = (eventName: string, x: number, y: number): Event => {
const evt = new Event(eventName, { bubbles: true, cancelable: true });
Object.defineProperty(evt, 'dataTransfer', { value: dataTransfer });
Object.defineProperty(evt, 'clientX', { value: x });
Object.defineProperty(evt, 'clientY', { value: y });
Object.defineProperty(evt, 'pageX', { value: x });
Object.defineProperty(evt, 'pageY', { value: y });
Object.defineProperty(evt, 'screenX', { value: x });
Object.defineProperty(evt, 'screenY', { value: y });
return evt;
};

sourceElm.dispatchEvent(createDragLikeEvent('dragstart', sourceX, sourceY));
targetElm.dispatchEvent(createDragLikeEvent('dragenter', targetX, targetY));
targetElm.dispatchEvent(createDragLikeEvent('dragover', targetX, targetY));
targetElm.dispatchEvent(createDragLikeEvent('drop', targetX, targetY));
sourceElm.dispatchEvent(createDragLikeEvent('dragend', targetX, targetY));

return $source;
});
});

// @ts-ignore
Cypress.Commands.add('dragStart', { prevSubject: true }, (subject: HTMLElement, { cellWidth = 90, cellHeight = 35 } = {}) => {
return cy
Expand Down
9 changes: 5 additions & 4 deletions demos/vue/test/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,27 @@
// -- This will overwrite an existing command --
import '@4tw/cypress-drag-drop';
import 'cypress-real-events';
import './drag';
import { convertPosition } from './common';

declare global {
// oxlint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
convertPosition(viewport: string): Chainable<HTMLElement | JQuery<HTMLElement> | { x: string; y: string }>;
convertPosition(viewport: string): Chainable<{ x: string; y: string }>;
getCell(
row: number,
col: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
getNthCell(
row: number,
nthCol: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
saveLocalStorage: () => void;
restoreLocalStorage: () => void;
getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable<number>;
Expand Down Expand Up @@ -74,7 +75,7 @@ Cypress.Commands.add('getNthCell', (row, nthCol, viewport = 'topLeft', { parentS
`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="transform: translateY(${row * rowHeight}px);"] > .slick-cell:nth(${nthCol})`
);
});
const LOCAL_STORAGE_MEMORY: any = {};
const LOCAL_STORAGE_MEMORY: Record<string, string> = {};

Cypress.Commands.add('saveLocalStorage', () => {
Object.keys(localStorage).forEach((key) => {
Expand Down
48 changes: 48 additions & 0 deletions demos/vue/test/cypress/support/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare global {
namespace Cypress {
interface Chainable {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
drag(target: string | HTMLElement | JQuery<HTMLElement>, options?: any): Chainable<any>;
dragOutside(
viewport?: string,
ms?: number,
Expand All @@ -18,6 +19,53 @@ declare global {
}
}
}

Cypress.Commands.overwrite('drag', (_originalFn: any, subject: any, target: any, _options: any = {}) => {
return cy.wrap(subject as JQuery<HTMLElement>).then(($source) => {
const rawSourceElm = $source?.[0] as HTMLElement | undefined;
const rawTargetElm =
typeof target === 'string'
? (Cypress.$(target).first()?.[0] as HTMLElement | undefined)
: ((target as JQuery<HTMLElement>)?.[0] as HTMLElement | undefined);
const sourceElm =
rawSourceElm?.closest<HTMLElement>('.slick-header-column, .slick-dropped-grouping, [draggable="true"]') ?? rawSourceElm;
const targetElm =
rawTargetElm?.closest<HTMLElement>('.slick-header-column, .slick-dropped-grouping, [draggable="true"]') ?? rawTargetElm;

if (!sourceElm || !targetElm) {
return $source;
}

const sourceRect = sourceElm.getBoundingClientRect();
const targetRect = targetElm.getBoundingClientRect();
const sourceX = Number.isFinite(sourceRect.left + sourceRect.width / 2) ? sourceRect.left + sourceRect.width / 2 : 0;
const sourceY = Number.isFinite(sourceRect.top + sourceRect.height / 2) ? sourceRect.top + sourceRect.height / 2 : 0;
const targetX = Number.isFinite(targetRect.left + targetRect.width / 2) ? targetRect.left + targetRect.width / 2 : 0;
const targetY = Number.isFinite(targetRect.top + targetRect.height / 2) ? targetRect.top + targetRect.height / 2 : 0;

const dataTransfer = new DataTransfer();
const createDragLikeEvent = (eventName: string, x: number, y: number): Event => {
const evt = new Event(eventName, { bubbles: true, cancelable: true });
Object.defineProperty(evt, 'dataTransfer', { value: dataTransfer });
Object.defineProperty(evt, 'clientX', { value: x });
Object.defineProperty(evt, 'clientY', { value: y });
Object.defineProperty(evt, 'pageX', { value: x });
Object.defineProperty(evt, 'pageY', { value: y });
Object.defineProperty(evt, 'screenX', { value: x });
Object.defineProperty(evt, 'screenY', { value: y });
return evt;
};

sourceElm.dispatchEvent(createDragLikeEvent('dragstart', sourceX, sourceY));
targetElm.dispatchEvent(createDragLikeEvent('dragenter', targetX, targetY));
targetElm.dispatchEvent(createDragLikeEvent('dragover', targetX, targetY));
targetElm.dispatchEvent(createDragLikeEvent('drop', targetX, targetY));
sourceElm.dispatchEvent(createDragLikeEvent('dragend', targetX, targetY));

return $source;
});
});

// @ts-ignore
Cypress.Commands.add('dragStart', { prevSubject: true }, (subject: HTMLElement, { cellWidth = 90, cellHeight = 35 } = {}) => {
return cy
Expand Down
35 changes: 18 additions & 17 deletions frameworks/angular-slickgrid/test/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable n/file-extension-in-import */
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
Expand All @@ -24,47 +25,34 @@
// -- This will overwrite an existing command --
import '@4tw/cypress-drag-drop';
import 'cypress-real-events';
import './drag';
import { convertPosition } from './common';

declare global {
// oxlint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
// triggerHover: (elements: NodeListOf<HTMLElement>) => void;
convertPosition(viewport: string): Chainable<HTMLElement | JQuery<HTMLElement> | { x: string; y: string }>;
convertPosition(viewport: string): Chainable<{ x: string; y: string }>;
getCell(
row: number,
col: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
getNthCell(
row: number,
nthCol: number,
viewport?: string,
options?: { parentSelector?: string; rowHeight?: number }
): Chainable<HTMLElement | JQuery<HTMLElement>>;
): Chainable<JQuery<HTMLElement>>;
saveLocalStorage: () => void;
restoreLocalStorage: () => void;
getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable<number>;
}
}
}

const LOCAL_STORAGE_MEMORY: any = {};

Cypress.Commands.add('saveLocalStorage', () => {
Object.keys(localStorage).forEach((key) => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
});
});

Cypress.Commands.add('restoreLocalStorage', () => {
Object.keys(LOCAL_STORAGE_MEMORY).forEach((key) => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
});
});

// convert position like 'topLeft' to the object { x: 'left|right', y: 'top|bottom' }
Cypress.Commands.add('convertPosition', (viewport = 'topLeft') => cy.wrap(convertPosition(viewport)));

Expand All @@ -87,6 +75,19 @@ Cypress.Commands.add('getNthCell', (row, nthCol, viewport = 'topLeft', { parentS
`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="transform: translateY(${row * rowHeight}px);"] > .slick-cell:nth(${nthCol})`
);
});
const LOCAL_STORAGE_MEMORY: Record<string, string> = {};

Cypress.Commands.add('saveLocalStorage', () => {
Object.keys(localStorage).forEach((key) => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
});
});

Cypress.Commands.add('restoreLocalStorage', () => {
Object.keys(LOCAL_STORAGE_MEMORY).forEach((key) => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
});
});

Cypress.Commands.add(
'getTransformValue',
Expand Down
Loading
Loading