Browse Source

fix(sorter): hit-test against the container's root node so sorting works in a shadow root

DropLocationDeterminer#getMouseTargetElement resolved the drop target with
`containerContext.document.elementFromPoint()`. `Document.elementFromPoint` does
not pierce shadow boundaries, so when a sorter container lives inside a shadow
root it returned the shadow host instead of the hovered item. The subsequent
`getFirstElementWithAModel` walk is bounded by
`containerContext.container.contains(el)`, which is false for the host, so it
returned null and handleMove bailed out on every sample: no placeholder, no
drop, silent failure.

This hits the Layer Manager when rendered into the host page via
`layerManager.appendTo` (ItemsView hardcodes `document: document`), making layer
reordering a no-op. The canvas is unaffected because its containerContext
document is the iframe document.

Fix: run the hit-test on `container.getRootNode()`, which is the ShadowRoot when
the container is in a shadow tree and the Document otherwise. `ShadowRoot`
implements the same DocumentOrShadowRoot mixin and resolves within its own tree,
so behaviour outside shadow DOM is unchanged; the isFunction guard falls back to
containerContext.document for a detached container.

Introduced by the sorter refactor in #6149 (0.22.1), which replaced the previous
`e.target` lookup — not retargeted for a listener inside the same shadow tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pull/6824/head
Guillaume Fauvet 1 month ago
parent
commit
150bf29da9
No known key found for this signature in database GPG Key ID: C4F3BC175FC1ED7D
  1. 19
      packages/core/src/utils/sorter/DropLocationDeterminer.ts

19
packages/core/src/utils/sorter/DropLocationDeterminer.ts

@ -4,7 +4,7 @@ import EditorModel from '../../editor/model/Editor';
import { isTextNode, off, on } from '../dom'; import { isTextNode, off, on } from '../dom';
import { SortableTreeNode } from './SortableTreeNode'; import { SortableTreeNode } from './SortableTreeNode';
import { Placement, PositionOptions, DragDirection, SorterEventHandlers, CustomTarget, DragSource } from './types'; import { Placement, PositionOptions, DragDirection, SorterEventHandlers, CustomTarget, DragSource } from './types';
import { bindAll, each } from 'underscore'; import { bindAll, each, isFunction } from 'underscore';
import { matches, findPosition, offset, isStyleInFlow } from './SorterUtils'; import { matches, findPosition, offset, isStyleInFlow } from './SorterUtils';
import { RateLimiter } from './RateLimiter'; import { RateLimiter } from './RateLimiter';
import Dimension from './Dimension'; import Dimension from './Dimension';
@ -287,9 +287,24 @@ export class DropLocationDeterminer<T, NodeType extends SortableTreeNode<T>> ext
return newHoveredNode; return newHoveredNode;
} }
/**
* Resolves the root to run the hit-test against.
*
* `Document.elementFromPoint` doesn't pierce shadow boundaries, so when the container lives
* inside a shadow root it would always return the host element instead of the hovered item.
* `ShadowRoot` implements the same `DocumentOrShadowRoot` mixin and resolves within its own tree.
*/
private getContainerContextRoot(): DocumentOrShadowRoot {
const root = this.containerContext.container?.getRootNode() as Partial<DocumentOrShadowRoot> | undefined;
if (isFunction(root?.elementFromPoint)) {
return root as DocumentOrShadowRoot;
}
return this.containerContext.document;
}
private getMouseTargetElement(mouseEvent: MouseEvent) { private getMouseTargetElement(mouseEvent: MouseEvent) {
const customTarget = this.containerContext.customTarget; const customTarget = this.containerContext.customTarget;
let mouseTarget = this.containerContext.document.elementFromPoint( let mouseTarget = this.getContainerContextRoot().elementFromPoint(
mouseEvent.clientX, mouseEvent.clientX,
mouseEvent.clientY, mouseEvent.clientY,
) as HTMLElement; ) as HTMLElement;

Loading…
Cancel
Save