feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -2,14 +2,30 @@ import type { BlockNode } from "@wrnexus/blocks";
|
||||
import { mountBlockEditor, type MountedEditor, type MountEditorOptions } from "./runtime.ts";
|
||||
|
||||
/** Annotates an editor-only rendered document; never changes the canonical tree. */
|
||||
export function mountBlockCanvas(root: HTMLElement, tree: BlockNode, options: MountEditorOptions): MountedEditor {
|
||||
const elements = new Map([...root.querySelectorAll<HTMLElement>("[data-block-id]")].map(element => [element.dataset.blockId!, element]));
|
||||
export function mountBlockCanvas(
|
||||
root: HTMLElement,
|
||||
tree: BlockNode,
|
||||
options: MountEditorOptions,
|
||||
): MountedEditor {
|
||||
const elements = new Map(
|
||||
[...root.querySelectorAll<HTMLElement>("[data-block-id]")].map((element) => [
|
||||
element.dataset.blockId!,
|
||||
element,
|
||||
]),
|
||||
);
|
||||
const cleanup: Array<() => void> = [];
|
||||
const visit = (node: BlockNode, parent?: BlockNode, slot?: string) => {
|
||||
const element = elements.get(node.id);
|
||||
if (element) {
|
||||
const old = ["data-block-parent", "data-block-slot", "data-block-type", "draggable"].map(name => [name, element.getAttribute(name)] as const);
|
||||
cleanup.push(() => { for (const [name, value] of old) value === null ? element.removeAttribute(name) : element.setAttribute(name, value); });
|
||||
const old = ["data-block-parent", "data-block-slot", "data-block-type", "draggable"].map(
|
||||
(name) => [name, element.getAttribute(name)] as const,
|
||||
);
|
||||
cleanup.push(() => {
|
||||
for (const [name, value] of old) {
|
||||
if (value === null) element.removeAttribute(name);
|
||||
else element.setAttribute(name, value);
|
||||
}
|
||||
});
|
||||
element.setAttribute("draggable", "false");
|
||||
if (parent && slot !== undefined) {
|
||||
element.dataset.blockParent = parent.id;
|
||||
@@ -25,7 +41,10 @@ export function mountBlockCanvas(root: HTMLElement, tree: BlockNode, options: Mo
|
||||
// Clicks must not bubble into selection or inline-edit handlers.
|
||||
// Pointer/keyboard sensor events remain available to the DnD backend.
|
||||
for (const name of ["click", "dblclick"])
|
||||
handle.addEventListener(name, event => { event.preventDefault(); event.stopPropagation(); });
|
||||
handle.addEventListener(name, (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
element.prepend(handle);
|
||||
cleanup.push(() => handle.remove());
|
||||
}
|
||||
@@ -38,18 +57,33 @@ export function mountBlockCanvas(root: HTMLElement, tree: BlockNode, options: Mo
|
||||
target.textContent = `Drop blocks in ${name}`;
|
||||
// Root is a presentation table; keep editor decorations in a valid cell.
|
||||
const container = element.tagName === "TABLE" ? element.querySelector("td") : element;
|
||||
if (container) { container.append(target); cleanup.push(() => target.remove()); }
|
||||
if (container) {
|
||||
container.append(target);
|
||||
cleanup.push(() => target.remove());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [name, children] of Object.entries(node.slots)) for (const child of children) visit(child, node, name);
|
||||
for (const [name, children] of Object.entries(node.slots))
|
||||
for (const child of children) visit(child, node, name);
|
||||
};
|
||||
visit(tree);
|
||||
let mounted: MountedEditor;
|
||||
try { mounted = mountBlockEditor(root, options); }
|
||||
catch (error) { cleanup.reverse().forEach(fn => fn()); throw error; }
|
||||
try {
|
||||
mounted = mountBlockEditor(root, options);
|
||||
} catch (error) {
|
||||
cleanup.reverse().forEach((fn) => fn());
|
||||
throw error;
|
||||
}
|
||||
let destroyed = false;
|
||||
return {
|
||||
refresh: () => { if (!destroyed) mounted.refresh(); },
|
||||
destroy() { if (destroyed) return; destroyed = true; mounted.destroy(); cleanup.reverse().forEach(fn => fn()); },
|
||||
refresh: () => {
|
||||
if (!destroyed) mounted.refresh();
|
||||
},
|
||||
destroy() {
|
||||
if (destroyed) return;
|
||||
destroyed = true;
|
||||
mounted.destroy();
|
||||
cleanup.reverse().forEach((fn) => fn());
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user