@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
144 lines • 6.38 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { useRef, useState } from 'react';
import { closestCenter, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core';
import { hasSortableData } from '@dnd-kit/sortable';
import { KeyboardSensor } from './keyboard-sensor';
var KeyboardCode;
(function (KeyboardCode) {
KeyboardCode["Space"] = "Space";
KeyboardCode["Down"] = "ArrowDown";
KeyboardCode["Right"] = "ArrowRight";
KeyboardCode["Left"] = "ArrowLeft";
KeyboardCode["Up"] = "ArrowUp";
KeyboardCode["Esc"] = "Escape";
KeyboardCode["Enter"] = "Enter";
})(KeyboardCode || (KeyboardCode = {}));
// A custom collision detection algorithm is used when using a keyboard to
// work around an unexpected behavior when reordering items of variable height
// with the keyboard.
// Neither closestCenter nor closestCorners work really well for this case,
// because the center (or corners) of a tall rectangle might be so low that it
// is detected as being closest to the rectangle below of the one it should
// actually swap with.
// Instead of relying on coordinates, the expected results are achieved by
// moving X positions up or down in the initially sorted array, depending on
// the desired direction.
// We let our collisionDetection and customCoordinateGetter use the same
// getClosestId function which takes its value from the current component
// state, to make sure they are always in sync.
export default function useDragAndDropReorder({ items, itemDefinition, }) {
const isKeyboard = useRef(false);
const positionDelta = useRef(0);
const [activeItemId, setActiveItemId] = useState(null);
const setActiveItem = (id) => {
setActiveItemId(id);
if (!id) {
isKeyboard.current = false;
positionDelta.current = 0;
}
};
const handleKeyDown = (event) => {
if (isKeyboard.current && activeItemId) {
const currentTargetIndex = items.findIndex(item => itemDefinition.id(item) === activeItemId) + positionDelta.current;
if (event.key === 'ArrowDown' && currentTargetIndex < items.length - 1) {
positionDelta.current += 1;
}
else if (event.key === 'ArrowUp' && currentTargetIndex > 0) {
positionDelta.current -= 1;
}
}
if (activeItemId && isEscape(event.key)) {
// Prevent modal from closing when pressing Esc to cancel the dragging action
event.stopPropagation();
}
};
const getClosestId = (active) => {
if (positionDelta.current === 0) {
return active.id;
}
const currentIndex = items.findIndex(item => itemDefinition.id(item) === active.id);
const newIndex = Math.max(0, Math.min(items.length - 1, currentIndex + positionDelta.current));
return itemDefinition.id(items[newIndex]);
};
const collisionDetection = ({ active, collisionRect, droppableContainers, droppableRects, pointerCoordinates, }) => {
if (isKeyboard.current) {
// For keyboard interaction, determine the colliding container based on the movements made by the arrow keys,
// via getClosestId
const collidingContainer = getCollidingContainer({
activeId: active.id,
closestId: getClosestId(active),
droppableContainers,
});
return collidingContainer ? [collidingContainer] : [];
}
else {
// For mouse interaction, use the closest center algorithm
return closestCenter({ active, collisionRect, droppableRects, droppableContainers, pointerCoordinates });
}
};
const coordinateGetter = (event, { context: { active, collisionRect, droppableRects, droppableContainers } }) => {
if (event.code === KeyboardCode.Up || event.code === KeyboardCode.Down) {
event.preventDefault();
if (!active || !collisionRect) {
return;
}
const closestId = getClosestId(active);
if (closestId !== null) {
const activeDroppable = droppableContainers.get(active.id);
const newDroppable = droppableContainers.get(closestId);
const newRect = newDroppable ? droppableRects.get(newDroppable.id) : null;
const newNode = newDroppable === null || newDroppable === void 0 ? void 0 : newDroppable.node.current;
if (newNode && newRect && activeDroppable && newDroppable) {
const isAfterActive = isAfter(activeDroppable, newDroppable);
const offset = {
x: isAfterActive ? collisionRect.width - newRect.width : 0,
y: isAfterActive ? collisionRect.height - newRect.height : 0,
};
const rectCoordinates = {
x: newRect.left,
y: newRect.top,
};
return {
x: rectCoordinates.x - offset.x,
y: rectCoordinates.y - offset.y,
};
}
}
}
};
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
coordinateGetter,
onActivation: () => {
isKeyboard.current = true;
},
}));
return {
activeItemId,
setActiveItemId: setActiveItem,
collisionDetection,
coordinateGetter,
handleKeyDown,
sensors,
};
}
function isAfter(a, b) {
return hasSortableData(a) && hasSortableData(b) && a.data.current.sortable.index < b.data.current.sortable.index;
}
function getCollidingContainer({ activeId, closestId, droppableContainers, }) {
if (closestId === activeId) {
return;
}
const collidingContainer = droppableContainers.find(({ id }) => id === closestId);
if (collidingContainer) {
return {
id: collidingContainer.id,
data: {
droppableContainer: collidingContainer,
value: 0,
},
};
}
}
const isEscape = (key) => key === 'Escape' || key === 'Esc';
//# sourceMappingURL=use-drag-and-drop-reorder.js.map