mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(studio): an escape cancels a preview marquee band and stops there (#4703)
* fix(studio): an escape cancels a preview marquee band and stops there * fix(studio): name the band escape handler instead of commenting it * test(studio): check the band is gone before the pointer is released
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { act, useRef } from "react";
|
||||
import type { Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, expect, it } from "vitest";
|
||||
import { installReactActEnvironment, mountReactHarness } from "../../hooks/domSelectionTestHarness";
|
||||
import { useMarqueeGestures } from "./marqueeCommit";
|
||||
|
||||
installReactActEnvironment();
|
||||
HTMLElement.prototype.setPointerCapture ??= () => {};
|
||||
HTMLElement.prototype.releasePointerCapture ??= () => {};
|
||||
|
||||
function Overlay() {
|
||||
const overlayRef = useRef<HTMLDivElement>(null);
|
||||
const marquee = useMarqueeGestures({
|
||||
iframeRef: useRef<HTMLIFrameElement>(null),
|
||||
overlayRef,
|
||||
activeCompositionPathRef: useRef<string | null>("index.html"),
|
||||
onMarqueeSelectRef: useRef(undefined),
|
||||
});
|
||||
return (
|
||||
<div
|
||||
ref={overlayRef}
|
||||
data-overlay
|
||||
onPointerDown={marquee.begin}
|
||||
onPointerMove={marquee.onPointerMove}
|
||||
onPointerUp={marquee.onPointerUp}
|
||||
>
|
||||
{marquee.marqueeRect && <div data-band />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const pointer = (type: string, clientX: number, clientY: number) =>
|
||||
act(() => {
|
||||
document
|
||||
.querySelector("[data-overlay]")!
|
||||
.dispatchEvent(
|
||||
new PointerEvent(type, { bubbles: true, button: 0, pointerId: 1, clientX, clientY }),
|
||||
);
|
||||
});
|
||||
const escape = () => {
|
||||
const event = new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true });
|
||||
act(() => {
|
||||
document.body.dispatchEvent(event);
|
||||
});
|
||||
return event;
|
||||
};
|
||||
|
||||
let root: Root;
|
||||
let hostEscapes: KeyboardEvent[];
|
||||
const hostListener = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") hostEscapes.push(e);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
hostEscapes = [];
|
||||
window.addEventListener("keydown", hostListener);
|
||||
root = mountReactHarness(<Overlay />);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.removeEventListener("keydown", hostListener);
|
||||
act(() => root.unmount());
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
it("an escape cancels a preview band while the pointer is still down, and stops there", () => {
|
||||
pointer("pointerdown", 10, 10);
|
||||
pointer("pointermove", 120, 90);
|
||||
expect(document.querySelector("[data-band]")).not.toBeNull();
|
||||
|
||||
const event = escape();
|
||||
|
||||
expect(document.querySelector("[data-band]")).toBeNull();
|
||||
expect(event.defaultPrevented).toBe(true);
|
||||
expect(hostEscapes).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("an escape with no band in flight still reaches the host", () => {
|
||||
const event = escape();
|
||||
|
||||
expect(event.defaultPrevented).toBe(false);
|
||||
expect(hostEscapes).toHaveLength(1);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
// fallow-ignore-file code-duplication
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { DomEditSelection } from "./domEditing";
|
||||
import { collectDomEditLayerItems, resolveDomEditSelection } from "./domEditingLayers";
|
||||
import { isElementComputedVisible } from "./domEditingElement";
|
||||
@@ -209,6 +209,18 @@ export function useMarqueeGestures<T>(deps: MarqueeGesturesDeps<T>): MarqueeGest
|
||||
reset();
|
||||
}, [reset]);
|
||||
|
||||
useEffect(() => {
|
||||
const cancelBandBeforeHostEscape = (e: KeyboardEvent) => {
|
||||
if (e.key !== "Escape" || !marqueeRef.current) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
cancel();
|
||||
};
|
||||
window.addEventListener("keydown", cancelBandBeforeHostEscape, { capture: true });
|
||||
return () =>
|
||||
window.removeEventListener("keydown", cancelBandBeforeHostEscape, { capture: true });
|
||||
}, [cancel]);
|
||||
|
||||
const onPointerMove = useCallback(
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
const m = marqueeRef.current;
|
||||
|
||||
Reference in New Issue
Block a user