Files
WRNexusJS/packages/block-editor/src/runtime.ts
T
Clintchiz edccf06658
Quality / quality (ubuntu-latest) (push) Failing after 10m0s
Quality / quality (windows-latest) (push) Canceled after 0s
fix(builder): correct move and sortable behavior
2026-09-05 10:49:33 +05:30

80 lines
2.7 KiB
TypeScript

import { createDndManager, type DndAnnouncement, type DndManager } from "@wrnexus/dnd";
export interface MountedEditor {
destroy(): void;
refresh(): void;
}
export interface MountEditorOptions {
direction?: "ltr" | "rtl";
announce?(announcement: DndAnnouncement): string;
onMove(intent: {
nodeIds: string[];
to: { parentId: string; slot: string; index: number };
}): boolean | void | Promise<boolean | void>;
}
export function mountBlockEditor(root: HTMLElement, options: MountEditorOptions): MountedEditor {
let manager: DndManager | undefined;
let cleanups: Array<() => void> = [];
const live = document.createElement("div");
live.setAttribute("role", "status");
live.setAttribute("aria-live", "polite");
live.setAttribute("aria-atomic", "true");
live.style.cssText =
"position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0";
root.append(live);
const refresh = () => {
cleanups.forEach((cleanup) => cleanup());
cleanups = [];
manager?.destroy();
manager = createDndManager({
direction: options.direction ?? (getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr"),
reducedMotion: matchMedia("(prefers-reduced-motion: reduce)").matches,
announce(announcement) {
live.textContent = options.announce?.(announcement) ?? announcement.type;
},
async onMoveIntent(intent) {
const [parentId, slot] = String(intent.to.group).split(":", 2);
if (!parentId || !slot) return false;
return options.onMove({
nodeIds: intent.ids.map(String),
to: { parentId, slot, index: intent.to.index },
});
},
});
for (const element of root.querySelectorAll<HTMLElement>(
"[data-block-id][data-block-parent][data-block-slot]",
)) {
const id = element.dataset.blockId!;
const parent = element.dataset.blockParent!;
const slot = element.dataset.blockSlot!;
const siblings = [
...root.querySelectorAll<HTMLElement>("[data-block-id][data-block-parent][data-block-slot]"),
].filter(
(candidate) =>
candidate.dataset.blockParent === parent && candidate.dataset.blockSlot === slot,
);
const handle = element.querySelector<HTMLElement>("[data-block-handle]") ?? undefined;
cleanups.push(
manager.registerSortable({
id,
group: `${parent}:${slot}`,
index: siblings.indexOf(element),
element,
handle,
type: element.dataset.blockType,
}),
);
}
};
refresh();
return {
refresh,
destroy() {
cleanups.forEach((cleanup) => cleanup());
manager?.destroy();
live.remove();
},
};
}