@edux-design/tree-select 0.2.5 → 0.3.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/CHANGELOG.md +83 -0
- package/README.md +94 -3
- package/dist/index.css +39 -0
- package/dist/index.d.mts +92 -19
- package/dist/index.d.ts +92 -19
- package/dist/index.js +222 -75
- package/dist/index.mjs +222 -75
- package/docs/DS15_INVENTORY.md +52 -0
- package/docs/DS15_VERIFICATION.md +62 -0
- package/package.json +7 -14
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/elements/TreeSelect.jsx
|
|
2
|
-
import React3, { useCallback as useCallback2, useMemo as useMemo2, useRef, useState } from "react";
|
|
2
|
+
import React3, { forwardRef, useCallback as useCallback2, useImperativeHandle, useMemo as useMemo2, useRef, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
DndContext,
|
|
5
5
|
DragOverlay,
|
|
@@ -30,6 +30,21 @@ import {
|
|
|
30
30
|
TooltipTrigger
|
|
31
31
|
} from "@edux-design/tooltips";
|
|
32
32
|
|
|
33
|
+
// src/elements/treeSelectInteraction.js
|
|
34
|
+
function isTreeRowControl(event) {
|
|
35
|
+
const control = event.target?.closest?.(
|
|
36
|
+
'button, input, textarea, select, a[href], [contenteditable="true"], [role="button"], [role="checkbox"], [role="menuitem"], [tabindex]:not([tabindex="-1"]), [data-tree-interactive]'
|
|
37
|
+
);
|
|
38
|
+
return Boolean(control && control !== event.currentTarget && event.currentTarget.contains(control));
|
|
39
|
+
}
|
|
40
|
+
function hasActivationModifier(event) {
|
|
41
|
+
return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
|
42
|
+
}
|
|
43
|
+
function getTreeItemLabel(node, getItemLabel) {
|
|
44
|
+
if (getItemLabel) return getItemLabel(node);
|
|
45
|
+
return typeof node.label === "string" || typeof node.label === "number" ? String(node.label) : String(node.id);
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
// src/elements/treeSelectConstants.js
|
|
34
49
|
var HANDLE_WIDTH = 28;
|
|
35
50
|
var TOGGLE_WIDTH = 28;
|
|
@@ -38,6 +53,8 @@ var INDICATOR_INSET = HANDLE_WIDTH + TOGGLE_WIDTH + 12;
|
|
|
38
53
|
|
|
39
54
|
// src/elements/TreeRow.jsx
|
|
40
55
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
56
|
+
var DRAG_ROW_BACKGROUND = "color-mix(in srgb, var(--color-fg-base) 4%, transparent)";
|
|
57
|
+
var SELECTED_ROW_BACKGROUND = "color-mix(in srgb, var(--color-primary-600) 8%, transparent)";
|
|
41
58
|
var TreeRow = ({
|
|
42
59
|
item,
|
|
43
60
|
depth,
|
|
@@ -49,6 +66,11 @@ var TreeRow = ({
|
|
|
49
66
|
dragAttributes,
|
|
50
67
|
dragRef,
|
|
51
68
|
rowRef,
|
|
69
|
+
rowAccessibility,
|
|
70
|
+
labelContext,
|
|
71
|
+
accessibleLabel,
|
|
72
|
+
rowClassName,
|
|
73
|
+
labelClassName,
|
|
52
74
|
isDragging,
|
|
53
75
|
isSelected,
|
|
54
76
|
onRowClick,
|
|
@@ -81,11 +103,12 @@ var TreeRow = ({
|
|
|
81
103
|
depth,
|
|
82
104
|
parentId: item.parentId,
|
|
83
105
|
hasChildren: item.hasChildren,
|
|
84
|
-
isExpanded
|
|
106
|
+
isExpanded,
|
|
107
|
+
...labelContext
|
|
85
108
|
}) : label;
|
|
86
109
|
const showToggle = item.hasChildren;
|
|
87
110
|
const [additionalMenuOpen, setAdditionalMenuOpen] = React.useState(false);
|
|
88
|
-
const rowBackground = isDragging ?
|
|
111
|
+
const rowBackground = isDragging ? DRAG_ROW_BACKGROUND : isSelected ? SELECTED_ROW_BACKGROUND : void 0;
|
|
89
112
|
const rowPaddingLeft = depth * INDENT_WIDTH + 8;
|
|
90
113
|
const hasAdditionalActionsMenu = Boolean(additionalActionsMenu);
|
|
91
114
|
const showActions = canAddChild || canAddSibling || canDelete || hasAdditionalActionsMenu;
|
|
@@ -106,9 +129,12 @@ var TreeRow = ({
|
|
|
106
129
|
"div",
|
|
107
130
|
{
|
|
108
131
|
ref: rowRef,
|
|
109
|
-
|
|
132
|
+
...rowAccessibility,
|
|
133
|
+
onClick: (event) => onRowClick?.(event, item.id),
|
|
110
134
|
"data-tree-row": item.id,
|
|
111
|
-
|
|
135
|
+
"data-selected": isSelected || void 0,
|
|
136
|
+
"data-current": labelContext?.isCurrent || void 0,
|
|
137
|
+
className: ["transition-colors hover:bg-bg-primary-subtle focus-visible:shadow-focus", labelContext?.isCurrent ? "bg-bg-primary-subtle text-fg-primary-emphasis" : "", rowClassName].filter(Boolean).join(" "),
|
|
112
138
|
style: {
|
|
113
139
|
display: "flex",
|
|
114
140
|
alignItems: "center",
|
|
@@ -127,7 +153,7 @@ var TreeRow = ({
|
|
|
127
153
|
...dragAttributes,
|
|
128
154
|
...dragHandleProps,
|
|
129
155
|
ref: dragRef,
|
|
130
|
-
"aria-label":
|
|
156
|
+
"aria-label": `Move ${accessibleLabel}`,
|
|
131
157
|
onClick: (event) => event.stopPropagation(),
|
|
132
158
|
className: "border border-border-subtle bg-bg-base text-fg-base",
|
|
133
159
|
style: {
|
|
@@ -147,7 +173,8 @@ var TreeRow = ({
|
|
|
147
173
|
"button",
|
|
148
174
|
{
|
|
149
175
|
type: "button",
|
|
150
|
-
"aria-label": toggleLabel
|
|
176
|
+
"aria-label": `${toggleLabel}: ${accessibleLabel}`,
|
|
177
|
+
"aria-expanded": isExpanded,
|
|
151
178
|
onClick: () => onToggle(item.id),
|
|
152
179
|
style: {
|
|
153
180
|
width: 24,
|
|
@@ -169,6 +196,8 @@ var TreeRow = ({
|
|
|
169
196
|
/* @__PURE__ */ jsx(
|
|
170
197
|
Checkbox,
|
|
171
198
|
{
|
|
199
|
+
"aria-label": `${toggleLabel}: ${accessibleLabel}`,
|
|
200
|
+
"aria-expanded": isExpanded,
|
|
172
201
|
checked: false,
|
|
173
202
|
plus: !isExpanded,
|
|
174
203
|
indeterminate: isExpanded,
|
|
@@ -180,19 +209,21 @@ var TreeRow = ({
|
|
|
180
209
|
"input",
|
|
181
210
|
{
|
|
182
211
|
type: "text",
|
|
212
|
+
"aria-label": `Edit ${accessibleLabel}`,
|
|
213
|
+
onClick: (event) => event.stopPropagation(),
|
|
183
214
|
value: draftLabel,
|
|
184
215
|
autoFocus: true,
|
|
185
216
|
"data-tree-input": item.id,
|
|
186
217
|
onChange: (event) => onDraftChange(event.target.value),
|
|
187
218
|
onBlur: onCommitEdit,
|
|
188
219
|
onKeyDown: (event) => {
|
|
220
|
+
event.stopPropagation();
|
|
189
221
|
if (event.key === "Enter") onCommitEdit();
|
|
190
222
|
if (event.key === "Escape") onCancelEdit();
|
|
191
223
|
},
|
|
192
224
|
style: {
|
|
193
225
|
fontSize: 14,
|
|
194
226
|
border: "none",
|
|
195
|
-
outline: "none",
|
|
196
227
|
background: "transparent",
|
|
197
228
|
padding: 0
|
|
198
229
|
}
|
|
@@ -201,19 +232,30 @@ var TreeRow = ({
|
|
|
201
232
|
"span",
|
|
202
233
|
{
|
|
203
234
|
onClick: (event) => {
|
|
204
|
-
if (!isEditable) return;
|
|
235
|
+
if (!isEditable || event.defaultPrevented || isTreeRowControl(event)) return;
|
|
205
236
|
event.stopPropagation();
|
|
206
|
-
onStartEdit
|
|
237
|
+
onStartEdit?.();
|
|
207
238
|
},
|
|
239
|
+
role: isEditable ? "button" : void 0,
|
|
240
|
+
tabIndex: isEditable ? 0 : void 0,
|
|
241
|
+
"aria-label": isEditable ? `Edit ${accessibleLabel}` : void 0,
|
|
242
|
+
onKeyDown: isEditable ? (event) => {
|
|
243
|
+
if (event.target !== event.currentTarget) return;
|
|
244
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
event.stopPropagation();
|
|
247
|
+
onStartEdit?.();
|
|
248
|
+
}
|
|
249
|
+
} : void 0,
|
|
208
250
|
"data-tree-label": item.id,
|
|
251
|
+
className: [renderLabel ? "inline-flex items-center gap-6" : "", labelClassName].filter(Boolean).join(" "),
|
|
209
252
|
style: {
|
|
210
253
|
fontSize: 14,
|
|
254
|
+
display: "inline-flex",
|
|
255
|
+
alignItems: "center",
|
|
256
|
+
minHeight: isEditable ? 24 : void 0,
|
|
211
257
|
cursor: isEditable ? "pointer" : "default",
|
|
212
|
-
|
|
213
|
-
display: "inline-flex",
|
|
214
|
-
alignItems: "center",
|
|
215
|
-
gap: 6
|
|
216
|
-
} : labelStyle
|
|
258
|
+
...!renderLabel ? labelStyle : {}
|
|
217
259
|
},
|
|
218
260
|
children: labelContent
|
|
219
261
|
}
|
|
@@ -233,7 +275,7 @@ var TreeRow = ({
|
|
|
233
275
|
isBare: true,
|
|
234
276
|
icon: /* @__PURE__ */ jsx(Plus, { "aria-hidden": "true" }),
|
|
235
277
|
"aria-label": "Add child",
|
|
236
|
-
onClick: () => onAddChild
|
|
278
|
+
onClick: () => onAddChild?.(item.id)
|
|
237
279
|
}
|
|
238
280
|
) }),
|
|
239
281
|
/* @__PURE__ */ jsx(TooltipContent, { children: addChildTooltip ?? "Add child" })
|
|
@@ -246,7 +288,7 @@ var TreeRow = ({
|
|
|
246
288
|
isBare: true,
|
|
247
289
|
icon: /* @__PURE__ */ jsx(List, { "aria-hidden": "true" }),
|
|
248
290
|
"aria-label": "Add sibling",
|
|
249
|
-
onClick: () => onAddSibling
|
|
291
|
+
onClick: () => onAddSibling?.(item.id)
|
|
250
292
|
}
|
|
251
293
|
) }),
|
|
252
294
|
/* @__PURE__ */ jsx(TooltipContent, { children: addSiblingTooltip ?? "Add sibling" })
|
|
@@ -259,7 +301,7 @@ var TreeRow = ({
|
|
|
259
301
|
isBare: true,
|
|
260
302
|
icon: /* @__PURE__ */ jsx(Close, { "aria-hidden": "true" }),
|
|
261
303
|
"aria-label": "Delete item",
|
|
262
|
-
onClick: () => onDelete
|
|
304
|
+
onClick: () => onDelete?.(item.id)
|
|
263
305
|
}
|
|
264
306
|
) }),
|
|
265
307
|
/* @__PURE__ */ jsx(TooltipContent, { children: deleteTooltip ?? "Delete item" })
|
|
@@ -300,6 +342,12 @@ var TreeSortableRow = ({
|
|
|
300
342
|
onToggle,
|
|
301
343
|
onRowClick,
|
|
302
344
|
rowRefs,
|
|
345
|
+
rowAccessibility,
|
|
346
|
+
labelContext,
|
|
347
|
+
accessibleLabel,
|
|
348
|
+
rowClassName,
|
|
349
|
+
labelClassName,
|
|
350
|
+
onRowSelect,
|
|
303
351
|
isActive,
|
|
304
352
|
isSelected,
|
|
305
353
|
isGroupDragging,
|
|
@@ -347,12 +395,11 @@ var TreeSortableRow = ({
|
|
|
347
395
|
() => ({
|
|
348
396
|
...listeners,
|
|
349
397
|
onPointerDown: (event) => {
|
|
350
|
-
|
|
351
|
-
(
|
|
352
|
-
onRowClick == null ? void 0 : onRowClick(event, item.id);
|
|
398
|
+
listeners?.onPointerDown?.(event);
|
|
399
|
+
onRowSelect?.(event, item.id);
|
|
353
400
|
}
|
|
354
401
|
}),
|
|
355
|
-
[item.id, listeners,
|
|
402
|
+
[item.id, listeners, onRowSelect]
|
|
356
403
|
);
|
|
357
404
|
return /* @__PURE__ */ jsx2(
|
|
358
405
|
TreeRow,
|
|
@@ -366,6 +413,11 @@ var TreeSortableRow = ({
|
|
|
366
413
|
dragAttributes: attributes,
|
|
367
414
|
dragRef: setActivatorNodeRef,
|
|
368
415
|
rowRef: rowRefCallback,
|
|
416
|
+
rowAccessibility,
|
|
417
|
+
labelContext,
|
|
418
|
+
accessibleLabel,
|
|
419
|
+
rowClassName,
|
|
420
|
+
labelClassName,
|
|
369
421
|
isDragging: isDragging || isActive || isGroupDragging,
|
|
370
422
|
isSelected,
|
|
371
423
|
isEditable,
|
|
@@ -440,11 +492,10 @@ var collectDraggedIds = (items, rootIds) => {
|
|
|
440
492
|
var getSelectedRootIds = (items, selectedIds) => {
|
|
441
493
|
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
442
494
|
return items.filter((item) => selectedIds.has(item.id)).filter((item) => {
|
|
443
|
-
|
|
444
|
-
let parentId = (_a = itemById.get(item.id)) == null ? void 0 : _a.parentId;
|
|
495
|
+
let parentId = itemById.get(item.id)?.parentId;
|
|
445
496
|
while (parentId) {
|
|
446
497
|
if (selectedIds.has(parentId)) return false;
|
|
447
|
-
parentId =
|
|
498
|
+
parentId = itemById.get(parentId)?.parentId;
|
|
448
499
|
}
|
|
449
500
|
return true;
|
|
450
501
|
}).map((item) => item.id);
|
|
@@ -744,7 +795,7 @@ var getDropIndicator = ({
|
|
|
744
795
|
|
|
745
796
|
// src/elements/TreeSelect.jsx
|
|
746
797
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
747
|
-
|
|
798
|
+
var TreeSelect = forwardRef(function TreeSelect2({
|
|
748
799
|
data,
|
|
749
800
|
onDataChange,
|
|
750
801
|
allowDragAndDrop = false,
|
|
@@ -764,8 +815,16 @@ function TreeSelect({
|
|
|
764
815
|
expanded,
|
|
765
816
|
onExpandedChange,
|
|
766
817
|
renderAdditionalActionsMenu,
|
|
767
|
-
renderLabel
|
|
768
|
-
|
|
818
|
+
renderLabel,
|
|
819
|
+
onNodeActivate,
|
|
820
|
+
currentId,
|
|
821
|
+
getItemLabel,
|
|
822
|
+
rowClassName,
|
|
823
|
+
labelClassName,
|
|
824
|
+
"aria-label": ariaLabel = "Tree",
|
|
825
|
+
"aria-labelledby": ariaLabelledBy,
|
|
826
|
+
...rootProps
|
|
827
|
+
}, forwardedRef) {
|
|
769
828
|
const [uncontrolledExpandedIds, setExpandedIds] = useState(
|
|
770
829
|
() => getExpandedIds(data ?? [], defaultExpanded)
|
|
771
830
|
);
|
|
@@ -775,6 +834,8 @@ function TreeSelect({
|
|
|
775
834
|
const [horizontalOffset, setHorizontalOffset] = useState(0);
|
|
776
835
|
const [selectedIds, setSelectedIds] = useState(() => /* @__PURE__ */ new Set());
|
|
777
836
|
const [lastSelectedId, setLastSelectedId] = useState(null);
|
|
837
|
+
const [focusedId, setFocusedId] = useState(null);
|
|
838
|
+
const [focusWithinId, setFocusWithinId] = useState(null);
|
|
778
839
|
const [editingId, setEditingId] = useState(null);
|
|
779
840
|
const [draftLabel, setDraftLabel] = useState("");
|
|
780
841
|
const idCounterRef = useRef(0);
|
|
@@ -794,6 +855,22 @@ function TreeSelect({
|
|
|
794
855
|
() => new Map(visibleItems.map((item) => [item.id, item])),
|
|
795
856
|
[visibleItems]
|
|
796
857
|
);
|
|
858
|
+
const getRowElement = useCallback2((id) => rowRefs.current.get(normaliseId(id)) ?? null, []);
|
|
859
|
+
useImperativeHandle(forwardedRef, () => ({
|
|
860
|
+
getRowElement,
|
|
861
|
+
focusItem(id, options) {
|
|
862
|
+
const row = getRowElement(id);
|
|
863
|
+
if (!row) return false;
|
|
864
|
+
row.focus(options);
|
|
865
|
+
return true;
|
|
866
|
+
},
|
|
867
|
+
scrollToItem(id, options = { block: "nearest", inline: "nearest" }) {
|
|
868
|
+
const row = getRowElement(id);
|
|
869
|
+
if (!row || typeof row.scrollIntoView !== "function") return false;
|
|
870
|
+
row.scrollIntoView(options);
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
}), [getRowElement]);
|
|
797
874
|
const siblingOrderMap = useMemo2(
|
|
798
875
|
() => buildSiblingOrderMap(treeData),
|
|
799
876
|
[treeData]
|
|
@@ -804,19 +881,18 @@ function TreeSelect({
|
|
|
804
881
|
}, [maxDepth]);
|
|
805
882
|
const resolvedLevelConfig = useCallback2(
|
|
806
883
|
(depth) => {
|
|
807
|
-
|
|
808
|
-
const
|
|
809
|
-
const perLevel = ((_a = levelConfig == null ? void 0 : levelConfig.levels) == null ? void 0 : _a[depth]) ?? {};
|
|
884
|
+
const defaults = levelConfig?.default ?? {};
|
|
885
|
+
const perLevel = levelConfig?.levels?.[depth] ?? {};
|
|
810
886
|
const editable = perLevel.editable ?? defaults.editable ?? isEditable;
|
|
811
887
|
const actions = {
|
|
812
|
-
addChild:
|
|
813
|
-
addSibling:
|
|
814
|
-
delete:
|
|
888
|
+
addChild: perLevel.actions?.addChild ?? defaults.actions?.addChild ?? editable,
|
|
889
|
+
addSibling: perLevel.actions?.addSibling ?? defaults.actions?.addSibling ?? editable,
|
|
890
|
+
delete: perLevel.actions?.delete ?? defaults.actions?.delete ?? (editable && allowDelete)
|
|
815
891
|
};
|
|
816
892
|
const tooltips = {
|
|
817
|
-
addChild:
|
|
818
|
-
addSibling:
|
|
819
|
-
delete:
|
|
893
|
+
addChild: perLevel.tooltips?.addChild ?? defaults.tooltips?.addChild ?? addChildTooltip,
|
|
894
|
+
addSibling: perLevel.tooltips?.addSibling ?? defaults.tooltips?.addSibling ?? addSiblingTooltip,
|
|
895
|
+
delete: perLevel.tooltips?.delete ?? defaults.tooltips?.delete ?? deleteTooltip
|
|
820
896
|
};
|
|
821
897
|
return { editable, actions, tooltips };
|
|
822
898
|
},
|
|
@@ -836,7 +912,7 @@ function TreeSelect({
|
|
|
836
912
|
(updater) => {
|
|
837
913
|
if (isExpandedControlled) {
|
|
838
914
|
const next = updater(new Set(expandedIds));
|
|
839
|
-
onExpandedChange
|
|
915
|
+
onExpandedChange?.(next);
|
|
840
916
|
return;
|
|
841
917
|
}
|
|
842
918
|
setExpandedIds((prev) => updater(new Set(prev)));
|
|
@@ -845,7 +921,7 @@ function TreeSelect({
|
|
|
845
921
|
);
|
|
846
922
|
const emitDataChange = useCallback2(
|
|
847
923
|
(nextTree, meta) => {
|
|
848
|
-
onDataChange
|
|
924
|
+
onDataChange?.(nextTree, meta);
|
|
849
925
|
},
|
|
850
926
|
[onDataChange]
|
|
851
927
|
);
|
|
@@ -859,17 +935,16 @@ function TreeSelect({
|
|
|
859
935
|
return prev;
|
|
860
936
|
});
|
|
861
937
|
}, [updateExpandedIds]);
|
|
862
|
-
const
|
|
938
|
+
const handleRowSelection = useCallback2(
|
|
863
939
|
(event, id) => {
|
|
864
|
-
var _a, _b;
|
|
865
940
|
if (!allowMultiDrag) return;
|
|
866
941
|
const hasMultiKey = event.metaKey || event.ctrlKey;
|
|
867
|
-
const parentId =
|
|
942
|
+
const parentId = itemById.get(id)?.parentId ?? null;
|
|
868
943
|
const siblingOrder = siblingOrderMap.get(parentId) ?? [];
|
|
869
944
|
if (event.shiftKey) {
|
|
870
945
|
const anchorIndex = siblingOrder.indexOf(lastSelectedId);
|
|
871
946
|
const targetIndex = siblingOrder.indexOf(id);
|
|
872
|
-
if (anchorIndex < 0 || targetIndex < 0 || (
|
|
947
|
+
if (anchorIndex < 0 || targetIndex < 0 || (itemById.get(lastSelectedId ?? "")?.parentId ?? null) !== parentId) {
|
|
873
948
|
setSelectedIds(/* @__PURE__ */ new Set([id]));
|
|
874
949
|
setLastSelectedId(id);
|
|
875
950
|
return;
|
|
@@ -891,10 +966,9 @@ function TreeSelect({
|
|
|
891
966
|
return;
|
|
892
967
|
}
|
|
893
968
|
setSelectedIds((prev) => {
|
|
894
|
-
var _a2;
|
|
895
969
|
if (prev.size) {
|
|
896
970
|
const firstSelected = prev.values().next().value;
|
|
897
|
-
const selectedParent =
|
|
971
|
+
const selectedParent = itemById.get(firstSelected)?.parentId ?? null;
|
|
898
972
|
if (selectedParent !== parentId) {
|
|
899
973
|
return /* @__PURE__ */ new Set([id]);
|
|
900
974
|
}
|
|
@@ -921,6 +995,16 @@ function TreeSelect({
|
|
|
921
995
|
siblingOrderMap
|
|
922
996
|
]
|
|
923
997
|
);
|
|
998
|
+
const activateItem = useCallback2((event, id, inputSource) => {
|
|
999
|
+
if (event.defaultPrevented || event.repeat || inputSource === "pointer" && event.button !== 0 || hasActivationModifier(event) || isTreeRowControl(event)) return;
|
|
1000
|
+
const item = itemById.get(id);
|
|
1001
|
+
if (item && !activeId) onNodeActivate?.({ ...item, event, inputSource });
|
|
1002
|
+
}, [activeId, itemById, onNodeActivate]);
|
|
1003
|
+
const handleRowClick = useCallback2((event, id) => {
|
|
1004
|
+
if (event.defaultPrevented || isTreeRowControl(event)) return;
|
|
1005
|
+
handleRowSelection(event, id);
|
|
1006
|
+
activateItem(event, id, "pointer");
|
|
1007
|
+
}, [activateItem, handleRowSelection]);
|
|
924
1008
|
const draggedRootIds = useMemo2(
|
|
925
1009
|
() => getDraggedRootIds({
|
|
926
1010
|
activeId,
|
|
@@ -952,13 +1036,11 @@ function TreeSelect({
|
|
|
952
1036
|
setHorizontalOffset(event.delta.x);
|
|
953
1037
|
}, []);
|
|
954
1038
|
const handleDragOver = useCallback2((event) => {
|
|
955
|
-
|
|
956
|
-
if ((_a = event.over) == null ? void 0 : _a.id) {
|
|
1039
|
+
if (event.over?.id) {
|
|
957
1040
|
setOverId(normaliseId(event.over.id));
|
|
958
1041
|
}
|
|
959
1042
|
}, []);
|
|
960
1043
|
const handleDragEnd = useCallback2(() => {
|
|
961
|
-
var _a;
|
|
962
1044
|
const projection = getDropProjection({
|
|
963
1045
|
items: visibleItems,
|
|
964
1046
|
activeId,
|
|
@@ -974,7 +1056,7 @@ function TreeSelect({
|
|
|
974
1056
|
return;
|
|
975
1057
|
}
|
|
976
1058
|
const { parentId, siblingIndex, projectedDepth } = projection;
|
|
977
|
-
const sourceParentId =
|
|
1059
|
+
const sourceParentId = itemById.get(draggedRootIds[0] ?? activeId)?.parentId ?? null;
|
|
978
1060
|
const { nextNodes, removedNodes } = removeNodes(treeData, draggedRootIds);
|
|
979
1061
|
if (!removedNodes.length) {
|
|
980
1062
|
setActiveId(null);
|
|
@@ -1087,8 +1169,7 @@ function TreeSelect({
|
|
|
1087
1169
|
);
|
|
1088
1170
|
const startEdit = useCallback2(
|
|
1089
1171
|
(id) => {
|
|
1090
|
-
|
|
1091
|
-
const currentLabel = ((_b = (_a = itemById.get(id)) == null ? void 0 : _a.node) == null ? void 0 : _b.label) ?? ((_d = (_c = itemById.get(id)) == null ? void 0 : _c.node) == null ? void 0 : _d.id) ?? "";
|
|
1172
|
+
const currentLabel = itemById.get(id)?.node?.label ?? itemById.get(id)?.node?.id ?? "";
|
|
1092
1173
|
setEditingId(id);
|
|
1093
1174
|
setDraftLabel(String(currentLabel));
|
|
1094
1175
|
},
|
|
@@ -1121,15 +1202,15 @@ function TreeSelect({
|
|
|
1121
1202
|
if (!item) return null;
|
|
1122
1203
|
if (!renderLabel) return item.node.label ?? item.id;
|
|
1123
1204
|
return renderLabel({
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1205
|
+
...item,
|
|
1206
|
+
isExpanded: expandedIds.has(item.id),
|
|
1207
|
+
isCurrent: currentId != null && normaliseId(currentId) === item.id,
|
|
1208
|
+
isSelected: selectedIds.has(item.id),
|
|
1209
|
+
isFocused: focusWithinId === item.id,
|
|
1210
|
+
getRowElement: () => null
|
|
1130
1211
|
});
|
|
1131
1212
|
},
|
|
1132
|
-
[expandedIds, renderLabel]
|
|
1213
|
+
[currentId, expandedIds, focusWithinId, renderLabel, selectedIds]
|
|
1133
1214
|
);
|
|
1134
1215
|
const indicator = useMemo2(() => {
|
|
1135
1216
|
if (!allowDragAndDrop) return null;
|
|
@@ -1152,7 +1233,61 @@ function TreeSelect({
|
|
|
1152
1233
|
visibleItems,
|
|
1153
1234
|
maxDepth
|
|
1154
1235
|
]);
|
|
1155
|
-
|
|
1236
|
+
const rowContext = (item) => ({
|
|
1237
|
+
...item,
|
|
1238
|
+
isExpanded: expandedIds.has(item.id),
|
|
1239
|
+
isCurrent: currentId != null && normaliseId(currentId) === item.id,
|
|
1240
|
+
isSelected: selectedIds.has(item.id),
|
|
1241
|
+
isFocused: focusWithinId === item.id,
|
|
1242
|
+
getRowElement: () => getRowElement(item.id)
|
|
1243
|
+
});
|
|
1244
|
+
const rowAccessibility = (item) => ({
|
|
1245
|
+
role: "treeitem",
|
|
1246
|
+
"aria-label": getTreeItemLabel(item.node, getItemLabel),
|
|
1247
|
+
"aria-level": item.depth + 1,
|
|
1248
|
+
"aria-expanded": item.hasChildren ? expandedIds.has(item.id) : void 0,
|
|
1249
|
+
"aria-selected": allowMultiDrag ? selectedIds.has(item.id) : void 0,
|
|
1250
|
+
"aria-current": currentId != null && normaliseId(currentId) === item.id ? "page" : void 0,
|
|
1251
|
+
"aria-posinset": (siblingOrderMap.get(item.parentId) || []).indexOf(item.id) + 1,
|
|
1252
|
+
"aria-setsize": (siblingOrderMap.get(item.parentId) || []).length,
|
|
1253
|
+
tabIndex: (focusedId && itemById.has(focusedId) ? focusedId : visibleItems[0]?.id) === item.id ? 0 : -1,
|
|
1254
|
+
onFocus: () => {
|
|
1255
|
+
setFocusedId(item.id);
|
|
1256
|
+
setFocusWithinId(item.id);
|
|
1257
|
+
},
|
|
1258
|
+
onBlur: (event) => {
|
|
1259
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setFocusWithinId(null);
|
|
1260
|
+
},
|
|
1261
|
+
onKeyDown: (event) => {
|
|
1262
|
+
if (event.target !== event.currentTarget || event.defaultPrevented) return;
|
|
1263
|
+
if (event.repeat && (event.key === "Enter" || event.key === " ")) {
|
|
1264
|
+
event.preventDefault();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const index = visibleItems.findIndex((row) => row.id === item.id);
|
|
1268
|
+
let target;
|
|
1269
|
+
if (event.key === "ArrowDown") target = visibleItems[Math.min(index + 1, visibleItems.length - 1)];
|
|
1270
|
+
if (event.key === "ArrowUp") target = visibleItems[Math.max(index - 1, 0)];
|
|
1271
|
+
if (event.key === "Home") target = visibleItems[0];
|
|
1272
|
+
if (event.key === "End") target = visibleItems[visibleItems.length - 1];
|
|
1273
|
+
if (event.key === "ArrowRight" && item.hasChildren) {
|
|
1274
|
+
if (!expandedIds.has(item.id)) toggleExpanded(item.id);
|
|
1275
|
+
else target = visibleItems[index + 1];
|
|
1276
|
+
}
|
|
1277
|
+
if (event.key === "ArrowLeft") {
|
|
1278
|
+
if (item.hasChildren && expandedIds.has(item.id)) toggleExpanded(item.id);
|
|
1279
|
+
else target = itemById.get(item.parentId);
|
|
1280
|
+
}
|
|
1281
|
+
if (event.key === "F2" && resolvedLevelConfig(item.depth).editable) startEdit(item.id);
|
|
1282
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1283
|
+
handleRowSelection(event, item.id);
|
|
1284
|
+
activateItem(event, item.id, "keyboard");
|
|
1285
|
+
}
|
|
1286
|
+
if (["ArrowDown", "ArrowUp", "ArrowRight", "ArrowLeft", "Home", "End", "F2", " ", "Enter"].includes(event.key)) event.preventDefault();
|
|
1287
|
+
if (target) getRowElement(target.id)?.focus();
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
return /* @__PURE__ */ jsx3(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsx3("div", { ...rootProps, "data-data-component": "tree-select", style: { position: "relative", padding: 8, ...rootProps.style }, children: allowDragAndDrop ? /* @__PURE__ */ jsxs2(
|
|
1156
1291
|
DndContext,
|
|
1157
1292
|
{
|
|
1158
1293
|
sensors,
|
|
@@ -1163,24 +1298,24 @@ function TreeSelect({
|
|
|
1163
1298
|
onDragEnd: handleDragEnd,
|
|
1164
1299
|
onDragCancel: handleDragCancel,
|
|
1165
1300
|
children: [
|
|
1166
|
-
/* @__PURE__ */ jsx3(SortableContext, { items: visibleItems.map((item) => item.id), children: /* @__PURE__ */ jsxs2("div", { ref: listRef, style: { position: "relative" }, children: [
|
|
1301
|
+
/* @__PURE__ */ jsx3(SortableContext, { items: visibleItems.map((item) => item.id), children: /* @__PURE__ */ jsxs2("div", { ref: listRef, role: "tree", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-multiselectable": allowMultiDrag || void 0, style: { position: "relative" }, children: [
|
|
1167
1302
|
indicator ? /* @__PURE__ */ jsx3(
|
|
1168
1303
|
"div",
|
|
1169
1304
|
{
|
|
1305
|
+
className: "bg-bg-primary-base",
|
|
1170
1306
|
style: {
|
|
1171
1307
|
position: "absolute",
|
|
1172
1308
|
top: indicator.top,
|
|
1173
1309
|
left: indicator.left,
|
|
1174
1310
|
right: 8,
|
|
1175
1311
|
height: 2,
|
|
1176
|
-
background: "#0f62fe",
|
|
1177
1312
|
borderRadius: 999
|
|
1178
1313
|
}
|
|
1179
1314
|
}
|
|
1180
1315
|
) : null,
|
|
1181
1316
|
visibleItems.map((item) => {
|
|
1182
1317
|
const levelSettings = resolvedLevelConfig(item.depth);
|
|
1183
|
-
const additionalActionsMenu = renderAdditionalActionsMenu
|
|
1318
|
+
const additionalActionsMenu = renderAdditionalActionsMenu?.({
|
|
1184
1319
|
id: item.id,
|
|
1185
1320
|
node: item.node,
|
|
1186
1321
|
depth: item.depth,
|
|
@@ -1195,6 +1330,12 @@ function TreeSelect({
|
|
|
1195
1330
|
onToggle: toggleExpanded,
|
|
1196
1331
|
onRowClick: handleRowClick,
|
|
1197
1332
|
rowRefs,
|
|
1333
|
+
onRowSelect: handleRowSelection,
|
|
1334
|
+
rowAccessibility: rowAccessibility(item),
|
|
1335
|
+
labelContext: rowContext(item),
|
|
1336
|
+
accessibleLabel: getTreeItemLabel(item.node, getItemLabel),
|
|
1337
|
+
rowClassName: typeof rowClassName === "function" ? rowClassName(rowContext(item)) : rowClassName,
|
|
1338
|
+
labelClassName: typeof labelClassName === "function" ? labelClassName(rowContext(item)) : labelClassName,
|
|
1198
1339
|
isActive: activeId === item.id,
|
|
1199
1340
|
isSelected: selectedIds.has(item.id),
|
|
1200
1341
|
isGroupDragging: Boolean(
|
|
@@ -1229,20 +1370,15 @@ function TreeSelect({
|
|
|
1229
1370
|
/* @__PURE__ */ jsx3(DragOverlay, { children: activeId ? /* @__PURE__ */ jsx3(
|
|
1230
1371
|
"div",
|
|
1231
1372
|
{
|
|
1232
|
-
className: "border border-border-subtle bg-bg-base text-fg-base",
|
|
1373
|
+
className: "border border-border-subtle bg-bg-base text-fg-base shadow-lg",
|
|
1233
1374
|
style: {
|
|
1234
1375
|
padding: "6px 8px",
|
|
1235
|
-
borderRadius: 6
|
|
1236
|
-
boxShadow: "0 8px 24px rgba(0, 0, 0, 0.12)"
|
|
1376
|
+
borderRadius: 6
|
|
1237
1377
|
},
|
|
1238
1378
|
children: draggedRootIds.length > 1 ? `${draggedRootIds.length} items` : /* @__PURE__ */ jsx3(
|
|
1239
1379
|
"span",
|
|
1240
1380
|
{
|
|
1241
|
-
|
|
1242
|
-
display: "inline-flex",
|
|
1243
|
-
alignItems: "center",
|
|
1244
|
-
gap: 6
|
|
1245
|
-
},
|
|
1381
|
+
className: "inline-flex items-center gap-6",
|
|
1246
1382
|
children: renderLabelContent(
|
|
1247
1383
|
visibleItems.find((item) => item.id === activeId)
|
|
1248
1384
|
) ?? activeId
|
|
@@ -1252,9 +1388,9 @@ function TreeSelect({
|
|
|
1252
1388
|
) : null })
|
|
1253
1389
|
]
|
|
1254
1390
|
}
|
|
1255
|
-
) : /* @__PURE__ */ jsx3("div", { ref: listRef, style: { position: "relative" }, children: visibleItems.map((item) => {
|
|
1391
|
+
) : /* @__PURE__ */ jsx3("div", { ref: listRef, role: "tree", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-multiselectable": allowMultiDrag || void 0, style: { position: "relative" }, children: visibleItems.map((item) => {
|
|
1256
1392
|
const levelSettings = resolvedLevelConfig(item.depth);
|
|
1257
|
-
const additionalActionsMenu = renderAdditionalActionsMenu
|
|
1393
|
+
const additionalActionsMenu = renderAdditionalActionsMenu?.({
|
|
1258
1394
|
id: item.id,
|
|
1259
1395
|
node: item.node,
|
|
1260
1396
|
depth: item.depth,
|
|
@@ -1269,7 +1405,15 @@ function TreeSelect({
|
|
|
1269
1405
|
isExpanded: expandedIds.has(item.id),
|
|
1270
1406
|
onToggle: toggleExpanded,
|
|
1271
1407
|
onRowClick: handleRowClick,
|
|
1272
|
-
rowRef:
|
|
1408
|
+
rowRef: (row) => {
|
|
1409
|
+
if (row) rowRefs.current.set(item.id, row);
|
|
1410
|
+
else rowRefs.current.delete(item.id);
|
|
1411
|
+
},
|
|
1412
|
+
rowAccessibility: rowAccessibility(item),
|
|
1413
|
+
labelContext: rowContext(item),
|
|
1414
|
+
accessibleLabel: getTreeItemLabel(item.node, getItemLabel),
|
|
1415
|
+
rowClassName: typeof rowClassName === "function" ? rowClassName(rowContext(item)) : rowClassName,
|
|
1416
|
+
labelClassName: typeof labelClassName === "function" ? labelClassName(rowContext(item)) : labelClassName,
|
|
1273
1417
|
isDraggable: false,
|
|
1274
1418
|
isDragging: false,
|
|
1275
1419
|
isSelected: selectedIds.has(item.id),
|
|
@@ -1298,7 +1442,10 @@ function TreeSelect({
|
|
|
1298
1442
|
item.id
|
|
1299
1443
|
);
|
|
1300
1444
|
}) }) }) });
|
|
1301
|
-
}
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1447
|
+
// src/index.ts
|
|
1448
|
+
var TreeSelect3 = TreeSelect;
|
|
1302
1449
|
export {
|
|
1303
|
-
TreeSelect
|
|
1450
|
+
TreeSelect3 as TreeSelect
|
|
1304
1451
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# DS15 interaction inventory
|
|
2
|
+
|
|
3
|
+
Inspected 29 October 2026 against Payload's installed public TreeSelect 0.2.5
|
|
4
|
+
and the local Edux source. Payload files were read only.
|
|
5
|
+
|
|
6
|
+
| Host | Interaction and local ownership | Replacement boundary |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| Manuscripts `ManuScriptNavigationTree` | Delegated row click resolves domain metadata and aborts previous asynchronous editor navigation; filtered trees disable reordering. `onDataChange` consumes exact drag metadata. | `onNodeActivate` ID resolves existing metadata. Keep editor readiness, abort controllers and reorder transactions local. |
|
|
9
|
+
| Curricula `CurriculaBuilderPanel` | Delegated click opens node editor. Labels are not inline editable at any configured level; add/delete and multi-drag remain active. | `onNodeActivate`; keep mutations, expansion and selection of domain editor local. |
|
|
10
|
+
| Book Setup `PageBuilderBookSetupNavigationPanel` | Filtered content block tree delegates IDs to section/view changes. Rich labels include icons. | `onNodeActivate`, `currentId`, accessible name override and public row/label classes. Keep filtering and view/section routing local. |
|
|
11
|
+
| Rights `PermissionsWorkspaceSourceNavigation` | Rich labels contain focusable role-button and status tooltip; selected label ref locates private ancestor for nearest scroll. | Row activation/current state, `getItemLabel`, imperative `scrollToItem`; remove nested navigation button. Keep source permissions/status/search local. |
|
|
12
|
+
| Rights `PermissionsWorkspaceAssetNavigation` | Rich label role-button duplicates keyboard navigation; selected label ref scrapes ancestor for scroll. | Row activation/current state, `getItemLabel`, `scrollToItem`; remove nested navigation button. Keep asset mapping/search local. |
|
|
13
|
+
|
|
14
|
+
Also inspected `pages/PageBuilderBookSetupEdit`: its debug summary and section
|
|
15
|
+
handler scrape row IDs from DOM events, but the handler already accepts a string
|
|
16
|
+
ID. Passing the activation ID at the navigation-panel boundary removes the event
|
|
17
|
+
lookup without moving debug logging or section routing into Edux. No host source
|
|
18
|
+
is modified by this package task.
|
|
19
|
+
|
|
20
|
+
## Compatibility baseline
|
|
21
|
+
|
|
22
|
+
The registry 0.2.5 archive matches the source interaction baseline. Existing
|
|
23
|
+
tracked source token work (`be4a9b2`) replaces registry raw state colours/drop
|
|
24
|
+
indicator/shadow classes with semantic equivalents; DS15 retains that source
|
|
25
|
+
baseline and introduces no tokens or further geometry changes. Payload's
|
|
26
|
+
installed 0.2.5 runtime is locally patched and has
|
|
27
|
+
roving row focus, tree/treeitem roles, level/position/set-size attributes,
|
|
28
|
+
`aria-selected` for multi-drag, labelled edit/toggle/move controls and keyboard
|
|
29
|
+
arrow/Home/End/F2 handling. Source and registry lack these. The extension adds supported equivalents while
|
|
30
|
+
preserving registry geometry, data operations and drag metadata.
|
|
31
|
+
|
|
32
|
+
The installed runtime also uses `--theme-radius` and `--theme-hover-surface`
|
|
33
|
+
fallbacks and `data-selected`; these are local patch hooks, not registry APIs.
|
|
34
|
+
The extension retains source semantic token styling and registry geometry; it
|
|
35
|
+
does not adopt the local theme variable overrides. Public current/selection
|
|
36
|
+
context and row/label classes replace host styling selectors.
|
|
37
|
+
|
|
38
|
+
## Interaction decisions
|
|
39
|
+
|
|
40
|
+
- Current item is controlled by `currentId`. Activation never changes it implicitly;
|
|
41
|
+
hosts can wait for navigation success. `aria-current="page"` expresses current
|
|
42
|
+
location; `aria-selected` continues to mean drag selection.
|
|
43
|
+
- Plain row pointer clicks and row Enter/Space activate once. Ctrl/Meta/Shift/Alt
|
|
44
|
+
suppress activation; Ctrl/Meta/Shift retain existing multi-selection behaviour.
|
|
45
|
+
- `defaultPrevented`, editable inputs, row controls and interactive custom label
|
|
46
|
+
descendants suppress activation. Pointer selection from a drag handle never
|
|
47
|
+
invokes navigation. Keyboard events from descendants stay with that control.
|
|
48
|
+
- Visible row access is explicit: get element, focus and scroll. Collapsed,
|
|
49
|
+
filtered and removed targets return null/false. No method implicitly expands
|
|
50
|
+
or changes current item, selection, routing or domain data.
|
|
51
|
+
- Rich labels need an accessible name: use `getItemLabel(node)`; fallback is a
|
|
52
|
+
string/number label, otherwise the stable normalised ID.
|