@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 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-42ea60cf.js';
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) === null || _a === void 0 ? void 0 : _a.length) ? merge(node.children) : undefined });
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
- const childCount = ((_a = item.children) === null || _a === void 0 ? void 0 : _a.length) ? countCheckedItems(item.children) : 0;
157
- return acc + (item.checked ? 1 : 0) + childCount;
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
- const filteredChildren = filterTree(item.children || [], q);
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
- return reorder({ list: items, startIndex: si, finishIndex: ti });
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 }) => source.data.parentId === parentId,
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 : SvgChevronRight, onClick: onToggleExpand, "aria-label": expandAriaLabel, className: "actionableListItem__toggle" })),
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 RenderNode = ({ item, level, parentId, withCheckbox, isDraggable, expandedIds, texts, dropGap, isSearchActive, onToggleExpand, onToggleCheck, onDragTargetChange }) => {
509
- var _a, _b, _c, _d, _e, _f;
510
- const childCount = ((_a = item.children) === null || _a === void 0 ? void 0 : _a.length) || 0;
511
- const canExpand = childCount > 0 && level < ACTIONABLE_LIST_MAX_NESTED_LEVEL;
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 = (_c = (_b = item.children) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
516
- const directChildFullySelected = (_e = (_d = item.children) === null || _d === void 0 ? void 0 : _d.filter((child) => isSubtreeFullySelected(child)).length) !== null && _e !== void 0 ? _e : 0;
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: canExpand, isExpanded: isExpanded, withCheckbox: withCheckbox }, (withCheckbox
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 }, (_f = item.children) === null || _f === void 0 ? void 0 : _f.map((child) => (React__default.createElement(RenderNode, { key: child.id, item: child, level: childLevel, parentId: item.id, withCheckbox: withCheckbox, isDraggable: isDraggable, expandedIds: expandedIds, texts: texts, dropGap: dropGap, isSearchActive: isSearchActive, onToggleExpand: onToggleExpand, onToggleCheck: onToggleCheck, onDragTargetChange: onDragTargetChange }))))))));
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
- if (typeof sourceId === "string" && gap && sourceId !== gap.targetId) {
561
- handleDropReorderRef.current(sourceId, gap.targetId);
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(localItems, searchValue), [localItems, searchValue]);
577
- const totalItemsCount = useMemo(() => countAllItems(localItems), [localItems]);
578
- const filteredItemsCount = useMemo(() => countAllItems(filteredItems), [filteredItems]);
579
- const selectedItemsCount = useMemo(() => countCheckedItems(localItems), [localItems]);
580
- const selectAllChecked = totalItemsCount > 0 && localItems.every((item) => isSubtreeFullySelected(item));
581
- const selectAllIndeterminate = !selectAllChecked && localItems.some((item) => isAnySelectionInSubtree(item));
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(localItems, id, (item) => applyCheckedToBranch(item, checked));
599
- syncItems(nextItems);
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 (totalItemsCount === 0)
697
+ if (selectAllScopeLeafCount === 0)
607
698
  return;
608
- const nextItems = localItems.map((item) => applyCheckedToBranch(item, checked));
609
- syncItems(nextItems);
610
- onSelectAllChange === null || onSelectAllChange === void 0 ? void 0 : onSelectAllChange(checked, nextItems);
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(localItems, sourceId, targetId));
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 = totalItemsCount > 0;
626
- const hasSearchResults = filteredItemsCount > 0;
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: totalItemsCount === 0, onChange: (event) => handleSelectAll(event.target.checked), className: "actionableList__selectAll" }),
633
- React__default.createElement(Text, { as: "span", variant: "bodyMediumMedium", className: "actionableList__bulkSelectedLabel" }, mergedTexts.bulkSelectedItemsLabel),
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(Text, { as: "span", variant: "bodyMediumMedium", className: "actionableList__statsLabel" }, mergedTexts.filteredItemsLabel),
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(Text, { as: "span", variant: "bodyMediumMedium", className: "actionableList__statsLabel" }, mergedTexts.totalItemsLabel),
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:wrap;gap:var(--guit-ref-spacing-medium);justify-content:space-between;width:100%}.actionableList__bulkSelection{align-items:center;display:flex;gap:var(--guit-ref-spacing-2xsmall);min-width:0}.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)}.actionableList__bulkSelectedCount{color:var(--guit-sem-color-foreground-neutral-2)}.actionableList__stats{-webkit-margin-start:auto;align-items:center;display:flex;flex-shrink:0;gap:var(--guit-ref-spacing-2xsmall);justify-content:flex-end;margin-inline-start:auto}.actionableList__statsLabel{color:var(--guit-sem-color-foreground-neutral-1)}.actionableList__statsCount{color:var(--guit-sem-color-foreground-neutral-2)}.actionableList__statsDivider{min-height:1.6rem}.actionableList__list{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__empty{width:100%}";
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
@@ -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-42ea60cf.js';
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);
@@ -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 and full updated `items` tree.
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
- onSelectAllChange?: (checked: boolean, items: IActionableListItem[]) => void;
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 };
@@ -0,0 +1,2 @@
1
+ export type { ITransferListChangePayload, ITransferListPanel, ITransferListProps, TTransferListDirection } from "./TransferList.types";
2
+ export { default } from "./TransferList";
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-42ea60cf.js';
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-2fa65ce-08062026",
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": {