@hirokisakabe/pom-editor 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/AstTree.d.ts.map +1 -1
- package/dist/AstTree.js +107 -73
- package/dist/ast.d.ts +3 -1
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js +97 -22
- package/package.json +4 -6
package/README.md
CHANGED
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
15
|
-
- **Drag-and-Drop
|
|
16
|
-
- **
|
|
15
|
+
- **Drag-and-Drop Structure Editing** — Reorder siblings, move nodes between `VStack` / `HStack` / `Layer` containers, pull container children up to the root, or nest a container inside another — all by dragging in the AST tree.
|
|
16
|
+
- **Distinct "between" vs "inside" Drop Targets** — Dropping on the gap before/after a row inserts as a sibling; dropping on a container body itself nests the node inside.
|
|
17
|
+
- **XML In / XML Out** — Accepts a pom XML string via `xml` and returns the updated XML via `onChange` after each edit, so it drops into any editor / preview layout.
|
|
17
18
|
- **AST-Aware Tree View** — Renders the parsed pom AST as a labeled tree so the structure of slides and nested containers is visible at a glance.
|
|
18
|
-
- **Powered by `@dnd-kit`** — Built on `@dnd-kit/core`
|
|
19
|
+
- **Powered by `@dnd-kit/core`** — Built on `@dnd-kit/core`'s `PointerSensor` for pointer-driven drag interactions.
|
|
19
20
|
|
|
20
21
|
## Installation
|
|
21
22
|
|
|
@@ -67,7 +68,12 @@ function App() {
|
|
|
67
68
|
| `xml` | `string` | pom XML string (one or more `<Slide>` elements) |
|
|
68
69
|
| `onChange` | `(xml: string) => void` | Called with updated XML after each drag-and-drop reorder |
|
|
69
70
|
|
|
70
|
-
Renders a tree of nodes from the parsed XML.
|
|
71
|
+
Renders a tree of nodes from the parsed XML. Each row supports two drop targets:
|
|
72
|
+
|
|
73
|
+
- A thin gap between rows — drop here to insert as a **sibling** at that position (works across parents, so a node can be moved to any container or pulled up to the root).
|
|
74
|
+
- The container row body itself — drop here to nest the dragged node as the **last child of that container** (`VStack` / `HStack` / `Layer` only). Drops on non-container bodies are ignored.
|
|
75
|
+
|
|
76
|
+
Top-level slides can be reordered via the root-level gaps. Cycle-forming drops (e.g. moving a container into its own descendant) are silently rejected.
|
|
71
77
|
|
|
72
78
|
## License
|
|
73
79
|
|
package/dist/AstTree.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AstTree.d.ts","sourceRoot":"","sources":["../src/AstTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8C,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AstTree.d.ts","sourceRoot":"","sources":["../src/AstTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8C,MAAM,OAAO,CAAC;AAYnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAmN3D,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,OAAO,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACtC;AAED,wBAAgB,OAAO,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,YAAY,qBAwFtD"}
|
package/dist/AstTree.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState } from "react";
|
|
3
|
-
import { DndContext, PointerSensor, useSensor, useSensors,
|
|
4
|
-
import {
|
|
5
|
-
import { CSS } from "@dnd-kit/utilities";
|
|
6
|
-
import { applyReorder, rebuildNodes } from "./ast.js";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { createContext, useContext, useState } from "react";
|
|
3
|
+
import { DndContext, PointerSensor, useDraggable, useDroppable, useSensor, useSensors, pointerWithin, } from "@dnd-kit/core";
|
|
4
|
+
import { applyMoveInside, applyMoveToGap, isContainerType, rebuildNodes, } from "./ast.js";
|
|
7
5
|
const NODE_LABELS = {
|
|
8
6
|
text: "Text",
|
|
9
7
|
image: "Image",
|
|
@@ -45,25 +43,64 @@ function nodeLabel(node) {
|
|
|
45
43
|
}
|
|
46
44
|
return base;
|
|
47
45
|
}
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
const OverIdContext = createContext(null);
|
|
47
|
+
const ActiveIdContext = createContext(null);
|
|
48
|
+
const GAP_PREFIX = "gap:";
|
|
49
|
+
const INSIDE_PREFIX = "inside:";
|
|
50
|
+
function gapId(parentId, index) {
|
|
51
|
+
return `${GAP_PREFIX}${parentId}:${index}`;
|
|
52
|
+
}
|
|
53
|
+
function insideId(nodeId) {
|
|
54
|
+
return `${INSIDE_PREFIX}${nodeId}`;
|
|
55
|
+
}
|
|
56
|
+
function parseGapId(id) {
|
|
57
|
+
if (!id.startsWith(GAP_PREFIX))
|
|
58
|
+
return null;
|
|
59
|
+
const rest = id.slice(GAP_PREFIX.length);
|
|
60
|
+
const sep = rest.lastIndexOf(":");
|
|
61
|
+
if (sep === -1)
|
|
62
|
+
return null;
|
|
63
|
+
const parentId = rest.slice(0, sep);
|
|
64
|
+
const index = Number.parseInt(rest.slice(sep + 1), 10);
|
|
65
|
+
if (Number.isNaN(index))
|
|
66
|
+
return null;
|
|
67
|
+
return { parentId, index };
|
|
68
|
+
}
|
|
69
|
+
function parseInsideId(id) {
|
|
70
|
+
if (!id.startsWith(INSIDE_PREFIX))
|
|
71
|
+
return null;
|
|
72
|
+
return id.slice(INSIDE_PREFIX.length);
|
|
73
|
+
}
|
|
74
|
+
function GapStrip({ parentId, index, depth }) {
|
|
75
|
+
const id = gapId(parentId, index);
|
|
76
|
+
const { setNodeRef } = useDroppable({ id });
|
|
77
|
+
const overId = useContext(OverIdContext);
|
|
78
|
+
const activeId = useContext(ActiveIdContext);
|
|
79
|
+
const isOver = overId === id;
|
|
80
|
+
const isDragging = activeId !== null;
|
|
81
|
+
return (_jsx("div", { ref: setNodeRef, "data-testid": id, style: {
|
|
82
|
+
height: isOver ? "10px" : isDragging ? "8px" : "2px",
|
|
83
|
+
marginLeft: `${depth * 16}px`,
|
|
84
|
+
backgroundColor: isOver ? "#3b82f6" : "transparent",
|
|
85
|
+
borderRadius: "2px",
|
|
86
|
+
} }));
|
|
87
|
+
}
|
|
88
|
+
function Row({ astNode, depth }) {
|
|
89
|
+
const isContainer = isContainerType(astNode.node.type);
|
|
90
|
+
const overId = useContext(OverIdContext);
|
|
91
|
+
const activeId = useContext(ActiveIdContext);
|
|
92
|
+
const drag = useDraggable({ id: astNode.id });
|
|
93
|
+
const isDragging = activeId === astNode.id;
|
|
94
|
+
const inside = useDroppable({
|
|
95
|
+
id: insideId(astNode.id),
|
|
96
|
+
disabled: !isContainer || isDragging,
|
|
55
97
|
});
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
opacity: isDragging ? 0.4 : 1,
|
|
98
|
+
const setBodyRef = (el) => {
|
|
99
|
+
inside.setNodeRef(el);
|
|
100
|
+
drag.setNodeRef(el);
|
|
60
101
|
};
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
: isDragging
|
|
64
|
-
? "grabbing"
|
|
65
|
-
: "grab";
|
|
66
|
-
return (_jsxs("div", { ref: setNodeRef, style: style, children: [_jsxs("div", { style: {
|
|
102
|
+
const isInsideOver = isContainer && overId === insideId(astNode.id);
|
|
103
|
+
return (_jsxs("div", { children: [_jsxs("div", { ref: setBodyRef, style: {
|
|
67
104
|
display: "flex",
|
|
68
105
|
alignItems: "center",
|
|
69
106
|
gap: "6px",
|
|
@@ -72,12 +109,13 @@ function SortableItem({ astNode, depth, onChange, ast }) {
|
|
|
72
109
|
paddingBottom: "3px",
|
|
73
110
|
borderRadius: "4px",
|
|
74
111
|
userSelect: "none",
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
112
|
+
opacity: isDragging ? 0.4 : 1,
|
|
113
|
+
backgroundColor: isInsideOver ? "#dbeafe" : undefined,
|
|
114
|
+
outline: isInsideOver ? "1px solid #2563eb" : undefined,
|
|
115
|
+
transition: "background-color 50ms",
|
|
116
|
+
}, children: [_jsx("span", { ...drag.listeners, ...drag.attributes, style: {
|
|
117
|
+
cursor: isDragging ? "grabbing" : "grab",
|
|
118
|
+
color: "#9ca3af",
|
|
81
119
|
fontSize: "12px",
|
|
82
120
|
lineHeight: 1,
|
|
83
121
|
flexShrink: 0,
|
|
@@ -88,63 +126,59 @@ function SortableItem({ astNode, depth, onChange, ast }) {
|
|
|
88
126
|
whiteSpace: "nowrap",
|
|
89
127
|
overflow: "hidden",
|
|
90
128
|
textOverflow: "ellipsis",
|
|
91
|
-
}, children: nodeLabel(astNode.node) })] }), astNode.children &&
|
|
129
|
+
}, children: nodeLabel(astNode.node) })] }), astNode.children && (_jsx(ChildList, { parentId: astNode.id, nodes: astNode.children, depth: depth + 1 }))] }));
|
|
92
130
|
}
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
return (_jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children: children.map((child) => (_jsx(SortableItem, { astNode: child, depth: depth, onChange: onChange, ast: ast }, child.id))) }));
|
|
96
|
-
}
|
|
97
|
-
function getParentId(data) {
|
|
98
|
-
if (typeof data === "object" && data !== null && "parentId" in data) {
|
|
99
|
-
const value = data.parentId;
|
|
100
|
-
return typeof value === "string" ? value : null;
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
131
|
+
function ChildList({ parentId, nodes, depth }) {
|
|
132
|
+
return (_jsxs(_Fragment, { children: [_jsx(GapStrip, { parentId: parentId, index: 0, depth: depth }), nodes.map((child, i) => (_jsxs(React.Fragment, { children: [_jsx(Row, { astNode: child, depth: depth }), _jsx(GapStrip, { parentId: parentId, index: i + 1, depth: depth })] }, child.id)))] }));
|
|
103
133
|
}
|
|
104
134
|
export function AstTree({ ast, onChange }) {
|
|
105
135
|
const sensors = useSensors(useSensor(PointerSensor, {
|
|
106
136
|
activationConstraint: { distance: 4 },
|
|
107
137
|
}));
|
|
108
|
-
const [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
const [overId, setOverId] = useState(null);
|
|
139
|
+
const [activeId, setActiveId] = useState(null);
|
|
140
|
+
function onDragStart(event) {
|
|
141
|
+
setActiveId(String(event.active.id));
|
|
142
|
+
}
|
|
143
|
+
function onDragOver({ over }) {
|
|
144
|
+
setOverId(over ? String(over.id) : null);
|
|
145
|
+
}
|
|
146
|
+
function applyDrop(activeId, overId) {
|
|
147
|
+
const gap = parseGapId(overId);
|
|
148
|
+
if (gap) {
|
|
149
|
+
const newAst = applyMoveToGap(ast, activeId, gap.parentId, gap.index);
|
|
150
|
+
if (newAst !== ast)
|
|
151
|
+
onChange(rebuildNodes(newAst));
|
|
112
152
|
return;
|
|
113
153
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
else {
|
|
120
|
-
setInvalidOverId(null);
|
|
154
|
+
const insideTarget = parseInsideId(overId);
|
|
155
|
+
if (insideTarget !== null) {
|
|
156
|
+
const newAst = applyMoveInside(ast, activeId, insideTarget);
|
|
157
|
+
if (newAst !== ast)
|
|
158
|
+
onChange(rebuildNodes(newAst));
|
|
121
159
|
}
|
|
122
160
|
}
|
|
123
161
|
function onDragEnd({ active, over }) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const activeParentId = getParentId(active.data.current);
|
|
128
|
-
const overParentId = getParentId(over.data.current);
|
|
129
|
-
if (activeParentId === null || activeParentId !== overParentId)
|
|
162
|
+
setOverId(null);
|
|
163
|
+
setActiveId(null);
|
|
164
|
+
if (!over)
|
|
130
165
|
return;
|
|
131
|
-
|
|
132
|
-
onChange(rebuildNodes(newAst));
|
|
166
|
+
applyDrop(String(active.id), String(over.id));
|
|
133
167
|
}
|
|
134
168
|
function onDragCancel() {
|
|
135
|
-
|
|
169
|
+
setOverId(null);
|
|
170
|
+
setActiveId(null);
|
|
136
171
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}, children: ["Slide ", i + 1] }), _jsx(SortableItem, { astNode: astNode, depth: 0, onChange: onChange, ast: ast })] }, astNode.id))) }) }) }) }));
|
|
172
|
+
return (_jsx("div", { children: _jsx(DndContext, { sensors: sensors, collisionDetection: pointerWithin, onDragStart: onDragStart, onDragOver: onDragOver, onDragEnd: onDragEnd, onDragCancel: onDragCancel, children: _jsx(ActiveIdContext.Provider, { value: activeId, children: _jsxs(OverIdContext.Provider, { value: overId, children: [_jsx(GapStrip, { parentId: "root", index: 0, depth: 0 }), ast.map((astNode, i) => (_jsxs(React.Fragment, { children: [i > 0 && (_jsx("div", { style: {
|
|
173
|
+
height: "1px",
|
|
174
|
+
backgroundColor: "#e5e7eb",
|
|
175
|
+
margin: "8px 0",
|
|
176
|
+
} })), _jsxs("div", { style: {
|
|
177
|
+
fontSize: "11px",
|
|
178
|
+
color: "#6b7280",
|
|
179
|
+
padding: "2px 0 4px 0",
|
|
180
|
+
fontWeight: 600,
|
|
181
|
+
letterSpacing: "0.05em",
|
|
182
|
+
textTransform: "uppercase",
|
|
183
|
+
}, children: ["Slide ", i + 1] }), _jsx(Row, { astNode: astNode, depth: 0 }), _jsx(GapStrip, { parentId: "root", index: i + 1, depth: 0 })] }, astNode.id)))] }) }) }) }));
|
|
150
184
|
}
|
package/dist/ast.d.ts
CHANGED
|
@@ -5,9 +5,11 @@ export interface AstNode {
|
|
|
5
5
|
parentId: string;
|
|
6
6
|
children?: AstNode[];
|
|
7
7
|
}
|
|
8
|
+
export declare function isContainerType(type: string): boolean;
|
|
8
9
|
export declare function buildAst(nodes: POMNode[], parentId: string, counter: {
|
|
9
10
|
value: number;
|
|
10
11
|
}): AstNode[];
|
|
11
12
|
export declare function rebuildNodes(astNodes: AstNode[]): POMNode[];
|
|
12
|
-
export declare function
|
|
13
|
+
export declare function applyMoveToGap(ast: AstNode[], activeId: string, newParentId: string, newIndex: number): AstNode[];
|
|
14
|
+
export declare function applyMoveInside(ast: AstNode[], activeId: string, containerId: string): AstNode[];
|
|
13
15
|
//# sourceMappingURL=ast.d.ts.map
|
package/dist/ast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAE3D,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,EAAE,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzB,OAAO,EAAE,CAeX;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAO3D;AAmFD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EAAE,EACd,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,EAAE,CA+BX;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,EAAE,EACd,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,EAAE,CAqBX"}
|
package/dist/ast.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { arrayMove } from "@dnd-kit/sortable";
|
|
2
1
|
const CONTAINER_TYPES = new Set(["vstack", "hstack", "layer"]);
|
|
2
|
+
export function isContainerType(type) {
|
|
3
|
+
return CONTAINER_TYPES.has(type);
|
|
4
|
+
}
|
|
3
5
|
export function buildAst(nodes, parentId, counter) {
|
|
4
6
|
return nodes.map((node) => {
|
|
5
7
|
const id = String(counter.value++);
|
|
@@ -35,37 +37,110 @@ function findNode(astNodes, id) {
|
|
|
35
37
|
}
|
|
36
38
|
return null;
|
|
37
39
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
function isDescendantOrSelf(node, id) {
|
|
41
|
+
if (node.id === id)
|
|
42
|
+
return true;
|
|
43
|
+
if (!node.children)
|
|
44
|
+
return false;
|
|
45
|
+
return node.children.some((c) => isDescendantOrSelf(c, id));
|
|
46
|
+
}
|
|
47
|
+
function removeById(astNodes, id) {
|
|
48
|
+
for (let i = 0; i < astNodes.length; i++) {
|
|
49
|
+
const current = astNodes[i];
|
|
50
|
+
if (current.id === id) {
|
|
51
|
+
return {
|
|
52
|
+
newAst: [...astNodes.slice(0, i), ...astNodes.slice(i + 1)],
|
|
53
|
+
removed: current,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (current.children) {
|
|
57
|
+
const result = removeById(current.children, id);
|
|
58
|
+
if (result) {
|
|
59
|
+
return {
|
|
60
|
+
newAst: [
|
|
61
|
+
...astNodes.slice(0, i),
|
|
62
|
+
{ ...current, children: result.newAst },
|
|
63
|
+
...astNodes.slice(i + 1),
|
|
64
|
+
],
|
|
65
|
+
removed: result.removed,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function insertAt(astNodes, parentId, index, insertion) {
|
|
73
|
+
if (parentId === "root") {
|
|
74
|
+
const node = { ...insertion, parentId: "root" };
|
|
75
|
+
return [...astNodes.slice(0, index), node, ...astNodes.slice(index)];
|
|
76
|
+
}
|
|
77
|
+
return astNodes.map((n) => {
|
|
78
|
+
if (n.id === parentId) {
|
|
79
|
+
const children = n.children ?? [];
|
|
80
|
+
const node = { ...insertion, parentId };
|
|
41
81
|
return {
|
|
42
|
-
...
|
|
43
|
-
children:
|
|
82
|
+
...n,
|
|
83
|
+
children: [...children.slice(0, index), node, ...children.slice(index)],
|
|
44
84
|
};
|
|
45
85
|
}
|
|
46
|
-
if (
|
|
86
|
+
if (n.children) {
|
|
47
87
|
return {
|
|
48
|
-
...
|
|
49
|
-
children:
|
|
88
|
+
...n,
|
|
89
|
+
children: insertAt(n.children, parentId, index, insertion),
|
|
50
90
|
};
|
|
51
91
|
}
|
|
52
|
-
return
|
|
92
|
+
return n;
|
|
53
93
|
});
|
|
54
94
|
}
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
95
|
+
function siblingsOf(ast, parentId) {
|
|
96
|
+
if (parentId === "root")
|
|
97
|
+
return ast;
|
|
98
|
+
const parent = findNode(ast, parentId);
|
|
99
|
+
return parent?.children ?? null;
|
|
100
|
+
}
|
|
101
|
+
export function applyMoveToGap(ast, activeId, newParentId, newIndex) {
|
|
102
|
+
const active = findNode(ast, activeId);
|
|
103
|
+
if (!active)
|
|
104
|
+
return ast;
|
|
105
|
+
if (newParentId !== "root") {
|
|
106
|
+
const newParent = findNode(ast, newParentId);
|
|
107
|
+
if (!newParent)
|
|
108
|
+
return ast;
|
|
109
|
+
if (!isContainerType(newParent.node.type))
|
|
60
110
|
return ast;
|
|
61
|
-
|
|
111
|
+
if (isDescendantOrSelf(active, newParentId))
|
|
112
|
+
return ast;
|
|
113
|
+
}
|
|
114
|
+
let adjustedIndex = newIndex;
|
|
115
|
+
if (active.parentId === newParentId) {
|
|
116
|
+
const siblings = siblingsOf(ast, newParentId);
|
|
117
|
+
if (siblings) {
|
|
118
|
+
const activeIndex = siblings.findIndex((c) => c.id === activeId);
|
|
119
|
+
if (activeIndex !== -1 && activeIndex < newIndex) {
|
|
120
|
+
adjustedIndex = newIndex - 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
62
123
|
}
|
|
63
|
-
const
|
|
64
|
-
if (!
|
|
124
|
+
const removeResult = removeById(ast, activeId);
|
|
125
|
+
if (!removeResult)
|
|
126
|
+
return ast;
|
|
127
|
+
return insertAt(removeResult.newAst, newParentId, adjustedIndex, removeResult.removed);
|
|
128
|
+
}
|
|
129
|
+
export function applyMoveInside(ast, activeId, containerId) {
|
|
130
|
+
const active = findNode(ast, activeId);
|
|
131
|
+
if (!active)
|
|
132
|
+
return ast;
|
|
133
|
+
if (isDescendantOrSelf(active, containerId))
|
|
134
|
+
return ast;
|
|
135
|
+
const container = findNode(ast, containerId);
|
|
136
|
+
if (!container)
|
|
137
|
+
return ast;
|
|
138
|
+
if (!isContainerType(container.node.type))
|
|
65
139
|
return ast;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
if (oldIndex === -1 || newIndex === -1)
|
|
140
|
+
const removeResult = removeById(ast, activeId);
|
|
141
|
+
if (!removeResult)
|
|
69
142
|
return ast;
|
|
70
|
-
|
|
143
|
+
const containerAfterRemove = findNode(removeResult.newAst, containerId);
|
|
144
|
+
const insertIndex = containerAfterRemove?.children?.length ?? 0;
|
|
145
|
+
return insertAt(removeResult.newAst, containerId, insertIndex, removeResult.removed);
|
|
71
146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hirokisakabe/pom-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Visual AST editor component for pom — drag-and-drop slide structure editing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,9 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@dnd-kit/core": "^6.3.1",
|
|
38
|
-
"@
|
|
39
|
-
"@dnd-kit/utilities": "^3.2.2",
|
|
40
|
-
"@hirokisakabe/pom": "^8.6.0"
|
|
38
|
+
"@hirokisakabe/pom": "^8.8.0"
|
|
41
39
|
},
|
|
42
40
|
"devDependencies": {
|
|
43
41
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -45,10 +43,10 @@
|
|
|
45
43
|
"@types/react": "^19",
|
|
46
44
|
"@types/react-dom": "^19",
|
|
47
45
|
"@vitejs/plugin-react": "^6.0.2",
|
|
48
|
-
"jsdom": "^
|
|
46
|
+
"jsdom": "^29.1.1",
|
|
49
47
|
"react": "^19",
|
|
50
48
|
"react-dom": "^19",
|
|
51
|
-
"vitest": "^4.1.
|
|
49
|
+
"vitest": "^4.1.9"
|
|
52
50
|
},
|
|
53
51
|
"publishConfig": {
|
|
54
52
|
"access": "public"
|