Avoid repeated canvas scans during drag registration
This commit is contained in:
1 parent
d41eda7050
commit
c023a98052
3 files changed
+49
-20
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/block-editor",
|
"name": "@wrnexus/block-editor",
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Hydrated accessible editor runtime for WRNexus block documents.",
|
"description": "Hydrated accessible editor runtime for WRNexus block documents.",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -119,38 +119,35 @@ export function mountBlockEditor(root: HTMLElement, options: MountEditorOptions)
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const elements = [...root.querySelectorAll<HTMLElement>(
|
||||||
|
"[data-block-id][data-block-parent][data-block-slot]",
|
||||||
|
)];
|
||||||
|
const groupSizes = new Map<string, number>();
|
||||||
|
const registrations = elements.map(element => {
|
||||||
|
const parentId = element.dataset.blockParent!;
|
||||||
|
const slot = element.dataset.blockSlot!;
|
||||||
|
const group = JSON.stringify([parentId, slot]);
|
||||||
|
const index = groupSizes.get(group) ?? 0;
|
||||||
|
groupSizes.set(group, index + 1);
|
||||||
|
groups.set(group, { parentId, slot });
|
||||||
|
return { element, group, index };
|
||||||
|
});
|
||||||
for (const element of root.querySelectorAll<HTMLElement>("[data-block-slot-target][data-block-parent]")) {
|
for (const element of root.querySelectorAll<HTMLElement>("[data-block-slot-target][data-block-parent]")) {
|
||||||
const parentId = element.dataset.blockParent!;
|
const parentId = element.dataset.blockParent!;
|
||||||
const slot = element.dataset.blockSlotTarget!;
|
const slot = element.dataset.blockSlotTarget!;
|
||||||
const hasChildren = [...root.querySelectorAll<HTMLElement>("[data-block-id][data-block-parent][data-block-slot]")]
|
|
||||||
.some(child => child.dataset.blockParent === parentId && child.dataset.blockSlot === slot);
|
|
||||||
if (hasChildren) continue;
|
|
||||||
const group = JSON.stringify([parentId, slot]);
|
const group = JSON.stringify([parentId, slot]);
|
||||||
|
if (groupSizes.has(group)) continue;
|
||||||
groups.set(group, { parentId, slot });
|
groups.set(group, { parentId, slot });
|
||||||
cleanups.push(manager.registerDropZone({ id: `wrn-slot:${group}`, group, index: 0, element }));
|
cleanups.push(manager.registerDropZone({ id: `wrn-slot:${group}`, group, index: 0, element }));
|
||||||
}
|
}
|
||||||
for (const element of root.querySelectorAll<HTMLElement>(
|
for (const { element, group, index } of registrations) {
|
||||||
"[data-block-id][data-block-parent][data-block-slot]",
|
|
||||||
)) {
|
|
||||||
const id = element.dataset.blockId!;
|
const id = element.dataset.blockId!;
|
||||||
const parent = element.dataset.blockParent!;
|
|
||||||
const slot = element.dataset.blockSlot!;
|
|
||||||
const group = JSON.stringify([parent, slot]);
|
|
||||||
groups.set(group, { parentId: parent, slot });
|
|
||||||
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;
|
const handle = element.querySelector<HTMLElement>("[data-block-handle]") ?? undefined;
|
||||||
cleanups.push(
|
cleanups.push(
|
||||||
manager.registerSortable({
|
manager.registerSortable({
|
||||||
id,
|
id,
|
||||||
group,
|
group,
|
||||||
index: siblings.indexOf(element),
|
index,
|
||||||
element,
|
element,
|
||||||
handle,
|
handle,
|
||||||
type: element.dataset.blockType,
|
type: element.dataset.blockType,
|
||||||
|
|||||||
@@ -74,6 +74,38 @@ function fixture(onMove: MountEditorOptions["onMove"] = () => false) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("large canvas registration scans blocks once and preserves interleaved slot order", () => {
|
||||||
|
const f = fixture();
|
||||||
|
f.root.replaceChildren();
|
||||||
|
for (let i = 0; i < 300; i++) {
|
||||||
|
const node = f.document.createElement("div");
|
||||||
|
node.dataset.blockId = String(i);
|
||||||
|
node.dataset.blockParent = `parent:${i % 3}`;
|
||||||
|
node.dataset.blockSlot = "content";
|
||||||
|
f.root.append(node);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const slot = f.document.createElement("div");
|
||||||
|
slot.dataset.blockParent = `parent:${i}`;
|
||||||
|
slot.dataset.blockSlotTarget = "content";
|
||||||
|
f.root.append(slot);
|
||||||
|
}
|
||||||
|
const query = f.root.querySelectorAll.bind(f.root);
|
||||||
|
let scans = 0;
|
||||||
|
f.root.querySelectorAll = ((selector: string) => {
|
||||||
|
if (selector === "[data-block-id][data-block-parent][data-block-slot]") scans++;
|
||||||
|
return query(selector);
|
||||||
|
}) as typeof f.root.querySelectorAll;
|
||||||
|
f.editor.refresh();
|
||||||
|
const backend = f.backends.at(-1)!;
|
||||||
|
expect(scans).toBe(1);
|
||||||
|
expect(backend.registrations.map(({ id, group, index }) => ({ id, group, index }))).toEqual(
|
||||||
|
Array.from({ length: 300 }, (_, i) => ({ id: String(i), group: JSON.stringify([`parent:${i % 3}`, "content"]), index: Math.floor(i / 3) })),
|
||||||
|
);
|
||||||
|
expect(backend.zones.map(zone => zone.group)).toEqual([JSON.stringify(["parent:3", "content"])]);
|
||||||
|
f.editor.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
test("cancelled optimistic moves restore original DOM nodes across containers", async () => {
|
test("cancelled optimistic moves restore original DOM nodes across containers", async () => {
|
||||||
const f = fixture();
|
const f = fixture();
|
||||||
const a = f.root.querySelector('[data-block-id="a"]')!;
|
const a = f.root.querySelector('[data-block-id="a"]')!;
|
||||||
|
|||||||
Reference in new issue
Block a user