mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-09 20:46:13 +00:00
fix(core): a section whose root sets pointer-events:none stays pickable
This commit is contained in:
@@ -25,6 +25,12 @@ beforeAll(() => {
|
||||
}
|
||||
});
|
||||
|
||||
function ancestorsOf(el: Element): Element[] {
|
||||
const chain: Element[] = [];
|
||||
for (let at = el.parentElement; at; at = at.parentElement) chain.push(at);
|
||||
return chain;
|
||||
}
|
||||
|
||||
function createMockPostMessage() {
|
||||
return vi.fn();
|
||||
}
|
||||
@@ -150,6 +156,42 @@ describe("createPickerModule", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("picks inside a mounted section whose root sets pointer-events:none", () => {
|
||||
const picker = createPickerModule({ postMessage: createMockPostMessage() });
|
||||
picker.installPickerApi();
|
||||
// The mount rescopes the section's own root rule onto its inner root.
|
||||
document.head.innerHTML =
|
||||
'<style>[data-composition-id="intro"] > [data-hf-inner-root] { pointer-events: none }' +
|
||||
" .vignette { pointer-events: none }</style>";
|
||||
document.body.innerHTML = `<div data-composition-id="intro" data-composition-src="intro.html">
|
||||
<div data-hf-inner-root="true"><div class="card"><code id="code">tl.to()</code></div>
|
||||
<div class="vignette" id="vignette"></div></div></div>`;
|
||||
const code = document.getElementById("code")!;
|
||||
// Paint order under the point, top first; like a browser, drop what has pointer-events:none.
|
||||
const painted = [document.getElementById("vignette")!, code, code.parentElement!];
|
||||
const originalElementsFromPoint = document.elementsFromPoint;
|
||||
Object.defineProperty(document, "elementsFromPoint", {
|
||||
configurable: true,
|
||||
value: vi.fn(() =>
|
||||
[...painted, ...ancestorsOf(code.parentElement!)].filter(
|
||||
(el) => getComputedStyle(el).pointerEvents !== "none",
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
const api = (window as any).__HF_PICKER_API;
|
||||
try {
|
||||
expect(api.getCandidatesAtPoint(10, 10)[0]?.selector).toBe("#code");
|
||||
expect(api.pickAtPoint(10, 10)?.selector).toBe("#code");
|
||||
expect(document.head.querySelectorAll("style")).toHaveLength(1);
|
||||
} finally {
|
||||
Object.defineProperty(document, "elementsFromPoint", {
|
||||
configurable: true,
|
||||
value: originalElementsFromPoint,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("pickAtPoint returns null for invalid coords", () => {
|
||||
const picker = createPickerModule({ postMessage: createMockPostMessage() });
|
||||
picker.installPickerApi();
|
||||
|
||||
@@ -20,6 +20,23 @@ const PICKER_BLOCK_SELECTOR = [
|
||||
"[data-hyper-shader-loading]",
|
||||
].join(",");
|
||||
|
||||
// A composition root's pointer-events:none is about playback, not editing: it inherits into the whole section
|
||||
// and hides it from the hit test. Roots take pointer events again while the picker looks; an element that
|
||||
// sets pointer-events:none itself (a vignette, a cursor) still passes through.
|
||||
const PICKABLE_ROOTS_CSS =
|
||||
"[data-composition-id],[data-hf-inner-root]{pointer-events:auto!important}";
|
||||
|
||||
function withPickableCompositionRoots<T>(run: () => T): T {
|
||||
const style = document.createElement("style");
|
||||
style.textContent = PICKABLE_ROOTS_CSS;
|
||||
(document.head ?? document.documentElement).appendChild(style);
|
||||
try {
|
||||
return run();
|
||||
} finally {
|
||||
style.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export type PickerModule = {
|
||||
enablePickMode: () => void;
|
||||
disablePickMode: () => void;
|
||||
@@ -183,14 +200,20 @@ export function createPickerModule(deps: PickerModuleDeps): PickerModule {
|
||||
clientY: number,
|
||||
limit?: number,
|
||||
): RuntimePickerElementInfo[] {
|
||||
return getPickCandidatesFromPoint(clientX, clientY, limit).map(extractElementInfo);
|
||||
return withPickableCompositionRoots(() =>
|
||||
getPickCandidatesFromPoint(clientX, clientY, limit),
|
||||
).map(extractElementInfo);
|
||||
}
|
||||
|
||||
function onPickMouseMove(event: MouseEvent): void {
|
||||
if (!pickModeActive) return;
|
||||
const candidates = getPickCandidatesFromPoint(event.clientX, event.clientY, 1);
|
||||
const target = candidates[0] ?? (isElementNode(event.target) ? event.target : null);
|
||||
if (!isPickableElement(target)) return;
|
||||
const target = withPickableCompositionRoots(() => {
|
||||
const hit =
|
||||
getPickCandidatesFromPoint(event.clientX, event.clientY, 1)[0] ??
|
||||
(isElementNode(event.target) ? event.target : null);
|
||||
return isPickableElement(hit) ? hit : null;
|
||||
});
|
||||
if (!target) return;
|
||||
if (pickModeHighlightEl === target) return;
|
||||
if (pickModeHighlightEl) {
|
||||
pickModeHighlightEl.classList.remove("__hf-pick-highlight");
|
||||
|
||||
Reference in New Issue
Block a user