From 5e166a101d57dc21d50cc4e9333f30077f9f8791 Mon Sep 17 00:00:00 2001 From: Austin Jackson Date: Mon, 3 Nov 2025 04:16:33 -0600 Subject: [PATCH] @uppy/dashboard: fix form appending for shadow dom (#6031) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #6028 I've also added `private/dev/dashboard_shadow.html` to more easily test ShadowDOM-specific bugs. I looked into writing a test case under `packages/@uppy/dashboard/src/index.browser.test.ts` but couldn't get it to work locally on Windows. --- > [!NOTE] > Ensure FileCard’s hidden form is appended/cleaned up in the correct root (Document body or ShadowRoot) using a ref-derived root node, and add a patch changeset. > > - **@uppy/dashboard – FileCard**: > - Append hidden `form` to the correct root using `getRootNode()` (handles Light DOM/iframes via `Document.body`, and Shadow DOM via `ShadowRoot`). > - Add `domRef` to root element to detect rendering context; attach `ref` and update effect accordingly. > - Clean up by removing the `form` from its actual `parentNode`. > - **Release**: > - Add changeset for patch: `Fix form appending for shadow dom`. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit edf81e871c3565b9bb1b521e44dbe6deebf4ef91. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: Murderlon --- .changeset/funny-insects-clean.md | 5 ++++ .../src/components/FileCard/index.tsx | 30 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 .changeset/funny-insects-clean.md diff --git a/.changeset/funny-insects-clean.md b/.changeset/funny-insects-clean.md new file mode 100644 index 000000000..43cb5ccd0 --- /dev/null +++ b/.changeset/funny-insects-clean.md @@ -0,0 +1,5 @@ +--- +"@uppy/dashboard": patch +--- + +Fix form appending for shadow dom diff --git a/packages/@uppy/dashboard/src/components/FileCard/index.tsx b/packages/@uppy/dashboard/src/components/FileCard/index.tsx index cddaedfbc..ca88cf2b3 100644 --- a/packages/@uppy/dashboard/src/components/FileCard/index.tsx +++ b/packages/@uppy/dashboard/src/components/FileCard/index.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames' import { nanoid } from 'nanoid/non-secure' -import { useCallback, useEffect, useState } from 'preact/hooks' +import { useCallback, useEffect, useRef, useState } from 'preact/hooks' import getFileTypeIcon from '../../utils/getFileTypeIcon.js' import ignoreEvent from '../../utils/ignoreEvent.js' import FilePreview from '../FilePreview.js' @@ -66,14 +66,37 @@ export default function FileCard(props: $TSFixMe) { return formEl }) + // We need to know where Uppy is being rendered + const domRef = useRef(null) + useEffect(() => { - document.body.appendChild(form) + /** + * Use the "rootNode" of whereever Uppy is rendered, falling back + * to `window.document` if domRef isn't initialized for some reason + */ + const rootNode = domRef.current?.getRootNode() ?? (document as Node) + /** + * This is the case for the Light DOM and . + * In these scenarios, we don't want to append a child to an + * element, but to the + */ + if (rootNode instanceof Document) { + rootNode.body.appendChild(form) + } + // This is the case for the Shadow DOM + else if (rootNode instanceof ShadowRoot) { + rootNode.appendChild(form) + } + // Everything else (realistically there isn't) + else { + rootNode.appendChild(form) + } form.addEventListener('submit', handleSave) return () => { form.removeEventListener('submit', handleSave) // check if form is still in the DOM before removing if (form.parentNode) { - document.body.removeChild(form) + form.parentNode.removeChild(form) } } }, [form, handleSave]) @@ -87,6 +110,7 @@ export default function FileCard(props: $TSFixMe) { onDragLeave={ignoreEvent} onDrop={ignoreEvent} onPaste={ignoreEvent} + ref={domRef} >