react-dnd-accessible-backend
Version:
An add-on backend for react-dnd that provides support for keyboards and screenreaders by default.
100 lines • 3.63 kB
JavaScript
import { createFocusManager } from "./util/FocusManager";
import getNodeClientOffset from "./util/getNodeClientOffset";
import stopEvent from "./util/stopEvent";
var NavigationKeys;
(function (NavigationKeys) {
NavigationKeys["UP"] = "ArrowUp";
NavigationKeys["DOWN"] = "ArrowDown";
NavigationKeys["DROP"] = "Shift";
NavigationKeys["CANCEL"] = "Escape";
})(NavigationKeys || (NavigationKeys = {}));
export class DropTargetNavigator {
targetNodes;
manager;
previewer;
announcer;
currentHoveredNode;
dragNode;
focusManager;
actions;
monitor;
constructor(sourceNode, targetNodes, manager, previewer, announcer) {
this.targetNodes = targetNodes;
this.manager = manager;
this.previewer = previewer;
this.announcer = announcer;
this.currentHoveredNode = sourceNode;
this.dragNode = sourceNode;
this.focusManager = createFocusManager({
getFocusableElements: () => this.getViableTargets(targetNodes),
});
this.actions = manager.getActions();
this.monitor = manager.getMonitor();
window.addEventListener("keydown", this.handleDraggedElementKeyDown, { capture: true });
}
disconnect() {
window.removeEventListener("keydown", this.handleDraggedElementKeyDown, { capture: true });
this.dragNode?.focus();
}
handleDraggedElementKeyDown = (event) => {
switch (event.key) {
case NavigationKeys.UP:
stopEvent(event);
this.hoverNode(this.getPreviousDropTarget());
return;
case NavigationKeys.DOWN:
stopEvent(event);
this.hoverNode(this.getNextDropTarget());
return;
}
};
hoverNode(node) {
const targetId = Array.from(this.targetNodes.entries()).find(([_key, value]) => node === value)?.[0];
if (targetId == null)
return;
this.actions.hover([targetId], { clientOffset: getNodeClientOffset(node) });
this.currentHoveredNode = node;
this.previewer.render(this.monitor);
this.announcer.announceHover(node, targetId);
node?.focus();
}
getNextDropTarget() {
return this.focusManager.getNextFocusableElement({
wrap: false,
from: this.currentHoveredNode ?? undefined,
});
}
getPreviousDropTarget() {
return this.focusManager.getPreviousFocusableElement({
wrap: false,
from: this.currentHoveredNode ?? undefined,
});
}
getViableTargets(nodes) {
const allowedTargets = this.getAllowedTargets(nodes);
return allowedTargets.sort((a, b) => {
if (a === b)
return 0;
const position = a.compareDocumentPosition(b);
if (position &
(Node.DOCUMENT_POSITION_FOLLOWING | (position & Node.DOCUMENT_POSITION_CONTAINED_BY)))
return -1;
else if (position &
(Node.DOCUMENT_POSITION_PRECEDING | (position & Node.DOCUMENT_POSITION_CONTAINS)))
return 1;
else
return 0;
});
}
getAllowedTargets(nodes) {
const sourceType = this.monitor.getItemType();
if (sourceType == null)
return Array.from(nodes.values());
return Array.from(nodes).reduce((acc, [id, node]) => {
if (this.manager.getMonitor().canDropOnTarget(id))
acc.push(node);
return acc;
}, []);
}
}
//# sourceMappingURL=DropTargetNavigator.js.map