@hirokisakabe/pom-editor 0.2.5 → 0.2.6
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 +24 -10
- package/dist/AstTree.d.ts.map +1 -1
- package/dist/AstTree.js +55 -18
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">pom-editor</h1>
|
|
2
|
+
<p align="center">
|
|
3
|
+
Visual AST editor for <a href="https://www.npmjs.com/package/@hirokisakabe/pom">pom</a> — drag-and-drop reordering of slide node trees.
|
|
4
|
+
</p>
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/@hirokisakabe/pom-editor"><img src="https://img.shields.io/npm/v/@hirokisakabe/pom-editor.svg" alt="npm version"></a>
|
|
8
|
+
<a href="https://github.com/hirokisakabe/pom/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@hirokisakabe/pom-editor.svg" alt="License"></a>
|
|
9
|
+
</p>
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Drag-and-Drop Reordering** — Sort sibling nodes within `VStack` / `HStack` / `Layer` containers, plus top-level slides, by dragging them in the AST tree.
|
|
16
|
+
- **XML In / XML Out** — Accepts a pom XML string via `xml` and returns the updated XML via `onChange` after each reorder, so it drops into any editor / preview layout.
|
|
17
|
+
- **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` + `@dnd-kit/sortable` for accessible, keyboard-friendly drag interactions.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
> Requires React 18+
|
|
6
23
|
|
|
7
24
|
```bash
|
|
8
|
-
npm install @hirokisakabe/pom-editor
|
|
25
|
+
npm install @hirokisakabe/pom-editor react
|
|
9
26
|
```
|
|
10
27
|
|
|
11
|
-
|
|
28
|
+
`@hirokisakabe/pom` is pulled in automatically as a regular dependency — no separate install needed.
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
12
31
|
|
|
13
32
|
```tsx
|
|
14
33
|
import { PomAstEditor } from "@hirokisakabe/pom-editor";
|
|
@@ -50,11 +69,6 @@ function App() {
|
|
|
50
69
|
|
|
51
70
|
Renders a tree of nodes from the parsed XML. Nodes within the same parent container (`VStack`, `HStack`, `Layer`) can be reordered by dragging. Top-level slides can also be reordered.
|
|
52
71
|
|
|
53
|
-
## Requirements
|
|
54
|
-
|
|
55
|
-
- React 18 or later
|
|
56
|
-
- `@hirokisakabe/pom` (peer dependency resolved automatically as a workspace dependency; install separately in non-monorepo setups)
|
|
57
|
-
|
|
58
72
|
## License
|
|
59
73
|
|
|
60
74
|
MIT
|
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,
|
|
1
|
+
{"version":3,"file":"AstTree.d.ts","sourceRoot":"","sources":["../src/AstTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8C,MAAM,OAAO,CAAC;AAgBnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAuK3D,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,OAAO,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACtC;AAUD,wBAAgB,OAAO,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,YAAY,qBA+FtD"}
|
package/dist/AstTree.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useState } from "react";
|
|
2
3
|
import { DndContext, PointerSensor, useSensor, useSensors, closestCenter, } from "@dnd-kit/core";
|
|
3
4
|
import { SortableContext, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable";
|
|
4
5
|
import { CSS } from "@dnd-kit/utilities";
|
|
@@ -44,7 +45,10 @@ function nodeLabel(node) {
|
|
|
44
45
|
}
|
|
45
46
|
return base;
|
|
46
47
|
}
|
|
48
|
+
const InvalidOverContext = createContext(null);
|
|
47
49
|
function SortableItem({ astNode, depth, onChange, ast }) {
|
|
50
|
+
const invalidOverId = useContext(InvalidOverContext);
|
|
51
|
+
const isInvalidOver = invalidOverId === astNode.id;
|
|
48
52
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({
|
|
49
53
|
id: astNode.id,
|
|
50
54
|
data: { parentId: astNode.parentId },
|
|
@@ -54,6 +58,11 @@ function SortableItem({ astNode, depth, onChange, ast }) {
|
|
|
54
58
|
transition,
|
|
55
59
|
opacity: isDragging ? 0.4 : 1,
|
|
56
60
|
};
|
|
61
|
+
const handleCursor = isInvalidOver
|
|
62
|
+
? "not-allowed"
|
|
63
|
+
: isDragging
|
|
64
|
+
? "grabbing"
|
|
65
|
+
: "grab";
|
|
57
66
|
return (_jsxs("div", { ref: setNodeRef, style: style, children: [_jsxs("div", { style: {
|
|
58
67
|
display: "flex",
|
|
59
68
|
alignItems: "center",
|
|
@@ -63,9 +72,12 @@ function SortableItem({ astNode, depth, onChange, ast }) {
|
|
|
63
72
|
paddingBottom: "3px",
|
|
64
73
|
borderRadius: "4px",
|
|
65
74
|
userSelect: "none",
|
|
75
|
+
backgroundColor: isInvalidOver ? "#fee2e2" : undefined,
|
|
76
|
+
outline: isInvalidOver ? "1px solid #dc2626" : undefined,
|
|
77
|
+
cursor: isInvalidOver ? "not-allowed" : undefined,
|
|
66
78
|
}, children: [_jsx("span", { ...listeners, ...attributes, style: {
|
|
67
|
-
cursor:
|
|
68
|
-
color: "#9ca3af",
|
|
79
|
+
cursor: handleCursor,
|
|
80
|
+
color: isInvalidOver ? "#dc2626" : "#9ca3af",
|
|
69
81
|
fontSize: "12px",
|
|
70
82
|
lineHeight: 1,
|
|
71
83
|
flexShrink: 0,
|
|
@@ -82,32 +94,57 @@ function SortableChildrenList({ children, depth, onChange, ast, }) {
|
|
|
82
94
|
const ids = children.map((c) => c.id);
|
|
83
95
|
return (_jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children: children.map((child) => (_jsx(SortableItem, { astNode: child, depth: depth, onChange: onChange, ast: ast }, child.id))) }));
|
|
84
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;
|
|
103
|
+
}
|
|
85
104
|
export function AstTree({ ast, onChange }) {
|
|
86
105
|
const sensors = useSensors(useSensor(PointerSensor, {
|
|
87
106
|
activationConstraint: { distance: 4 },
|
|
88
107
|
}));
|
|
108
|
+
const [invalidOverId, setInvalidOverId] = useState(null);
|
|
109
|
+
function onDragOver({ active, over }) {
|
|
110
|
+
if (!over || active.id === over.id) {
|
|
111
|
+
setInvalidOverId(null);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const activeParentId = getParentId(active.data.current);
|
|
115
|
+
const overParentId = getParentId(over.data.current);
|
|
116
|
+
if (activeParentId !== overParentId) {
|
|
117
|
+
setInvalidOverId(over.id);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
setInvalidOverId(null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
89
123
|
function onDragEnd({ active, over }) {
|
|
124
|
+
setInvalidOverId(null);
|
|
90
125
|
if (!over || active.id === over.id)
|
|
91
126
|
return;
|
|
92
|
-
const activeParentId = active.data.current
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (activeParentId !== overParentId)
|
|
127
|
+
const activeParentId = getParentId(active.data.current);
|
|
128
|
+
const overParentId = getParentId(over.data.current);
|
|
129
|
+
if (activeParentId === null || activeParentId !== overParentId)
|
|
96
130
|
return;
|
|
97
131
|
const newAst = applyReorder(ast, active.id, over.id, activeParentId);
|
|
98
132
|
onChange(rebuildNodes(newAst));
|
|
99
133
|
}
|
|
134
|
+
function onDragCancel() {
|
|
135
|
+
setInvalidOverId(null);
|
|
136
|
+
}
|
|
100
137
|
const rootIds = ast.map((n) => n.id);
|
|
101
|
-
return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: onDragEnd, children: _jsx(SortableContext, { items: rootIds, strategy: verticalListSortingStrategy, children: ast.map((astNode, i) => (_jsxs("div", { children: [i > 0 && (_jsx("div", { style: {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
138
|
+
return (_jsx("div", { style: { cursor: invalidOverId !== null ? "not-allowed" : undefined }, children: _jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragOver: onDragOver, onDragEnd: onDragEnd, onDragCancel: onDragCancel, children: _jsx(InvalidOverContext.Provider, { value: invalidOverId, children: _jsx(SortableContext, { items: rootIds, strategy: verticalListSortingStrategy, children: ast.map((astNode, i) => (_jsxs("div", { children: [i > 0 && (_jsx("div", { style: {
|
|
139
|
+
height: "1px",
|
|
140
|
+
backgroundColor: "#e5e7eb",
|
|
141
|
+
margin: "8px 0",
|
|
142
|
+
} })), _jsxs("div", { style: {
|
|
143
|
+
fontSize: "11px",
|
|
144
|
+
color: "#6b7280",
|
|
145
|
+
padding: "2px 0 4px 0",
|
|
146
|
+
fontWeight: 600,
|
|
147
|
+
letterSpacing: "0.05em",
|
|
148
|
+
textTransform: "uppercase",
|
|
149
|
+
}, children: ["Slide ", i + 1] }), _jsx(SortableItem, { astNode: astNode, depth: 0, onChange: onChange, ast: ast })] }, astNode.id))) }) }) }) }));
|
|
113
150
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hirokisakabe/pom-editor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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",
|
|
@@ -37,20 +37,30 @@
|
|
|
37
37
|
"@dnd-kit/core": "^6.3.1",
|
|
38
38
|
"@dnd-kit/sortable": "^10.0.0",
|
|
39
39
|
"@dnd-kit/utilities": "^3.2.2",
|
|
40
|
-
"@hirokisakabe/pom": "^8.
|
|
40
|
+
"@hirokisakabe/pom": "^8.6.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@
|
|
43
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
44
|
+
"@testing-library/react": "^16.3.2",
|
|
45
|
+
"@types/react": "^19",
|
|
46
|
+
"@types/react-dom": "^19",
|
|
47
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
48
|
+
"jsdom": "^28.0.1",
|
|
49
|
+
"react": "^19",
|
|
50
|
+
"react-dom": "^19",
|
|
51
|
+
"vitest": "^4.1.8"
|
|
44
52
|
},
|
|
45
53
|
"publishConfig": {
|
|
46
54
|
"access": "public"
|
|
47
55
|
},
|
|
48
56
|
"scripts": {
|
|
49
|
-
"build": "tsc",
|
|
57
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
58
|
"fmt": "prettier --write .",
|
|
51
59
|
"fmt:check": "prettier --check .",
|
|
52
60
|
"knip": "knip",
|
|
53
61
|
"lint": "eslint",
|
|
62
|
+
"test": "vitest",
|
|
63
|
+
"test:run": "vitest run",
|
|
54
64
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
55
65
|
}
|
|
56
66
|
}
|