refactor(packages): change package structure

This commit is contained in:
moklick
2023-06-05 15:40:18 +02:00
parent b2e296901e
commit 8354759f57
332 changed files with 980 additions and 3244 deletions
+269
View File
@@ -0,0 +1,269 @@
import { drag } from 'd3-drag';
import { select } from 'd3-selection';
import { calcAutoPan, getEventPosition, getPointerPosition, calcNextPosition } from '../utils';
import { getDragItems, getEventHandlerParams, hasSelector, wrapSelectionDragFunc } from './utils';
import type {
BaseNode,
NodeDragItem,
UseDragEvent,
XYPosition,
BaseEdge,
CoordinateExtent,
NodeOrigin,
OnError,
SnapGrid,
Transform,
PanBy,
OnNodeDrag,
OnSelectionDrag,
UpdateNodePositions,
} from '../types';
export type OnDrag = (event: MouseEvent, dragItems: NodeDragItem[], node: BaseNode, nodes: BaseNode[]) => void;
type StoreItems = {
nodes: BaseNode[];
edges: BaseEdge[];
nodeExtent: CoordinateExtent;
snapGrid: SnapGrid;
snapToGrid: boolean;
nodeOrigin: NodeOrigin;
multiSelectionActive: boolean;
domNode?: Element | null;
transform: Transform;
autoPanOnNodeDrag: boolean;
nodesDraggable: boolean;
selectNodesOnDrag: boolean;
panBy: PanBy;
unselectNodesAndEdges: () => void;
onError?: OnError;
onNodeDragStart?: OnNodeDrag;
onNodeDrag?: OnNodeDrag;
onNodeDragStop?: OnNodeDrag;
onSelectionDragStart?: OnSelectionDrag;
onSelectionDrag?: OnSelectionDrag;
onSelectionDragStop?: OnSelectionDrag;
updateNodePositions: UpdateNodePositions;
};
export type XYDragParams = {
domNode: Element;
getStoreItems: () => StoreItems;
onDragStart?: OnDrag;
onDrag?: OnDrag;
onDragStop?: OnDrag;
onNodeClick?: () => void;
};
export type XYDragInstance = {
update: (params: DragUpdateParams) => void;
destroy: () => void;
};
export type DragUpdateParams = {
noDragClassName?: string;
handleSelector?: string;
isSelectable?: boolean;
nodeId?: string;
domNode: Element;
};
export function XYDrag({
domNode,
onNodeClick,
getStoreItems,
onDragStart,
onDrag,
onDragStop,
}: XYDragParams): XYDragInstance {
let lastPos: { x: number | null; y: number | null } = { x: null, y: null };
let autoPanId = 0;
let dragItems: NodeDragItem[] = [];
let autoPanStarted = false;
let mousePosition: XYPosition = { x: 0, y: 0 };
let dragEvent: MouseEvent | null = null;
let containerBounds: DOMRect | null = null;
const d3Selection = select(domNode);
// public functions
function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) {
function updateNodes({ x, y }: XYPosition) {
const {
nodes,
nodeExtent,
snapGrid,
snapToGrid,
nodeOrigin,
onNodeDrag,
onSelectionDrag,
onError,
updateNodePositions,
} = getStoreItems();
lastPos = { x, y };
let hasChange = false;
dragItems = dragItems.map((n) => {
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
if (snapToGrid) {
nextPosition.x = snapGrid[0] * Math.round(nextPosition.x / snapGrid[0]);
nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]);
}
const updatedPos = calcNextPosition(n, nextPosition, nodes, nodeExtent, nodeOrigin, onError);
// we want to make sure that we only fire a change event when there is a changes
hasChange = hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y;
n.position = updatedPos.position;
n.positionAbsolute = updatedPos.positionAbsolute;
return n;
});
if (!hasChange) {
return;
}
updateNodePositions(dragItems, true, true);
const onNodeOrSelectionDrag = nodeId ? onNodeDrag : wrapSelectionDragFunc(onSelectionDrag);
if (dragEvent) {
const [currentNode, currentNodes] = getEventHandlerParams({
nodeId,
dragItems,
nodes,
});
onDrag?.(dragEvent as MouseEvent, dragItems, currentNode, currentNodes);
onNodeOrSelectionDrag?.(dragEvent as MouseEvent, currentNode, currentNodes);
}
}
function autoPan() {
if (!containerBounds) {
return;
}
const [xMovement, yMovement] = calcAutoPan(mousePosition, containerBounds);
if (xMovement !== 0 || yMovement !== 0) {
const { transform, panBy } = getStoreItems();
lastPos.x = (lastPos.x ?? 0) - xMovement / transform[2];
lastPos.y = (lastPos.y ?? 0) - yMovement / transform[2];
if (panBy({ x: xMovement, y: yMovement })) {
updateNodes(lastPos as XYPosition);
}
}
autoPanId = requestAnimationFrame(autoPan);
}
const d3DragInstance = drag()
.on('start', (event: UseDragEvent) => {
const {
nodes,
multiSelectionActive,
domNode,
nodesDraggable,
transform,
snapGrid,
snapToGrid,
onNodeDragStart,
onSelectionDragStart,
unselectNodesAndEdges,
selectNodesOnDrag,
} = getStoreItems();
if (!selectNodesOnDrag && !multiSelectionActive && nodeId) {
if (!nodes.find((n) => n.id === nodeId)?.selected) {
// we need to reset selected nodes when selectNodesOnDrag=false
unselectNodesAndEdges();
}
}
if (isSelectable && selectNodesOnDrag) {
onNodeClick?.();
}
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
lastPos = pointerPos;
dragItems = getDragItems(nodes, nodesDraggable, pointerPos, nodeId);
const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart);
if (dragItems) {
const [currentNode, currentNodes] = getEventHandlerParams({
nodeId,
dragItems,
nodes,
});
onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
}
containerBounds = domNode?.getBoundingClientRect() || null;
mousePosition = getEventPosition(event.sourceEvent, containerBounds!);
})
.on('drag', (event: UseDragEvent) => {
const { autoPanOnNodeDrag, transform, snapGrid, snapToGrid } = getStoreItems();
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
if (!autoPanStarted && autoPanOnNodeDrag) {
autoPanStarted = true;
autoPan();
}
// skip events without movement
if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems) {
dragEvent = event.sourceEvent as MouseEvent;
mousePosition = getEventPosition(event.sourceEvent, containerBounds!);
updateNodes(pointerPos);
}
})
.on('end', (event: UseDragEvent) => {
autoPanStarted = false;
cancelAnimationFrame(autoPanId);
if (dragItems) {
const { nodes, updateNodePositions, onNodeDragStop, onSelectionDragStop } = getStoreItems();
const onNodeOrSelectionDragStop = nodeId ? onNodeDragStop : wrapSelectionDragFunc(onSelectionDragStop);
updateNodePositions(dragItems, false, false);
const [currentNode, currentNodes] = getEventHandlerParams({
nodeId,
dragItems,
nodes,
});
onDragStop?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
onNodeOrSelectionDragStop?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
}
})
.filter((event: MouseEvent) => {
const target = event.target as HTMLDivElement;
const isDraggable =
!event.button &&
(!noDragClassName || !hasSelector(target, `.${noDragClassName}`, domNode)) &&
(!handleSelector || hasSelector(target, handleSelector, domNode));
return isDraggable;
});
d3Selection.call(d3DragInstance);
}
function destroy() {
d3Selection.on('.drag', null);
}
return {
update,
destroy,
};
}
+1
View File
@@ -0,0 +1 @@
export * from './XYDrag';
+94
View File
@@ -0,0 +1,94 @@
import { type NodeDragItem, type XYPosition, BaseNode } from '../types';
export function wrapSelectionDragFunc(selectionFunc?: (event: MouseEvent, nodes: BaseNode[]) => void) {
return (event: MouseEvent, _: BaseNode, nodes: BaseNode[]) => selectionFunc?.(event, nodes);
}
export function isParentSelected<NodeType extends BaseNode>(node: NodeType, nodes: NodeType[]): boolean {
if (!node.parentNode) {
return false;
}
const parentNode = nodes.find((node) => node.id === node.parentNode);
if (!parentNode) {
return false;
}
if (parentNode.selected) {
return true;
}
return isParentSelected(parentNode, nodes);
}
export function hasSelector(target: Element, selector: string, domNode: Element): boolean {
let current = target;
do {
if (current?.matches(selector)) return true;
if (current === domNode) return false;
current = current.parentElement as Element;
} while (current);
return false;
}
// looks for all selected nodes and created a NodeDragItem for each of them
export function getDragItems<NodeType extends BaseNode>(
nodes: NodeType[],
nodesDraggable: boolean,
mousePos: XYPosition,
nodeId?: string
): NodeDragItem[] {
return nodes
.filter(
(n) =>
(n.selected || n.id === nodeId) &&
(!n.parentNode || !isParentSelected(n, nodes)) &&
(n.draggable || (nodesDraggable && typeof n.draggable === 'undefined'))
)
.map((n) => ({
id: n.id,
position: n.position || { x: 0, y: 0 },
positionAbsolute: n.positionAbsolute || { x: 0, y: 0 },
distance: {
x: mousePos.x - (n.positionAbsolute?.x ?? 0),
y: mousePos.y - (n.positionAbsolute?.y ?? 0),
},
delta: {
x: 0,
y: 0,
},
extent: n.extent,
parentNode: n.parentNode,
width: n.width,
height: n.height,
origin: n.origin,
}));
}
// returns two params:
// 1. the dragged node (or the first of the list, if we are dragging a node selection)
// 2. array of selected nodes (for multi selections)
export function getEventHandlerParams<NodeType extends BaseNode>({
nodeId,
dragItems,
nodes,
}: {
nodeId?: string;
dragItems: NodeDragItem[];
nodes: NodeType[];
}): [NodeType, NodeType[]] {
const extentedDragItems: NodeType[] = dragItems.map((n) => {
const node = nodes.find((node) => node.id === n.id)!;
return {
...node,
position: n.position,
positionAbsolute: n.positionAbsolute,
};
});
return [nodeId ? extentedDragItems.find((n) => n.id === nodeId)! : extentedDragItems[0], extentedDragItems];
}