mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
* feat(studio): edit and style text in the preview Double-press a text element in the canvas and the caret opens where you pressed, in the element itself rather than in a panel. Select characters and a small toolbar offers colour, bold, italic and underline, applied to exactly those characters. The toolbar lives in Studio's document rather than the composition's. Putting it in the preview would inject Studio's chrome into the user's composition, where a render would capture it and the composition's own styling would inherit into it. In a flex or grid container the rebuilt runs go inside one wrapper, so a coloured word cannot reflow the element it sits in. Also fixes the keyboard: the shortcut guards matched contenteditable=true only, so playback shortcuts ate letters typed into the composition. * refactor(studio): keep domEditingLayers under the size cap The rich-text operation pushed this file past the 600-line gate. Same change the branch made later, landed with the commit that caused it. * test(studio): wrap selection changes in act * fix(studio): restore rich text after failed save * fix(studio): polish inline text editing * fix(studio): harden inline text editing
87 lines
2.7 KiB
TypeScript
87 lines
2.7 KiB
TypeScript
// @vitest-environment happy-dom
|
|
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import type { DomEditSelection, DomEditTextField } from "./domEditingTypes";
|
|
|
|
const editable = vi.hoisted(() => ({ current: true }));
|
|
vi.mock("./domEditingLayers", () => ({
|
|
isTextEditableSelection: () => editable.current,
|
|
}));
|
|
|
|
const { canEditTextInline, isDoublePress } = await import("./domEditInlineText");
|
|
|
|
function field(key: string): DomEditTextField {
|
|
return {
|
|
key,
|
|
label: key,
|
|
value: "text",
|
|
tagName: "SPAN",
|
|
attributes: [],
|
|
inlineStyles: {},
|
|
computedStyles: {},
|
|
source: "self",
|
|
};
|
|
}
|
|
|
|
function selection(partial: Partial<DomEditSelection> = {}): DomEditSelection {
|
|
return {
|
|
label: "Heading",
|
|
tagName: "H1",
|
|
isCompositionHost: false,
|
|
isInsideLockedComposition: false,
|
|
textFields: [field("self")],
|
|
...partial,
|
|
} as DomEditSelection;
|
|
}
|
|
|
|
describe("canEditTextInline", () => {
|
|
it("allows an element the panel would let you edit text on", () => {
|
|
editable.current = true;
|
|
expect(canEditTextInline(selection())).toBe(true);
|
|
});
|
|
|
|
// The bar is the panel's bar: nothing becomes editable here that is not
|
|
// editable there.
|
|
it("refuses an element whose text the panel cannot edit either", () => {
|
|
editable.current = false;
|
|
expect(canEditTextInline(selection())).toBe(false);
|
|
});
|
|
|
|
// Editing the whole element would flatten its children into one string.
|
|
it("refuses an element with several text fields", () => {
|
|
editable.current = true;
|
|
expect(canEditTextInline(selection({ textFields: [field("a"), field("b")] }))).toBe(false);
|
|
});
|
|
|
|
it("allows an element with no separate text fields", () => {
|
|
editable.current = true;
|
|
expect(canEditTextInline(selection({ textFields: [] }))).toBe(true);
|
|
});
|
|
|
|
it("refuses the composition host, which is the document rather than copy", () => {
|
|
editable.current = true;
|
|
expect(canEditTextInline(selection({ isCompositionHost: true }))).toBe(false);
|
|
});
|
|
|
|
it("refuses anything inside a locked composition", () => {
|
|
editable.current = true;
|
|
expect(canEditTextInline(selection({ isInsideLockedComposition: true }))).toBe(false);
|
|
});
|
|
|
|
it("refuses nothing at all", () => {
|
|
editable.current = true;
|
|
expect(canEditTextInline(null)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("isDoublePress", () => {
|
|
it("pairs presses only when they land on the same element", () => {
|
|
const first = document.createElement("div");
|
|
const second = document.createElement("div");
|
|
const previous = { x: 10, y: 10, at: 100, element: first };
|
|
|
|
expect(isDoublePress(previous, { x: 12, y: 12, at: 200, element: first })).toBe(true);
|
|
expect(isDoublePress(previous, { x: 12, y: 12, at: 200, element: second })).toBe(false);
|
|
});
|
|
});
|