From b7b5032c026614d2f04899b9d07e9f6d8dfa996e Mon Sep 17 00:00:00 2001 From: CRIMX Date: Tue, 10 Dec 2024 12:56:30 +0800 Subject: [PATCH 1/2] fix(system): safe check all event target --- .../react/src/examples/AddNodeOnEdgeDrop/index.tsx | 2 +- .../src/routes/examples/add-node-on-drop/Flow.svelte | 2 +- packages/react/src/components/Handle/index.tsx | 2 +- packages/react/src/container/Pane/index.tsx | 4 ++-- packages/svelte/src/lib/container/Pane/Pane.svelte | 4 ++-- packages/system/src/utils/dom.ts | 12 +++++++----- packages/system/src/xydrag/XYDrag.ts | 2 +- packages/system/src/xydrag/utils.ts | 8 ++++---- packages/system/src/xyhandle/XYHandle.ts | 2 +- 9 files changed, 20 insertions(+), 18 deletions(-) diff --git a/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx b/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx index cdd55a4b..7c0345d2 100644 --- a/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx +++ b/examples/react/src/examples/AddNodeOnEdgeDrop/index.tsx @@ -47,7 +47,7 @@ const AddNodeOnEdgeDrop = () => { (event) => { if (!connectingNodeId.current) return; - const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('react-flow__pane'); + const targetIsPane = (event.target as Partial | null)?.classList?.contains('react-flow__pane'); if (targetIsPane && 'clientX' in event && 'clientY' in event) { // we need to remove the wrapper bounds, in order to get the correct position diff --git a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte index e767e4ac..0fb89484 100644 --- a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte +++ b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte @@ -33,7 +33,7 @@ if (!connectingNodeId) return; // See of connection landed inside the flow pane - const targetIsPane = (event.target as HTMLDivElement)?.classList.contains('svelte-flow__pane'); + const targetIsPane = (event.target as Partial | null)?.classList?.contains('svelte-flow__pane'); if (targetIsPane && 'clientX' in event && 'clientY' in event) { const id = getId(); const position = { diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index 3ea98530..cc86e189 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -175,7 +175,7 @@ function HandleComponent( return; } - const doc = getHostForElement(event.target as HTMLElement); + const doc = getHostForElement(event.target); const isValidConnectionHandler = isValidConnection || isValidConnectionStore; const { connection, isValid } = XYHandle.isValid(event.nativeEvent, { handle: { diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index 48c23eba..bdecb84d 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -131,7 +131,7 @@ export function Pane({ return; } - (event.target as Element)?.setPointerCapture?.(event.pointerId); + (event.target as Partial | null)?.setPointerCapture?.(event.pointerId); selectionStarted.current = true; selectionInProgress.current = false; @@ -229,7 +229,7 @@ export function Pane({ return; } - (event.target as Element)?.releasePointerCapture?.(event.pointerId); + (event.target as Partial)?.releasePointerCapture?.(event.pointerId); const { userSelectionRect } = store.getState(); // We only want to trigger click functions when in selection mode if // the user did not move the mouse. diff --git a/packages/svelte/src/lib/container/Pane/Pane.svelte b/packages/svelte/src/lib/container/Pane/Pane.svelte index 1d797af9..aaa36560 100644 --- a/packages/svelte/src/lib/container/Pane/Pane.svelte +++ b/packages/svelte/src/lib/container/Pane/Pane.svelte @@ -103,7 +103,7 @@ return; } - (event.target as Element)?.setPointerCapture?.(event.pointerId); + (event.target as Partial | null)?.setPointerCapture?.(event.pointerId); const { x, y } = getEventPosition(event, containerBounds); @@ -175,7 +175,7 @@ return; } - (event.target as Element)?.releasePointerCapture?.(event.pointerId); + (event.target as Partial | null)?.releasePointerCapture?.(event.pointerId); // We only want to trigger click functions when in selection mode if // the user did not move the mouse. diff --git a/packages/system/src/utils/dom.ts b/packages/system/src/utils/dom.ts index b3648ede..12188e9b 100644 --- a/packages/system/src/utils/dom.ts +++ b/packages/system/src/utils/dom.ts @@ -32,18 +32,20 @@ export const getDimensions = (node: HTMLDivElement): Dimensions => ({ height: node.offsetHeight, }); -export const getHostForElement = (element: HTMLElement): Document | ShadowRoot => - (element.getRootNode?.() as Document | ShadowRoot) || window?.document; +export const getHostForElement = (element: HTMLElement | EventTarget | null): Document | ShadowRoot => + ((element as Partial | null)?.getRootNode?.() as Document | ShadowRoot) || window?.document; const inputTags = ['INPUT', 'SELECT', 'TEXTAREA']; export function isInputDOMNode(event: KeyboardEvent): boolean { // using composed path for handling shadow dom - const target = (event.composedPath?.()?.[0] || event.target) as HTMLElement; - const isInput = inputTags.includes(target?.nodeName) || target?.hasAttribute?.('contenteditable'); + const target = (event.composedPath?.()?.[0] || event.target) as Element | null; + if (target?.nodeType !== 1 /* Node.ELEMENT_NODE */) return false; + + const isInput = inputTags.includes(target.nodeName) || target.hasAttribute('contenteditable'); // when an input field is focused we don't want to trigger deletion or movement of nodes - return isInput || !!target?.closest('.nokey'); + return isInput || !!target.closest('.nokey'); } export const isMouseEvent = (event: MouseEvent | TouchEvent): event is MouseEvent => 'clientX' in event; diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index adf72e5c..eb2ea80a 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -364,7 +364,7 @@ export function XYDrag voi } }) .filter((event: MouseEvent) => { - const target = event.target as HTMLDivElement; + const target = event.target; const isDraggable = !event.button && (!noDragClassName || !hasSelector(target, `.${noDragClassName}`, domNode)) && diff --git a/packages/system/src/xydrag/utils.ts b/packages/system/src/xydrag/utils.ts index 0fc048e8..5c0eda0a 100644 --- a/packages/system/src/xydrag/utils.ts +++ b/packages/system/src/xydrag/utils.ts @@ -18,13 +18,13 @@ export function isParentSelected(node: NodeType, node return isParentSelected(parentNode, nodeLookup); } -export function hasSelector(target: Element, selector: string, domNode: Element): boolean { - let current = target; +export function hasSelector(target: Element | EventTarget | null, selector: string, domNode: Element): boolean { + let current = target as Partial | null | undefined; do { - if (current?.matches(selector)) return true; + if (current?.matches?.(selector)) return true; if (current === domNode) return false; - current = current.parentElement as Element; + current = current?.parentElement; } while (current); return false; diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index b15befc9..5f4fc610 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -48,7 +48,7 @@ function onPointerDown( }: OnPointerDownParams ) { // when xyflow is used inside a shadow root we can't use document - const doc = getHostForElement(event.target as HTMLElement); + const doc = getHostForElement(event.target); let autoPanId = 0; let closestHandle: Handle | null; From e2d849dca63aee5952f676aef1c675c6232bb69a Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 10 Jan 2025 10:26:55 +0100 Subject: [PATCH 2/2] chore(changeset): add --- .changeset/little-toys-brake.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/little-toys-brake.md diff --git a/.changeset/little-toys-brake.md b/.changeset/little-toys-brake.md new file mode 100644 index 00000000..a91bce8e --- /dev/null +++ b/.changeset/little-toys-brake.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +'@xyflow/system': patch +--- + +Add type check for all event targets