@geneui/components 3.0.0-next-2fa65ce-08062026 → 3.0.0-next-bc8245e-24062026
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/ActionableList.js +222 -64
- package/{ActionableListNodeWrapper-42ea60cf.js → ActionableListNodeWrapper-0a3308ca.js} +1 -1
- package/SegmentedControl.js +2 -1
- package/TransferList-55c2e836.js +4 -0
- package/TransferList.js +396 -0
- package/components/molecules/ActionableList/ActionableList.d.ts +1 -1
- package/components/molecules/ActionableList/ActionableList.helpers.d.ts +8 -1
- package/components/molecules/ActionableList/ActionableList.types.d.ts +55 -2
- package/components/molecules/ActionableList/ActionableListItem/ActionableListItem.d.ts +8 -0
- package/components/molecules/ActionableList/index.d.ts +1 -1
- package/components/organisms/TransferList/TransferList.d.ts +8 -0
- package/components/organisms/TransferList/TransferList.helpers.d.ts +25 -0
- package/components/organisms/TransferList/TransferList.types.d.ts +62 -0
- package/components/organisms/TransferList/index.d.ts +2 -0
- package/index.d.ts +1 -0
- package/index.js +3 -1
- package/package.json +1 -1
package/ActionableList.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React__default, { useContext, useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
-
import { _ as _slicedToArray, d as dist, a as _toConsumableArray, g as getBindingsForBrokenDrags, m as monitorForElements, c as combine, b as dropTargetForElements, e as draggable, f as disableNativeDragPreview } from './ActionableListNodeWrapper-
|
|
1
|
+
import React__default, { useContext, useState, useRef, useEffect, useLayoutEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { _ as _slicedToArray, d as dist, a as _toConsumableArray, g as getBindingsForBrokenDrags, m as monitorForElements, c as combine, b as dropTargetForElements, e as draggable, f as disableNativeDragPreview } from './ActionableListNodeWrapper-0a3308ca.js';
|
|
3
3
|
import { c as classNames } from './index-ce02421b.js';
|
|
4
4
|
import { S as SvgMagnifier } from './Magnifier-8df67f2d.js';
|
|
5
5
|
import Divider from './Divider.js';
|
|
@@ -9,16 +9,16 @@ import Text from './Text.js';
|
|
|
9
9
|
import Checkbox from './Checkbox.js';
|
|
10
10
|
import { E as Empty } from './index-a46fb8ec.js';
|
|
11
11
|
import TextField from './TextField.js';
|
|
12
|
+
import Tooltip from './Tooltip.js';
|
|
12
13
|
import { u as useDebouncedCallback } from './useDebounceCallback-999deae7.js';
|
|
14
|
+
import { u as useEllipsisDetection } from './useEllipsisDetection-c5f9b175.js';
|
|
13
15
|
import { S as SvgChevronDown } from './ChevronDown-ee53f094.js';
|
|
14
|
-
import { S as SvgChevronRight } from './ChevronRight-af87e2a4.js';
|
|
16
|
+
import { a as SvgChevronLeft, S as SvgChevronRight } from './ChevronRight-af87e2a4.js';
|
|
15
17
|
import { _ as _objectWithoutProperties, a as _extends } from './ActivityRecent-b88e2ba8.js';
|
|
16
18
|
import Button from './Button.js';
|
|
17
19
|
import Info from './Info.js';
|
|
18
20
|
import { createPortal } from 'react-dom';
|
|
19
21
|
import { a as GeneUIDesignSystemContext } from './index-9a69bcc6.js';
|
|
20
|
-
import Tooltip from './Tooltip.js';
|
|
21
|
-
import { u as useEllipsisDetection } from './useEllipsisDetection-c5f9b175.js';
|
|
22
22
|
import './style-inject.es-746bb8ed.js';
|
|
23
23
|
import 'prop-types';
|
|
24
24
|
import './index-15303b91.js';
|
|
@@ -125,6 +125,7 @@ const isAnySelectionInSubtree = (item) => {
|
|
|
125
125
|
return children.some((child) => isAnySelectionInSubtree(child));
|
|
126
126
|
};
|
|
127
127
|
const nextLevel = (level) => Math.min(level + 1, ACTIONABLE_LIST_MAX_NESTED_LEVEL);
|
|
128
|
+
const isGroupItem = (item) => Array.isArray(item.children);
|
|
128
129
|
const mergeItemsFromProps = (incoming, previous) => {
|
|
129
130
|
const prevById = new Map();
|
|
130
131
|
const index = (nodes) => {
|
|
@@ -146,15 +147,30 @@ const mergeItemsFromProps = (incoming, previous) => {
|
|
|
146
147
|
else if (prev !== undefined && typeof prev.checked === "boolean") {
|
|
147
148
|
checked = prev.checked;
|
|
148
149
|
}
|
|
149
|
-
return Object.assign(Object.assign({}, node), { checked, children: ((_a = node.children)
|
|
150
|
+
return Object.assign(Object.assign({}, node), { checked, children: isGroupItem(node) ? merge((_a = node.children) !== null && _a !== void 0 ? _a : []) : undefined });
|
|
150
151
|
});
|
|
151
152
|
return merge(incoming);
|
|
152
153
|
};
|
|
153
154
|
const countAllItems = (items) => items.reduce((acc, item) => acc + 1 + countAllItems(item.children || []), 0);
|
|
155
|
+
/** Counts leaf nodes only — parent rows with children are grouping headers, not items. */
|
|
156
|
+
const countLeafItems = (items) => items.reduce((acc, item) => {
|
|
157
|
+
var _a;
|
|
158
|
+
if (isGroupItem(item))
|
|
159
|
+
return acc + countLeafItems((_a = item.children) !== null && _a !== void 0 ? _a : []);
|
|
160
|
+
return acc + 1;
|
|
161
|
+
}, 0);
|
|
154
162
|
const countCheckedItems = (items) => items.reduce((acc, item) => {
|
|
155
163
|
var _a;
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
if (isGroupItem(item))
|
|
165
|
+
return acc + countCheckedItems((_a = item.children) !== null && _a !== void 0 ? _a : []);
|
|
166
|
+
return acc + (item.checked ? 1 : 0);
|
|
167
|
+
}, 0);
|
|
168
|
+
/** Counts checked leaves in `items` whose ids are in `scopeLeafIds`. */
|
|
169
|
+
const countCheckedLeavesInScope = (items, scopeLeafIds) => items.reduce((acc, item) => {
|
|
170
|
+
var _a;
|
|
171
|
+
if (isGroupItem(item))
|
|
172
|
+
return acc + countCheckedLeavesInScope((_a = item.children) !== null && _a !== void 0 ? _a : [], scopeLeafIds);
|
|
173
|
+
return acc + (item.checked && scopeLeafIds.has(item.id) ? 1 : 0);
|
|
158
174
|
}, 0);
|
|
159
175
|
const findItemById = (nodes, targetId) => {
|
|
160
176
|
const direct = nodes.find((node) => node.id === targetId);
|
|
@@ -182,34 +198,46 @@ const applyCheckedToBranch = (item, checked) => {
|
|
|
182
198
|
var _a;
|
|
183
199
|
return (Object.assign(Object.assign({}, item), { checked, children: (_a = item.children) === null || _a === void 0 ? void 0 : _a.map((child) => applyCheckedToBranch(child, checked)) }));
|
|
184
200
|
};
|
|
201
|
+
const collectLeafIds = (items) => items.flatMap((item) => { var _a; return (isGroupItem(item) ? collectLeafIds((_a = item.children) !== null && _a !== void 0 ? _a : []) : [item.id]); });
|
|
202
|
+
const applyCheckedToLeavesById = (items, leafIds, checked) => items.map((item) => {
|
|
203
|
+
var _a;
|
|
204
|
+
if (isGroupItem(item)) {
|
|
205
|
+
return Object.assign(Object.assign({}, item), { children: applyCheckedToLeavesById((_a = item.children) !== null && _a !== void 0 ? _a : [], leafIds, checked) });
|
|
206
|
+
}
|
|
207
|
+
return leafIds.has(item.id) ? Object.assign(Object.assign({}, item), { checked }) : item;
|
|
208
|
+
});
|
|
185
209
|
const filterTree = (items, query) => {
|
|
186
210
|
const q = query.trim().toLowerCase();
|
|
187
211
|
if (!q)
|
|
188
212
|
return items;
|
|
189
213
|
return items.reduce((acc, item) => {
|
|
190
|
-
|
|
214
|
+
var _a;
|
|
215
|
+
const filteredChildren = isGroupItem(item) ? filterTree((_a = item.children) !== null && _a !== void 0 ? _a : [], q) : [];
|
|
191
216
|
if (item.title.toLowerCase().includes(q) || filteredChildren.length > 0) {
|
|
192
|
-
acc.push(Object.assign(Object.assign({}, item), { children: filteredChildren }));
|
|
217
|
+
acc.push(Object.assign(Object.assign({}, item), { children: isGroupItem(item) ? filteredChildren : undefined }));
|
|
193
218
|
}
|
|
194
219
|
return acc;
|
|
195
220
|
}, []);
|
|
196
221
|
};
|
|
197
|
-
const reorderSiblingsById = (items, sourceId, targetId) => {
|
|
222
|
+
const reorderSiblingsById = (items, sourceId, targetId, edge) => {
|
|
198
223
|
const si = items.findIndex((n) => n.id === sourceId);
|
|
199
224
|
const ti = items.findIndex((n) => n.id === targetId);
|
|
200
225
|
if (si < 0 || ti < 0 || si === ti)
|
|
201
226
|
return items;
|
|
202
|
-
|
|
227
|
+
let finishIndex = edge === "top" ? ti : ti + 1;
|
|
228
|
+
if (si < finishIndex)
|
|
229
|
+
finishIndex -= 1;
|
|
230
|
+
return reorder({ list: items, startIndex: si, finishIndex });
|
|
203
231
|
};
|
|
204
|
-
const reorderInTree = (items, sourceId, targetId) => {
|
|
205
|
-
const siblingResult = reorderSiblingsById(items, sourceId, targetId);
|
|
232
|
+
const reorderInTree = (items, sourceId, targetId, edge = "bottom") => {
|
|
233
|
+
const siblingResult = reorderSiblingsById(items, sourceId, targetId, edge);
|
|
206
234
|
if (siblingResult !== items)
|
|
207
235
|
return siblingResult;
|
|
208
236
|
return items.map((item) => {
|
|
209
237
|
var _a;
|
|
210
238
|
if (!((_a = item.children) === null || _a === void 0 ? void 0 : _a.length))
|
|
211
239
|
return item;
|
|
212
|
-
return Object.assign(Object.assign({}, item), { children: reorderInTree(item.children, sourceId, targetId) });
|
|
240
|
+
return Object.assign(Object.assign({}, item), { children: reorderInTree(item.children, sourceId, targetId, edge) });
|
|
213
241
|
});
|
|
214
242
|
};
|
|
215
243
|
const getExpandedIdsFromItems = (items, ids = new Set()) => {
|
|
@@ -405,7 +433,7 @@ const CustomDragLayer = () => {
|
|
|
405
433
|
React__default.createElement(ClonedNodeRenderer, { node: dragState.previewNode })), providerCurrent);
|
|
406
434
|
};
|
|
407
435
|
|
|
408
|
-
const ActionableListItem = ({ id, title, level, infoText, isExpandable = false, isExpanded = false, withCheckbox = false, checkboxChecked, checkboxIndeterminate, selectedCount = 0, totalCount = 0, descendantsSelectedCount = 0, descendantsTotalCount = 0, selectedLabel = "Selected", parentId = "root", isDraggable = false, dropGapEdge = null, expandAriaLabel = "Toggle nested items", onToggleExpand, onToggleCheck, onDragTargetChange, className }) => {
|
|
436
|
+
const ActionableListItem = ({ id, title, level, infoText, isExpandable = false, isExpandDisabled = false, isExpanded = false, withCheckbox = false, checkboxChecked, checkboxIndeterminate, selectedCount = 0, totalCount = 0, descendantsSelectedCount = 0, descendantsTotalCount = 0, selectedLabel = "Selected", parentId = "root", dragListId, isDraggable = false, dropGapEdge = null, expandAriaLabel = "Toggle nested items", onToggleExpand, onToggleCheck, onDragTargetChange, className }) => {
|
|
409
437
|
const rowRef = useRef(null);
|
|
410
438
|
const dragHandleRef = useRef(null);
|
|
411
439
|
const titleTextRef = useRef(null);
|
|
@@ -414,6 +442,8 @@ const ActionableListItem = ({ id, title, level, infoText, isExpandable = false,
|
|
|
414
442
|
const isTruncated = useEllipsisDetection(titleTextRef);
|
|
415
443
|
const isSelectedLabelTruncated = useEllipsisDetection(selectedLabelRef);
|
|
416
444
|
const [isDragging, setIsDragging] = useState(false);
|
|
445
|
+
const isRTLMode = typeof document !== "undefined" && document.dir === "rtl";
|
|
446
|
+
const collapsedExpandIcon = isRTLMode ? SvgChevronLeft : SvgChevronRight;
|
|
417
447
|
onDragTargetChangeRef.current = onDragTargetChange;
|
|
418
448
|
const computeEdge = (clientY) => {
|
|
419
449
|
const rowEl = rowRef.current;
|
|
@@ -446,12 +476,7 @@ const ActionableListItem = ({ id, title, level, infoText, isExpandable = false,
|
|
|
446
476
|
width: `${rect.width}px`,
|
|
447
477
|
height: `${rect.height}px`
|
|
448
478
|
});
|
|
449
|
-
return {
|
|
450
|
-
sourceId: id,
|
|
451
|
-
parentId,
|
|
452
|
-
previewNode: clone,
|
|
453
|
-
initialRect: rect
|
|
454
|
-
};
|
|
479
|
+
return Object.assign(Object.assign({ sourceId: id, parentId }, (dragListId ? { dragListId } : {})), { previewNode: clone, initialRect: rect });
|
|
455
480
|
},
|
|
456
481
|
onGenerateDragPreview: ({ nativeSetDragImage }) => {
|
|
457
482
|
disableNativeDragPreview({ nativeSetDragImage });
|
|
@@ -466,8 +491,15 @@ const ActionableListItem = ({ id, title, level, infoText, isExpandable = false,
|
|
|
466
491
|
}
|
|
467
492
|
}), dropTargetForElements({
|
|
468
493
|
element: rowEl,
|
|
469
|
-
getData: () => ({ targetId: id, parentId }),
|
|
470
|
-
canDrop: ({ source }) =>
|
|
494
|
+
getData: () => (Object.assign({ targetId: id, parentId }, (dragListId ? { targetListId: dragListId } : {}))),
|
|
495
|
+
canDrop: ({ source }) => {
|
|
496
|
+
const sourceListId = source.data.dragListId;
|
|
497
|
+
const sourceParentId = source.data.parentId;
|
|
498
|
+
if (dragListId && sourceListId && sourceListId !== dragListId) {
|
|
499
|
+
return parentId === "root";
|
|
500
|
+
}
|
|
501
|
+
return sourceParentId === parentId;
|
|
502
|
+
},
|
|
471
503
|
onDragEnter: ({ location }) => {
|
|
472
504
|
var _a;
|
|
473
505
|
(_a = onDragTargetChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onDragTargetChangeRef, computeEdge(location.current.input.clientY));
|
|
@@ -477,7 +509,7 @@ const ActionableListItem = ({ id, title, level, infoText, isExpandable = false,
|
|
|
477
509
|
(_a = onDragTargetChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onDragTargetChangeRef, computeEdge(location.current.input.clientY));
|
|
478
510
|
}
|
|
479
511
|
}));
|
|
480
|
-
}, [isDraggable, id, parentId]);
|
|
512
|
+
}, [isDraggable, id, parentId, dragListId]);
|
|
481
513
|
return (React__default.createElement("div", { ref: rowRef, className: classNames("actionableListItem", className, {
|
|
482
514
|
actionableListItem_nested: level > 1,
|
|
483
515
|
actionableListItem_draggable: isDraggable,
|
|
@@ -486,7 +518,7 @@ const ActionableListItem = ({ id, title, level, infoText, isExpandable = false,
|
|
|
486
518
|
actionableListItem_dropGapBottom: dropGapEdge === "bottom"
|
|
487
519
|
}) },
|
|
488
520
|
React__default.createElement(CustomDragLayer, null),
|
|
489
|
-
isExpandable && (React__default.createElement(Button, { size: "smallNudge", appearance: "secondary", layout: "text", Icon: isExpanded ? SvgChevronDown :
|
|
521
|
+
isExpandable && (React__default.createElement(Button, { size: "smallNudge", appearance: "secondary", layout: "text", Icon: isExpanded ? SvgChevronDown : collapsedExpandIcon, onClick: onToggleExpand, disabled: isExpandDisabled, "aria-label": expandAriaLabel, className: "actionableListItem__toggle" })),
|
|
490
522
|
withCheckbox && (React__default.createElement(Checkbox, { checked: resolvedCheckboxChecked, indeterminate: resolvedCheckboxIndeterminate, onChange: (event) => onToggleCheck === null || onToggleCheck === void 0 ? void 0 : onToggleCheck(event.target.checked), className: "actionableListItem__checkbox" })),
|
|
491
523
|
React__default.createElement(Tooltip, { text: title, isVisible: isTruncated },
|
|
492
524
|
React__default.createElement(Text, { ref: titleTextRef, as: "span", variant: "bodyMediumMedium", className: "actionableListItem__title ellipsis-text" }, title)),
|
|
@@ -505,45 +537,72 @@ const ActionableListNodeWrapper = ({ level, children }) => {
|
|
|
505
537
|
return React__default.createElement("div", { className: `actionableList__wrapper actionableList__wrapper_level_${level}` }, children);
|
|
506
538
|
};
|
|
507
539
|
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
540
|
+
const isDroppedOnOtherList = (location, listId) => location.current.dropTargets.some(({ data: { targetListId, isPanelDropTarget } }) => {
|
|
541
|
+
return typeof targetListId === "string" && targetListId !== listId && !isPanelDropTarget;
|
|
542
|
+
});
|
|
543
|
+
const RenderNode = ({ item, level, parentId, withCheckbox, isDraggable, dragListId, expandedIds, texts, dropGap, isSearchActive, onToggleExpand, onToggleCheck, onDragTargetChange }) => {
|
|
544
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
545
|
+
const isGroup = isGroupItem(item);
|
|
546
|
+
const childCount = (_b = (_a = item.children) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
|
|
547
|
+
const canExpand = isGroup && childCount > 0 && level < ACTIONABLE_LIST_MAX_NESTED_LEVEL;
|
|
512
548
|
const isExpanded = canExpand ? isSearchActive || expandedIds.has(item.id) : false;
|
|
513
549
|
const branchFullySelected = isSubtreeFullySelected(item);
|
|
514
550
|
const branchIndeterminate = !branchFullySelected && isAnySelectionInSubtree(item);
|
|
515
|
-
const directChildTotal = (
|
|
516
|
-
const directChildFullySelected = (
|
|
551
|
+
const directChildTotal = (_d = (_c = item.children) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
|
|
552
|
+
const directChildFullySelected = (_f = (_e = item.children) === null || _e === void 0 ? void 0 : _e.filter((child) => isSubtreeFullySelected(child)).length) !== null && _f !== void 0 ? _f : 0;
|
|
517
553
|
const childLevel = nextLevel(level);
|
|
518
554
|
const dropGapEdge = (dropGap === null || dropGap === void 0 ? void 0 : dropGap.targetId) === item.id ? dropGap.edge : null;
|
|
519
555
|
return (React__default.createElement(React__default.Fragment, null,
|
|
520
|
-
React__default.createElement(ActionableListItem, Object.assign({ id: item.id, title: item.title, level: level, parentId: parentId, infoText: item.infoText, isExpandable:
|
|
556
|
+
React__default.createElement(ActionableListItem, Object.assign({ id: item.id, title: item.title, level: level, parentId: parentId, infoText: item.infoText, isExpandable: isGroup && level < ACTIONABLE_LIST_MAX_NESTED_LEVEL, isExpandDisabled: isGroup && childCount === 0, isExpanded: isExpanded, withCheckbox: withCheckbox }, (withCheckbox
|
|
521
557
|
? {
|
|
522
558
|
checkboxChecked: branchFullySelected,
|
|
523
559
|
checkboxIndeterminate: branchIndeterminate,
|
|
524
560
|
descendantsSelectedCount: directChildFullySelected,
|
|
525
561
|
descendantsTotalCount: directChildTotal
|
|
526
562
|
}
|
|
527
|
-
: {}), { selectedLabel: texts.selectedItemsLabel, isDraggable: isDraggable, dropGapEdge: dropGapEdge, expandAriaLabel: texts.expandButtonAriaLabel, onToggleExpand: () => onToggleExpand(item.id), onToggleCheck: (checked) => onToggleCheck(item.id, checked), onDragTargetChange: (edge) => onDragTargetChange(item.id, edge) })),
|
|
563
|
+
: {}), { selectedLabel: texts.selectedItemsLabel, isDraggable: isDraggable, dragListId: dragListId, dropGapEdge: dropGapEdge, expandAriaLabel: texts.expandButtonAriaLabel, onToggleExpand: () => onToggleExpand(item.id), onToggleCheck: (checked) => onToggleCheck(item.id, checked), onDragTargetChange: (edge) => onDragTargetChange(item.id, edge) })),
|
|
528
564
|
canExpand && isExpanded && (React__default.createElement("div", { className: "actionableList__children" },
|
|
529
|
-
React__default.createElement(ActionableListNodeWrapper, { level: childLevel }, (
|
|
565
|
+
React__default.createElement(ActionableListNodeWrapper, { level: childLevel }, (_g = item.children) === null || _g === void 0 ? void 0 : _g.map((child) => (React__default.createElement(RenderNode, { key: child.id, item: child, level: childLevel, parentId: item.id, withCheckbox: withCheckbox, isDraggable: isDraggable, dragListId: dragListId, expandedIds: expandedIds, texts: texts, dropGap: dropGap, isSearchActive: isSearchActive, onToggleExpand: onToggleExpand, onToggleCheck: onToggleCheck, onDragTargetChange: onDragTargetChange }))))))));
|
|
530
566
|
};
|
|
531
567
|
/**
|
|
532
568
|
* Actionable List component provides users with a highly interactive, multi-functional list of items, organized in a hierarchical structure that supports up to five levels of nested items.
|
|
533
569
|
*/
|
|
534
|
-
const ActionableList = ({ className, items = [], withCheckbox = false, draggable: isDraggable = false, loading = false, defaultExpandAll = false, texts, onItemsChange, onItemCheck, onSelectAllChange, onSearch }) => {
|
|
570
|
+
const ActionableList = ({ className, items = [], withCheckbox = false, draggable: isDraggable = false, dragListId, loading = false, defaultExpandAll = false, texts, onItemsChange, onItemCheck, onSelectAllChange, managedSelection = false, onCrossListDrop, delegateCrossListDrop = false, dropGapOutletRef, onSearch }) => {
|
|
571
|
+
var _a;
|
|
535
572
|
const mergedTexts = Object.assign(Object.assign({}, ACTIONABLE_LIST_DEFAULT_TEXTS), texts);
|
|
536
573
|
const wasExpansionToggledRef = useRef(false);
|
|
574
|
+
const bulkSelectedLabelRef = useRef(null);
|
|
575
|
+
const filteredItemsLabelRef = useRef(null);
|
|
576
|
+
const totalItemsLabelRef = useRef(null);
|
|
577
|
+
const isBulkSelectedLabelTruncated = useEllipsisDetection(bulkSelectedLabelRef, [
|
|
578
|
+
mergedTexts.bulkSelectedItemsLabel
|
|
579
|
+
]);
|
|
580
|
+
const isFilteredItemsLabelTruncated = useEllipsisDetection(filteredItemsLabelRef, [mergedTexts.filteredItemsLabel]);
|
|
581
|
+
const isTotalItemsLabelTruncated = useEllipsisDetection(totalItemsLabelRef, [mergedTexts.totalItemsLabel]);
|
|
537
582
|
const [localItems, setLocalItems] = useState(() => mergeItemsFromProps(items, []));
|
|
583
|
+
const workingItems = managedSelection ? items : localItems;
|
|
538
584
|
const [searchValue, setSearchValue] = useState("");
|
|
539
585
|
const [expandedIds, setExpandedIds] = useState(() => defaultExpandAll ? getExpandedIdsFromItems(items) : new Set());
|
|
540
586
|
const [dropGap, setDropGap] = useState(null);
|
|
587
|
+
const [isEmptyDropActive, setIsEmptyDropActive] = useState(false);
|
|
588
|
+
const emptyDropRef = useRef(null);
|
|
589
|
+
const listEndDropRef = useRef(null);
|
|
541
590
|
const handleDropReorderRef = useRef(() => { });
|
|
591
|
+
const onCrossListDropRef = useRef(onCrossListDrop);
|
|
542
592
|
const dropGapRef = useRef(dropGap);
|
|
543
593
|
dropGapRef.current = dropGap;
|
|
594
|
+
onCrossListDropRef.current = onCrossListDrop;
|
|
544
595
|
useEffect(() => {
|
|
596
|
+
if (!dropGapOutletRef)
|
|
597
|
+
return;
|
|
598
|
+
// eslint-disable-next-line no-param-reassign -- mirror drop gap for parent-coordinated cross-list DnD
|
|
599
|
+
dropGapOutletRef.current = dropGap;
|
|
600
|
+
}, [dropGap, dropGapOutletRef]);
|
|
601
|
+
useLayoutEffect(() => {
|
|
602
|
+
if (managedSelection)
|
|
603
|
+
return;
|
|
545
604
|
setLocalItems((prev) => mergeItemsFromProps(items, prev));
|
|
546
|
-
}, [items]);
|
|
605
|
+
}, [items, managedSelection]);
|
|
547
606
|
useEffect(() => {
|
|
548
607
|
if (wasExpansionToggledRef.current)
|
|
549
608
|
return;
|
|
@@ -553,16 +612,41 @@ const ActionableList = ({ className, items = [], withCheckbox = false, draggable
|
|
|
553
612
|
if (!isDraggable)
|
|
554
613
|
return () => undefined;
|
|
555
614
|
return monitorForElements({
|
|
556
|
-
onDrop: ({ source }) => {
|
|
615
|
+
onDrop: ({ source, location }) => {
|
|
557
616
|
const gap = dropGapRef.current;
|
|
558
|
-
setDropGap(null);
|
|
559
617
|
const { sourceId } = source.data;
|
|
560
|
-
|
|
561
|
-
|
|
618
|
+
const sourceListId = source.data.dragListId;
|
|
619
|
+
const isCrossList = Boolean(dragListId && typeof sourceListId === "string" && sourceListId !== dragListId);
|
|
620
|
+
if (isCrossList && delegateCrossListDrop) {
|
|
621
|
+
requestAnimationFrame(() => setDropGap(null));
|
|
622
|
+
return;
|
|
562
623
|
}
|
|
624
|
+
setDropGap(null);
|
|
625
|
+
if (typeof sourceId !== "string" || !gap || sourceId === gap.targetId)
|
|
626
|
+
return;
|
|
627
|
+
if (isCrossList) {
|
|
628
|
+
if (!dragListId ||
|
|
629
|
+
!location.current.dropTargets.some(({ data }) => data.targetListId === dragListId) ||
|
|
630
|
+
!onCrossListDropRef.current) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
onCrossListDropRef.current({
|
|
634
|
+
sourceListId: sourceListId,
|
|
635
|
+
targetListId: dragListId,
|
|
636
|
+
sourceId,
|
|
637
|
+
targetId: gap.targetId,
|
|
638
|
+
edge: gap.edge
|
|
639
|
+
});
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (dragListId && typeof sourceListId === "string" && sourceListId === dragListId) {
|
|
643
|
+
if (isDroppedOnOtherList(location, dragListId))
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
handleDropReorderRef.current(sourceId, gap.targetId, gap.edge);
|
|
563
647
|
}
|
|
564
648
|
});
|
|
565
|
-
}, [isDraggable]);
|
|
649
|
+
}, [isDraggable, dragListId, delegateCrossListDrop]);
|
|
566
650
|
const { debouncedCallback, clearDebounce } = useDebouncedCallback((value) => {
|
|
567
651
|
if (typeof value !== "string")
|
|
568
652
|
return;
|
|
@@ -573,12 +657,17 @@ const ActionableList = ({ className, items = [], withCheckbox = false, draggable
|
|
|
573
657
|
setSearchValue(value);
|
|
574
658
|
debouncedCallback(value);
|
|
575
659
|
};
|
|
576
|
-
const filteredItems = useMemo(() => filterTree(
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
const
|
|
580
|
-
const
|
|
581
|
-
const
|
|
660
|
+
const filteredItems = useMemo(() => filterTree(workingItems, searchValue), [workingItems, searchValue]);
|
|
661
|
+
const isSearchActive = searchValue.trim().length > 0;
|
|
662
|
+
const filteredLeafIds = useMemo(() => new Set(collectLeafIds(filteredItems)), [filteredItems]);
|
|
663
|
+
const totalItemsCount = useMemo(() => countLeafItems(workingItems), [workingItems]);
|
|
664
|
+
const filteredItemsCount = useMemo(() => countLeafItems(filteredItems), [filteredItems]);
|
|
665
|
+
const selectedItemsCount = useMemo(() => countCheckedItems(workingItems), [workingItems]);
|
|
666
|
+
const selectedFilteredLeafCount = useMemo(() => countCheckedLeavesInScope(workingItems, filteredLeafIds), [workingItems, filteredLeafIds]);
|
|
667
|
+
const selectAllScopeLeafCount = isSearchActive ? filteredItemsCount : totalItemsCount;
|
|
668
|
+
const selectedScopeLeafCount = isSearchActive ? selectedFilteredLeafCount : selectedItemsCount;
|
|
669
|
+
const selectAllChecked = selectAllScopeLeafCount > 0 && selectedScopeLeafCount === selectAllScopeLeafCount;
|
|
670
|
+
const selectAllIndeterminate = selectedScopeLeafCount > 0 && selectedScopeLeafCount < selectAllScopeLeafCount;
|
|
582
671
|
const handleToggleExpand = (id) => {
|
|
583
672
|
wasExpansionToggledRef.current = true;
|
|
584
673
|
setExpandedIds((prev) => {
|
|
@@ -595,24 +684,30 @@ const ActionableList = ({ className, items = [], withCheckbox = false, draggable
|
|
|
595
684
|
onItemsChange === null || onItemsChange === void 0 ? void 0 : onItemsChange(nextItems);
|
|
596
685
|
};
|
|
597
686
|
const handleToggleCheck = (id, checked) => {
|
|
598
|
-
const nextItems = updateItemById(
|
|
599
|
-
|
|
687
|
+
const nextItems = updateItemById(workingItems, id, (item) => applyCheckedToBranch(item, checked));
|
|
688
|
+
if (!managedSelection) {
|
|
689
|
+
syncItems(nextItems);
|
|
690
|
+
}
|
|
600
691
|
const toggled = findItemById(nextItems, id);
|
|
601
692
|
if (toggled !== undefined) {
|
|
602
693
|
onItemCheck === null || onItemCheck === void 0 ? void 0 : onItemCheck(toggled, checked, nextItems);
|
|
603
694
|
}
|
|
604
695
|
};
|
|
605
696
|
const handleSelectAll = (checked) => {
|
|
606
|
-
if (
|
|
697
|
+
if (selectAllScopeLeafCount === 0)
|
|
607
698
|
return;
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
699
|
+
const scopeLeafIds = collectLeafIds(isSearchActive ? filteredItems : workingItems);
|
|
700
|
+
const targetLeafIds = new Set(scopeLeafIds);
|
|
701
|
+
const nextItems = applyCheckedToLeavesById(workingItems, targetLeafIds, checked);
|
|
702
|
+
onSelectAllChange === null || onSelectAllChange === void 0 ? void 0 : onSelectAllChange(checked, nextItems, scopeLeafIds);
|
|
703
|
+
if (!managedSelection) {
|
|
704
|
+
syncItems(nextItems);
|
|
705
|
+
}
|
|
611
706
|
};
|
|
612
|
-
const handleDropReorder = (sourceId, targetId) => {
|
|
707
|
+
const handleDropReorder = (sourceId, targetId, edge) => {
|
|
613
708
|
if (sourceId === targetId)
|
|
614
709
|
return;
|
|
615
|
-
syncItems(reorderInTree(
|
|
710
|
+
syncItems(reorderInTree(workingItems, sourceId, targetId, edge));
|
|
616
711
|
};
|
|
617
712
|
handleDropReorderRef.current = handleDropReorder;
|
|
618
713
|
const handleDragTargetChange = useCallback((targetId, edge) => {
|
|
@@ -622,31 +717,94 @@ const ActionableList = ({ className, items = [], withCheckbox = false, draggable
|
|
|
622
717
|
return { targetId, edge };
|
|
623
718
|
});
|
|
624
719
|
}, []);
|
|
625
|
-
const hasData =
|
|
626
|
-
const hasSearchResults =
|
|
720
|
+
const hasData = useMemo(() => countAllItems(workingItems) > 0, [workingItems]);
|
|
721
|
+
const hasSearchResults = useMemo(() => countAllItems(filteredItems) > 0, [filteredItems]);
|
|
722
|
+
const showEmptyDropZone = isDraggable && Boolean(dragListId) && Boolean(onCrossListDrop || delegateCrossListDrop) && !loading && !hasData;
|
|
723
|
+
useEffect(() => {
|
|
724
|
+
const element = emptyDropRef.current;
|
|
725
|
+
if (!element || !showEmptyDropZone)
|
|
726
|
+
return () => undefined;
|
|
727
|
+
return dropTargetForElements({
|
|
728
|
+
element,
|
|
729
|
+
getData: () => (Object.assign({ isEmptyListTarget: true }, (dragListId ? { targetListId: dragListId } : {}))),
|
|
730
|
+
canDrop: ({ source }) => {
|
|
731
|
+
const sourceListId = source.data.dragListId;
|
|
732
|
+
return typeof sourceListId === "string" && sourceListId !== dragListId;
|
|
733
|
+
},
|
|
734
|
+
onDragEnter: () => setIsEmptyDropActive(true),
|
|
735
|
+
onDragLeave: () => setIsEmptyDropActive(false),
|
|
736
|
+
onDrop: ({ source }) => {
|
|
737
|
+
setIsEmptyDropActive(false);
|
|
738
|
+
if (delegateCrossListDrop)
|
|
739
|
+
return;
|
|
740
|
+
const { sourceId } = source.data;
|
|
741
|
+
const sourceListId = source.data.dragListId;
|
|
742
|
+
if (typeof sourceId !== "string" ||
|
|
743
|
+
typeof sourceListId !== "string" ||
|
|
744
|
+
!dragListId ||
|
|
745
|
+
!onCrossListDropRef.current) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
onCrossListDropRef.current({
|
|
749
|
+
sourceListId,
|
|
750
|
+
targetListId: dragListId,
|
|
751
|
+
sourceId,
|
|
752
|
+
isEmptyTarget: true
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
}, [showEmptyDropZone, dragListId, delegateCrossListDrop]);
|
|
757
|
+
const lastRootItemId = (_a = filteredItems[filteredItems.length - 1]) === null || _a === void 0 ? void 0 : _a.id;
|
|
758
|
+
useEffect(() => {
|
|
759
|
+
const element = listEndDropRef.current;
|
|
760
|
+
if (!element || !isDraggable || !hasSearchResults || !lastRootItemId)
|
|
761
|
+
return () => undefined;
|
|
762
|
+
const targetId = lastRootItemId;
|
|
763
|
+
return dropTargetForElements({
|
|
764
|
+
element,
|
|
765
|
+
getData: () => (Object.assign({ targetId, edge: "bottom" }, (dragListId ? { targetListId: dragListId } : {}))),
|
|
766
|
+
canDrop: ({ source }) => {
|
|
767
|
+
const sourceListId = source.data.dragListId;
|
|
768
|
+
const sourceParentId = source.data.parentId;
|
|
769
|
+
if (dragListId && typeof sourceListId === "string" && sourceListId !== dragListId) {
|
|
770
|
+
return sourceParentId === "root";
|
|
771
|
+
}
|
|
772
|
+
return sourceParentId === "root";
|
|
773
|
+
},
|
|
774
|
+
onDragEnter: () => handleDragTargetChange(targetId, "bottom"),
|
|
775
|
+
onDrag: () => handleDragTargetChange(targetId, "bottom")
|
|
776
|
+
});
|
|
777
|
+
}, [isDraggable, hasSearchResults, lastRootItemId, dragListId, handleDragTargetChange]);
|
|
627
778
|
return (React__default.createElement("div", { className: classNames("actionableList", className, { actionableList_hasDropGap: dropGap !== null }) },
|
|
628
779
|
React__default.createElement("div", { className: "actionableList__headerWrapper" },
|
|
629
780
|
React__default.createElement(TextField, { label: mergedTexts.searchLabel, placeholder: mergedTexts.searchPlaceholder, value: searchValue, onChange: (event) => handleSearchChange(event.target.value), IconBefore: SvgMagnifier, disabled: loading, className: "actionableList__search", clearable: true, onClear: () => handleSearchChange("") }),
|
|
630
781
|
!loading && (React__default.createElement("div", { className: "actionableList__statsRow" },
|
|
631
782
|
withCheckbox && (React__default.createElement("div", { className: "actionableList__bulkSelection" },
|
|
632
|
-
React__default.createElement(Checkbox, { checked: selectAllChecked, indeterminate: selectAllIndeterminate, disabled:
|
|
633
|
-
React__default.createElement(
|
|
783
|
+
React__default.createElement(Checkbox, { checked: selectAllChecked, indeterminate: selectAllIndeterminate, disabled: selectAllScopeLeafCount === 0, onChange: (event) => handleSelectAll(event.target.checked), className: "actionableList__selectAll" }),
|
|
784
|
+
React__default.createElement("div", { className: "actionableList__bulkSelectedLabelWrapper" },
|
|
785
|
+
React__default.createElement(Tooltip, { text: mergedTexts.bulkSelectedItemsLabel, isVisible: isBulkSelectedLabelTruncated },
|
|
786
|
+
React__default.createElement(Text, { ref: bulkSelectedLabelRef, as: "span", variant: "bodyMediumMedium", className: "actionableList__bulkSelectedLabel ellipsis-text" }, mergedTexts.bulkSelectedItemsLabel))),
|
|
634
787
|
React__default.createElement(Text, { as: "span", variant: "bodyMediumMedium", className: "actionableList__bulkSelectedCount" }, selectedItemsCount))),
|
|
635
788
|
React__default.createElement("div", { className: "actionableList__stats" },
|
|
636
|
-
React__default.createElement(
|
|
789
|
+
React__default.createElement(Tooltip, { text: mergedTexts.filteredItemsLabel, isVisible: isFilteredItemsLabelTruncated },
|
|
790
|
+
React__default.createElement(Text, { ref: filteredItemsLabelRef, as: "span", variant: "bodyMediumMedium", className: "actionableList__statsLabel ellipsis-text" }, mergedTexts.filteredItemsLabel)),
|
|
637
791
|
React__default.createElement(Text, { as: "span", variant: "bodyMediumMedium", className: "actionableList__statsCount" }, filteredItemsCount),
|
|
638
792
|
React__default.createElement(Divider, { direction: "vertical", className: "actionableList__statsDivider" }),
|
|
639
|
-
React__default.createElement(
|
|
793
|
+
React__default.createElement(Tooltip, { text: mergedTexts.totalItemsLabel, isVisible: isTotalItemsLabelTruncated },
|
|
794
|
+
React__default.createElement(Text, { ref: totalItemsLabelRef, as: "span", variant: "bodyMediumMedium", className: "actionableList__statsLabel ellipsis-text" }, mergedTexts.totalItemsLabel)),
|
|
640
795
|
React__default.createElement(Text, { as: "span", variant: "bodyMediumMedium", className: "actionableList__statsCount" }, totalItemsCount))))),
|
|
641
796
|
loading && (React__default.createElement("div", { className: "actionableList__state actionableList__state_loading" },
|
|
642
797
|
React__default.createElement(Loader, { text: mergedTexts.loadingTitle, size: "xLarge", textPosition: "below" }))),
|
|
643
798
|
React__default.createElement(Scrollbar, null, !loading && (React__default.createElement(React__default.Fragment, null,
|
|
644
|
-
!hasData && (React__default.createElement("div", { className: "actionableList__state"
|
|
799
|
+
!hasData && (React__default.createElement("div", { ref: showEmptyDropZone ? emptyDropRef : undefined, className: classNames("actionableList__state", {
|
|
800
|
+
actionableList__state_dropTarget: isEmptyDropActive
|
|
801
|
+
}) },
|
|
645
802
|
React__default.createElement(Empty, { size: "small", appearance: "noData", title: mergedTexts.noDataTitle, description: mergedTexts.noDataDescription, className: "actionableList__empty" }))),
|
|
646
803
|
hasData && !hasSearchResults && (React__default.createElement("div", { className: "actionableList__state" },
|
|
647
804
|
React__default.createElement(Empty, { size: "small", appearance: "noResult", title: mergedTexts.noResultsTitle, description: mergedTexts.noResultsDescription, className: "actionableList__empty" }))),
|
|
648
805
|
hasData && hasSearchResults && (React__default.createElement("div", { className: "actionableList__list" },
|
|
649
|
-
React__default.createElement(ActionableListNodeWrapper, { level: 1 }, filteredItems.map((item) => (React__default.createElement(RenderNode, { key: item.id, item: item, level: 1, parentId: "root", withCheckbox: withCheckbox, isDraggable: isDraggable, expandedIds: expandedIds, texts: mergedTexts, dropGap: dropGap, isSearchActive: searchValue.trim().length > 0, onToggleExpand: handleToggleExpand, onToggleCheck: handleToggleCheck, onDragTargetChange: handleDragTargetChange }))))
|
|
806
|
+
React__default.createElement(ActionableListNodeWrapper, { level: 1 }, filteredItems.map((item) => (React__default.createElement(RenderNode, { key: item.id, item: item, level: 1, parentId: "root", withCheckbox: withCheckbox, isDraggable: isDraggable, dragListId: dragListId, expandedIds: expandedIds, texts: mergedTexts, dropGap: dropGap, isSearchActive: searchValue.trim().length > 0, onToggleExpand: handleToggleExpand, onToggleCheck: handleToggleCheck, onDragTargetChange: handleDragTargetChange })))),
|
|
807
|
+
isDraggable && (React__default.createElement("div", { ref: listEndDropRef, className: "actionableList__listEndDrop", "aria-hidden": true })))))))));
|
|
650
808
|
};
|
|
651
809
|
|
|
652
810
|
export { ActionableList as default };
|
|
@@ -2105,7 +2105,7 @@ function draggable(args) {
|
|
|
2105
2105
|
|
|
2106
2106
|
/** Arguments given to all monitor feedback functions (eg `canMonitor()`) for a `monitorForElements` */
|
|
2107
2107
|
|
|
2108
|
-
var css_248z$2 = ".actionableList{display:flex;flex-direction:column;height:100%;width:100%}.actionableList__headerWrapper{display:flex;flex-direction:column;gap:var(--guit-ref-spacing-medium)}.actionableList__search{width:100%}.actionableList__statsRow{align-items:center;display:flex;flex-wrap:
|
|
2108
|
+
var css_248z$2 = ".actionableList{display:flex;flex-direction:column;height:100%;width:100%}.actionableList__headerWrapper{display:flex;flex-direction:column;gap:var(--guit-ref-spacing-medium)}.actionableList__search{width:100%}.actionableList__statsRow{align-items:center;display:flex;flex-wrap:nowrap;gap:var(--guit-ref-spacing-medium);justify-content:space-between;width:100%}.actionableList__bulkSelection{align-items:center;display:flex;flex-shrink:1;gap:var(--guit-ref-spacing-2xsmall);min-width:0}.actionableList__selectAll{flex-shrink:0}.actionableList__bulkSelectedLabelWrapper{flex:1 1 auto;min-width:0;overflow:hidden}.actionableList__checkbox{align-items:center;display:flex;flex-shrink:0;height:var(--guit-sem-dimension-height-small);justify-content:center;width:var(--guit-sem-dimension-width-small)}.actionableList__bulkSelectedLabel{color:var(--guit-sem-color-foreground-neutral-1);display:block;min-width:0}.actionableList__bulkSelectedCount{color:var(--guit-sem-color-foreground-neutral-2);flex-shrink:0}.actionableList__stats{-webkit-margin-start:auto;align-items:center;display:flex;flex-shrink:1;gap:var(--guit-ref-spacing-2xsmall);justify-content:flex-end;margin-inline-start:auto;min-width:0}.actionableList__statsLabel{color:var(--guit-sem-color-foreground-neutral-1);display:block;min-width:0}.actionableList__statsCount{color:var(--guit-sem-color-foreground-neutral-2);flex-shrink:0}.actionableList__statsDivider{flex-shrink:0;min-height:1.6rem}.actionableList__list{display:flex;flex-direction:column;min-height:100%;width:100%}.actionableList__listEndDrop{flex:1 1 auto;height:100%;width:100%}.actionableList__children{overflow:hidden;position:relative;width:100%}.actionableList__state{align-items:center;display:flex;justify-content:center;min-height:22.6rem;width:100%}.actionableList__state_dropTarget{background-color:var(--guit-sem-color-background-brand-1-subtle);border-radius:var(--guit-ref-radius-xsmall);outline:var(--guit-ref-border-width-thin) var(--guit-ref-border-style-solid) var(--guit-sem-color-border-brand-1);outline-offset:calc(var(--guit-ref-border-width-thin)*-1)}.actionableList__empty{width:100%}";
|
|
2109
2109
|
styleInject(css_248z$2);
|
|
2110
2110
|
|
|
2111
2111
|
// In order to disable the native drag preview you can
|
package/SegmentedControl.js
CHANGED
|
@@ -61,11 +61,12 @@ import './OTPField-af15abb1.js';
|
|
|
61
61
|
import './index-3dc2323a.js';
|
|
62
62
|
import './PageHeader-cfa2c255.js';
|
|
63
63
|
import './PopoverConfirm-d6880a4f.js';
|
|
64
|
-
import './ActionableListNodeWrapper-
|
|
64
|
+
import './ActionableListNodeWrapper-0a3308ca.js';
|
|
65
65
|
import './List-586cdebf.js';
|
|
66
66
|
import './DataTable-c7132016.js';
|
|
67
67
|
import './GlobalHeader-e3be2af9.js';
|
|
68
68
|
import './DataCardList-eb674418.js';
|
|
69
|
+
import './TransferList-55c2e836.js';
|
|
69
70
|
import './useEllipsisDetection-c5f9b175.js';
|
|
70
71
|
import './useWindowSize-6faa0d0f.js';
|
|
71
72
|
import './useDebounceCallback-999deae7.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { s as styleInject } from './style-inject.es-746bb8ed.js';
|
|
2
|
+
|
|
3
|
+
var css_248z = ".transferList{border:var(--guit-ref-border-width-thin) var(--guit-ref-border-style-solid) var(--guit-sem-color-border-neutral-2);border-radius:var(--guit-ref-radius-xsmall);display:grid;height:39.8rem;overflow:hidden;width:100%}.transferList_panels2{grid-template-columns:repeat(1,1fr auto) 1fr}.transferList_panels3{grid-template-columns:repeat(2,1fr auto) 1fr}.transferList_panels4{grid-template-columns:repeat(3,1fr auto) 1fr}.transferList__panel{display:flex;flex-direction:column;min-height:0;min-width:0;padding:var(--guit-ref-spacing-large)}.transferList__actionableList{flex:1;min-height:0}.transferList__controls{border-inline:var(--guit-ref-border-width-thin) var(--guit-ref-border-style-solid) var(--guit-sem-color-border-neutral-2);display:flex;flex-direction:column;gap:var(--guit-ref-spacing-2xsmall);justify-content:center;padding:var(--guit-ref-spacing-large)}";
|
|
4
|
+
styleInject(css_248z);
|
package/TransferList.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import React__default, { useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect, Fragment } from 'react';
|
|
2
|
+
import { m as monitorForElements, b as dropTargetForElements } from './ActionableListNodeWrapper-0a3308ca.js';
|
|
3
|
+
import { c as classNames } from './index-ce02421b.js';
|
|
4
|
+
import { a as SvgChevronLeft, S as SvgChevronRight } from './ChevronRight-af87e2a4.js';
|
|
5
|
+
import Button from './Button.js';
|
|
6
|
+
import ActionableList from './ActionableList.js';
|
|
7
|
+
import './TransferList-55c2e836.js';
|
|
8
|
+
import './index-4388be47.js';
|
|
9
|
+
import 'prop-types';
|
|
10
|
+
import 'react-dom';
|
|
11
|
+
import './useDebounceCallback-999deae7.js';
|
|
12
|
+
import './style-inject.es-746bb8ed.js';
|
|
13
|
+
import './ActivityRecent-b88e2ba8.js';
|
|
14
|
+
import './Loader.js';
|
|
15
|
+
import './Magnifier-8df67f2d.js';
|
|
16
|
+
import './Divider.js';
|
|
17
|
+
import './Text.js';
|
|
18
|
+
import './Checkbox.js';
|
|
19
|
+
import './index-15303b91.js';
|
|
20
|
+
import './CheckMark-34798545.js';
|
|
21
|
+
import './Minus-c3ff7a21.js';
|
|
22
|
+
import './HelperText.js';
|
|
23
|
+
import './ErrorFilled-f4226310.js';
|
|
24
|
+
import './TriangleAlert-ba250f4c.js';
|
|
25
|
+
import './Label.js';
|
|
26
|
+
import './Info.js';
|
|
27
|
+
import './Info-0f0741e0.js';
|
|
28
|
+
import './Tooltip.js';
|
|
29
|
+
import './index-7d785c81.js';
|
|
30
|
+
import './index-9a69bcc6.js';
|
|
31
|
+
import './useWindowSize-6faa0d0f.js';
|
|
32
|
+
import './useDeviceInfo-8340f573.js';
|
|
33
|
+
import './useEllipsisDetection-c5f9b175.js';
|
|
34
|
+
import './Checkbox-11eae830.js';
|
|
35
|
+
import './index-a46fb8ec.js';
|
|
36
|
+
import './ButtonGroup-f8e59eb9.js';
|
|
37
|
+
import './tslib.es6-f211516f.js';
|
|
38
|
+
import './X-20da3b4a.js';
|
|
39
|
+
import './index-5becfc74.js';
|
|
40
|
+
import './TextField.js';
|
|
41
|
+
import './regex-d4e84e78.js';
|
|
42
|
+
import './ChevronDown-ee53f094.js';
|
|
43
|
+
|
|
44
|
+
const TRANSFER_LIST_MIN_PANELS = 2;
|
|
45
|
+
const TRANSFER_LIST_MAX_PANELS = 4;
|
|
46
|
+
const assertPanelCount = (panels) => {
|
|
47
|
+
if (panels.length < TRANSFER_LIST_MIN_PANELS || panels.length > TRANSFER_LIST_MAX_PANELS) {
|
|
48
|
+
throw new Error(`TransferList requires between ${TRANSFER_LIST_MIN_PANELS} and ${TRANSFER_LIST_MAX_PANELS} panels, received ${panels.length}.`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const collectNodeIds = (item) => {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
return [
|
|
54
|
+
item.id,
|
|
55
|
+
...((_b = (_a = item.children) === null || _a === void 0 ? void 0 : _a.flatMap((child) => collectNodeIds(child))) !== null && _b !== void 0 ? _b : [])
|
|
56
|
+
];
|
|
57
|
+
};
|
|
58
|
+
const collectTreeIds = (items) => items.flatMap((item) => collectNodeIds(item));
|
|
59
|
+
const mergeSelectionWithScope = (current, scopeLeafIds, checked) => {
|
|
60
|
+
const scopeSet = new Set(scopeLeafIds);
|
|
61
|
+
if (!checked) {
|
|
62
|
+
const next = new Set(current);
|
|
63
|
+
scopeSet.forEach((id) => next.delete(id));
|
|
64
|
+
return next;
|
|
65
|
+
}
|
|
66
|
+
const preservedOutsideScope = [...current].filter((id) => !scopeSet.has(id));
|
|
67
|
+
return new Set([...preservedOutsideScope, ...scopeSet]);
|
|
68
|
+
};
|
|
69
|
+
const applySelectionToTree = (items, selectedIds) => items.map((item) => (Object.assign(Object.assign({}, item), { checked: selectedIds.has(item.id), children: Array.isArray(item.children) ? applySelectionToTree(item.children, selectedIds) : undefined })));
|
|
70
|
+
const partitionTreeByIds = (items, selectedIds) => {
|
|
71
|
+
const remaining = [];
|
|
72
|
+
const moved = [];
|
|
73
|
+
const movedIds = new Set();
|
|
74
|
+
const walk = (node) => {
|
|
75
|
+
var _a;
|
|
76
|
+
if (selectedIds.has(node.id)) {
|
|
77
|
+
collectNodeIds(node).forEach((id) => movedIds.add(id));
|
|
78
|
+
moved.push(node);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (!((_a = node.children) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
const keptChildren = [];
|
|
85
|
+
node.children.forEach((child) => {
|
|
86
|
+
const keptChild = walk(child);
|
|
87
|
+
if (keptChild)
|
|
88
|
+
keptChildren.push(keptChild);
|
|
89
|
+
});
|
|
90
|
+
return Object.assign(Object.assign({}, node), { children: Array.isArray(node.children) ? keptChildren : undefined });
|
|
91
|
+
};
|
|
92
|
+
items.forEach((item) => {
|
|
93
|
+
const keptItem = walk(item);
|
|
94
|
+
if (keptItem)
|
|
95
|
+
remaining.push(keptItem);
|
|
96
|
+
});
|
|
97
|
+
return { remaining, moved, movedIds: Array.from(movedIds) };
|
|
98
|
+
};
|
|
99
|
+
const clearChecked = (item) => {
|
|
100
|
+
var _a;
|
|
101
|
+
return (Object.assign(Object.assign({}, item), { checked: false, children: (_a = item.children) === null || _a === void 0 ? void 0 : _a.map((child) => clearChecked(child)) }));
|
|
102
|
+
};
|
|
103
|
+
const mergeMovedItems = (target, moved) => {
|
|
104
|
+
if (!moved.length)
|
|
105
|
+
return target;
|
|
106
|
+
const existingIds = new Set(collectTreeIds(target));
|
|
107
|
+
const dedupedMoved = moved
|
|
108
|
+
.filter((item) => !existingIds.has(item.id))
|
|
109
|
+
.map((item) => {
|
|
110
|
+
const cleared = clearChecked(item);
|
|
111
|
+
collectNodeIds(cleared).forEach((id) => existingIds.add(id));
|
|
112
|
+
return cleared;
|
|
113
|
+
});
|
|
114
|
+
return [...target, ...dedupedMoved];
|
|
115
|
+
};
|
|
116
|
+
const insertAtRoot = (items, item, targetId, edge = "bottom") => {
|
|
117
|
+
const targetIndex = items.findIndex((node) => node.id === targetId);
|
|
118
|
+
if (targetIndex === -1)
|
|
119
|
+
return [...items, item];
|
|
120
|
+
const insertIndex = edge === "top" ? targetIndex : targetIndex + 1;
|
|
121
|
+
const next = [...items];
|
|
122
|
+
next.splice(insertIndex, 0, item);
|
|
123
|
+
return next;
|
|
124
|
+
};
|
|
125
|
+
const moveBetweenPanels = (sourceItems, targetItems, selectedIds) => {
|
|
126
|
+
const { remaining, moved, movedIds } = partitionTreeByIds(sourceItems, selectedIds);
|
|
127
|
+
if (!moved.length) {
|
|
128
|
+
return { sourceItems, targetItems, movedIds: [] };
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
sourceItems: remaining,
|
|
132
|
+
targetItems: mergeMovedItems(targetItems, moved),
|
|
133
|
+
movedIds
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
const moveItemByDrag = (sourceItems, targetItems, sourceId, targetId, edge = "bottom", isEmptyTarget = false) => {
|
|
137
|
+
const { remaining, moved, movedIds } = partitionTreeByIds(sourceItems, new Set([sourceId]));
|
|
138
|
+
if (!moved.length) {
|
|
139
|
+
return { sourceItems, targetItems, movedIds: [] };
|
|
140
|
+
}
|
|
141
|
+
const cleared = moved.map((item) => clearChecked(item));
|
|
142
|
+
const existingIds = new Set(collectTreeIds(targetItems));
|
|
143
|
+
const deduped = cleared.filter((item) => !existingIds.has(item.id));
|
|
144
|
+
if (!deduped.length) {
|
|
145
|
+
return { sourceItems: remaining, targetItems, movedIds: [] };
|
|
146
|
+
}
|
|
147
|
+
if (isEmptyTarget || !targetItems.length || !targetId) {
|
|
148
|
+
return {
|
|
149
|
+
sourceItems: remaining,
|
|
150
|
+
targetItems: mergeMovedItems(targetItems, deduped),
|
|
151
|
+
movedIds
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
let nextTarget = targetItems;
|
|
155
|
+
deduped.forEach((item) => {
|
|
156
|
+
nextTarget = insertAtRoot(nextTarget, item, targetId, edge);
|
|
157
|
+
});
|
|
158
|
+
return { sourceItems: remaining, targetItems: nextTarget, movedIds };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const applyDropGap = (targetListId, payload, panelDropGapRefs) => {
|
|
162
|
+
var _a;
|
|
163
|
+
const gap = (_a = panelDropGapRefs[targetListId]) === null || _a === void 0 ? void 0 : _a.current;
|
|
164
|
+
if (!gap)
|
|
165
|
+
return payload;
|
|
166
|
+
return Object.assign(Object.assign({}, payload), { targetId: gap.targetId, edge: gap.edge });
|
|
167
|
+
};
|
|
168
|
+
const resolveCrossListDrop = (sourceData, dropTargets, panelDropGapRefs) => {
|
|
169
|
+
const { sourceId } = sourceData;
|
|
170
|
+
const sourceListId = sourceData.dragListId;
|
|
171
|
+
if (typeof sourceId !== "string" || typeof sourceListId !== "string")
|
|
172
|
+
return null;
|
|
173
|
+
const crossListTargets = dropTargets.filter(({ data }) => {
|
|
174
|
+
const { targetListId } = data;
|
|
175
|
+
return typeof targetListId === "string" && targetListId !== sourceListId;
|
|
176
|
+
});
|
|
177
|
+
if (!crossListTargets.length)
|
|
178
|
+
return null;
|
|
179
|
+
const emptyTarget = crossListTargets.find(({ data }) => data.isEmptyListTarget);
|
|
180
|
+
if (emptyTarget) {
|
|
181
|
+
return {
|
|
182
|
+
sourceListId,
|
|
183
|
+
sourceId,
|
|
184
|
+
targetListId: emptyTarget.data.targetListId,
|
|
185
|
+
isEmptyTarget: true
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const rowTargets = crossListTargets.filter(({ data }) => !data.isPanelDropTarget);
|
|
189
|
+
if (rowTargets.length) {
|
|
190
|
+
const { data } = rowTargets[rowTargets.length - 1];
|
|
191
|
+
const targetListId = data.targetListId;
|
|
192
|
+
return applyDropGap(targetListId, {
|
|
193
|
+
sourceListId,
|
|
194
|
+
sourceId,
|
|
195
|
+
targetListId,
|
|
196
|
+
targetId: typeof data.targetId === "string" ? data.targetId : undefined,
|
|
197
|
+
edge: data.edge === "top" || data.edge === "bottom" ? data.edge : "bottom"
|
|
198
|
+
}, panelDropGapRefs);
|
|
199
|
+
}
|
|
200
|
+
const panelTargets = crossListTargets.filter(({ data }) => data.isPanelDropTarget);
|
|
201
|
+
if (panelTargets.length) {
|
|
202
|
+
const { data } = panelTargets[panelTargets.length - 1];
|
|
203
|
+
const targetListId = data.targetListId;
|
|
204
|
+
return applyDropGap(targetListId, {
|
|
205
|
+
sourceListId,
|
|
206
|
+
sourceId,
|
|
207
|
+
targetListId,
|
|
208
|
+
targetId: typeof data.targetId === "string" ? data.targetId : undefined,
|
|
209
|
+
edge: data.edge === "top" || data.edge === "bottom" ? data.edge : "bottom"
|
|
210
|
+
}, panelDropGapRefs);
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Transfer List component enables users to move items between two to four actionable lists.
|
|
216
|
+
*/
|
|
217
|
+
const TransferList = ({ className, panels, draggable = false, onChange }) => {
|
|
218
|
+
assertPanelCount(panels);
|
|
219
|
+
const isRTLMode = typeof document !== "undefined" && document.dir === "rtl";
|
|
220
|
+
const ForwardIcon = isRTLMode ? SvgChevronLeft : SvgChevronRight;
|
|
221
|
+
const BackwardIcon = isRTLMode ? SvgChevronRight : SvgChevronLeft;
|
|
222
|
+
const isControlled = panels.every((panel) => panel.items !== undefined);
|
|
223
|
+
const [uncontrolledPanelItems, setUncontrolledPanelItems] = useState(() => panels.map((panel) => { var _a; return (_a = panel.defaultItems) !== null && _a !== void 0 ? _a : []; }));
|
|
224
|
+
const [selectedIdsByPanel, setSelectedIdsByPanel] = useState(() => panels.map(() => new Set()));
|
|
225
|
+
const panelRefs = useRef({});
|
|
226
|
+
const panelDropGapRefs = useRef({});
|
|
227
|
+
const getDropGapOutletRef = (panelId) => {
|
|
228
|
+
if (!panelDropGapRefs.current[panelId]) {
|
|
229
|
+
panelDropGapRefs.current[panelId] = { current: null };
|
|
230
|
+
}
|
|
231
|
+
return panelDropGapRefs.current[panelId];
|
|
232
|
+
};
|
|
233
|
+
const panelTreeIdSignature = useMemo(() => (isControlled ? panels.map((panel) => { var _a; return (_a = panel.items) !== null && _a !== void 0 ? _a : []; }) : uncontrolledPanelItems)
|
|
234
|
+
.map((items) => collectTreeIds(items).sort().join(","))
|
|
235
|
+
.join("|"), [isControlled, panels, uncontrolledPanelItems]);
|
|
236
|
+
const resolvedPanelItems = useMemo(() => (isControlled ? panels.map((panel) => { var _a; return (_a = panel.items) !== null && _a !== void 0 ? _a : []; }) : uncontrolledPanelItems), [isControlled, panels, uncontrolledPanelItems, panelTreeIdSignature]);
|
|
237
|
+
const panelViewItems = useMemo(() => resolvedPanelItems.map((items, index) => { var _a; return applySelectionToTree(items, (_a = selectedIdsByPanel[index]) !== null && _a !== void 0 ? _a : new Set()); }), [resolvedPanelItems, selectedIdsByPanel]);
|
|
238
|
+
const emitChange = useCallback((payload) => {
|
|
239
|
+
if (!isControlled) {
|
|
240
|
+
setUncontrolledPanelItems(payload.panels);
|
|
241
|
+
}
|
|
242
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(payload);
|
|
243
|
+
}, [isControlled, onChange]);
|
|
244
|
+
const updateSelection = (item, checked, panelIndex) => {
|
|
245
|
+
const branchIds = collectNodeIds(item);
|
|
246
|
+
setSelectedIdsByPanel((prev) => prev.map((set, index) => {
|
|
247
|
+
if (index !== panelIndex)
|
|
248
|
+
return set;
|
|
249
|
+
const next = new Set(set);
|
|
250
|
+
branchIds.forEach((id) => {
|
|
251
|
+
if (checked)
|
|
252
|
+
next.add(id);
|
|
253
|
+
else
|
|
254
|
+
next.delete(id);
|
|
255
|
+
});
|
|
256
|
+
return next;
|
|
257
|
+
}));
|
|
258
|
+
};
|
|
259
|
+
const handleSelectAll = (checked, panelIndex, scopeLeafIds = []) => {
|
|
260
|
+
if (checked && scopeLeafIds.length === 0)
|
|
261
|
+
return;
|
|
262
|
+
setSelectedIdsByPanel((prev) => prev.map((set, index) => (index === panelIndex ? mergeSelectionWithScope(set, scopeLeafIds, checked) : set)));
|
|
263
|
+
};
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
setSelectedIdsByPanel((prev) => {
|
|
266
|
+
const next = resolvedPanelItems.map((items, index) => {
|
|
267
|
+
var _a;
|
|
268
|
+
const panelIds = new Set(collectTreeIds(items));
|
|
269
|
+
const current = (_a = prev[index]) !== null && _a !== void 0 ? _a : new Set();
|
|
270
|
+
return new Set(Array.from(current).filter((id) => panelIds.has(id)));
|
|
271
|
+
});
|
|
272
|
+
const isUnchanged = next.every((set, index) => {
|
|
273
|
+
var _a;
|
|
274
|
+
const previous = (_a = prev[index]) !== null && _a !== void 0 ? _a : new Set();
|
|
275
|
+
if (set.size !== previous.size)
|
|
276
|
+
return false;
|
|
277
|
+
return Array.from(set).every((id) => previous.has(id));
|
|
278
|
+
});
|
|
279
|
+
return isUnchanged ? prev : next;
|
|
280
|
+
});
|
|
281
|
+
}, [panelTreeIdSignature, resolvedPanelItems]);
|
|
282
|
+
const handleMove = (fromPanelIndex, toPanelIndex) => {
|
|
283
|
+
const selectedIds = selectedIdsByPanel[fromPanelIndex];
|
|
284
|
+
if (!(selectedIds === null || selectedIds === void 0 ? void 0 : selectedIds.size))
|
|
285
|
+
return;
|
|
286
|
+
const sourceItems = resolvedPanelItems[fromPanelIndex];
|
|
287
|
+
const targetItems = resolvedPanelItems[toPanelIndex];
|
|
288
|
+
const { sourceItems: nextSource, targetItems: nextTarget, movedIds } = moveBetweenPanels(sourceItems, targetItems, selectedIds);
|
|
289
|
+
if (!movedIds.length)
|
|
290
|
+
return;
|
|
291
|
+
const nextPanels = resolvedPanelItems.map((items, index) => {
|
|
292
|
+
if (index === fromPanelIndex)
|
|
293
|
+
return nextSource;
|
|
294
|
+
if (index === toPanelIndex)
|
|
295
|
+
return nextTarget;
|
|
296
|
+
return items;
|
|
297
|
+
});
|
|
298
|
+
setSelectedIdsByPanel((prev) => prev.map((set, index) => (index === fromPanelIndex ? new Set() : set)));
|
|
299
|
+
emitChange({
|
|
300
|
+
fromPanelIndex,
|
|
301
|
+
toPanelIndex,
|
|
302
|
+
direction: toPanelIndex > fromPanelIndex ? "forward" : "backward",
|
|
303
|
+
movedIds,
|
|
304
|
+
panels: nextPanels
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const handlePanelItemsChange = (panelIndex, items) => {
|
|
308
|
+
const nextPanels = resolvedPanelItems.map((panelItems, index) => (index === panelIndex ? items : panelItems));
|
|
309
|
+
emitChange({
|
|
310
|
+
fromPanelIndex: panelIndex,
|
|
311
|
+
toPanelIndex: panelIndex,
|
|
312
|
+
direction: "forward",
|
|
313
|
+
movedIds: [],
|
|
314
|
+
panels: nextPanels
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
const handleCrossListDrop = useCallback(({ sourceListId, targetListId, sourceId, targetId, edge, isEmptyTarget }) => {
|
|
318
|
+
const fromPanelIndex = panels.findIndex(({ id }) => id === sourceListId);
|
|
319
|
+
const toPanelIndex = panels.findIndex(({ id }) => id === targetListId);
|
|
320
|
+
if (fromPanelIndex < 0 || toPanelIndex < 0 || fromPanelIndex === toPanelIndex)
|
|
321
|
+
return;
|
|
322
|
+
const { sourceItems, targetItems, movedIds } = moveItemByDrag(resolvedPanelItems[fromPanelIndex], resolvedPanelItems[toPanelIndex], sourceId, targetId, edge, isEmptyTarget);
|
|
323
|
+
if (!movedIds.length)
|
|
324
|
+
return;
|
|
325
|
+
const nextPanels = resolvedPanelItems.map((items, index) => {
|
|
326
|
+
if (index === fromPanelIndex)
|
|
327
|
+
return sourceItems;
|
|
328
|
+
if (index === toPanelIndex)
|
|
329
|
+
return targetItems;
|
|
330
|
+
return items;
|
|
331
|
+
});
|
|
332
|
+
emitChange({
|
|
333
|
+
fromPanelIndex,
|
|
334
|
+
toPanelIndex,
|
|
335
|
+
direction: toPanelIndex > fromPanelIndex ? "forward" : "backward",
|
|
336
|
+
movedIds,
|
|
337
|
+
panels: nextPanels
|
|
338
|
+
});
|
|
339
|
+
}, [emitChange, panels, resolvedPanelItems]);
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
if (!draggable)
|
|
342
|
+
return () => undefined;
|
|
343
|
+
return monitorForElements({
|
|
344
|
+
onDrop: ({ source, location }) => {
|
|
345
|
+
const payload = resolveCrossListDrop(source.data, location.current.dropTargets, panelDropGapRefs.current);
|
|
346
|
+
if (!payload)
|
|
347
|
+
return;
|
|
348
|
+
handleCrossListDrop(payload);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}, [draggable, handleCrossListDrop]);
|
|
352
|
+
useLayoutEffect(() => {
|
|
353
|
+
if (!draggable)
|
|
354
|
+
return () => undefined;
|
|
355
|
+
const cleanups = [];
|
|
356
|
+
panels.forEach((panel, panelIndex) => {
|
|
357
|
+
const element = panelRefs.current[panel.id];
|
|
358
|
+
if (!element)
|
|
359
|
+
return;
|
|
360
|
+
const panelItems = resolvedPanelItems[panelIndex];
|
|
361
|
+
cleanups.push(dropTargetForElements({
|
|
362
|
+
element,
|
|
363
|
+
getData: () => {
|
|
364
|
+
if (!panelItems.length) {
|
|
365
|
+
return { targetListId: panel.id, isEmptyListTarget: true };
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
targetListId: panel.id,
|
|
369
|
+
targetId: panelItems[panelItems.length - 1].id,
|
|
370
|
+
edge: "bottom",
|
|
371
|
+
isPanelDropTarget: true
|
|
372
|
+
};
|
|
373
|
+
},
|
|
374
|
+
canDrop: ({ source }) => {
|
|
375
|
+
const sourceListId = source.data.dragListId;
|
|
376
|
+
return typeof sourceListId === "string" && sourceListId !== panel.id;
|
|
377
|
+
}
|
|
378
|
+
}));
|
|
379
|
+
});
|
|
380
|
+
return () => cleanups.forEach((cleanup) => cleanup());
|
|
381
|
+
}, [draggable, panels, resolvedPanelItems, panelTreeIdSignature]);
|
|
382
|
+
const panelCount = panels.length;
|
|
383
|
+
return (React__default.createElement("div", { className: classNames("transferList", `transferList_panels${panelCount}`, className) }, panels.map((panel, panelIndex) => {
|
|
384
|
+
var _a, _b, _c, _d;
|
|
385
|
+
return (React__default.createElement(Fragment, { key: panel.id },
|
|
386
|
+
React__default.createElement("div", { ref: (element) => {
|
|
387
|
+
panelRefs.current[panel.id] = element;
|
|
388
|
+
}, className: "transferList__panel" },
|
|
389
|
+
React__default.createElement(ActionableList, { className: "transferList__actionableList", withCheckbox: true, managedSelection: true, draggable: draggable, dragListId: draggable ? panel.id : undefined, delegateCrossListDrop: draggable, dropGapOutletRef: draggable ? getDropGapOutletRef(panel.id) : undefined, items: panelViewItems[panelIndex], texts: panel.texts, onItemCheck: (item, checked) => updateSelection(item, checked, panelIndex), onSelectAllChange: (checked, _items, scopeLeafIds) => handleSelectAll(checked, panelIndex, scopeLeafIds), onItemsChange: draggable ? (items) => handlePanelItemsChange(panelIndex, items) : undefined })),
|
|
390
|
+
panelIndex < panelCount - 1 && (React__default.createElement("div", { className: "transferList__controls" },
|
|
391
|
+
React__default.createElement(Button, { size: "medium", appearance: "secondary", layout: "text", Icon: ForwardIcon, disabled: ((_b = (_a = selectedIdsByPanel[panelIndex]) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) === 0, onClick: () => handleMove(panelIndex, panelIndex + 1), "aria-label": "Move selected to next panel" }),
|
|
392
|
+
React__default.createElement(Button, { size: "medium", appearance: "secondary", layout: "text", Icon: BackwardIcon, disabled: ((_d = (_c = selectedIdsByPanel[panelIndex + 1]) === null || _c === void 0 ? void 0 : _c.size) !== null && _d !== void 0 ? _d : 0) === 0, onClick: () => handleMove(panelIndex + 1, panelIndex), "aria-label": "Move selected to previous panel" })))));
|
|
393
|
+
})));
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
export { TransferList as default };
|
|
@@ -5,5 +5,5 @@ import type { IActionableListProps } from "./ActionableList.types";
|
|
|
5
5
|
* Actionable List component provides users with a highly interactive, multi-functional list of items, organized in a hierarchical structure that supports up to five levels of nested items.
|
|
6
6
|
*/
|
|
7
7
|
declare const ActionableList: FC<IActionableListProps>;
|
|
8
|
-
export type { IActionableListItem, IActionableListProps, IActionableListTexts } from "./ActionableList.types";
|
|
8
|
+
export type { IActionableListDropGap, IActionableListItem, IActionableListProps, IActionableListTexts, ICrossListDropPayload } from "./ActionableList.types";
|
|
9
9
|
export { ActionableList as default };
|
|
@@ -6,12 +6,19 @@ export declare const ACTIONABLE_LIST_DEFAULT_TEXTS: IActionableListTexts;
|
|
|
6
6
|
export declare const isSubtreeFullySelected: (item: IActionableListItem) => boolean;
|
|
7
7
|
export declare const isAnySelectionInSubtree: (item: IActionableListItem) => boolean;
|
|
8
8
|
export declare const nextLevel: (level: TActionableListLevel) => TActionableListLevel;
|
|
9
|
+
export declare const isGroupItem: (item: IActionableListItem) => boolean;
|
|
9
10
|
export declare const mergeItemsFromProps: (incoming: IActionableListItem[], previous: IActionableListItem[]) => IActionableListItem[];
|
|
10
11
|
export declare const countAllItems: (items: IActionableListItem[]) => number;
|
|
12
|
+
/** Counts leaf nodes only — parent rows with children are grouping headers, not items. */
|
|
13
|
+
export declare const countLeafItems: (items: IActionableListItem[]) => number;
|
|
11
14
|
export declare const countCheckedItems: (items: IActionableListItem[]) => number;
|
|
15
|
+
/** Counts checked leaves in `items` whose ids are in `scopeLeafIds`. */
|
|
16
|
+
export declare const countCheckedLeavesInScope: (items: IActionableListItem[], scopeLeafIds: ReadonlySet<string>) => number;
|
|
12
17
|
export declare const findItemById: (nodes: IActionableListItem[], targetId: string) => IActionableListItem | undefined;
|
|
13
18
|
export declare const updateItemById: (items: IActionableListItem[], targetId: string, updater: (item: IActionableListItem) => IActionableListItem) => IActionableListItem[];
|
|
14
19
|
export declare const applyCheckedToBranch: (item: IActionableListItem, checked: boolean) => IActionableListItem;
|
|
20
|
+
export declare const collectLeafIds: (items: IActionableListItem[]) => string[];
|
|
21
|
+
export declare const applyCheckedToLeavesById: (items: IActionableListItem[], leafIds: ReadonlySet<string>, checked: boolean) => IActionableListItem[];
|
|
15
22
|
export declare const filterTree: (items: IActionableListItem[], query: string) => IActionableListItem[];
|
|
16
|
-
export declare const reorderInTree: (items: IActionableListItem[], sourceId: string, targetId: string) => IActionableListItem[];
|
|
23
|
+
export declare const reorderInTree: (items: IActionableListItem[], sourceId: string, targetId: string, edge?: "top" | "bottom") => IActionableListItem[];
|
|
17
24
|
export declare const getExpandedIdsFromItems: (items: IActionableListItem[], ids?: Set<string>) => Set<string>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MutableRefObject } from "react";
|
|
1
2
|
export interface IActionableListItem {
|
|
2
3
|
/**
|
|
3
4
|
* Unique item identifier.
|
|
@@ -21,6 +22,36 @@ export interface IActionableListItem {
|
|
|
21
22
|
*/
|
|
22
23
|
checked?: boolean;
|
|
23
24
|
}
|
|
25
|
+
export interface ICrossListDropPayload {
|
|
26
|
+
/**
|
|
27
|
+
* Drag scope id of the list the item was dragged from.
|
|
28
|
+
*/
|
|
29
|
+
sourceListId: string;
|
|
30
|
+
/**
|
|
31
|
+
* Drag scope id of the list the item was dropped onto.
|
|
32
|
+
*/
|
|
33
|
+
targetListId: string;
|
|
34
|
+
/**
|
|
35
|
+
* Id of the dragged item.
|
|
36
|
+
*/
|
|
37
|
+
sourceId: string;
|
|
38
|
+
/**
|
|
39
|
+
* Id of the root-level row used as the drop target. Omitted when dropping onto an empty list.
|
|
40
|
+
*/
|
|
41
|
+
targetId?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Whether the item is inserted above or below the target row.
|
|
44
|
+
*/
|
|
45
|
+
edge?: "top" | "bottom";
|
|
46
|
+
/**
|
|
47
|
+
* True when the item was dropped onto the empty-list drop zone.
|
|
48
|
+
*/
|
|
49
|
+
isEmptyTarget?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface IActionableListDropGap {
|
|
52
|
+
targetId: string;
|
|
53
|
+
edge: "top" | "bottom";
|
|
54
|
+
}
|
|
24
55
|
export interface IActionableListTexts {
|
|
25
56
|
/**
|
|
26
57
|
* Search field label.
|
|
@@ -89,6 +120,22 @@ export interface IActionableListProps {
|
|
|
89
120
|
* Enables drag and drop variant.
|
|
90
121
|
*/
|
|
91
122
|
draggable?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Scope id included in drag data for cross-list transfer (e.g. TransferList panel id).
|
|
125
|
+
*/
|
|
126
|
+
dragListId?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Called when an item is dropped onto a root row from another list (requires `dragListId`).
|
|
129
|
+
*/
|
|
130
|
+
onCrossListDrop?: (payload: ICrossListDropPayload) => void;
|
|
131
|
+
/**
|
|
132
|
+
* When true, cross-list drops are coordinated by a parent (e.g. TransferList).
|
|
133
|
+
*/
|
|
134
|
+
delegateCrossListDrop?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Mirrors the active drop gap for parent-coordinated cross-list drops.
|
|
137
|
+
*/
|
|
138
|
+
dropGapOutletRef?: MutableRefObject<IActionableListDropGap | null>;
|
|
92
139
|
/**
|
|
93
140
|
* Optional controlled loading state.
|
|
94
141
|
*/
|
|
@@ -112,9 +159,15 @@ export interface IActionableListProps {
|
|
|
112
159
|
*/
|
|
113
160
|
onItemCheck?: (item: IActionableListItem, checked: boolean, items: IActionableListItem[]) => void;
|
|
114
161
|
/**
|
|
115
|
-
* Emits when the toolbar **Select all** checkbox is toggled: target `checked` state
|
|
162
|
+
* Emits when the toolbar **Select all** checkbox is toggled: target `checked` state, full updated `items` tree,
|
|
163
|
+
* and leaf ids in the current select-all scope (filtered leaves while searching, otherwise all leaves).
|
|
164
|
+
*/
|
|
165
|
+
onSelectAllChange?: (checked: boolean, items: IActionableListItem[], scopeLeafIds: string[]) => void;
|
|
166
|
+
/**
|
|
167
|
+
* When `true`, selection is read from the `items` prop and checkbox toggles only emit callbacks.
|
|
168
|
+
* Used by TransferList; not intended for standalone ActionableList usage.
|
|
116
169
|
*/
|
|
117
|
-
|
|
170
|
+
managedSelection?: boolean;
|
|
118
171
|
/**
|
|
119
172
|
* Emits debounced search value.
|
|
120
173
|
*/
|
|
@@ -23,6 +23,10 @@ interface IActionableListItemProps {
|
|
|
23
23
|
* Whether the row has children and can be expanded.
|
|
24
24
|
*/
|
|
25
25
|
isExpandable?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Disables expand/collapse when a group has no children.
|
|
28
|
+
*/
|
|
29
|
+
isExpandDisabled?: boolean;
|
|
26
30
|
/**
|
|
27
31
|
* Whether the row is currently expanded.
|
|
28
32
|
*/
|
|
@@ -63,6 +67,10 @@ interface IActionableListItemProps {
|
|
|
63
67
|
* Identifier of the parent group for same-sibling-group drop restriction.
|
|
64
68
|
*/
|
|
65
69
|
parentId?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Drag scope id for cross-list transfer.
|
|
72
|
+
*/
|
|
73
|
+
dragListId?: string;
|
|
66
74
|
/**
|
|
67
75
|
* Whether drag-and-drop handle is shown.
|
|
68
76
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { IActionableListItem, IActionableListProps, IActionableListTexts } from "./ActionableList.types";
|
|
1
|
+
export type { IActionableListDropGap, IActionableListItem, IActionableListProps, IActionableListTexts, ICrossListDropPayload } from "./ActionableList.types";
|
|
2
2
|
export { default } from "./ActionableList";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import "./TransferList.scss";
|
|
3
|
+
import type { ITransferListProps } from "./TransferList.types";
|
|
4
|
+
/**
|
|
5
|
+
* Transfer List component enables users to move items between two to four actionable lists.
|
|
6
|
+
*/
|
|
7
|
+
declare const TransferList: FC<ITransferListProps>;
|
|
8
|
+
export { TransferList as default };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IActionableListItem } from "@components/molecules/ActionableList";
|
|
2
|
+
import type { ITransferListPanel } from "./TransferList.types";
|
|
3
|
+
export declare const TRANSFER_LIST_MIN_PANELS = 2;
|
|
4
|
+
export declare const TRANSFER_LIST_MAX_PANELS = 4;
|
|
5
|
+
export declare const assertPanelCount: (panels: ITransferListPanel[]) => void;
|
|
6
|
+
export declare const collectNodeIds: (item: IActionableListItem) => string[];
|
|
7
|
+
export declare const collectTreeIds: (items: IActionableListItem[]) => string[];
|
|
8
|
+
export declare const mergeSelectionWithScope: (current: ReadonlySet<string>, scopeLeafIds: readonly string[], checked: boolean) => Set<string>;
|
|
9
|
+
export declare const applySelectionToTree: (items: IActionableListItem[], selectedIds: Set<string>) => IActionableListItem[];
|
|
10
|
+
interface IPartitionResult {
|
|
11
|
+
remaining: IActionableListItem[];
|
|
12
|
+
moved: IActionableListItem[];
|
|
13
|
+
movedIds: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare const partitionTreeByIds: (items: IActionableListItem[], selectedIds: Set<string>) => IPartitionResult;
|
|
16
|
+
export declare const mergeMovedItems: (target: IActionableListItem[], moved: IActionableListItem[]) => IActionableListItem[];
|
|
17
|
+
export declare const insertAtRoot: (items: IActionableListItem[], item: IActionableListItem, targetId: string, edge?: "top" | "bottom") => IActionableListItem[];
|
|
18
|
+
interface IMoveBetweenPanelsResult {
|
|
19
|
+
sourceItems: IActionableListItem[];
|
|
20
|
+
targetItems: IActionableListItem[];
|
|
21
|
+
movedIds: string[];
|
|
22
|
+
}
|
|
23
|
+
export declare const moveBetweenPanels: (sourceItems: IActionableListItem[], targetItems: IActionableListItem[], selectedIds: Set<string>) => IMoveBetweenPanelsResult;
|
|
24
|
+
export declare const moveItemByDrag: (sourceItems: IActionableListItem[], targetItems: IActionableListItem[], sourceId: string, targetId?: string, edge?: "top" | "bottom", isEmptyTarget?: boolean) => IMoveBetweenPanelsResult;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { IActionableListItem, IActionableListTexts } from "@components/molecules/ActionableList";
|
|
2
|
+
type TTransferListDirection = "forward" | "backward";
|
|
3
|
+
interface ITransferListPanel {
|
|
4
|
+
/**
|
|
5
|
+
* Stable panel key and drag scope identifier.
|
|
6
|
+
*/
|
|
7
|
+
id: string;
|
|
8
|
+
/**
|
|
9
|
+
* Controlled tree for this panel.
|
|
10
|
+
*/
|
|
11
|
+
items?: IActionableListItem[];
|
|
12
|
+
/**
|
|
13
|
+
* Uncontrolled initial tree for this panel.
|
|
14
|
+
*/
|
|
15
|
+
defaultItems?: IActionableListItem[];
|
|
16
|
+
/**
|
|
17
|
+
* Per-panel ActionableList texts; `searchLabel` is the panel label.
|
|
18
|
+
*/
|
|
19
|
+
texts?: Partial<IActionableListTexts>;
|
|
20
|
+
}
|
|
21
|
+
interface ITransferListChangePayload {
|
|
22
|
+
/**
|
|
23
|
+
* Index of the panel items were moved from.
|
|
24
|
+
*/
|
|
25
|
+
fromPanelIndex: number;
|
|
26
|
+
/**
|
|
27
|
+
* Index of the panel items were moved to.
|
|
28
|
+
*/
|
|
29
|
+
toPanelIndex: number;
|
|
30
|
+
/**
|
|
31
|
+
* Move direction relative to panel order.
|
|
32
|
+
*/
|
|
33
|
+
direction: TTransferListDirection;
|
|
34
|
+
/**
|
|
35
|
+
* IDs moved by the action (empty for in-panel reorder).
|
|
36
|
+
*/
|
|
37
|
+
movedIds: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Next state for all panels.
|
|
40
|
+
*/
|
|
41
|
+
panels: IActionableListItem[][];
|
|
42
|
+
}
|
|
43
|
+
interface ITransferListProps {
|
|
44
|
+
/**
|
|
45
|
+
* Additional class for the parent element.
|
|
46
|
+
* This prop should be used to set placement properties for the element relative to its parent using BEM conventions.
|
|
47
|
+
*/
|
|
48
|
+
className?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Two to four actionable list panels.
|
|
51
|
+
*/
|
|
52
|
+
panels: ITransferListPanel[];
|
|
53
|
+
/**
|
|
54
|
+
* Enables drag-and-drop reorder within a panel and transfer to root rows of adjacent panels.
|
|
55
|
+
*/
|
|
56
|
+
draggable?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Called after move, cross-panel drag, or in-panel reorder.
|
|
59
|
+
*/
|
|
60
|
+
onChange?: (payload: ITransferListChangePayload) => void;
|
|
61
|
+
}
|
|
62
|
+
export type { ITransferListChangePayload, ITransferListPanel, ITransferListProps, TTransferListDirection };
|
package/index.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ export { default as List, IListProps, IListItemData } from "./components/molecul
|
|
|
64
64
|
export { default as DataTable, DataTableGetRowStatus, DataTableRenderExpandedRow, DataTableRowExpandChangeHandler, DataTableRowExpandChangePayload, DataTableRowStatus, IDataTableProps, IDataTableRowAction } from "@components/organisms/DataTable";
|
|
65
65
|
export { default as GlobalHeader, IGlobalHeaderProps, IAction, IProducts, IPartnersProps } from "./components/organisms/GlobalHeader";
|
|
66
66
|
export { default as DataCardList, IDataCardListProps } from "@components/organisms/DataCardList";
|
|
67
|
+
export { default as TransferList } from "./components/organisms/TransferList";
|
|
67
68
|
export { default as GeneUIProvider, GeneUIDesignSystemContext, IGeneUIDesignSystemContext, IGeneUIProviderProps } from "./components/providers/GeneUIProvider";
|
|
68
69
|
export { default as useDebounce } from "./hooks/useDebounceCallback";
|
|
69
70
|
export { default as useEllipsisDetection } from "./hooks/useEllipsisDetection";
|
package/index.js
CHANGED
|
@@ -70,6 +70,7 @@ export { default as List } from './List.js';
|
|
|
70
70
|
export { default as DataTable } from './DataTable.js';
|
|
71
71
|
export { default as GlobalHeader } from './GlobalHeader.js';
|
|
72
72
|
export { default as DataCardList } from './DataCardList.js';
|
|
73
|
+
export { default as TransferList } from './TransferList.js';
|
|
73
74
|
export { a as GeneUIDesignSystemContext, G as GeneUIProvider, u as useBreakpoint } from './index-9a69bcc6.js';
|
|
74
75
|
export { u as useDebounce } from './useDebounceCallback-999deae7.js';
|
|
75
76
|
export { u as useEllipsisDetection } from './useEllipsisDetection-c5f9b175.js';
|
|
@@ -135,10 +136,11 @@ import './ColorPicker-c6af3618.js';
|
|
|
135
136
|
import './OTPField-af15abb1.js';
|
|
136
137
|
import './PageHeader-cfa2c255.js';
|
|
137
138
|
import './PopoverConfirm-d6880a4f.js';
|
|
138
|
-
import './ActionableListNodeWrapper-
|
|
139
|
+
import './ActionableListNodeWrapper-0a3308ca.js';
|
|
139
140
|
import './List-586cdebf.js';
|
|
140
141
|
import './DataTable-c7132016.js';
|
|
141
142
|
import './GlobalHeader-e3be2af9.js';
|
|
142
143
|
import './DataCardList-eb674418.js';
|
|
144
|
+
import './TransferList-55c2e836.js';
|
|
143
145
|
import './Magnifier-8df67f2d.js';
|
|
144
146
|
import './index-8156dbce.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geneui/components",
|
|
3
3
|
"description": "The Gene UI components library designed for BI tools",
|
|
4
|
-
"version": "3.0.0-next-
|
|
4
|
+
"version": "3.0.0-next-bc8245e-24062026",
|
|
5
5
|
"author": "SoftConstruct",
|
|
6
6
|
"homepage": "https://github.com/softconstruct/gene-ui-components#readme",
|
|
7
7
|
"repository": {
|