@purpurds/table 8.7.0 → 8.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LICENSE.txt +16 -16
- package/dist/styles.css +1 -1
- package/dist/table-column-header-cell.d.ts.map +1 -1
- package/dist/table.cjs.js +65 -65
- package/dist/table.cjs.js.map +1 -1
- package/dist/table.d.ts.map +1 -1
- package/dist/table.es.js +3814 -3788
- package/dist/table.es.js.map +1 -1
- package/dist/use-drag-handle.hook.d.ts +2 -6
- package/dist/use-drag-handle.hook.d.ts.map +1 -1
- package/dist/utils/custom-keyboard-coordinates.d.ts +3 -2
- package/dist/utils/custom-keyboard-coordinates.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/table-column-header-cell.tsx +9 -5
- package/src/table.tsx +11 -6
- package/src/use-drag-handle.hook.tsx +7 -26
- package/src/use-drag-handle.test.tsx +21 -174
- package/src/utils/custom-keyboard-coordinates.ts +27 -66
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
|
|
3
|
-
export declare function useDragHandle(): {
|
|
4
|
-
mouseDownActive: boolean;
|
|
5
|
-
handleMouseDown: () => void;
|
|
6
|
-
};
|
|
7
3
|
export type TableColumnDragHandleProps = {
|
|
8
|
-
onMouseDown: () => void;
|
|
9
4
|
overlayActive?: boolean;
|
|
10
5
|
isFirstColumn: boolean;
|
|
11
6
|
isLastColumn: boolean;
|
|
12
7
|
columnDragAriaLabel: string;
|
|
8
|
+
[key: string]: unknown;
|
|
13
9
|
};
|
|
14
|
-
export declare function TableColumnDragHandle({
|
|
10
|
+
export declare function TableColumnDragHandle({ overlayActive, isFirstColumn, isLastColumn, columnDragAriaLabel, ...dragListeners }: TableColumnDragHandleProps): React.JSX.Element;
|
|
15
11
|
//# sourceMappingURL=use-drag-handle.hook.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-drag-handle.hook.d.ts","sourceRoot":"","sources":["../src/use-drag-handle.hook.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"use-drag-handle.hook.d.ts","sourceRoot":"","sources":["../src/use-drag-handle.hook.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAS1B,MAAM,MAAM,0BAA0B,GAAG;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAE5B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EACpC,aAAa,EACb,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,GAAG,aAAa,EACjB,EAAE,0BAA0B,qBAgB5B"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { KeyboardCoordinateGetter } from '@dnd-kit/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Simple keyboard coordinate handler that moves the drag overlay
|
|
5
|
+
* left or right incrementally and lets collision detection determine
|
|
6
|
+
* which column we're over.
|
|
6
7
|
*/
|
|
7
8
|
export declare const enhancedColumnKeyboardCoordinates: KeyboardCoordinateGetter;
|
|
8
9
|
//# sourceMappingURL=custom-keyboard-coordinates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-keyboard-coordinates.d.ts","sourceRoot":"","sources":["../../src/utils/custom-keyboard-coordinates.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"custom-keyboard-coordinates.d.ts","sourceRoot":"","sources":["../../src/utils/custom-keyboard-coordinates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAE5E;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,EAAE,wBAoC/C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@purpurds/table",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.8.0",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"main": "./dist/table.cjs.js",
|
|
6
6
|
"types": "./dist/table.d.ts",
|
|
@@ -21,26 +21,26 @@
|
|
|
21
21
|
"@dnd-kit/utilities": "~3.2.2",
|
|
22
22
|
"@tanstack/react-table": "~8.21.2",
|
|
23
23
|
"classnames": "~2.5.0",
|
|
24
|
-
"@purpurds/badge": "8.
|
|
25
|
-
"@purpurds/
|
|
26
|
-
"@purpurds/cta-link": "8.
|
|
27
|
-
"@purpurds/
|
|
28
|
-
"@purpurds/
|
|
29
|
-
"@purpurds/
|
|
30
|
-
"@purpurds/
|
|
31
|
-
"@purpurds/
|
|
32
|
-
"@purpurds/
|
|
33
|
-
"@purpurds/
|
|
34
|
-
"@purpurds/
|
|
35
|
-
"@purpurds/
|
|
36
|
-
"@purpurds/
|
|
37
|
-
"@purpurds/
|
|
38
|
-
"@purpurds/
|
|
39
|
-
"@purpurds/
|
|
24
|
+
"@purpurds/badge": "8.8.0",
|
|
25
|
+
"@purpurds/checkbox": "8.8.0",
|
|
26
|
+
"@purpurds/cta-link": "8.8.0",
|
|
27
|
+
"@purpurds/button": "8.8.0",
|
|
28
|
+
"@purpurds/drawer": "8.8.0",
|
|
29
|
+
"@purpurds/heading": "8.8.0",
|
|
30
|
+
"@purpurds/icon": "8.8.0",
|
|
31
|
+
"@purpurds/paragraph": "8.8.0",
|
|
32
|
+
"@purpurds/link": "8.8.0",
|
|
33
|
+
"@purpurds/select": "8.8.0",
|
|
34
|
+
"@purpurds/skeleton": "8.8.0",
|
|
35
|
+
"@purpurds/text-field": "8.8.0",
|
|
36
|
+
"@purpurds/toggle": "8.8.0",
|
|
37
|
+
"@purpurds/tokens": "8.8.0",
|
|
38
|
+
"@purpurds/tooltip": "8.8.0",
|
|
39
|
+
"@purpurds/visually-hidden": "8.8.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@rushstack/eslint-patch": "~1.10.0",
|
|
43
|
-
"@storybook/react-vite": "^
|
|
43
|
+
"@storybook/react-vite": "^10.0.7",
|
|
44
44
|
"@testing-library/dom": "~10.4.0",
|
|
45
45
|
"@testing-library/jest-dom": "~6.4.0",
|
|
46
46
|
"@testing-library/react": "~16.2.0",
|
|
@@ -54,19 +54,19 @@
|
|
|
54
54
|
"prettier": "~2.8.8",
|
|
55
55
|
"react-dom": "^19.0.0",
|
|
56
56
|
"react": "^19.0.0",
|
|
57
|
-
"storybook": "^
|
|
57
|
+
"storybook": "^10.0.7",
|
|
58
58
|
"typescript": "^5.6.3",
|
|
59
59
|
"vite": "^6.2.1",
|
|
60
60
|
"vitest-axe": "~0.1.0",
|
|
61
61
|
"vitest-canvas-mock": "~0.3.3",
|
|
62
62
|
"vitest": "^3.1.2",
|
|
63
|
-
"@purpurds/autocomplete": "8.
|
|
63
|
+
"@purpurds/autocomplete": "8.8.0",
|
|
64
64
|
"@purpurds/component-rig": "1.0.0",
|
|
65
|
-
"@purpurds/
|
|
66
|
-
"@purpurds/
|
|
67
|
-
"@purpurds/
|
|
68
|
-
"@purpurds/
|
|
69
|
-
"@purpurds/pagination": "8.
|
|
65
|
+
"@purpurds/illustrative-icon": "8.8.0",
|
|
66
|
+
"@purpurds/grid": "8.8.0",
|
|
67
|
+
"@purpurds/label": "8.8.0",
|
|
68
|
+
"@purpurds/listbox": "8.8.0",
|
|
69
|
+
"@purpurds/pagination": "8.8.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build:dev": "vite",
|
|
@@ -22,7 +22,7 @@ import c from "classnames/bind";
|
|
|
22
22
|
|
|
23
23
|
import { DragIndicatorCircle } from "./drag-indicator-circle";
|
|
24
24
|
import styles from "./table.module.scss";
|
|
25
|
-
import { TableColumnDragHandle
|
|
25
|
+
import { TableColumnDragHandle } from "./use-drag-handle.hook";
|
|
26
26
|
import { useDragIndicatorPosition } from "./use-drag-indicator-position.hook";
|
|
27
27
|
import { useDropIndicator } from "./use-drop-indicator.hook";
|
|
28
28
|
import { useElementVisibility } from "./use-element-visibility.hook";
|
|
@@ -100,9 +100,8 @@ export const TableColumnHeaderCell = <TData extends RowData>({
|
|
|
100
100
|
disabled: !enableColumnDrag || activeId !== null,
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
const { mouseDownActive, handleMouseDown } = useDragHandle();
|
|
104
103
|
const isActiveColumn = activeId === header.id || sortableId === activeId;
|
|
105
|
-
const draggingActive = (overlayActive || isSorting
|
|
104
|
+
const draggingActive = (overlayActive || isSorting) && isActiveColumn;
|
|
106
105
|
const isCheckBox = header.column.id === "row-selection" || header.id === "row-selection";
|
|
107
106
|
const isRadiobutton = header.column.id === "row-radio" || header.id === "row-radio";
|
|
108
107
|
const canSort = enableSorting && header.column.getCanSort();
|
|
@@ -184,12 +183,17 @@ export const TableColumnHeaderCell = <TData extends RowData>({
|
|
|
184
183
|
aria-sort={getSortingDirection(header.column.getIsSorted())}
|
|
185
184
|
aria-label={ariaLabel}
|
|
186
185
|
{...(enableColumnDrag
|
|
187
|
-
? {
|
|
186
|
+
? {
|
|
187
|
+
...attributes,
|
|
188
|
+
role: "columnheader", // Override the role="button" from useSortable
|
|
189
|
+
tabIndex: -1, // Make th non-focusable
|
|
190
|
+
id: `header-${sortableId || header.id}`,
|
|
191
|
+
}
|
|
188
192
|
: {})}
|
|
189
193
|
>
|
|
190
194
|
{enableColumnDrag && (
|
|
191
195
|
<TableColumnDragHandle
|
|
192
|
-
|
|
196
|
+
{...listeners}
|
|
193
197
|
overlayActive={overlayActive}
|
|
194
198
|
isFirstColumn={isFirstColumn}
|
|
195
199
|
isLastColumn={isLastColumn}
|
package/src/table.tsx
CHANGED
|
@@ -205,7 +205,7 @@ export const Table = <TData extends RowData>({
|
|
|
205
205
|
...restProps,
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
const rowCount =
|
|
208
|
+
const rowCount = tanstackTable.getRowCount();
|
|
209
209
|
|
|
210
210
|
useEffect(() => {
|
|
211
211
|
if (onRowsCountChange) {
|
|
@@ -316,10 +316,6 @@ export const Table = <TData extends RowData>({
|
|
|
316
316
|
const tableRows = tanstackTable.getRowModel().rows;
|
|
317
317
|
const emptyTable = tableRows.length === 0 && Boolean(emptyTableCopy);
|
|
318
318
|
|
|
319
|
-
const memoizedGetColumnWidths = React.useCallback(() => {
|
|
320
|
-
return tanstackTable.getVisibleLeafColumns().map((column) => column.getSize() || "100%");
|
|
321
|
-
}, [tanstackTable]);
|
|
322
|
-
|
|
323
319
|
// Extract header groups to avoid complex expression in dependency array
|
|
324
320
|
const headerGroups = tanstackTable.getHeaderGroups();
|
|
325
321
|
|
|
@@ -379,6 +375,13 @@ export const Table = <TData extends RowData>({
|
|
|
379
375
|
sortingAriaLabels,
|
|
380
376
|
columnDragAriaLabelsCopy,
|
|
381
377
|
tanstackTable,
|
|
378
|
+
emptyTable,
|
|
379
|
+
stickyFirstColumn,
|
|
380
|
+
stickyHeaders,
|
|
381
|
+
isScrolled,
|
|
382
|
+
getStickyColumn,
|
|
383
|
+
showBorder,
|
|
384
|
+
activeId,
|
|
382
385
|
]
|
|
383
386
|
);
|
|
384
387
|
|
|
@@ -399,7 +402,9 @@ export const Table = <TData extends RowData>({
|
|
|
399
402
|
showBorder={showBorder}
|
|
400
403
|
enableColumnDrag={enableColumnDrag}
|
|
401
404
|
activeId={activeId}
|
|
402
|
-
getColumnWidths={
|
|
405
|
+
getColumnWidths={() =>
|
|
406
|
+
tanstackTable.getVisibleLeafColumns().map((column) => column.getSize() || "100%")
|
|
407
|
+
}
|
|
403
408
|
variant={variant}
|
|
404
409
|
emptyTableHeadingTag={emptyTableHeadingTag}
|
|
405
410
|
emptyTableCopy={emptyTableCopy}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { IconDragHorizontal } from "@purpurds/icon/drag-horizontal";
|
|
3
3
|
import c from "classnames/bind";
|
|
4
4
|
|
|
@@ -7,52 +7,33 @@ import styles from "./table.module.scss";
|
|
|
7
7
|
const cx = c.bind(styles);
|
|
8
8
|
const rootClassName = "purpur-table-column-header-cell";
|
|
9
9
|
|
|
10
|
-
export function useDragHandle() {
|
|
11
|
-
const [mouseDownActive, setMouseDownActive] = useState(false);
|
|
12
|
-
|
|
13
|
-
const handleMouseDown = () => {
|
|
14
|
-
setMouseDownActive(true);
|
|
15
|
-
window.addEventListener("mouseup", handleMouseUp, { once: true });
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const handleMouseUp = () => {
|
|
19
|
-
setMouseDownActive(false);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
return { mouseDownActive, handleMouseDown };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
10
|
export type TableColumnDragHandleProps = {
|
|
26
|
-
onMouseDown: () => void;
|
|
27
11
|
overlayActive?: boolean;
|
|
28
12
|
isFirstColumn: boolean;
|
|
29
13
|
isLastColumn: boolean;
|
|
30
14
|
columnDragAriaLabel: string;
|
|
15
|
+
// Add any additional props from listeners
|
|
16
|
+
[key: string]: unknown;
|
|
31
17
|
};
|
|
32
18
|
|
|
33
19
|
export function TableColumnDragHandle({
|
|
34
|
-
onMouseDown,
|
|
35
20
|
overlayActive,
|
|
36
21
|
isFirstColumn,
|
|
37
22
|
isLastColumn,
|
|
38
23
|
columnDragAriaLabel,
|
|
24
|
+
...dragListeners // Capture the listeners from useSortable
|
|
39
25
|
}: TableColumnDragHandleProps) {
|
|
40
26
|
return (
|
|
41
27
|
<div
|
|
28
|
+
role="button"
|
|
29
|
+
tabIndex={0}
|
|
42
30
|
className={cx(`${rootClassName}__drag-handle`, {
|
|
43
31
|
[`${rootClassName}__border-radius-first-cell`]: isFirstColumn && !overlayActive,
|
|
44
32
|
[`${rootClassName}__border-radius-last-cell`]: isLastColumn && !overlayActive,
|
|
45
33
|
[`${rootClassName}__drag-handle--active`]: overlayActive,
|
|
46
34
|
})}
|
|
47
|
-
role="button"
|
|
48
|
-
tabIndex={0}
|
|
49
35
|
aria-label={columnDragAriaLabel}
|
|
50
|
-
|
|
51
|
-
onKeyDown={(e) => {
|
|
52
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
53
|
-
onMouseDown();
|
|
54
|
-
}
|
|
55
|
-
}}
|
|
36
|
+
{...dragListeners}
|
|
56
37
|
>
|
|
57
38
|
<IconDragHorizontal className={cx(`${rootClassName}__drag-handle-icon`)} size="sm" />
|
|
58
39
|
</div>
|
|
@@ -1,102 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
3
|
import userEvent from "@testing-library/user-event";
|
|
4
4
|
import { axe } from "vitest-axe";
|
|
5
5
|
|
|
6
|
-
import { TableColumnDragHandle
|
|
7
|
-
|
|
8
|
-
// Test component to test the hook
|
|
9
|
-
function TestDragHandleComponent() {
|
|
10
|
-
const { mouseDownActive, handleMouseDown } = useDragHandle();
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<div>
|
|
14
|
-
<span data-testid="mouse-down-state">{mouseDownActive ? "active" : "inactive"}</span>
|
|
15
|
-
<button onClick={handleMouseDown} data-testid="trigger-button">
|
|
16
|
-
Trigger Mouse Down
|
|
17
|
-
</button>
|
|
18
|
-
</div>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe("useDragHandle Hook", () => {
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
// Clean up any event listeners
|
|
25
|
-
vi.clearAllMocks();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
// Clean up global event listeners
|
|
30
|
-
const events = ["mouseup"];
|
|
31
|
-
events.forEach((event) => {
|
|
32
|
-
window.removeEventListener(event, () => {});
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("should initialize with mouseDownActive as false", () => {
|
|
37
|
-
render(<TestDragHandleComponent />);
|
|
38
|
-
|
|
39
|
-
const stateElement = screen.getByTestId("mouse-down-state");
|
|
40
|
-
expect(stateElement).toHaveTextContent("inactive");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("should set mouseDownActive to true when handleMouseDown is called", async () => {
|
|
44
|
-
const user = userEvent.setup();
|
|
45
|
-
render(<TestDragHandleComponent />);
|
|
46
|
-
|
|
47
|
-
const triggerButton = screen.getByTestId("trigger-button");
|
|
48
|
-
const stateElement = screen.getByTestId("mouse-down-state");
|
|
49
|
-
|
|
50
|
-
expect(stateElement).toHaveTextContent("inactive");
|
|
51
|
-
|
|
52
|
-
await user.click(triggerButton);
|
|
53
|
-
|
|
54
|
-
expect(stateElement).toHaveTextContent("active");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should reset mouseDownActive to false when mouse up event occurs", async () => {
|
|
58
|
-
const user = userEvent.setup();
|
|
59
|
-
render(<TestDragHandleComponent />);
|
|
60
|
-
|
|
61
|
-
const triggerButton = screen.getByTestId("trigger-button");
|
|
62
|
-
const stateElement = screen.getByTestId("mouse-down-state");
|
|
63
|
-
|
|
64
|
-
// Trigger mouse down
|
|
65
|
-
await user.click(triggerButton);
|
|
66
|
-
expect(stateElement).toHaveTextContent("active");
|
|
67
|
-
|
|
68
|
-
// Simulate mouse up on window
|
|
69
|
-
fireEvent.mouseUp(window);
|
|
70
|
-
|
|
71
|
-
expect(stateElement).toHaveTextContent("inactive");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("should add and remove mouseup event listener correctly", async () => {
|
|
75
|
-
const addEventListenerSpy = vi.spyOn(window, "addEventListener");
|
|
76
|
-
const removeEventListenerSpy = vi.spyOn(window, "removeEventListener");
|
|
77
|
-
|
|
78
|
-
const user = userEvent.setup();
|
|
79
|
-
render(<TestDragHandleComponent />);
|
|
80
|
-
|
|
81
|
-
const triggerButton = screen.getByTestId("trigger-button");
|
|
82
|
-
|
|
83
|
-
await user.click(triggerButton);
|
|
84
|
-
|
|
85
|
-
expect(addEventListenerSpy).toHaveBeenCalledWith("mouseup", expect.any(Function), {
|
|
86
|
-
once: true,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Trigger mouseup to remove listener
|
|
90
|
-
fireEvent.mouseUp(window);
|
|
91
|
-
|
|
92
|
-
addEventListenerSpy.mockRestore();
|
|
93
|
-
removeEventListenerSpy.mockRestore();
|
|
94
|
-
});
|
|
95
|
-
});
|
|
6
|
+
import { TableColumnDragHandle } from "./use-drag-handle.hook";
|
|
96
7
|
|
|
97
8
|
describe("TableColumnDragHandle Component", () => {
|
|
98
9
|
const defaultProps = {
|
|
99
|
-
onMouseDown: vi.fn(),
|
|
100
10
|
overlayActive: false,
|
|
101
11
|
isFirstColumn: false,
|
|
102
12
|
isLastColumn: false,
|
|
@@ -197,81 +107,31 @@ describe("TableColumnDragHandle Component", () => {
|
|
|
197
107
|
});
|
|
198
108
|
|
|
199
109
|
describe("Event Handling", () => {
|
|
200
|
-
it("should
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const dragHandle = screen.getByRole("button");
|
|
207
|
-
await user.click(dragHandle);
|
|
208
|
-
|
|
209
|
-
expect(onMouseDownMock).toHaveBeenCalledTimes(1);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("should call onMouseDown when mouse down event is triggered", () => {
|
|
213
|
-
const onMouseDownMock = vi.fn();
|
|
214
|
-
|
|
215
|
-
render(<TableColumnDragHandle {...defaultProps} onMouseDown={onMouseDownMock} />);
|
|
216
|
-
|
|
217
|
-
const dragHandle = screen.getByRole("button");
|
|
218
|
-
fireEvent.mouseDown(dragHandle);
|
|
110
|
+
it("should pass through dragListeners props", () => {
|
|
111
|
+
const mockListener = vi.fn();
|
|
112
|
+
const dragListeners = {
|
|
113
|
+
onMouseDown: mockListener,
|
|
114
|
+
onTouchStart: mockListener,
|
|
115
|
+
};
|
|
219
116
|
|
|
220
|
-
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("should call onMouseDown when Enter key is pressed", async () => {
|
|
224
|
-
const user = userEvent.setup();
|
|
225
|
-
const onMouseDownMock = vi.fn();
|
|
226
|
-
|
|
227
|
-
render(<TableColumnDragHandle {...defaultProps} onMouseDown={onMouseDownMock} />);
|
|
228
|
-
|
|
229
|
-
const dragHandle = screen.getByRole("button");
|
|
230
|
-
dragHandle.focus();
|
|
231
|
-
await user.keyboard("{Enter}");
|
|
232
|
-
|
|
233
|
-
expect(onMouseDownMock).toHaveBeenCalledTimes(1);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("should call onMouseDown when Space key is pressed", async () => {
|
|
237
|
-
const user = userEvent.setup();
|
|
238
|
-
const onMouseDownMock = vi.fn();
|
|
239
|
-
|
|
240
|
-
render(<TableColumnDragHandle {...defaultProps} onMouseDown={onMouseDownMock} />);
|
|
117
|
+
render(<TableColumnDragHandle {...defaultProps} {...dragListeners} />);
|
|
241
118
|
|
|
242
119
|
const dragHandle = screen.getByRole("button");
|
|
243
|
-
dragHandle.focus();
|
|
244
|
-
await user.keyboard(" ");
|
|
245
|
-
|
|
246
|
-
expect(onMouseDownMock).toHaveBeenCalledTimes(1);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("should not call onMouseDown for other key presses", async () => {
|
|
250
|
-
const user = userEvent.setup();
|
|
251
|
-
const onMouseDownMock = vi.fn();
|
|
252
120
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const dragHandle = screen.getByRole("button");
|
|
256
|
-
dragHandle.focus();
|
|
257
|
-
await user.keyboard("{Escape}");
|
|
258
|
-
|
|
259
|
-
expect(onMouseDownMock).not.toHaveBeenCalled();
|
|
121
|
+
// Verify the listeners are attached
|
|
122
|
+
expect(dragHandle).toHaveAttribute("role", "button");
|
|
260
123
|
});
|
|
261
124
|
|
|
262
|
-
it("should
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
await user.click(dragHandle);
|
|
273
|
-
|
|
274
|
-
expect(onMouseDownMock).toHaveBeenCalledTimes(3);
|
|
125
|
+
it("should not throw when receiving additional props", () => {
|
|
126
|
+
expect(() => {
|
|
127
|
+
render(
|
|
128
|
+
<TableColumnDragHandle
|
|
129
|
+
{...defaultProps}
|
|
130
|
+
data-testid="custom-test-id"
|
|
131
|
+
customProp="value"
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}).not.toThrow();
|
|
275
135
|
});
|
|
276
136
|
});
|
|
277
137
|
|
|
@@ -337,19 +197,6 @@ describe("TableColumnDragHandle Component", () => {
|
|
|
337
197
|
});
|
|
338
198
|
|
|
339
199
|
describe("Props Validation", () => {
|
|
340
|
-
it("should handle undefined onMouseDown gracefully", () => {
|
|
341
|
-
expect(() => {
|
|
342
|
-
render(
|
|
343
|
-
<TableColumnDragHandle
|
|
344
|
-
{...defaultProps}
|
|
345
|
-
onMouseDown={
|
|
346
|
-
undefined as unknown as Parameters<typeof TableColumnDragHandle>[0]["onMouseDown"]
|
|
347
|
-
}
|
|
348
|
-
/>
|
|
349
|
-
);
|
|
350
|
-
}).not.toThrow();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
200
|
it("should handle empty aria label", () => {
|
|
354
201
|
render(<TableColumnDragHandle {...defaultProps} columnDragAriaLabel="" />);
|
|
355
202
|
|
|
@@ -1,83 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
closestCorners,
|
|
3
|
-
type DroppableContainer,
|
|
4
|
-
getFirstCollision,
|
|
5
|
-
KeyboardCode,
|
|
6
|
-
type KeyboardCoordinateGetter,
|
|
7
|
-
} from "@dnd-kit/core";
|
|
1
|
+
import { KeyboardCode, type KeyboardCoordinateGetter } from "@dnd-kit/core";
|
|
8
2
|
|
|
9
3
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Simple keyboard coordinate handler that moves the drag overlay
|
|
5
|
+
* left or right incrementally and lets collision detection determine
|
|
6
|
+
* which column we're over.
|
|
12
7
|
*/
|
|
13
8
|
export const enhancedColumnKeyboardCoordinates: KeyboardCoordinateGetter = (
|
|
14
9
|
event,
|
|
15
|
-
{ context: { active,
|
|
10
|
+
{ currentCoordinates, context: { active, droppableRects } }
|
|
16
11
|
) => {
|
|
17
|
-
if (!active || !
|
|
12
|
+
if (!active || !currentCoordinates) {
|
|
18
13
|
return;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
|
-
// Only handle left/right movements
|
|
22
|
-
if (event.code
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const filteredContainers: DroppableContainer[] = [];
|
|
26
|
-
|
|
27
|
-
droppableContainers.getEnabled().forEach((container) => {
|
|
28
|
-
if (container?.disabled) return;
|
|
29
|
-
|
|
30
|
-
const rect = droppableRects.get(container.id);
|
|
31
|
-
if (!rect) return;
|
|
32
|
-
|
|
33
|
-
// Filter based on direction
|
|
34
|
-
if (event.code === KeyboardCode.Right && collisionRect.left < rect.left) {
|
|
35
|
-
filteredContainers.push(container);
|
|
36
|
-
} else if (event.code === KeyboardCode.Left && collisionRect.left > rect.left) {
|
|
37
|
-
filteredContainers.push(container);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Sort containers by distance (nearest first) for left movement
|
|
42
|
-
if (event.code === KeyboardCode.Left && filteredContainers.length > 0) {
|
|
43
|
-
filteredContainers.sort((a, b) => {
|
|
44
|
-
const rectA = droppableRects.get(a.id);
|
|
45
|
-
const rectB = droppableRects.get(b.id);
|
|
46
|
-
if (!rectA || !rectB) return 0;
|
|
47
|
-
|
|
48
|
-
// Sort by closest to furthest from the current position (for left movement)
|
|
49
|
-
return rectB.left - rectA.left;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
16
|
+
// Only handle left/right movements
|
|
17
|
+
if (event.code !== KeyboardCode.Right && event.code !== KeyboardCode.Left) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
52
20
|
|
|
53
|
-
|
|
54
|
-
const collisions = closestCorners({
|
|
55
|
-
active,
|
|
56
|
-
collisionRect,
|
|
57
|
-
droppableRects,
|
|
58
|
-
droppableContainers: filteredContainers,
|
|
59
|
-
pointerCoordinates: null,
|
|
60
|
-
});
|
|
21
|
+
event.preventDefault();
|
|
61
22
|
|
|
62
|
-
|
|
23
|
+
// Get the active column's width to determine appropriate step size
|
|
24
|
+
const activeRect = droppableRects.get(active.id);
|
|
25
|
+
if (!activeRect) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
63
28
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
targetId = collisions[1].id;
|
|
67
|
-
}
|
|
29
|
+
// Move by half the width of the active column or max 100px
|
|
30
|
+
const stepSize = Math.min(activeRect.width * 0.5, 100);
|
|
68
31
|
|
|
69
|
-
|
|
70
|
-
const targetRect = droppableRects.get(targetId);
|
|
32
|
+
let newX: number;
|
|
71
33
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
34
|
+
if (event.code === KeyboardCode.Right) {
|
|
35
|
+
newX = currentCoordinates.x + stepSize;
|
|
36
|
+
} else {
|
|
37
|
+
newX = currentCoordinates.x - stepSize;
|
|
79
38
|
}
|
|
80
39
|
|
|
81
|
-
|
|
82
|
-
|
|
40
|
+
return {
|
|
41
|
+
x: newX,
|
|
42
|
+
y: currentCoordinates.y,
|
|
43
|
+
};
|
|
83
44
|
};
|