fix(builder): correct move and sortable behavior
This commit is contained in:
1 parent
0bd2a6860d
commit
edccf06658
5 files changed
+70
-5
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/blocks",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"type": "module",
|
||||
"description": "Pure dynamic block document engine for WRNexus builders.",
|
||||
"exports": {
|
||||
|
||||
@@ -17,6 +17,7 @@ export type BlockCommand =
|
||||
| { type: "insert"; location: BlockLocation; nodes: BlockNode[] }
|
||||
| { type: "remove"; nodeIds: string[] }
|
||||
| { type: "move"; intent: MoveIntent }
|
||||
| { type: "duplicate"; nodeIds: string[]; ids: string[] }
|
||||
| { type: "updateProps"; nodeId: string; patch: JsonObject }
|
||||
| { type: "updateStyles"; nodeId: string; patch: BlockStyles }
|
||||
| { type: "replace"; nodeId: string; node: BlockNode }
|
||||
@@ -152,9 +153,15 @@ export function applyCommand(
|
||||
},
|
||||
],
|
||||
};
|
||||
const removedBeforeDestination = command.intent.from.filter(
|
||||
(location) =>
|
||||
location.parentId === command.intent.to.parentId &&
|
||||
location.slot === command.intent.to.slot &&
|
||||
location.index < command.intent.to.index,
|
||||
).length;
|
||||
root = insert(
|
||||
remove(root, new Set(command.intent.nodeIds)),
|
||||
command.intent.to,
|
||||
{ ...command.intent.to, index: Math.max(0, command.intent.to.index - removedBeforeDestination) },
|
||||
moving as BlockNode[],
|
||||
);
|
||||
affected = command.intent.nodeIds;
|
||||
@@ -169,6 +176,29 @@ export function applyCommand(
|
||||
},
|
||||
})),
|
||||
};
|
||||
} else if (command.type === "duplicate") {
|
||||
const originals = command.nodeIds.map((id) => findBlock(root, id));
|
||||
const locations = command.nodeIds.map((id) => locationOf(root, id));
|
||||
if (originals.some((node) => !node) || locations.some((location) => !location))
|
||||
return { ok: false, errors: [{ code: "command.missing", message: "A duplicated block does not exist.", severity: "error" }] };
|
||||
const countNodes = (node: BlockNode): number =>
|
||||
1 + Object.values(node.slots).flat().reduce((total, child) => total + countNodes(child), 0);
|
||||
const requiredIds = (originals as BlockNode[]).reduce(
|
||||
(total, node) => total + countNodes(node),
|
||||
0,
|
||||
);
|
||||
if (command.ids.length !== requiredIds || new Set(command.ids).size !== command.ids.length)
|
||||
return { ok: false, errors: [{ code: "command.ids", message: "Duplicate requires one unique id for every copied node.", severity: "error" }] };
|
||||
const copies = duplicateNodes(originals as BlockNode[], (() => {
|
||||
let index = 0;
|
||||
return () => command.ids[index++]!;
|
||||
})());
|
||||
const commands = copies.map((node, index) => ({
|
||||
type: "insert" as const,
|
||||
location: { ...locations[index]!, index: locations[index]!.index + 1 },
|
||||
nodes: [node],
|
||||
}));
|
||||
return applyCommand(document, { type: "batch", commands }, kind, registry);
|
||||
} else if (command.type === "updateProps") {
|
||||
const previous = findBlock(root, command.nodeId);
|
||||
if (!previous)
|
||||
|
||||
@@ -137,4 +137,36 @@ describe("dynamic blocks engine", () => {
|
||||
expect(decoded.ok && decoded.value.roots[0]?.id).not.toBe(node.id);
|
||||
expect(duplicateNodes([node], id)[0]?.props).toEqual(node.props);
|
||||
});
|
||||
|
||||
test("moves forward within one slot without an off-by-one error", () => {
|
||||
const document = doc();
|
||||
document.root.slots.content = ["a", "b", "c"].map((nodeId) => ({
|
||||
...text.create({ id: () => nodeId }),
|
||||
props: { text: nodeId },
|
||||
}));
|
||||
const moved = applyCommand(document, {
|
||||
type: "move",
|
||||
intent: {
|
||||
nodeIds: ["a"],
|
||||
from: [{ parentId: "root", slot: "content", index: 0 }],
|
||||
to: { parentId: "root", slot: "content", index: 3 },
|
||||
},
|
||||
}, kind, registry);
|
||||
expect(moved.ok && moved.value.document.root.slots.content.map((node) => node.id)).toEqual([
|
||||
"b", "c", "a",
|
||||
]);
|
||||
});
|
||||
|
||||
test("duplicates blocks with deterministic injected ids", () => {
|
||||
const document = doc();
|
||||
document.root.slots.content = [{ ...text.create({ id: () => "a" }), props: { text: "A" } }];
|
||||
const duplicated = applyCommand(document, {
|
||||
type: "duplicate",
|
||||
nodeIds: ["a"],
|
||||
ids: ["a-copy"],
|
||||
}, kind, registry);
|
||||
expect(duplicated.ok && duplicated.value.document.root.slots.content.map((node) => node.id)).toEqual([
|
||||
"a", "a-copy",
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user