@pallasoft/ps-table-components 1.22.2 → 1.23.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.
Files changed (64) hide show
  1. package/dist/components/ps-data-table.d.ts +2 -1
  2. package/dist/components/ps-data-table.js +45 -10
  3. package/dist/components/ps-table-reorder-frame.js +15 -2
  4. package/dist/components/ps-table-reorder-handle.js +1 -1
  5. package/dist/components/ps-table-sort-header.js +1 -1
  6. package/dist/components/ps-table-sortable-row.d.ts +5 -1
  7. package/dist/components/ps-table-sortable-row.js +12 -4
  8. package/dist/constants/ps-table-drop-classes.d.ts +2 -0
  9. package/dist/constants/ps-table-drop-classes.js +8 -0
  10. package/dist/constants/ps-table-drop-placement.d.ts +6 -0
  11. package/dist/constants/ps-table-drop-placement.js +8 -0
  12. package/dist/constants/ps-table-pin-classes.d.ts +16 -0
  13. package/dist/constants/ps-table-pin-classes.js +19 -0
  14. package/dist/constants/table-breakpoint.d.ts +7 -0
  15. package/dist/constants/table-breakpoint.js +13 -0
  16. package/dist/esm/components/ps-data-table.js +46 -11
  17. package/dist/esm/components/ps-table-reorder-frame.js +15 -2
  18. package/dist/esm/components/ps-table-reorder-handle.js +1 -1
  19. package/dist/esm/components/ps-table-sort-header.js +1 -1
  20. package/dist/esm/components/ps-table-sortable-row.js +12 -4
  21. package/dist/esm/constants/ps-table-drop-classes.js +5 -0
  22. package/dist/esm/constants/ps-table-drop-placement.js +5 -0
  23. package/dist/esm/constants/ps-table-pin-classes.js +16 -0
  24. package/dist/esm/constants/table-breakpoint.js +10 -0
  25. package/dist/esm/hooks/use-ps-table-overflow.js +19 -0
  26. package/dist/esm/hooks/use-ps-table-reorder.js +89 -15
  27. package/dist/esm/index.js +10 -0
  28. package/dist/esm/interfaces/table-drop.js +1 -0
  29. package/dist/esm/interfaces/table-nesting-announcements.js +1 -0
  30. package/dist/esm/interfaces/table-overflow-edges.js +1 -0
  31. package/dist/esm/interfaces/table-pin-classes.js +1 -0
  32. package/dist/esm/interfaces/table-reorder-nesting-binding.js +1 -0
  33. package/dist/esm/interfaces/table-reorder-nesting.js +1 -0
  34. package/dist/esm/services/ps-table-overflow-service.js +11 -0
  35. package/dist/esm/services/ps-table-pin-service.js +22 -0
  36. package/dist/esm/services/ps-table-reorder-service.js +38 -1
  37. package/dist/hooks/use-ps-table-overflow.d.ts +6 -0
  38. package/dist/hooks/use-ps-table-overflow.js +23 -0
  39. package/dist/hooks/use-ps-table-reorder.d.ts +6 -2
  40. package/dist/hooks/use-ps-table-reorder.js +87 -13
  41. package/dist/index.d.ts +10 -0
  42. package/dist/index.js +10 -0
  43. package/dist/interfaces/table-column.d.ts +3 -0
  44. package/dist/interfaces/table-drop.d.ts +5 -0
  45. package/dist/interfaces/table-drop.js +2 -0
  46. package/dist/interfaces/table-nesting-announcements.d.ts +5 -0
  47. package/dist/interfaces/table-nesting-announcements.js +2 -0
  48. package/dist/interfaces/table-overflow-edges.d.ts +4 -0
  49. package/dist/interfaces/table-overflow-edges.js +2 -0
  50. package/dist/interfaces/table-pin-classes.d.ts +5 -0
  51. package/dist/interfaces/table-pin-classes.js +2 -0
  52. package/dist/interfaces/table-reorder-nesting-binding.d.ts +8 -0
  53. package/dist/interfaces/table-reorder-nesting-binding.js +2 -0
  54. package/dist/interfaces/table-reorder-nesting.d.ts +8 -0
  55. package/dist/interfaces/table-reorder-nesting.js +2 -0
  56. package/dist/interfaces/table-reorder-row-props.d.ts +3 -0
  57. package/dist/interfaces/table-reorder.d.ts +4 -2
  58. package/dist/services/ps-table-overflow-service.d.ts +6 -0
  59. package/dist/services/ps-table-overflow-service.js +15 -0
  60. package/dist/services/ps-table-pin-service.d.ts +5 -0
  61. package/dist/services/ps-table-pin-service.js +26 -0
  62. package/dist/services/ps-table-reorder-service.d.ts +11 -0
  63. package/dist/services/ps-table-reorder-service.js +38 -1
  64. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { PsDragGrip } from '@pallasoft/ps-core-components';
3
3
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
4
- export const PsTableReorderHandle = ({ activatorRef, className, label, ...buttonProps }) => (_jsx("button", { ...buttonProps, "aria-label": label, className: PallasoftClassNameService.merge('ps-focus-ring flex h-7 w-6 shrink-0 cursor-grab touch-none items-center justify-center rounded-ps-control text-ink/40 transition-colors hover:bg-mist hover:text-ink active:cursor-grabbing', className), onClick: (event) => event.stopPropagation(), ref: activatorRef, title: label, type: 'button', children: _jsx(PsDragGrip, {}) }));
4
+ export const PsTableReorderHandle = ({ activatorRef, className, label, ...buttonProps }) => (_jsx("button", { ...buttonProps, "aria-label": label, className: PallasoftClassNameService.merge('ps-focus-ring ps-touch-target relative flex h-7 w-6 shrink-0 cursor-grab touch-none items-center justify-center rounded-ps-control text-ink/40 transition-colors hover:bg-mist hover:text-ink active:cursor-grabbing', className), onClick: (event) => event.stopPropagation(), ref: activatorRef, title: label, type: 'button', children: _jsx(PsDragGrip, {}) }));
@@ -10,5 +10,5 @@ export const PsTableSortHeader = ({ align, columnId, label, onSort, sort, }) =>
10
10
  : PsIconKey.SortDescending;
11
11
  return (_jsxs("button", { className: PallasoftClassNameService.merge('ps-focus-ring group inline-flex items-center gap-1 rounded-ps-control uppercase tracking-[0.18em] transition-colors hover:text-ink', align === 'right' ? 'flex-row-reverse' : '', isSorted ? 'text-ink' : ''), onClick: () => onSort(columnId), type: 'button', children: [_jsx("span", { children: label }), _jsx(PsIcon, { "aria-hidden": 'true', className: PallasoftClassNameService.merge('block h-3.5 w-3.5 shrink-0', isSorted
12
12
  ? 'text-primary'
13
- : 'opacity-0 group-hover:opacity-60 group-focus-visible:opacity-60'), iconKey: iconKey })] }));
13
+ : 'opacity-0 group-hover:opacity-60 group-focus-visible:opacity-60 [@media(hover:none)]:opacity-40'), iconKey: iconKey })] }));
14
14
  };
@@ -2,10 +2,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useSortable } from '@dnd-kit/sortable';
3
3
  import { CSS } from '@dnd-kit/utilities';
4
4
  import { PsDragConfig } from '@pallasoft/ps-core-components';
5
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
6
+ import { PsTableDropClasses } from '../constants/ps-table-drop-classes';
5
7
  import { PsTableReorderHandle } from './ps-table-reorder-handle';
6
- export const PsTableSortableRow = ({ children, className, handleLabel, hasHandle, isDisabled, measureRef, rowId, style, ...rowProps }) => {
7
- const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({ disabled: isDisabled || !hasHandle, id: rowId });
8
- return (_jsxs("tr", { ...rowProps, className: className, ref: (element) => {
8
+ export const PsTableSortableRow = ({ children, className, dropPlacement = null, handleCellClassName, handleLabel, hasHandle, isDisabled, isDropTarget = false, measureRef, rowId, style, ...rowProps }) => {
9
+ const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({
10
+ disabled: {
11
+ draggable: isDisabled || !hasHandle,
12
+ droppable: isDisabled || (!hasHandle && !isDropTarget),
13
+ },
14
+ id: rowId,
15
+ });
16
+ return (_jsxs("tr", { ...rowProps, className: PallasoftClassNameService.merge(className, dropPlacement === null ? '' : PsTableDropClasses[dropPlacement]), ref: (element) => {
9
17
  setNodeRef(element);
10
18
  if (measureRef)
11
19
  measureRef(element);
@@ -14,5 +22,5 @@ export const PsTableSortableRow = ({ children, className, handleLabel, hasHandle
14
22
  opacity: isDragging ? PsDragConfig.placeholderOpacity : style?.opacity,
15
23
  transform: CSS.Translate.toString(transform),
16
24
  transition,
17
- }, children: [_jsx("td", { className: 'w-9 pl-3 pr-0', onClick: (event) => event.stopPropagation(), children: hasHandle ? (_jsx(PsTableReorderHandle, { ...attributes, ...listeners, activatorRef: setActivatorNodeRef, label: handleLabel })) : null }), children] }));
25
+ }, children: [_jsx("td", { className: PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', handleCellClassName), onClick: (event) => event.stopPropagation(), children: hasHandle ? (_jsx(PsTableReorderHandle, { ...attributes, ...listeners, activatorRef: setActivatorNodeRef, label: handleLabel })) : null }), children] }));
18
26
  };
@@ -0,0 +1,5 @@
1
+ export const PsTableDropClasses = {
2
+ after: '[&>td]:shadow-[inset_0_-2px_0_0_rgb(var(--ps-color-primary-rgb))]',
3
+ before: '[&>td]:shadow-[inset_0_2px_0_0_rgb(var(--ps-color-primary-rgb))]',
4
+ into: 'bg-primary/10 [&>td]:shadow-[inset_0_0_0_2px_rgb(var(--ps-color-primary-rgb)/0.5)]',
5
+ };
@@ -0,0 +1,5 @@
1
+ export const PsTableDropPlacement = {
2
+ after: 'after',
3
+ before: 'before',
4
+ into: 'into',
5
+ };
@@ -0,0 +1,16 @@
1
+ export const PsTablePinClasses = {
2
+ cell: 'ps-table-pin',
3
+ edge: 'ps-table-pin-start-edge',
4
+ end: 'ps-table-pin ps-table-pin-end right-0',
5
+ first: {
6
+ both: 'left-[4.75rem]',
7
+ none: 'left-0',
8
+ reorder: 'left-9',
9
+ select: 'left-10',
10
+ },
11
+ reorder: 'left-0',
12
+ select: {
13
+ alone: 'left-0',
14
+ withReorder: 'left-9',
15
+ },
16
+ };
@@ -0,0 +1,10 @@
1
+ export const PsTableBreakpoint = {
2
+ md: 'md',
3
+ lg: 'lg',
4
+ xl: 'xl',
5
+ };
6
+ export const PsTableHideBelowClasses = {
7
+ md: 'hidden md:table-cell',
8
+ lg: 'hidden lg:table-cell',
9
+ xl: 'hidden xl:table-cell',
10
+ };
@@ -0,0 +1,19 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { PsTableOverflowService } from '../services/ps-table-overflow-service';
3
+ export const usePsTableOverflow = (layoutKey) => {
4
+ const ref = useRef(null);
5
+ const [edges, setEdges] = useState(PsTableOverflowService.none);
6
+ const update = useCallback(() => {
7
+ const element = ref.current;
8
+ if (element === null)
9
+ return;
10
+ const next = PsTableOverflowService.edges(element.scrollLeft, element.clientWidth, element.scrollWidth);
11
+ setEdges((current) => (PsTableOverflowService.isSame(current, next) ? current : next));
12
+ }, []);
13
+ useEffect(() => {
14
+ update();
15
+ window.addEventListener('resize', update);
16
+ return () => window.removeEventListener('resize', update);
17
+ }, [layoutKey, update]);
18
+ return { edges, ref, update };
19
+ };
@@ -1,9 +1,16 @@
1
- import { useMemo, useState } from 'react';
2
- import { closestCenter, } from '@dnd-kit/core';
1
+ import { useMemo, useRef, useState } from 'react';
2
+ import { closestCenter, pointerWithin, } from '@dnd-kit/core';
3
3
  import { usePsDragSensors } from '@pallasoft/ps-core-components';
4
+ import { PsTableDropPlacement } from '../constants/ps-table-drop-placement';
4
5
  import { PsTableReorderService } from '../services/ps-table-reorder-service';
5
- export const usePsTableReorder = ({ announcements: texts, enabled, instructions, items, onReorder, }) => {
6
+ const pointerThenCenter = (args) => {
7
+ const within = pointerWithin(args);
8
+ return within.length > 0 ? within : closestCenter(args);
9
+ };
10
+ export const usePsTableReorder = ({ announcements: texts, enabled, instructions, items, nesting, onReorder, }) => {
6
11
  const [activeId, setActiveId] = useState(null);
12
+ const [drop, setDrop] = useState(null);
13
+ const dropRef = useRef(null);
7
14
  const sensors = usePsDragSensors();
8
15
  const runs = useMemo(() => enabled
9
16
  ? PsTableReorderService.runs(items, (item) => item.groupKey, (item) => item.id)
@@ -11,6 +18,7 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
11
18
  const placements = useMemo(() => PsTableReorderService.placements(runs, (item) => item.id), [runs]);
12
19
  const activePlacement = activeId === null ? undefined : placements.get(activeId);
13
20
  const itemById = useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
21
+ const indexById = useMemo(() => new Map(items.map((item, index) => [item.id, index])), [items]);
14
22
  const labelOf = (id) => {
15
23
  const item = itemById.get(id);
16
24
  return item === undefined ? id : item.label;
@@ -23,23 +31,83 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
23
31
  const placement = placements.get(id);
24
32
  return placement === undefined ? 0 : placement.size;
25
33
  };
34
+ const edgeFor = (activeKey, overId) => {
35
+ const activeIndex = indexById.get(activeKey);
36
+ const overIndex = indexById.get(overId);
37
+ return activeIndex !== undefined && overIndex !== undefined && activeIndex < overIndex
38
+ ? PsTableDropPlacement.after
39
+ : PsTableDropPlacement.before;
40
+ };
41
+ const resolveDrop = (event) => {
42
+ if (nesting === undefined || event.over === null || event.over.id === event.active.id)
43
+ return null;
44
+ const activeKey = String(event.active.id);
45
+ const overId = String(event.over.id);
46
+ const placement = PsTableReorderService.dropPlacement(PsTableReorderService.pointerY(event.activatorEvent, event.delta.y), event.over.rect, edgeFor(activeKey, overId), (candidate) => nesting.canDrop(activeKey, overId, candidate));
47
+ return placement === null ? null : { overId, placement };
48
+ };
49
+ const announcedDrop = (activeKey, overId) => {
50
+ if (nesting === undefined)
51
+ return null;
52
+ const known = dropRef.current;
53
+ if (known !== null && known.overId === overId)
54
+ return known;
55
+ const placement = edgeFor(activeKey, overId);
56
+ return nesting.canDrop(activeKey, overId, placement) ? { overId, placement } : null;
57
+ };
58
+ const describeDrop = (active, target) => nesting === undefined
59
+ ? undefined
60
+ : nesting.announcements[target.placement](labelOf(active), labelOf(target.overId));
26
61
  const announcements = {
27
62
  onDragCancel: ({ active }) => texts.cancelled(labelOf(String(active.id))),
28
- onDragEnd: ({ active, over }) => over === null
29
- ? texts.cancelled(labelOf(String(active.id)))
30
- : texts.dropped(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id))),
31
- onDragOver: ({ active, over }) => over === null
32
- ? undefined
33
- : texts.moved(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id))),
63
+ onDragEnd: ({ active, over }) => {
64
+ if (nesting !== undefined) {
65
+ const target = over === null ? null : announcedDrop(String(active.id), String(over.id));
66
+ return target === null
67
+ ? texts.cancelled(labelOf(String(active.id)))
68
+ : describeDrop(String(active.id), target);
69
+ }
70
+ return over === null
71
+ ? texts.cancelled(labelOf(String(active.id)))
72
+ : texts.dropped(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id)));
73
+ },
74
+ onDragOver: ({ active, over }) => {
75
+ if (over === null)
76
+ return undefined;
77
+ if (nesting !== undefined) {
78
+ const target = announcedDrop(String(active.id), String(over.id));
79
+ return target === null ? undefined : describeDrop(String(active.id), target);
80
+ }
81
+ return texts.moved(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id)));
82
+ },
34
83
  onDragStart: ({ active }) => texts.lifted(labelOf(String(active.id)), positionOf(String(active.id)), countOf(String(active.id))),
35
84
  };
36
- const handleDragEnd = ({ active, over }) => {
85
+ const handleDragMove = (event) => {
86
+ const next = resolveDrop(event);
87
+ dropRef.current = next;
88
+ setDrop((current) => current !== null &&
89
+ next !== null &&
90
+ current.overId === next.overId &&
91
+ current.placement === next.placement
92
+ ? current
93
+ : next);
94
+ };
95
+ const handleDragEnd = (event) => {
96
+ const { active, over } = event;
97
+ const target = resolveDrop(event);
37
98
  setActiveId(null);
38
- if (over === null || active.id === over.id)
99
+ setDrop(null);
100
+ dropRef.current = null;
101
+ if (nesting !== undefined) {
102
+ if (target !== null)
103
+ nesting.onMove(String(active.id), target.overId, target.placement);
104
+ return;
105
+ }
106
+ if (over === null || active.id === over.id || onReorder === undefined)
39
107
  return;
40
108
  const placement = placements.get(String(active.id));
41
- const target = placements.get(String(over.id));
42
- if (placement === undefined || target === undefined || placement.runId !== target.runId)
109
+ const sibling = placements.get(String(over.id));
110
+ if (placement === undefined || sibling === undefined || placement.runId !== sibling.runId)
43
111
  return;
44
112
  const run = runs.find((candidate) => candidate.id === placement.runId);
45
113
  if (run === undefined)
@@ -51,13 +119,19 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
51
119
  activeRunId: activePlacement === undefined ? null : activePlacement.runId,
52
120
  contextProps: {
53
121
  accessibility: { announcements, screenReaderInstructions: { draggable: instructions } },
54
- collisionDetection: closestCenter,
122
+ collisionDetection: nesting === undefined ? closestCenter : pointerThenCenter,
55
123
  modifiers: [PsTableReorderService.lockToVerticalAxis],
56
- onDragCancel: () => setActiveId(null),
124
+ onDragCancel: () => {
125
+ setActiveId(null);
126
+ setDrop(null);
127
+ dropRef.current = null;
128
+ },
57
129
  onDragEnd: handleDragEnd,
130
+ onDragMove: nesting === undefined ? undefined : handleDragMove,
58
131
  onDragStart: ({ active }) => setActiveId(String(active.id)),
59
132
  sensors,
60
133
  },
134
+ drop,
61
135
  placements,
62
136
  };
63
137
  };
package/dist/esm/index.js CHANGED
@@ -3,7 +3,9 @@ export * from './components/ps-streamed-rows';
3
3
  export * from './components/ps-table-stream-status';
4
4
  export * from './components/ps-table-group-header';
5
5
  export * from './components/ps-table-pagination';
6
+ export * from './constants/table-breakpoint';
6
7
  export * from './constants/table-card-role';
8
+ export * from './constants/ps-table-pin-classes';
7
9
  export * from './constants/table-column-size';
8
10
  export * from './constants/table-filter-kind';
9
11
  export * from './interfaces/table-column';
@@ -27,6 +29,11 @@ export * from './services/ps-table-group-service';
27
29
  export * from './services/ps-table-query-service';
28
30
  export * from './services/ps-table-search-service';
29
31
  export * from './services/ps-table-scroll-service';
32
+ export * from './services/ps-table-pin-service';
33
+ export * from './services/ps-table-overflow-service';
34
+ export * from './hooks/use-ps-table-overflow';
35
+ export * from './interfaces/table-pin-classes';
36
+ export * from './interfaces/table-overflow-edges';
30
37
  export * from './services/ps-table-stream-service';
31
38
  export * from './components/ps-table-export-button';
32
39
  export * from './constants/ps-table-download-config';
@@ -36,4 +43,7 @@ export * from './hooks/use-ps-table-page';
36
43
  export * from './interfaces/ps-table-page-state';
37
44
  export * from './interfaces/table-reorder';
38
45
  export * from './interfaces/table-reorder-announcements';
46
+ export * from './interfaces/table-reorder-nesting';
47
+ export * from './interfaces/table-nesting-announcements';
48
+ export * from './constants/ps-table-drop-placement';
39
49
  export * from './services/ps-table-reorder-service';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ export class PsTableOverflowService {
2
+ static edges(scrollLeft, clientWidth, scrollWidth) {
3
+ const start = Math.abs(scrollLeft) > 0.5;
4
+ const end = scrollWidth - clientWidth - Math.abs(scrollLeft) > 0.5;
5
+ return { end, start };
6
+ }
7
+ static isSame(current, next) {
8
+ return current.end === next.end && current.start === next.start;
9
+ }
10
+ }
11
+ PsTableOverflowService.none = { end: false, start: false };
@@ -0,0 +1,22 @@
1
+ import { PsTablePinClasses } from '../constants/ps-table-pin-classes';
2
+ export class PsTablePinService {
3
+ static leading(isPinned, hasReorder, isSelectable) {
4
+ if (!isPinned)
5
+ return { first: '', reorder: '', select: '' };
6
+ const firstOffset = hasReorder && isSelectable
7
+ ? PsTablePinClasses.first.both
8
+ : hasReorder
9
+ ? PsTablePinClasses.first.reorder
10
+ : isSelectable
11
+ ? PsTablePinClasses.first.select
12
+ : PsTablePinClasses.first.none;
13
+ return {
14
+ first: `${PsTablePinClasses.cell} ${PsTablePinClasses.edge} ${firstOffset}`,
15
+ reorder: `${PsTablePinClasses.cell} ${PsTablePinClasses.reorder}`,
16
+ select: `${PsTablePinClasses.cell} ${hasReorder ? PsTablePinClasses.select.withReorder : PsTablePinClasses.select.alone}`,
17
+ };
18
+ }
19
+ static end(isPinned) {
20
+ return isPinned ? PsTablePinClasses.end : '';
21
+ }
22
+ }
@@ -1,3 +1,4 @@
1
+ import { PsTableDropPlacement } from '../constants/ps-table-drop-placement';
1
2
  export class PsTableReorderService {
2
3
  static runs(items, getKey, getId) {
3
4
  const runs = [];
@@ -32,11 +33,47 @@ export class PsTableReorderService {
32
33
  next.splice(to, 0, moved);
33
34
  return next;
34
35
  }
36
+ static pointerY(event, deltaY) {
37
+ if (event instanceof MouseEvent)
38
+ return event.clientY + deltaY;
39
+ if (typeof TouchEvent !== 'undefined' &&
40
+ event instanceof TouchEvent &&
41
+ event.touches.length > 0)
42
+ return event.touches[0].clientY + deltaY;
43
+ return null;
44
+ }
45
+ static dropPlacement(pointerY, rect, fallback, isAllowed) {
46
+ const allowed = PsTableReorderService.candidates(pointerY, rect, fallback).find(isAllowed);
47
+ return allowed === undefined ? null : allowed;
48
+ }
49
+ static candidates(pointerY, rect, fallback) {
50
+ if (pointerY === null || rect.height <= 0)
51
+ return [fallback];
52
+ const ratio = (pointerY - rect.top) / rect.height;
53
+ if (ratio < PsTableReorderService.edgeRatio)
54
+ return [PsTableDropPlacement.before, PsTableDropPlacement.into];
55
+ if (ratio > 1 - PsTableReorderService.edgeRatio)
56
+ return [PsTableDropPlacement.after, PsTableDropPlacement.into];
57
+ return [
58
+ PsTableDropPlacement.into,
59
+ ratio < 0.5 ? PsTableDropPlacement.before : PsTableDropPlacement.after,
60
+ ];
61
+ }
35
62
  }
63
+ PsTableReorderService.edgeRatio = 0.25;
64
+ PsTableReorderService.treeKey = 'tree';
36
65
  PsTableReorderService.plain = {
37
66
  Row: 'tr',
38
67
  isActive: false,
39
- rowProps: (rowId) => ({ handleLabel: '', hasHandle: false, isDisabled: true, rowId }),
68
+ rowProps: (rowId) => ({
69
+ dropPlacement: null,
70
+ handleLabel: '',
71
+ hasHandle: false,
72
+ isDisabled: true,
73
+ isDropTarget: false,
74
+ rowId,
75
+ }),
40
76
  run: (_runId, _ids, children) => children,
41
77
  };
78
+ PsTableReorderService.still = () => null;
42
79
  PsTableReorderService.lockToVerticalAxis = ({ transform }) => ({ ...transform, x: 0 });
@@ -0,0 +1,6 @@
1
+ import type { PsTableOverflowEdges } from '../interfaces/table-overflow-edges';
2
+ export declare const usePsTableOverflow: (layoutKey: string) => {
3
+ edges: PsTableOverflowEdges;
4
+ ref: import("react").RefObject<HTMLDivElement>;
5
+ update: () => void;
6
+ };
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsTableOverflow = void 0;
4
+ const react_1 = require("react");
5
+ const ps_table_overflow_service_1 = require("../services/ps-table-overflow-service");
6
+ const usePsTableOverflow = (layoutKey) => {
7
+ const ref = (0, react_1.useRef)(null);
8
+ const [edges, setEdges] = (0, react_1.useState)(ps_table_overflow_service_1.PsTableOverflowService.none);
9
+ const update = (0, react_1.useCallback)(() => {
10
+ const element = ref.current;
11
+ if (element === null)
12
+ return;
13
+ const next = ps_table_overflow_service_1.PsTableOverflowService.edges(element.scrollLeft, element.clientWidth, element.scrollWidth);
14
+ setEdges((current) => (ps_table_overflow_service_1.PsTableOverflowService.isSame(current, next) ? current : next));
15
+ }, []);
16
+ (0, react_1.useEffect)(() => {
17
+ update();
18
+ window.addEventListener('resize', update);
19
+ return () => window.removeEventListener('resize', update);
20
+ }, [layoutKey, update]);
21
+ return { edges, ref, update };
22
+ };
23
+ exports.usePsTableOverflow = usePsTableOverflow;
@@ -1,18 +1,22 @@
1
1
  import { type DndContextProps } from '@dnd-kit/core';
2
+ import type { PsTableDrop } from '../interfaces/table-drop';
2
3
  import type { PsTableReorderAnnouncements } from '../interfaces/table-reorder-announcements';
3
4
  import type { PsTableReorderItem } from '../interfaces/table-reorder-item';
5
+ import type { PsTableReorderNestingBinding } from '../interfaces/table-reorder-nesting-binding';
4
6
  import type { PsTableReorderPlacement } from '../interfaces/table-reorder-placement';
5
7
  export interface UsePsTableReorderInput {
6
8
  announcements: PsTableReorderAnnouncements;
7
9
  enabled: boolean;
8
10
  instructions: string;
9
11
  items: PsTableReorderItem[];
10
- onReorder: (groupKey: string, orderedIds: string[]) => void;
12
+ nesting?: PsTableReorderNestingBinding;
13
+ onReorder?: (groupKey: string, orderedIds: string[]) => void;
11
14
  }
12
15
  export interface PsTableReorderState {
13
16
  activeId: string | null;
14
17
  activeRunId: string | null;
15
18
  contextProps: Omit<DndContextProps, 'children'>;
19
+ drop: PsTableDrop | null;
16
20
  placements: Map<string, PsTableReorderPlacement>;
17
21
  }
18
- export declare const usePsTableReorder: ({ announcements: texts, enabled, instructions, items, onReorder, }: UsePsTableReorderInput) => PsTableReorderState;
22
+ export declare const usePsTableReorder: ({ announcements: texts, enabled, instructions, items, nesting, onReorder, }: UsePsTableReorderInput) => PsTableReorderState;
@@ -4,9 +4,16 @@ exports.usePsTableReorder = void 0;
4
4
  const react_1 = require("react");
5
5
  const core_1 = require("@dnd-kit/core");
6
6
  const ps_core_components_1 = require("@pallasoft/ps-core-components");
7
+ const ps_table_drop_placement_1 = require("../constants/ps-table-drop-placement");
7
8
  const ps_table_reorder_service_1 = require("../services/ps-table-reorder-service");
8
- const usePsTableReorder = ({ announcements: texts, enabled, instructions, items, onReorder, }) => {
9
+ const pointerThenCenter = (args) => {
10
+ const within = (0, core_1.pointerWithin)(args);
11
+ return within.length > 0 ? within : (0, core_1.closestCenter)(args);
12
+ };
13
+ const usePsTableReorder = ({ announcements: texts, enabled, instructions, items, nesting, onReorder, }) => {
9
14
  const [activeId, setActiveId] = (0, react_1.useState)(null);
15
+ const [drop, setDrop] = (0, react_1.useState)(null);
16
+ const dropRef = (0, react_1.useRef)(null);
10
17
  const sensors = (0, ps_core_components_1.usePsDragSensors)();
11
18
  const runs = (0, react_1.useMemo)(() => enabled
12
19
  ? ps_table_reorder_service_1.PsTableReorderService.runs(items, (item) => item.groupKey, (item) => item.id)
@@ -14,6 +21,7 @@ const usePsTableReorder = ({ announcements: texts, enabled, instructions, items,
14
21
  const placements = (0, react_1.useMemo)(() => ps_table_reorder_service_1.PsTableReorderService.placements(runs, (item) => item.id), [runs]);
15
22
  const activePlacement = activeId === null ? undefined : placements.get(activeId);
16
23
  const itemById = (0, react_1.useMemo)(() => new Map(items.map((item) => [item.id, item])), [items]);
24
+ const indexById = (0, react_1.useMemo)(() => new Map(items.map((item, index) => [item.id, index])), [items]);
17
25
  const labelOf = (id) => {
18
26
  const item = itemById.get(id);
19
27
  return item === undefined ? id : item.label;
@@ -26,23 +34,83 @@ const usePsTableReorder = ({ announcements: texts, enabled, instructions, items,
26
34
  const placement = placements.get(id);
27
35
  return placement === undefined ? 0 : placement.size;
28
36
  };
37
+ const edgeFor = (activeKey, overId) => {
38
+ const activeIndex = indexById.get(activeKey);
39
+ const overIndex = indexById.get(overId);
40
+ return activeIndex !== undefined && overIndex !== undefined && activeIndex < overIndex
41
+ ? ps_table_drop_placement_1.PsTableDropPlacement.after
42
+ : ps_table_drop_placement_1.PsTableDropPlacement.before;
43
+ };
44
+ const resolveDrop = (event) => {
45
+ if (nesting === undefined || event.over === null || event.over.id === event.active.id)
46
+ return null;
47
+ const activeKey = String(event.active.id);
48
+ const overId = String(event.over.id);
49
+ const placement = ps_table_reorder_service_1.PsTableReorderService.dropPlacement(ps_table_reorder_service_1.PsTableReorderService.pointerY(event.activatorEvent, event.delta.y), event.over.rect, edgeFor(activeKey, overId), (candidate) => nesting.canDrop(activeKey, overId, candidate));
50
+ return placement === null ? null : { overId, placement };
51
+ };
52
+ const announcedDrop = (activeKey, overId) => {
53
+ if (nesting === undefined)
54
+ return null;
55
+ const known = dropRef.current;
56
+ if (known !== null && known.overId === overId)
57
+ return known;
58
+ const placement = edgeFor(activeKey, overId);
59
+ return nesting.canDrop(activeKey, overId, placement) ? { overId, placement } : null;
60
+ };
61
+ const describeDrop = (active, target) => nesting === undefined
62
+ ? undefined
63
+ : nesting.announcements[target.placement](labelOf(active), labelOf(target.overId));
29
64
  const announcements = {
30
65
  onDragCancel: ({ active }) => texts.cancelled(labelOf(String(active.id))),
31
- onDragEnd: ({ active, over }) => over === null
32
- ? texts.cancelled(labelOf(String(active.id)))
33
- : texts.dropped(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id))),
34
- onDragOver: ({ active, over }) => over === null
35
- ? undefined
36
- : texts.moved(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id))),
66
+ onDragEnd: ({ active, over }) => {
67
+ if (nesting !== undefined) {
68
+ const target = over === null ? null : announcedDrop(String(active.id), String(over.id));
69
+ return target === null
70
+ ? texts.cancelled(labelOf(String(active.id)))
71
+ : describeDrop(String(active.id), target);
72
+ }
73
+ return over === null
74
+ ? texts.cancelled(labelOf(String(active.id)))
75
+ : texts.dropped(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id)));
76
+ },
77
+ onDragOver: ({ active, over }) => {
78
+ if (over === null)
79
+ return undefined;
80
+ if (nesting !== undefined) {
81
+ const target = announcedDrop(String(active.id), String(over.id));
82
+ return target === null ? undefined : describeDrop(String(active.id), target);
83
+ }
84
+ return texts.moved(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id)));
85
+ },
37
86
  onDragStart: ({ active }) => texts.lifted(labelOf(String(active.id)), positionOf(String(active.id)), countOf(String(active.id))),
38
87
  };
39
- const handleDragEnd = ({ active, over }) => {
88
+ const handleDragMove = (event) => {
89
+ const next = resolveDrop(event);
90
+ dropRef.current = next;
91
+ setDrop((current) => current !== null &&
92
+ next !== null &&
93
+ current.overId === next.overId &&
94
+ current.placement === next.placement
95
+ ? current
96
+ : next);
97
+ };
98
+ const handleDragEnd = (event) => {
99
+ const { active, over } = event;
100
+ const target = resolveDrop(event);
40
101
  setActiveId(null);
41
- if (over === null || active.id === over.id)
102
+ setDrop(null);
103
+ dropRef.current = null;
104
+ if (nesting !== undefined) {
105
+ if (target !== null)
106
+ nesting.onMove(String(active.id), target.overId, target.placement);
107
+ return;
108
+ }
109
+ if (over === null || active.id === over.id || onReorder === undefined)
42
110
  return;
43
111
  const placement = placements.get(String(active.id));
44
- const target = placements.get(String(over.id));
45
- if (placement === undefined || target === undefined || placement.runId !== target.runId)
112
+ const sibling = placements.get(String(over.id));
113
+ if (placement === undefined || sibling === undefined || placement.runId !== sibling.runId)
46
114
  return;
47
115
  const run = runs.find((candidate) => candidate.id === placement.runId);
48
116
  if (run === undefined)
@@ -54,13 +122,19 @@ const usePsTableReorder = ({ announcements: texts, enabled, instructions, items,
54
122
  activeRunId: activePlacement === undefined ? null : activePlacement.runId,
55
123
  contextProps: {
56
124
  accessibility: { announcements, screenReaderInstructions: { draggable: instructions } },
57
- collisionDetection: core_1.closestCenter,
125
+ collisionDetection: nesting === undefined ? core_1.closestCenter : pointerThenCenter,
58
126
  modifiers: [ps_table_reorder_service_1.PsTableReorderService.lockToVerticalAxis],
59
- onDragCancel: () => setActiveId(null),
127
+ onDragCancel: () => {
128
+ setActiveId(null);
129
+ setDrop(null);
130
+ dropRef.current = null;
131
+ },
60
132
  onDragEnd: handleDragEnd,
133
+ onDragMove: nesting === undefined ? undefined : handleDragMove,
61
134
  onDragStart: ({ active }) => setActiveId(String(active.id)),
62
135
  sensors,
63
136
  },
137
+ drop,
64
138
  placements,
65
139
  };
66
140
  };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,9 @@ export * from './components/ps-streamed-rows';
3
3
  export * from './components/ps-table-stream-status';
4
4
  export * from './components/ps-table-group-header';
5
5
  export * from './components/ps-table-pagination';
6
+ export * from './constants/table-breakpoint';
6
7
  export * from './constants/table-card-role';
8
+ export * from './constants/ps-table-pin-classes';
7
9
  export * from './constants/table-column-size';
8
10
  export * from './constants/table-filter-kind';
9
11
  export * from './interfaces/table-column';
@@ -27,6 +29,11 @@ export * from './services/ps-table-group-service';
27
29
  export * from './services/ps-table-query-service';
28
30
  export * from './services/ps-table-search-service';
29
31
  export * from './services/ps-table-scroll-service';
32
+ export * from './services/ps-table-pin-service';
33
+ export * from './services/ps-table-overflow-service';
34
+ export * from './hooks/use-ps-table-overflow';
35
+ export * from './interfaces/table-pin-classes';
36
+ export * from './interfaces/table-overflow-edges';
30
37
  export * from './services/ps-table-stream-service';
31
38
  export * from './components/ps-table-export-button';
32
39
  export * from './constants/ps-table-download-config';
@@ -36,4 +43,7 @@ export * from './hooks/use-ps-table-page';
36
43
  export * from './interfaces/ps-table-page-state';
37
44
  export * from './interfaces/table-reorder';
38
45
  export * from './interfaces/table-reorder-announcements';
46
+ export * from './interfaces/table-reorder-nesting';
47
+ export * from './interfaces/table-nesting-announcements';
48
+ export * from './constants/ps-table-drop-placement';
39
49
  export * from './services/ps-table-reorder-service';