UNPKG

dnd-kit-tree

Version:
898 lines (879 loc) 42.6 kB
import { arrayMove, useSortable, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { jsx, jsxs } from 'react/jsx-runtime'; import { createPortal } from 'react-dom'; import { CSS } from '@dnd-kit/utilities'; import * as React from 'react'; import { forwardRef, useState, useRef, useMemo, useEffect } from 'react'; import { MeasuringStrategy, useSensors, useSensor, PointerSensor, DndContext, closestCenter, DragOverlay, defaultDropAnimation } from '@dnd-kit/core'; const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); function getDragDepth(offset, indentationWidth) { return Math.round(offset / indentationWidth); } function getProjection(items, activeId, overId, dragOffset, indentationWidth, depthLimit) { const item = items.find(({ id }) => id === activeId); const overItemIndex = items.findIndex(({ id }) => id === overId); const activeItemIndex = items.findIndex(({ id }) => id === activeId); const activeItem = items[activeItemIndex]; const newItems = arrayMove(items, activeItemIndex, overItemIndex); const previousItem = newItems[overItemIndex - 1]; const nextItem = newItems.length > 1 ? newItems[overItemIndex + 1] : undefined; const dragDepth = getDragDepth(dragOffset, indentationWidth); const projectedDepth = activeItem.depth + dragDepth; let depth = projectedDepth; const minDepth = nextItem ? getMinDepth({ nextItem }) : 0; let maxDepth = getMaxDepth({ previousItem, depthLimit, }); if (projectedDepth >= maxDepth) { depth = maxDepth; } else if (projectedDepth < minDepth) { depth = minDepth; } const parentNode = getParentNode(newItems, overItemIndex, depth, previousItem); // If the item has children, we need to check if the depth limit is reached if (depthLimit !== undefined && item && parentNode) { const treeDepth = getMaxDepthFromFlattenItem(item); const treeMaxDepth = treeDepth + parentNode.depth; if (treeMaxDepth >= depthLimit) { maxDepth = parentNode.depth; } } if (depth >= maxDepth) { depth = maxDepth; } else if (depth < minDepth) { depth = minDepth; } return { depth, maxDepth, minDepth, parentId: getParentNode(newItems, overItemIndex, depth, previousItem)?.id ?? null, }; } function getParentNode(items, overIndex, depth, previousItem) { if (depth === 0 || !previousItem) { return null; } if (depth === previousItem.depth) { return items.find(({ id }) => id === previousItem.parentId); } if (depth > previousItem.depth) { return items.find(({ id }) => id === previousItem.id); } const parentId = items .slice(0, overIndex) .reverse() .find((item) => item.depth === depth)?.parentId; return parentId ? items.find(({ id }) => id === parentId) : undefined; } function getMaxDepth({ depthLimit, previousItem, }) { if (previousItem) { const depth = previousItem.depth + 1; return depthLimit !== undefined && depth > depthLimit ? depthLimit : depth; } return 0; } function getMinDepth({ nextItem }) { if (nextItem) { return nextItem.depth; } return 0; } function flatten(items, parentId = null, depth = 0) { return items.reduce((acc, item, index) => { return [ ...acc, { ...item, parentId, depth, index }, ...flatten(item.children, item.id, depth + 1), ]; }, []); } function flattenTree(items) { return flatten(items); } function buildTree(flattenedItems) { const root = { id: "root", children: [] }; const nodes = { [root.id]: root }; const items = flattenedItems.map((item) => ({ ...item, children: [] })); for (const item of items) { const { id, children } = item; const parentId = item.parentId ?? root.id; const parent = nodes[parentId] ?? findItem(items, parentId); nodes[id] = { id, children }; parent.children.push(item); } return root.children; } function findItem(items, itemId) { return items.find(({ id }) => id === itemId); } function findItemDeep(items, itemId) { for (const item of items) { const { id, children } = item; if (id === itemId) { return item; } if (children.length) { const child = findItemDeep(children, itemId); if (child) { return child; } } } return undefined; } function removeItem(items, id) { const newItems = []; for (const item of items) { if (item.id === id) { continue; } if (item.children.length) { item.children = removeItem(item.children, id); } newItems.push(item); } return newItems; } function setProperty(items, id, property, setter) { for (const item of items) { if (item.id === id) { item[property] = setter(item[property]); continue; } if (item.children.length) { item.children = setProperty(item.children, id, property, setter); } } return [...items]; } function countChildren(items, count = 0) { return items.reduce((acc, { children }) => { if (children.length) { return countChildren(children, acc + 1); } return acc + 1; }, count); } function getChildCount(items, id) { const item = findItemDeep(items, id); return item ? countChildren(item.children) : 0; } function removeChildrenOf(items, ids) { const excludeParentIds = [...ids]; return items.filter((item) => { if (item.parentId && excludeParentIds.includes(item.parentId)) { if (item.children.length) { excludeParentIds.push(item.id); } return false; } return true; }); } function getMaxDepthFromFlattenItem(item, initial = 0) { let res = initial; if (item.children.length) { res += 1; for (const child of item.children) { res = Math.max(res, getMaxDepthFromFlattenItem(child, res)); } } return res; } var ALIGNMENT; (function (ALIGNMENT) { ALIGNMENT["AUTO"] = "auto"; ALIGNMENT["START"] = "start"; ALIGNMENT["CENTER"] = "center"; ALIGNMENT["END"] = "end"; })(ALIGNMENT || (ALIGNMENT = {})); var DIRECTION; (function (DIRECTION) { DIRECTION["HORIZONTAL"] = "horizontal"; DIRECTION["VERTICAL"] = "vertical"; })(DIRECTION || (DIRECTION = {})); var SCROLL_CHANGE_REASON; (function (SCROLL_CHANGE_REASON) { SCROLL_CHANGE_REASON["OBSERVED"] = "observed"; SCROLL_CHANGE_REASON["REQUESTED"] = "requested"; })(SCROLL_CHANGE_REASON || (SCROLL_CHANGE_REASON = {})); const scrollProp = { [DIRECTION.VERTICAL]: "scrollTop", [DIRECTION.HORIZONTAL]: "scrollLeft", }; const sizeProp = { [DIRECTION.VERTICAL]: "height", [DIRECTION.HORIZONTAL]: "width", }; const positionProp = { [DIRECTION.VERTICAL]: "top", [DIRECTION.HORIZONTAL]: "left", }; const marginProp = { [DIRECTION.VERTICAL]: "marginTop", [DIRECTION.HORIZONTAL]: "marginLeft", }; const oppositeMarginProp = { [DIRECTION.VERTICAL]: "marginBottom", [DIRECTION.HORIZONTAL]: "marginRight", }; /* Forked from react-virtualized 💖 */ class SizeAndPositionManager { itemSizeGetter; itemCount; estimatedItemSize; lastMeasuredIndex; itemSizeAndPositionData; constructor({ itemCount, itemSizeGetter, estimatedItemSize }) { this.itemSizeGetter = itemSizeGetter; this.itemCount = itemCount; this.estimatedItemSize = estimatedItemSize; // Cache of size and position data for items, mapped by item index. this.itemSizeAndPositionData = {}; // Measurements for items up to this index can be trusted; items afterward should be estimated. this.lastMeasuredIndex = -1; } updateConfig({ itemCount, itemSizeGetter, estimatedItemSize }) { if (itemCount != null) { this.itemCount = itemCount; } if (estimatedItemSize != null) { this.estimatedItemSize = estimatedItemSize; } if (itemSizeGetter != null) { this.itemSizeGetter = itemSizeGetter; } } getLastMeasuredIndex() { return this.lastMeasuredIndex; } /** * This method returns the size and position for the item at the specified index. * It just-in-time calculates (or used cached values) for items leading up to the index. */ getSizeAndPositionForIndex(index) { if (index < 0 || index >= this.itemCount) { throw Error(`Requested index ${index} is outside of range 0..${this.itemCount}`); } if (index > this.lastMeasuredIndex) { const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem(); let offset = lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size; for (let i = this.lastMeasuredIndex + 1; i <= index; i++) { const size = this.itemSizeGetter(i); if (size == null || isNaN(size)) { throw Error(`Invalid size returned for index ${i} of value ${size}`); } this.itemSizeAndPositionData[i] = { offset, size, }; offset += size; } this.lastMeasuredIndex = index; } return this.itemSizeAndPositionData[index]; } getSizeAndPositionOfLastMeasuredItem() { return this.lastMeasuredIndex >= 0 ? this.itemSizeAndPositionData[this.lastMeasuredIndex] : { offset: 0, size: 0 }; } /** * Total size of all items being measured. * This value will be completedly estimated initially. * As items as measured the estimate will be updated. */ getTotalSize() { const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem(); return (lastMeasuredSizeAndPosition.offset + lastMeasuredSizeAndPosition.size + (this.itemCount - this.lastMeasuredIndex - 1) * this.estimatedItemSize); } /** * Determines a new offset that ensures a certain item is visible, given the alignment. * * @param align Desired alignment within container; one of "start" (default), "center", or "end" * @param containerSize Size (width or height) of the container viewport * @return Offset to use to ensure the specified item is visible */ getUpdatedOffsetForIndex({ align = ALIGNMENT.START, containerSize, currentOffset, targetIndex, }) { if (containerSize <= 0) { return 0; } const datum = this.getSizeAndPositionForIndex(targetIndex); const maxOffset = datum.offset; const minOffset = maxOffset - containerSize + datum.size; let idealOffset; switch (align) { case ALIGNMENT.END: idealOffset = minOffset; break; case ALIGNMENT.CENTER: idealOffset = maxOffset - (containerSize - datum.size) / 2; break; case ALIGNMENT.START: idealOffset = maxOffset; break; default: idealOffset = Math.max(minOffset, Math.min(maxOffset, currentOffset)); } const totalSize = this.getTotalSize(); return Math.max(0, Math.min(totalSize - containerSize, idealOffset)); } getVisibleRange({ containerSize, offset, overscanCount, }) { const totalSize = this.getTotalSize(); if (totalSize === 0) { return {}; } const maxOffset = offset + containerSize; let start = this.findNearestItem(offset); if (typeof start === "undefined") { throw Error(`Invalid offset ${offset} specified`); } const datum = this.getSizeAndPositionForIndex(start); offset = datum.offset + datum.size; let stop = start; while (offset < maxOffset && stop < this.itemCount - 1) { stop++; offset += this.getSizeAndPositionForIndex(stop).size; } if (overscanCount) { start = Math.max(0, start - overscanCount); stop = Math.min(stop + overscanCount, this.itemCount - 1); } return { start, stop, }; } /** * Clear all cached values for items after the specified index. * This method should be called for any item that has changed its size. * It will not immediately perform any calculations; they'll be performed the next time getSizeAndPositionForIndex() is called. */ resetItem(index) { this.lastMeasuredIndex = Math.min(this.lastMeasuredIndex, index - 1); } /** * Searches for the item (index) nearest the specified offset. * * If no exact match is found the next lowest item index will be returned. * This allows partially visible items (with offsets just before/above the fold) to be visible. */ findNearestItem(offset) { if (isNaN(offset)) { throw Error(`Invalid offset ${offset} specified`); } // Our search algorithms find the nearest match at or below the specified offset. // So make sure the offset is at least 0 or no match will be found. offset = Math.max(0, offset); const lastMeasuredSizeAndPosition = this.getSizeAndPositionOfLastMeasuredItem(); const lastMeasuredIndex = Math.max(0, this.lastMeasuredIndex); if (lastMeasuredSizeAndPosition.offset >= offset) { // If we've already measured items within this range just use a binary search as it's faster. return this.binarySearch({ high: lastMeasuredIndex, low: 0, offset, }); } else { // If we haven't yet measured this high, fallback to an exponential search with an inner binary search. // The exponential search avoids pre-computing sizes for the full set of items as a binary search would. // The overall complexity for this approach is O(log n). return this.exponentialSearch({ index: lastMeasuredIndex, offset, }); } } binarySearch({ low, high, offset }) { let middle = 0; let currentOffset = 0; while (low <= high) { middle = low + Math.floor((high - low) / 2); currentOffset = this.getSizeAndPositionForIndex(middle).offset; if (currentOffset === offset) { return middle; } else if (currentOffset < offset) { low = middle + 1; } else if (currentOffset > offset) { high = middle - 1; } } if (low > 0) { return low - 1; } return 0; } exponentialSearch({ index, offset }) { let interval = 1; while (index < this.itemCount && this.getSizeAndPositionForIndex(index).offset < offset) { index += interval; interval *= 2; } return this.binarySearch({ high: Math.min(index, this.itemCount - 1), low: Math.floor(index / 2), offset, }); } } const STYLE_WRAPPER = { overflow: "auto", willChange: "transform", WebkitOverflowScrolling: "touch", }; const STYLE_INNER = { position: "relative", width: "100%", minHeight: "100%", }; const STYLE_ITEM = { position: "absolute", top: 0, left: 0, width: "100%", }; const STYLE_STICKY_ITEM = { ...STYLE_ITEM, position: "sticky", }; class VirtualList extends React.PureComponent { static defaultProps = { overscanCount: 3, scrollDirection: DIRECTION.VERTICAL, width: "100%", }; itemSizeGetter = (itemSize) => { return (index) => this.getSize(index, itemSize); }; sizeAndPositionManager = new SizeAndPositionManager({ itemCount: this.props.itemCount, itemSizeGetter: this.itemSizeGetter(this.props.itemSize), estimatedItemSize: this.getEstimatedItemSize(), }); state = { offset: this.props.scrollOffset || (this.props.scrollToIndex != null && this.getOffsetForIndex(this.props.scrollToIndex)) || 0, scrollChangeReason: SCROLL_CHANGE_REASON.REQUESTED, }; rootNode; styleCache = {}; componentDidMount() { const { scrollOffset, scrollToIndex } = this.props; this.rootNode?.addEventListener("scroll", this.handleScroll, { passive: true, }); if (scrollOffset != null) { this.scrollTo(scrollOffset); } else if (scrollToIndex != null) { this.scrollTo(this.getOffsetForIndex(scrollToIndex)); } } componentWillReceiveProps(nextProps) { const { estimatedItemSize, itemCount, itemSize, scrollOffset, scrollToAlignment, scrollToIndex, } = this.props; const scrollPropsHaveChanged = nextProps.scrollToIndex !== scrollToIndex || nextProps.scrollToAlignment !== scrollToAlignment; const itemPropsHaveChanged = nextProps.itemCount !== itemCount || nextProps.itemSize !== itemSize || nextProps.estimatedItemSize !== estimatedItemSize; if (nextProps.itemSize !== itemSize) { this.sizeAndPositionManager.updateConfig({ itemSizeGetter: this.itemSizeGetter(nextProps.itemSize), }); } if (nextProps.itemCount !== itemCount || nextProps.estimatedItemSize !== estimatedItemSize) { this.sizeAndPositionManager.updateConfig({ itemCount: nextProps.itemCount, estimatedItemSize: this.getEstimatedItemSize(nextProps), }); } if (itemPropsHaveChanged) { this.recomputeSizes(); } if (nextProps.scrollOffset !== scrollOffset) { this.setState({ offset: nextProps.scrollOffset || 0, scrollChangeReason: SCROLL_CHANGE_REASON.REQUESTED, }); } else if (typeof nextProps.scrollToIndex === "number" && (scrollPropsHaveChanged || itemPropsHaveChanged)) { this.setState({ offset: this.getOffsetForIndex(nextProps.scrollToIndex, nextProps.scrollToAlignment, nextProps.itemCount), scrollChangeReason: SCROLL_CHANGE_REASON.REQUESTED, }); } } componentDidUpdate(_, prevState) { const { offset, scrollChangeReason } = this.state; if (prevState.offset !== offset && scrollChangeReason === SCROLL_CHANGE_REASON.REQUESTED) { this.scrollTo(offset); } } scrollTo(value) { const { scrollDirection = DIRECTION.VERTICAL } = this.props; if (this.rootNode) { this.rootNode[scrollProp[scrollDirection]] = value; } } getOffsetForIndex(index, scrollToAlignment = this.props.scrollToAlignment, itemCount = this.props.itemCount) { const { scrollDirection = DIRECTION.VERTICAL } = this.props; if (index < 0 || index >= itemCount) { index = 0; } return this.sizeAndPositionManager.getUpdatedOffsetForIndex({ targetIndex: index, align: scrollToAlignment, currentOffset: (this.state && this.state.offset) || 0, containerSize: this.props[sizeProp[scrollDirection]], }); } recomputeSizes(startIndex = 0) { this.styleCache = {}; this.sizeAndPositionManager.resetItem(startIndex); } render() { const { estimatedItemSize, height, overscanCount = 3, renderItem, itemCount, itemSize, onItemsRendered, onScroll, scrollDirection = DIRECTION.VERTICAL, scrollOffset, scrollToIndex, scrollToAlignment, stickyIndices, style, width, ...props } = this.props; const { offset } = this.state; const { start, stop } = this.sizeAndPositionManager.getVisibleRange({ offset, overscanCount, containerSize: this.props[sizeProp[scrollDirection]] || 0, }); const items = []; const wrapperStyle = { ...STYLE_WRAPPER, ...style, height, width }; const innerStyle = { ...STYLE_INNER, [sizeProp[scrollDirection]]: this.sizeAndPositionManager.getTotalSize(), }; if (stickyIndices != null && stickyIndices.length !== 0) { stickyIndices.forEach((index) => items.push(renderItem({ index, style: this.getStyle(index, true), }))); if (scrollDirection === DIRECTION.HORIZONTAL) { innerStyle.display = "flex"; } } if (typeof start !== "undefined" && typeof stop !== "undefined") { for (let index = start; index <= stop; index++) { if (stickyIndices != null && stickyIndices.includes(index)) { continue; } items.push(renderItem({ index, style: this.getStyle(index, false), })); } if (typeof onItemsRendered === "function") { onItemsRendered({ startIndex: start, stopIndex: stop, }); } } return (jsx("div", { ref: this.getRef, ...props, style: wrapperStyle, children: jsx("div", { style: innerStyle, children: items }) })); } getRef = (node) => { this.rootNode = node; }; handleScroll = (e) => { const event = e; const { onScroll } = this.props; const offset = this.getNodeOffset(); if (offset < 0 || this.state.offset === offset || event.target !== this.rootNode) { return; } this.setState({ offset, scrollChangeReason: SCROLL_CHANGE_REASON.OBSERVED, }); if (typeof onScroll === "function") { onScroll(offset, event); } }; getNodeOffset() { const { scrollDirection = DIRECTION.VERTICAL } = this.props; return this.rootNode[scrollProp[scrollDirection]]; } getEstimatedItemSize(props = this.props) { return props.estimatedItemSize || (typeof props.itemSize === "number" && props.itemSize) || 50; } getSize(index, itemSize) { if (typeof itemSize === "function") { return itemSize(index); } return Array.isArray(itemSize) ? itemSize[index] : itemSize; } getStyle(index, sticky) { const style = this.styleCache[index]; if (style) { return style; } const { scrollDirection = DIRECTION.VERTICAL } = this.props; const { size, offset } = this.sizeAndPositionManager.getSizeAndPositionForIndex(index); return (this.styleCache[index] = sticky ? { ...STYLE_STICKY_ITEM, [sizeProp[scrollDirection]]: size, [marginProp[scrollDirection]]: offset, [oppositeMarginProp[scrollDirection]]: -(offset + size), zIndex: 1, } : { ...STYLE_ITEM, [sizeProp[scrollDirection]]: size, [positionProp[scrollDirection]]: offset, }); } } function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n} function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css_248z = ".DndKitTree-module_item__Wml-H {\n margin: 10px 0;\n box-sizing: border-box;\n padding-left: var(--padding);\n}\n\n.DndKitTree-module_itemContent__BS7IC {\n flex-grow: 1;\n padding: 0 10px;\n}\n\n.DndKitTree-module_itemClone__NT1fI {\n display: inline-block;\n pointer-events: none;\n padding: 20px 0 0 25px;\n}\n\n.DndKitTree-module_itemContainerClone__isYwK {\n padding: 10px;\n border-radius: 5px;\n box-shadow: 0 15px 15px 0 rgba(34, 33, 81, 0.1);\n}\n\n.DndKitTree-module_itemActions__MAjT6 {\n display: flex;\n align-items: center;\n}\n\n.DndKitTree-module_itemAction__uZ-SR {\n display: flex;\n position: relative;\n transition: color 0.2s;\n justify-content: center;\n\n margin: 0;\n padding: 5px;\n\n color: #888;\n border: none;\n appearance: none;\n background: none;\n user-select: none;\n cursor: var(--cursor);\n\n svg {\n fill: #777;\n transition: all 0.2s ease-in-out;\n }\n\n &:hover {\n color: #000;\n outline: none;\n\n svg {\n fill: #000;\n }\n }\n\n &:focus,\n &:active {\n outline: none;\n }\n}\n\n.DndKitTree-module_itemDragging__VK2sx {\n & > * {\n box-shadow: none;\n background-color: transparent;\n }\n}\n\n.DndKitTree-module_itemDraggingIndicator__Mybv1 {\n z-index: 1;\n position: relative;\n margin-bottom: -1px;\n opacity: 1;\n}\n\n.DndKitTree-module_itemContainer__9YaRW {\n display: flex;\n position: relative;\n align-items: center;\n box-sizing: border-box;\n justify-content: flex-start;\n\n padding: 10px;\n\n color: #222;\n background-color: #fff;\n\n border-radius: 5px;\n border: 1px solid #dedede;\n}\n\n.DndKitTree-module_itemGhostSorting__j7xr7 {\n pointer-events: none;\n}\n\n.DndKitTree-module_itemContainerGhostIndicator__kC1N- {\n position: relative;\n padding: 0;\n height: 8px;\n border-color: #2389ff;\n background-color: #56a1f8;\n\n > * {\n height: 0;\n opacity: 0;\n }\n}\n\n.DndKitTree-module_itemChildrenCount__YWfN9 {\n position: absolute;\n top: -10px;\n right: -10px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background-color: #2389ff;\n font-weight: 600;\n color: #fff;\n line-height: 1;\n font-size: 12px;\n\n}\n\n.DndKitTree-module_itemActionCollapse__KHrsL {\n svg {\n transition: transform 250ms ease;\n }\n}\n\n.DndKitTree-module_itemActionCollapsed__ZphWA {\n svg {\n transform: rotate(-90deg);\n }\n}\n\n.DndKitTree-module_virtualListWrapper__RYOVw {\n scrollbar-width: none;\n &::-webkit-scrollbar {\n display: none;\n }\n}\n"; var appStyles = {"item":"DndKitTree-module_item__Wml-H","itemContent":"DndKitTree-module_itemContent__BS7IC","itemClone":"DndKitTree-module_itemClone__NT1fI","itemContainerClone":"DndKitTree-module_itemContainerClone__isYwK","itemActions":"DndKitTree-module_itemActions__MAjT6","itemAction":"DndKitTree-module_itemAction__uZ-SR","itemDragging":"DndKitTree-module_itemDragging__VK2sx","itemDraggingIndicator":"DndKitTree-module_itemDraggingIndicator__Mybv1","itemContainer":"DndKitTree-module_itemContainer__9YaRW","itemGhostSorting":"DndKitTree-module_itemGhostSorting__j7xr7","itemContainerGhostIndicator":"DndKitTree-module_itemContainerGhostIndicator__kC1N-","itemChildrenCount":"DndKitTree-module_itemChildrenCount__YWfN9","itemActionCollapse":"DndKitTree-module_itemActionCollapse__KHrsL","itemActionCollapsed":"DndKitTree-module_itemActionCollapsed__ZphWA","virtualListWrapper":"DndKitTree-module_virtualListWrapper__RYOVw"}; styleInject(css_248z); const Action = forwardRef(({ cursor, ...props }, ref) => { return (jsx("button", { ref: ref, tabIndex: 0, ...props, className: clsx(appStyles.itemAction, props.className), style: { ...props.style, "--cursor": cursor || "pointer" } })); }); Action.displayName = "Action"; const Handle = forwardRef((props, ref) => { return (jsx(Action, { ref: ref, cursor: "grab", ...props, children: jsx("svg", { viewBox: "0 0 20 20", width: "12", children: jsx("path", { d: "M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" }) }) })); }); Handle.displayName = "Handle"; function Remove(props) { return (jsx(Action, { ...props, children: jsx("svg", { width: "8", viewBox: "0 0 22 22", xmlns: "http://www.w3.org/2000/svg", children: jsx("path", { d: "M2.99998 -0.000206962C2.7441 -0.000206962 2.48794 0.0972617 2.29294 0.292762L0.292945 2.29276C-0.0980552 2.68376 -0.0980552 3.31682 0.292945 3.70682L7.58591 10.9998L0.292945 18.2928C-0.0980552 18.6838 -0.0980552 19.3168 0.292945 19.7068L2.29294 21.7068C2.68394 22.0978 3.31701 22.0978 3.70701 21.7068L11 14.4139L18.2929 21.7068C18.6829 22.0978 19.317 22.0978 19.707 21.7068L21.707 19.7068C22.098 19.3158 22.098 18.6828 21.707 18.2928L14.414 10.9998L21.707 3.70682C22.098 3.31682 22.098 2.68276 21.707 2.29276L19.707 0.292762C19.316 -0.0982383 18.6829 -0.0982383 18.2929 0.292762L11 7.58573L3.70701 0.292762C3.51151 0.0972617 3.25585 -0.000206962 2.99998 -0.000206962Z" }) }) })); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const TreeItem = forwardRef(({ node, clone, depth, style, isSorting, indicator, childCount, isDragging, handleProps, indentationWidth, onRemove, onCollapse, wrapperRef, renderItem, renderContent, ...props }, ref) => { if (renderItem) { return renderItem({ node, depth, clone: !!clone, containerRef: ref, containerStyle: style, wrapperRef: wrapperRef, isSorting: !!isSorting, isRemovable: !!onRemove, isDragging: !!isDragging, handleProps: handleProps, childCount: childCount || 0, isCollapsible: !!onCollapse, onRemove: onRemove || (() => { }), onCollapse: onCollapse || (() => { }), }); } return (jsx("div", { ref: wrapperRef, ...props, style: { "--padding": `${depth * indentationWidth}px` }, className: clsx([ appStyles.item, isDragging && appStyles.itemDragging, isDragging && indicator && appStyles.itemDraggingIndicator, isDragging && isSorting && appStyles.itemGhostSorting, clone && appStyles.itemClone, "dnd-tree-item", clone && "dnd-tree-item-clone", isDragging && indicator && "dnd-tree-item-indicator", props.className, ]), children: jsxs("div", { ref: ref, style: style, className: clsx(appStyles.itemContainer, isDragging && indicator && appStyles.itemContainerGhostIndicator, clone && appStyles.itemContainerClone, "dnd-tree-item-container", clone && "dnd-tree-item-container-clone", isDragging && indicator && "dnd-tree-item-container-indicator"), children: [jsxs("div", { className: clsx(appStyles.itemActions, "dnd-tree-item-actions"), children: [jsx(Handle, { className: "dnd-tree-item-action-handle", ...handleProps }), onCollapse && (jsx(Action, { onClick: onCollapse, className: clsx([ appStyles.itemActionCollapse, node.collapsed && node.children.length > 0 && appStyles.itemActionCollapsed, "dnd-tree-item-action-collapse", node.collapsed && node.children.length > 0 && "dnd-tree-item-action-collapsed", ]), children: collapseIcon }))] }), jsx("div", { className: clsx(appStyles.itemContent, "dnd-tree-item-content"), children: renderContent ? renderContent(node) : node.id }), !clone && onRemove && (jsx(Remove, { className: "dnd-tree-item-action-delete", onClick: onRemove })), clone && childCount && childCount > 1 ? (jsx("div", { className: clsx(appStyles.itemChildrenCount, "dnd-tree-item-children-count"), children: childCount })) : null] }) })); }); const collapseIcon = (jsx("svg", { width: "10", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 70 41", children: jsx("path", { d: "M30.76 39.2402C31.885 40.3638 33.41 40.995 35 40.995C36.59 40.995 38.115 40.3638 39.24 39.2402L68.24 10.2402C69.2998 9.10284 69.8768 7.59846 69.8494 6.04406C69.822 4.48965 69.1923 3.00657 68.093 1.90726C66.9937 0.807959 65.5106 0.178263 63.9562 0.150837C62.4018 0.123411 60.8974 0.700397 59.76 1.76024L35 26.5102L10.24 1.76024C9.10259 0.700397 7.59822 0.123411 6.04381 0.150837C4.4894 0.178263 3.00632 0.807959 1.90702 1.90726C0.807714 3.00657 0.178019 4.48965 0.150593 6.04406C0.123167 7.59846 0.700153 9.10284 1.75999 10.2402L30.76 39.2402Z" }) })); TreeItem.displayName = "TreeItem"; const animateLayoutChanges = ({ isSorting, wasDragging }) => !(isSorting || wasDragging); function SortableTreeItem({ node, renderItem, renderItemContent, ...props }) { const { isSorting, listeners, transform, isDragging, transition, attributes, setDraggableNodeRef, setDroppableNodeRef, } = useSortable({ id: node.id, animateLayoutChanges, }); const style = { transform: CSS.Translate.toString(transform), transition, }; return (jsx(TreeItem, { node: node, style: style, isSorting: isSorting, isDragging: isDragging, renderItem: renderItem, ref: setDraggableNodeRef, wrapperRef: setDroppableNodeRef, renderContent: renderItemContent, handleProps: { ...attributes, ...listeners, style: { ...props.handleProps?.style, touchAction: "none", }, }, ...props })); } const measuring = { droppable: { strategy: MeasuringStrategy.Always, }, }; const dropAnimationConfig = { keyframes({ transform }) { return [ { opacity: 1, transform: CSS.Transform.toString(transform.initial) }, { opacity: 0, transform: CSS.Transform.toString({ ...transform.final, x: transform.final.x + 5, y: transform.final.y + 5, }), }, ]; }, easing: "ease-out", sideEffects({ active }) { active.node.animate([{ opacity: 0 }, { opacity: 1 }], { duration: defaultDropAnimation.duration, easing: defaultDropAnimation.easing, }); }, }; function SortableTree({ value, virtual, maxDepth, removable, collapsible, onMove, onChange, renderItem, indentationWidth = 50, adjustTranslateY = -25, grabbingCursor = "grabbing", renderItemContent = (item) => jsx("div", { children: item.id }), }) { const indicator = true; const [activeNode, setActiveNode] = useState(null); const [overId, setOverId] = useState(null); const [offsetLeft, setOffsetLeft] = useState(0); const defaultBodyCursor = useRef(null); const defaultBodyUserSelect = useRef(null); const flattenedItems = useMemo(() => { const flattenedTree = flattenTree(value); const collapsedItems = flattenedTree.reduce((acc, { children, collapsed, id }) => (collapsed && children.length ? [...acc, id] : acc), []); return removeChildrenOf(flattenedTree, activeNode?.id != null ? [activeNode.id, ...collapsedItems] : collapsedItems); }, [activeNode, value]); const projected = activeNode?.id && overId ? getProjection(flattenedItems, activeNode.id, overId, offsetLeft, indentationWidth, maxDepth) : null; const sensorContext = useRef({ items: flattenedItems, offset: offsetLeft, }); const sensors = useSensors(useSensor(PointerSensor)); const sortedIds = useMemo(() => flattenedItems.map(({ id }) => id), [flattenedItems]); useEffect(() => { sensorContext.current = { items: flattenedItems, offset: offsetLeft, }; }, [flattenedItems, offsetLeft]); function handleDragStart({ active: { id: activeId } }) { setOverId(activeId); const activeItem = flattenedItems.find(({ id }) => id === activeId); setActiveNode(activeItem || null); defaultBodyCursor.current = document.body.style.getPropertyValue("cursor"); defaultBodyUserSelect.current = document.body.style.getPropertyValue("user-select"); document.body.style.setProperty("cursor", grabbingCursor); document.body.style.setProperty("user-select", "none"); } function handleDragMove({ delta }) { setOffsetLeft(delta.x); } function handleDragOver({ over }) { setOverId(over?.id ?? null); } function handleDragEnd({ active, over }) { resetState(); if (projected && over) { const { depth, parentId } = projected; const initialJson = JSON.stringify(flattenTree(value)); const clonedItems = JSON.parse(JSON.stringify(flattenTree(value))); const overIndex = clonedItems.findIndex(({ id }) => id === over.id); const activeIndex = clonedItems.findIndex(({ id }) => id === active.id); const activeTreeItem = clonedItems[activeIndex]; clonedItems[activeIndex] = { ...activeTreeItem, depth, parentId }; const sortedItems = arrayMove(clonedItems, activeIndex, overIndex); if (JSON.stringify(sortedItems) === initialJson) { return; } const newItems = buildTree(sortedItems); const depthItems = sortedItems.filter(({ parentId }) => parentId === clonedItems[activeIndex].parentId); const currentDepthIndex = depthItems.findIndex(({ id }) => id === clonedItems[activeIndex].id); onMove?.({ id: clonedItems[activeIndex]?.id?.toString(), parentId: clonedItems[activeIndex]?.parentId?.toString(), afterId: currentDepthIndex > 0 ? depthItems[currentDepthIndex - 1]?.id?.toString() : undefined, }); onChange?.(newItems); } } function handleDragCancel() { resetState(); } function resetState() { setOverId(null); setActiveNode(null); setOffsetLeft(0); document.body.style.setProperty("cursor", defaultBodyCursor.current); document.body.style.setProperty("user-select", defaultBodyUserSelect.current); } function handleRemove(id) { onChange?.(removeItem(value, id)); } function handleCollapse(id) { onChange?.(setProperty(value, id, "collapsed", (value) => { return !value; })); } const adjustTranslate = ({ transform }) => { return { ...transform, // Fix Y position to prevent the clone from jumping y: transform.y + adjustTranslateY, }; }; const renderSortableItem = (node) => (jsx(SortableTreeItem, { node: node, indicator: indicator, renderItem: renderItem, renderContent: renderItemContent, indentationWidth: indentationWidth, onRemove: removable ? () => handleRemove(node.id) : undefined, depth: node.id === activeNode?.id && projected ? projected.depth : node.depth, onCollapse: collapsible && node.children.length ? () => handleCollapse(node.id) : undefined }, node.id)); return (jsx(DndContext, { sensors: sensors, measuring: measuring, onDragEnd: handleDragEnd, onDragMove: handleDragMove, onDragOver: handleDragOver, onDragStart: handleDragStart, onDragCancel: handleDragCancel, collisionDetection: closestCenter, children: jsxs(SortableContext, { items: sortedIds, strategy: verticalListSortingStrategy, children: [virtual ? (jsx(VirtualList, { height: virtual.height, itemSize: virtual.itemSize, itemCount: flattenedItems.length, className: virtual?.hideScrollbar ? appStyles.virtualListWrapper : undefined, stickyIndices: activeNode ? [flattenedItems.findIndex(({ id }) => id === activeNode.id)] : undefined, onScroll: (_, e) => { // Fix Safari automatic scroll issue if (isSafari) { const el = e.target; const displayOriginal = el.style.getPropertyValue("display"); if (activeNode) { el.style.setProperty("display", "none"); void el.offsetHeight; el.style.setProperty("display", displayOriginal); } else { el.style.setProperty("display", displayOriginal); } } }, renderItem: ({ index, style }) => { const node = flattenedItems[index]; if (!node) { return null; } return (jsx("div", { style: { ...style, position: "absolute" }, children: renderSortableItem(node) }, index)); } })) : (flattenedItems.map((node) => renderSortableItem(node))), createPortal(jsx(DragOverlay, { dropAnimation: dropAnimationConfig, modifiers: [adjustTranslate] , children: activeNode?.id && activeNode ? (jsx(SortableTreeItem, { clone: true, depth: 0, node: activeNode, renderItem: renderItem, indentationWidth: indentationWidth, childCount: getChildCount(value, activeNode?.id) + 1, renderItemContent: renderItemContent })) : null }), document.body)] }) })); } export { SortableTree, buildTree, findItem, findItemDeep, flattenTree, getChildCount, getProjection, isSafari, removeChildrenOf, removeItem, setProperty }; //# sourceMappingURL=index.esm.js.map