@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
302 lines • 15.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TreeView = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const core_1 = require("@dnd-kit/core");
const sortable_1 = require("@dnd-kit/sortable");
const react_1 = require("react");
const react_dom_1 = require("react-dom");
const tree_view_keyboard_coordinates_1 = require("./tree-view-keyboard-coordinates");
const tree_view_utils_1 = require("./tree-view-utils");
const TreeItemWrapper_1 = require("./TreeItemWrapper");
const TreeViewItem_1 = require("./TreeViewItem");
const TreeViewTextItem_1 = require("./TreeViewTextItem");
const indentationWidth = 20;
const ACTIVE_PLACEHOLDER_ID = 'ndl-active-tree-item';
const dropAnimationConfig = {
keyframes({ transform }) {
return [
{
opacity: 1,
transform: (0, tree_view_utils_1.transformToString)(transform.initial),
},
{
opacity: 0,
transform: (0, tree_view_utils_1.transformToString)(Object.assign(Object.assign({}, transform.final), { y: transform.final.y + 5, x: transform.final.x + 5 })),
},
];
},
easing: 'ease-out',
sideEffects({ active }) {
active.node.animate([{ opacity: 0 }, { opacity: 1 }], {
duration: core_1.defaultDropAnimation.duration,
easing: core_1.defaultDropAnimation.easing,
});
},
};
const TreeView = ({ items, TreeItemComponent, onItemsChanged, htmlAttributes, }) => {
var _a;
const flattenItems = (0, react_1.useMemo)(() => (0, tree_view_utils_1.flattenTree)(items), [items]);
const [activeId, setActiveId] = (0, react_1.useState)(null);
const [overId, setOverId] = (0, react_1.useState)(null);
const [focusedRowId, setFocusedRowId] = (0, react_1.useState)(null);
const [currentPosition, setCurrentPosition] = (0, react_1.useState)(null);
const [offsetLeft, setOffsetLeft] = (0, react_1.useState)(0);
/* ------------------------------ Setup of Data ------------------------------ */
const flattenedAndRemovedCollapsedItems = (0, react_1.useMemo)(() => {
const collapsedItemsIds = flattenItems
.filter((item) => item.canHaveSubItems && item.isCollapsed)
.map((item) => item.id);
return (0, tree_view_utils_1.removeChildrenOf)([...flattenItems], activeId !== null ? [activeId, ...collapsedItemsIds] : collapsedItemsIds);
}, [activeId, flattenItems]);
const activeItem = (0, react_1.useMemo)(() => (activeId ? flattenItems.find(({ id }) => id === activeId) : null), [activeId, flattenItems]);
const flattenedAndRemovedCollapsedItemsWithActive = (0, react_1.useMemo)(() => {
const collapsedItems = flattenItems.reduce((acc, item) => {
if (item.canHaveSubItems && item.isCollapsed) {
return [...acc, item.id];
}
return acc;
}, []);
const ItemsWithCollapsedRemoved = (0, tree_view_utils_1.removeChildrenOf)(flattenItems, activeId ? [activeId, ...collapsedItems] : collapsedItems);
const newArray = [];
for (const item of ItemsWithCollapsedRemoved) {
newArray.push(item);
if (item.id === activeId) {
newArray.push(Object.assign(Object.assign({}, item), { id: ACTIVE_PLACEHOLDER_ID }));
}
}
return newArray;
}, [flattenItems, activeId]);
if ((activeItem === null || activeItem === void 0 ? void 0 : activeItem.canHaveSubItems) && !(activeItem === null || activeItem === void 0 ? void 0 : activeItem.isCollapsed)) {
flattenedAndRemovedCollapsedItems.push(Object.assign(Object.assign({}, activeItem), { id: ACTIVE_PLACEHOLDER_ID }));
}
const sensorContext = (0, react_1.useRef)({
items: flattenedAndRemovedCollapsedItemsWithActive,
offset: offsetLeft,
});
const coordinateGetter = (0, react_1.useMemo)(() => {
sensorContext.current.items = flattenedAndRemovedCollapsedItemsWithActive;
sensorContext.current.offset = offsetLeft;
return (0, tree_view_keyboard_coordinates_1.sortableTreeKeyboardCoordinates)(sensorContext, true, indentationWidth);
}, [flattenedAndRemovedCollapsedItemsWithActive, offsetLeft]);
const sensors = (0, core_1.useSensors)((0, core_1.useSensor)(core_1.PointerSensor), (0, core_1.useSensor)(core_1.KeyboardSensor, {
coordinateGetter: coordinateGetter,
}));
const projected = (0, react_1.useMemo)(() => activeId && overId
? (0, tree_view_utils_1.getProjection)(flattenedAndRemovedCollapsedItemsWithActive, activeId, overId, offsetLeft, indentationWidth)
: null, [activeId, overId, flattenedAndRemovedCollapsedItemsWithActive, offsetLeft]);
/* ------------------------------ Handlers ------------------------------ */
function handleDragStart({ active }) {
const activeId = active.id;
setActiveId(activeId);
setOverId(activeId);
const activeItem = flattenItems.find(({ id }) => id === activeId);
if (activeItem) {
setCurrentPosition({
parentId: activeItem.parentId,
overId: activeId,
});
}
document.body.style.setProperty('cursor', 'grabbing');
}
function handleDragMove({ delta }) {
setOffsetLeft(delta.x);
}
function handleDragOver({ over }) {
var _a;
setOverId((_a = over === null || over === void 0 ? void 0 : over.id) !== null && _a !== void 0 ? _a : null);
}
function resetState() {
setOverId(null);
setActiveId(null);
setOffsetLeft(0);
setCurrentPosition(null);
document.body.style.setProperty('cursor', '');
}
const handleDragEnd = (0, react_1.useCallback)((event) => {
const { active, over } = event;
resetState();
let overIndex = flattenItems.findIndex((item) => item.id === (over === null || over === void 0 ? void 0 : over.id));
if ((over === null || over === void 0 ? void 0 : over.id) === ACTIVE_PLACEHOLDER_ID) {
overIndex = flattenItems.findIndex((item) => item.id === active.id);
}
if (projected && over) {
const { depth, parentId } = projected;
const clonedItems = [...flattenItems];
const parentIndex = clonedItems.findIndex((item) => item.id === parentId);
const activeIndex = clonedItems.findIndex(({ id }) => id === active.id);
const activeTreeItem = clonedItems[activeIndex];
clonedItems[activeIndex] = Object.assign(Object.assign({}, activeTreeItem), { depth, parentId });
const parentItem = clonedItems[parentIndex];
if (parentItem && parentItem.canHaveSubItems) {
clonedItems[parentIndex] = Object.assign(Object.assign({}, parentItem), { isCollapsed: false });
}
const newItems = (0, tree_view_utils_1.buildTree)((0, sortable_1.arrayMove)(clonedItems, activeIndex, overIndex));
onItemsChanged(newItems, {
reason: 'dropped',
item: activeTreeItem,
});
}
}, [flattenItems, onItemsChanged, projected]);
function handleToggleCollapse(id) {
const item = flattenItems.find((item) => item.id === id);
if (!item || item.canHaveSubItems !== true)
return;
onItemsChanged((0, tree_view_utils_1.buildTree)(flattenItems.map((item) => {
if (item.id === id && item.canHaveSubItems === true) {
return Object.assign(Object.assign({}, item), { isCollapsed: !item.isCollapsed });
}
return item;
})), {
reason: item.isCollapsed ? 'expanded' : 'collapsed',
item,
});
}
function handleDragCancel() {
resetState();
}
/* ------------------------------ Keyboard functions ------------------------------ */
const getMovementAnnouncement = (eventName, activeId, overId) => {
if (overId && projected) {
if (eventName !== 'onDragEnd') {
if (currentPosition &&
projected.parentId === currentPosition.parentId &&
overId === currentPosition.overId) {
return;
}
else {
setCurrentPosition({
parentId: projected.parentId,
overId,
});
}
}
const clonedItems = [
...flattenedAndRemovedCollapsedItemsWithActive,
];
const overIndex = clonedItems.findIndex(({ id }) => id === overId);
const activeIndex = clonedItems.findIndex(({ id }) => id === activeId);
const sortedItems = (0, sortable_1.arrayMove)(clonedItems, activeIndex, overIndex);
const previousItem = sortedItems[overIndex - 1];
let announcement;
const movedVerb = eventName === 'onDragEnd' ? 'dropped' : 'moved';
const nestedVerb = eventName === 'onDragEnd' ? 'dropped' : 'nested';
if (!previousItem) {
const nextItem = sortedItems[overIndex + 1];
announcement = `${activeId} was ${movedVerb} before ${nextItem.id}.`;
}
else {
if (projected.depth > previousItem.depth) {
announcement = `${activeId} was ${nestedVerb} under ${previousItem.id}.`;
}
else {
let previousSibling = previousItem;
while (previousSibling && projected.depth < previousSibling.depth) {
const parentId = previousSibling.parentId;
previousSibling = sortedItems.find(({ id }) => id === parentId);
}
if (previousSibling) {
announcement = `${activeId} was ${movedVerb} after ${previousSibling.id}.`;
}
}
}
return announcement;
}
return;
};
const announcements = {
onDragStart({ active }) {
return `Picked up ${active.id}.`;
},
onDragMove({ active, over }) {
return getMovementAnnouncement('onDragMove', active.id, over === null || over === void 0 ? void 0 : over.id);
},
onDragOver({ active, over }) {
return getMovementAnnouncement('onDragOver', active.id, over === null || over === void 0 ? void 0 : over.id);
},
onDragEnd({ active, over }) {
return getMovementAnnouncement('onDragEnd', active.id, over === null || over === void 0 ? void 0 : over.id);
},
onDragCancel({ active }) {
return `Moving was cancelled. ${active.id} was dropped in its original position.`;
},
};
/* ------------------------------ Visual ------------------------------ */
function getTrailList() {
const trails = {};
// Loop through the items in reverse
[...flattenedAndRemovedCollapsedItemsWithActive]
.reverse()
.forEach((item, itemIndex, reversedList) => {
const trailList = [];
const nextItem = reversedList[itemIndex - 1];
// Loop through the depth of the item
for (let trailDepth = 0; trailDepth < item.depth; trailDepth++) {
// Not closest to the item so should be straight or none
if (trailDepth < item.depth - 1) {
if (nextItem === undefined) {
trailList.push('none');
continue;
}
else if (trails[nextItem === null || nextItem === void 0 ? void 0 : nextItem.id][trailDepth] === 'none') {
trailList.push('none');
continue;
}
else if (nextItem && nextItem.depth <= trailDepth) {
trailList.push('none');
continue;
}
trailList.push('straight');
}
else {
if ((nextItem && trails[nextItem === null || nextItem === void 0 ? void 0 : nextItem.id][trailDepth] === 'none') ||
nextItem === undefined ||
nextItem.depth <= trailDepth) {
trailList.push('curved');
}
else {
trailList.push('straight-curved');
}
}
}
trails[item.id] = trailList;
});
return trails;
}
const measuring = {
droppable: {
strategy: core_1.MeasuringStrategy.Always,
},
};
const trails = getTrailList();
const rowIdWithFocus = focusedRowId !== null && focusedRowId !== void 0 ? focusedRowId : (_a = flattenedAndRemovedCollapsedItemsWithActive[0]) === null || _a === void 0 ? void 0 : _a.id;
return ((0, jsx_runtime_1.jsx)(core_1.DndContext, { accessibility: { announcements }, sensors: sensors, collisionDetection: core_1.closestCenter, onDragEnd: handleDragEnd, onDragStart: handleDragStart, onDragMove: handleDragMove, onDragOver: handleDragOver, onDragCancel: handleDragCancel, measuring: measuring, children: (0, jsx_runtime_1.jsxs)(sortable_1.SortableContext, { items: flattenedAndRemovedCollapsedItemsWithActive.map((item) => item.id), strategy: sortable_1.verticalListSortingStrategy, children: [(0, jsx_runtime_1.jsx)("ol", Object.assign({ className: "ndl-tree-view-list", role: "tree" }, htmlAttributes, { children: flattenedAndRemovedCollapsedItemsWithActive.map((item) => {
var _a;
return ((0, jsx_runtime_1.jsx)(TreeViewItem_1.SortableTreeViewItem, { depth: item.id === activeId && projected ? projected.depth : item.depth, indentationWidth: indentationWidth, item: item, isLast: false, parent: (_a = flattenItems.find((i) => i.id === item.parentId)) !== null && _a !== void 0 ? _a : null, id: item.id, trails: trails[item.id], keepGhostInPlace: item.id === ACTIVE_PLACEHOLDER_ID, TreeItemComponent: TreeItemComponent === undefined
? TreeViewTextItem_1.TreeViewTextItem
: TreeItemComponent, onCollapse: () => handleToggleCollapse(item.id), onItemsChanged: (newItems, itemChangedReason) => onItemsChanged((0, tree_view_utils_1.buildTree)(newItems), itemChangedReason), items: flattenItems, tabIndex: item.id === rowIdWithFocus ? 0 : -1, onFocus: () => setFocusedRowId(item.id), shouldDisableSorting: item.isSortable === false }, item.id));
}) })), (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)(core_1.DragOverlay, { dropAnimation: dropAnimationConfig }), document.body)] }) }));
};
exports.TreeView = TreeView;
exports.TreeView.TreeItemWrapper = TreeItemWrapper_1.TreeItemWrapper;
//# sourceMappingURL=TreeView.js.map