@bluprynt/forms-builder 1.0.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 +147 -0
- package/dist/index.cjs +393 -0
- package/dist/index.d.cts +64 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +394 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @bluprynt/forms-builder
|
|
2
|
+
|
|
3
|
+
Headless React component for building and editing form schemas with drag-and-drop reordering. Pairs with `@bluprynt/forms-core` to handle schema mutations via `FormDefinitionEditor`.
|
|
4
|
+
|
|
5
|
+
## Key Capabilities
|
|
6
|
+
|
|
7
|
+
- **Drag-and-drop reordering** — reorder fields and sections with depth-aware nesting via @dnd-kit, respecting the maximum nesting depth of 3 levels.
|
|
8
|
+
- **Schema CRUD** — add, update, and remove fields and sections. All mutations go through `FormDefinitionEditor` from `@bluprynt/forms-core`.
|
|
9
|
+
- **Headless architecture** — all rendering is delegated to components you provide (`container`, `section`, `field`, `addPlaceholder`), so the package has zero UI opinions.
|
|
10
|
+
- **Depth-aware nesting** — horizontal drag offset controls nesting depth. Only sections can accept nested children, enforced during drag projection.
|
|
11
|
+
- **Add placeholders** — automatic insertion points at the end of each section and at the root level for adding new items.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @bluprynt/forms-builder @bluprynt/forms-core ajv react react-dom @dnd-kit/abstract @dnd-kit/dom @dnd-kit/helpers @dnd-kit/react @dnd-kit/state
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`@bluprynt/forms-core`, `react`, `react-dom`, and `@dnd-kit/*` packages are peer dependencies and must be installed in your project.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { useState, type FC } from 'react'
|
|
25
|
+
import { FormBuilder } from '@bluprynt/forms-builder'
|
|
26
|
+
import type {
|
|
27
|
+
FieldRenderProps,
|
|
28
|
+
SectionRenderProps,
|
|
29
|
+
AddPlaceholderRenderProps,
|
|
30
|
+
NewContentItem,
|
|
31
|
+
} from '@bluprynt/forms-builder'
|
|
32
|
+
import type { FormDefinition } from '@bluprynt/forms-core'
|
|
33
|
+
|
|
34
|
+
const MyField: FC<FieldRenderProps> = ({ ref, handleRef, item, isDragging, onRemove }) => (
|
|
35
|
+
<div ref={ref} style={{ opacity: isDragging ? 0.5 : 1 }}>
|
|
36
|
+
<span ref={handleRef}>⠿</span>
|
|
37
|
+
{item.label}
|
|
38
|
+
<button onClick={onRemove}>Remove</button>
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const MySection: FC<SectionRenderProps> = ({ ref, handleRef, item, isDragging }) => (
|
|
43
|
+
<div ref={ref} style={{ opacity: isDragging ? 0.5 : 1 }}>
|
|
44
|
+
<span ref={handleRef}>⠿</span>
|
|
45
|
+
{item.title}
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const MyAddPlaceholder: FC<AddPlaceholderRenderProps> = ({ onAdd }) => (
|
|
50
|
+
<button onClick={() => onAdd({ type: 'string', label: 'New Field' })}>
|
|
51
|
+
+ Add Field
|
|
52
|
+
</button>
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const Builder: FC = () => {
|
|
56
|
+
const [definition, setDefinition] = useState<FormDefinition>(initialDefinition)
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<FormBuilder
|
|
60
|
+
definition={definition}
|
|
61
|
+
container={({ children }) => <div>{children}</div>}
|
|
62
|
+
section={MySection}
|
|
63
|
+
field={MyField}
|
|
64
|
+
addPlaceholder={MyAddPlaceholder}
|
|
65
|
+
selectedId={null}
|
|
66
|
+
onDefinitionChange={setDefinition}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Handling Selection
|
|
73
|
+
|
|
74
|
+
Track which item is selected by passing `selectedId`. Each field and section component receives `isSelected`:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const [selectedId, setSelectedId] = useState<number | null>(null)
|
|
78
|
+
|
|
79
|
+
<FormBuilder
|
|
80
|
+
definition={definition}
|
|
81
|
+
container={({ children }) => <div>{children}</div>}
|
|
82
|
+
section={(props) => (
|
|
83
|
+
<div ref={props.ref} onClick={() => setSelectedId(props.id)}
|
|
84
|
+
style={{ outline: props.isSelected ? '2px solid blue' : 'none' }}>
|
|
85
|
+
<span ref={props.handleRef}>⠿</span>
|
|
86
|
+
{props.item.title}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
field={(props) => (
|
|
90
|
+
<div ref={props.ref} onClick={() => setSelectedId(props.id)}
|
|
91
|
+
style={{ outline: props.isSelected ? '2px solid blue' : 'none' }}>
|
|
92
|
+
<span ref={props.handleRef}>⠿</span>
|
|
93
|
+
{props.item.label}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
addPlaceholder={MyAddPlaceholder}
|
|
97
|
+
selectedId={selectedId}
|
|
98
|
+
onDefinitionChange={setDefinition}
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Render Component Props
|
|
103
|
+
|
|
104
|
+
### FieldRenderProps
|
|
105
|
+
|
|
106
|
+
| Property | Type | Description |
|
|
107
|
+
|----------|------|-------------|
|
|
108
|
+
| `ref` | `(element: Element \| null) => void` | Sortable item ref — attach to outermost DOM element |
|
|
109
|
+
| `handleRef` | `(element: Element \| null) => void` | Drag handle ref — attach to the drag handle element |
|
|
110
|
+
| `id` | `number` | Field ID |
|
|
111
|
+
| `depth` | `number` | Nesting depth (0 = root level) |
|
|
112
|
+
| `index` | `number` | Position index in the flat list |
|
|
113
|
+
| `parentId` | `number \| null` | Parent section ID, or `null` for root-level |
|
|
114
|
+
| `type` | `FieldType` | Field type (`'string'`, `'number'`, etc.) |
|
|
115
|
+
| `item` | `FieldContentItem` | Full field content item from the definition |
|
|
116
|
+
| `isDragging` | `boolean` | `true` when this item is being dragged |
|
|
117
|
+
| `isSelected` | `boolean` | `true` when this item's ID matches `selectedId` |
|
|
118
|
+
| `onAdd` | `(item: NewContentItem) => number` | Add a new item to this field's parent section; returns the new ID |
|
|
119
|
+
| `onRemove` | `() => void` | Remove this field |
|
|
120
|
+
| `onChange` | `(item: ContentItem) => void` | Update this field |
|
|
121
|
+
|
|
122
|
+
### SectionRenderProps
|
|
123
|
+
|
|
124
|
+
Same shape as `FieldRenderProps` except:
|
|
125
|
+
|
|
126
|
+
| Property | Type | Description |
|
|
127
|
+
|----------|------|-------------|
|
|
128
|
+
| `item` | `SectionContentItem` | Full section content item from the definition |
|
|
129
|
+
| `onAdd` | `(item: NewContentItem) => number` | Add a new item **inside** this section; returns the new ID |
|
|
130
|
+
| `onRemove` | `() => void` | Remove this section and all descendants |
|
|
131
|
+
|
|
132
|
+
### AddPlaceholderRenderProps
|
|
133
|
+
|
|
134
|
+
| Property | Type | Description |
|
|
135
|
+
|----------|------|-------------|
|
|
136
|
+
| `depth` | `number` | Nesting depth where the placeholder appears |
|
|
137
|
+
| `parentId` | `number \| null` | Parent section ID, or `null` for root-level |
|
|
138
|
+
| `onAdd` | `(item: NewContentItem) => number` | Add a new item at this location; returns the new ID |
|
|
139
|
+
| `onChange` | `(id: number, item: ContentItem) => void` | Update an existing item by ID |
|
|
140
|
+
|
|
141
|
+
## Documentation
|
|
142
|
+
|
|
143
|
+
| Document | Description |
|
|
144
|
+
|----------|-------------|
|
|
145
|
+
| [Architecture](./docs/architecture.md) | Project structure, module dependency graph, drag-and-drop flow, tree flattening |
|
|
146
|
+
| [API Reference](./docs/api-reference.md) | Complete public API — components, hooks, types, props tables |
|
|
147
|
+
| [Development Guide](./docs/development-guide.md) | How to build, test, lint, and extend the builder |
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _dnd_kit_react = require("@dnd-kit/react");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
let _dnd_kit_react_sortable = require("@dnd-kit/react/sortable");
|
|
5
|
+
let react = require("react");
|
|
6
|
+
let _bluprynt_forms_core = require("@bluprynt/forms-core");
|
|
7
|
+
let _dnd_kit_helpers = require("@dnd-kit/helpers");
|
|
8
|
+
//#region src/add-placeholder.tsx
|
|
9
|
+
const AddPlaceholder = ({ depth, parentId, component: Component, onAdd, onChange }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
10
|
+
depth,
|
|
11
|
+
parentId,
|
|
12
|
+
onAdd,
|
|
13
|
+
onChange
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/core/types.ts
|
|
17
|
+
/**
|
|
18
|
+
* Type guard that narrows a `TreeItem` to a section (`type` is `'section'`, `item` is `SectionContentItem`).
|
|
19
|
+
*/
|
|
20
|
+
const isSection = (item) => item.type === "section";
|
|
21
|
+
/**
|
|
22
|
+
* Type guard that narrows a `TreeItem` to a field (`type` is not `'section'`, `item` is `FieldContentItem`).
|
|
23
|
+
*/
|
|
24
|
+
const isField = (item) => item.type !== "section" && item.type !== "add-placeholder";
|
|
25
|
+
/**
|
|
26
|
+
* Type guard that narrows a `TreeItem` to an "add" placeholder.
|
|
27
|
+
*/
|
|
28
|
+
const isAddPlaceholder = (item) => item.type === "add-placeholder";
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/core/constants.ts
|
|
31
|
+
const SORTABLE_CONFIG = {
|
|
32
|
+
alignment: {
|
|
33
|
+
x: "start",
|
|
34
|
+
y: "center"
|
|
35
|
+
},
|
|
36
|
+
transition: { idle: true }
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/field.tsx
|
|
40
|
+
const Field = ({ id, depth, index, parentId, type, item, isSelected, component: Component, onAdd, onRemove, onChange }) => {
|
|
41
|
+
const { ref, handleRef, isDragSource } = (0, _dnd_kit_react_sortable.useSortable)({
|
|
42
|
+
...SORTABLE_CONFIG,
|
|
43
|
+
id,
|
|
44
|
+
index,
|
|
45
|
+
data: {
|
|
46
|
+
depth,
|
|
47
|
+
parentId
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
51
|
+
ref,
|
|
52
|
+
handleRef,
|
|
53
|
+
id,
|
|
54
|
+
depth,
|
|
55
|
+
index,
|
|
56
|
+
parentId,
|
|
57
|
+
type,
|
|
58
|
+
item,
|
|
59
|
+
isDragging: isDragSource,
|
|
60
|
+
isSelected,
|
|
61
|
+
onAdd,
|
|
62
|
+
onRemove,
|
|
63
|
+
onChange
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/section.tsx
|
|
68
|
+
const Section = ({ id, depth, index, parentId, item, isSelected, component: Component, onAdd, onRemove, onChange }) => {
|
|
69
|
+
const { ref, handleRef, isDragSource } = (0, _dnd_kit_react_sortable.useSortable)({
|
|
70
|
+
...SORTABLE_CONFIG,
|
|
71
|
+
id,
|
|
72
|
+
index,
|
|
73
|
+
data: {
|
|
74
|
+
depth,
|
|
75
|
+
parentId
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
|
|
79
|
+
ref,
|
|
80
|
+
handleRef,
|
|
81
|
+
id,
|
|
82
|
+
depth,
|
|
83
|
+
index,
|
|
84
|
+
parentId,
|
|
85
|
+
item,
|
|
86
|
+
isDragging: isDragSource,
|
|
87
|
+
isSelected,
|
|
88
|
+
onAdd,
|
|
89
|
+
onRemove,
|
|
90
|
+
onChange
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/core/projection.ts
|
|
95
|
+
/**
|
|
96
|
+
* Converts a horizontal pixel offset into a depth level based on indentation width.
|
|
97
|
+
*/
|
|
98
|
+
const getDragDepth = (offset, indentationWidth) => Math.round(offset / indentationWidth);
|
|
99
|
+
/**
|
|
100
|
+
* Returns the maximum allowed nesting depth when dragging above a target item.
|
|
101
|
+
* Only sections can accept nested children.
|
|
102
|
+
*/
|
|
103
|
+
const getMaxDepth = (targetItem, previousItem) => {
|
|
104
|
+
if (!previousItem) return 0;
|
|
105
|
+
if (previousItem.type !== "section") return previousItem.depth;
|
|
106
|
+
return Math.min(targetItem.depth + 1, previousItem.depth + 1);
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Returns the minimum depth based on the next sibling item.
|
|
110
|
+
* If there is no next item, the minimum depth is 0 (root level).
|
|
111
|
+
*/
|
|
112
|
+
const getMinDepth = (nextItem) => nextItem ? nextItem.depth : 0;
|
|
113
|
+
/**
|
|
114
|
+
* Calculates the projected depth and parent for a dragged item relative to a target position.
|
|
115
|
+
* Clamps depth between min/max bounds and enforces `maxAllowedDepth`.
|
|
116
|
+
*/
|
|
117
|
+
const getProjection = (items, targetId, projectedDepth, maxAllowedDepth) => {
|
|
118
|
+
const targetItemIndex = items.findIndex(({ id }) => id === targetId);
|
|
119
|
+
const previousItem = items[targetItemIndex - 1];
|
|
120
|
+
const targetItem = items[targetItemIndex];
|
|
121
|
+
const nextItem = items[targetItemIndex + 1];
|
|
122
|
+
if (!targetItem) return {
|
|
123
|
+
depth: 0,
|
|
124
|
+
maxDepth: 0,
|
|
125
|
+
minDepth: 0,
|
|
126
|
+
parentId: null
|
|
127
|
+
};
|
|
128
|
+
const maxDepth = getMaxDepth(targetItem, previousItem);
|
|
129
|
+
const minDepth = getMinDepth(nextItem);
|
|
130
|
+
let depth = projectedDepth;
|
|
131
|
+
if (depth >= maxDepth) depth = maxDepth;
|
|
132
|
+
else if (depth < minDepth) depth = minDepth;
|
|
133
|
+
if (depth > maxAllowedDepth) depth = maxAllowedDepth;
|
|
134
|
+
const getParentId = () => {
|
|
135
|
+
if (depth === 0 || !previousItem) return null;
|
|
136
|
+
if (depth === previousItem.depth) return previousItem.parentId;
|
|
137
|
+
if (depth > previousItem.depth) {
|
|
138
|
+
if (previousItem.type !== "section") return previousItem.parentId;
|
|
139
|
+
return previousItem.id;
|
|
140
|
+
}
|
|
141
|
+
return items.slice(0, targetItemIndex).reverse().find((item) => item.depth === depth)?.parentId ?? null;
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
depth,
|
|
145
|
+
maxDepth,
|
|
146
|
+
minDepth,
|
|
147
|
+
parentId: getParentId()
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/core/tree.ts
|
|
152
|
+
let placeholderIdCounter = -1e3;
|
|
153
|
+
/**
|
|
154
|
+
* Resets the placeholder id counter. Call before each full flatten to ensure stable ids per flatten pass.
|
|
155
|
+
*/
|
|
156
|
+
const resetPlaceholderIds = () => {
|
|
157
|
+
placeholderIdCounter = -1e3;
|
|
158
|
+
};
|
|
159
|
+
const nextPlaceholderId = () => --placeholderIdCounter;
|
|
160
|
+
/**
|
|
161
|
+
* Flattens a nested content tree into a flat list of items with depth and parent info.
|
|
162
|
+
* Each item preserves its position metadata for rendering in a sortable tree.
|
|
163
|
+
* Appends an "add-placeholder" at the end of each section and at the root level.
|
|
164
|
+
*/
|
|
165
|
+
const flattenTree = (items, parentId = null, depth = 0) => {
|
|
166
|
+
if (parentId === null) resetPlaceholderIds();
|
|
167
|
+
const result = items.reduce((result, current, index) => {
|
|
168
|
+
const isSection = current.type === "section";
|
|
169
|
+
result.push({
|
|
170
|
+
id: current.id,
|
|
171
|
+
type: current.type,
|
|
172
|
+
parentId,
|
|
173
|
+
depth,
|
|
174
|
+
index,
|
|
175
|
+
item: current
|
|
176
|
+
});
|
|
177
|
+
if (isSection) {
|
|
178
|
+
const children = current.content;
|
|
179
|
+
result.push(...flattenTree(children, current.id, depth + 1));
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}, []);
|
|
183
|
+
result.push({
|
|
184
|
+
id: nextPlaceholderId(),
|
|
185
|
+
type: "add-placeholder",
|
|
186
|
+
parentId,
|
|
187
|
+
depth,
|
|
188
|
+
index: items.length,
|
|
189
|
+
item: void 0
|
|
190
|
+
});
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Collects all transitive descendant ids for a given parent from a flat item list.
|
|
195
|
+
*/
|
|
196
|
+
const getDescendants = (items, parentId) => {
|
|
197
|
+
return items.filter((item) => item.parentId === parentId).reduce((result, child) => {
|
|
198
|
+
result.add(child.id);
|
|
199
|
+
for (const descendant of getDescendants(items, child.id)) result.add(descendant);
|
|
200
|
+
return result;
|
|
201
|
+
}, /* @__PURE__ */ new Set());
|
|
202
|
+
};
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/use-form-builder.ts
|
|
205
|
+
/**
|
|
206
|
+
* Manages the form builder state: flattened tree items, drag-and-drop reordering,
|
|
207
|
+
* item editing, and removal. Uses {@link FormDefinitionEditor} for all definition mutations
|
|
208
|
+
* and notifies the consumer of structural changes via {@link onDefinitionChange}.
|
|
209
|
+
*
|
|
210
|
+
* @param definition - The current form definition to build upon.
|
|
211
|
+
* @param onDefinitionChange - Optional callback invoked with the updated `FormDefinition`
|
|
212
|
+
* whenever the tree structure, item content, or item order changes.
|
|
213
|
+
* @returns Flattened items and handlers for item/drag operations.
|
|
214
|
+
*/
|
|
215
|
+
const useFormBuilder = (definition, onDefinitionChange) => {
|
|
216
|
+
const [items, setItems] = (0, react.useState)(() => flattenTree(definition.content));
|
|
217
|
+
const initialDepth = (0, react.useRef)(0);
|
|
218
|
+
const sourceChildren = (0, react.useRef)([]);
|
|
219
|
+
const dragSourceId = (0, react.useRef)(null);
|
|
220
|
+
const definitionRef = (0, react.useRef)(definition);
|
|
221
|
+
definitionRef.current = definition;
|
|
222
|
+
const onDefinitionChangeRef = (0, react.useRef)(onDefinitionChange);
|
|
223
|
+
onDefinitionChangeRef.current = onDefinitionChange;
|
|
224
|
+
(0, react.useEffect)(() => {
|
|
225
|
+
setItems(flattenTree(definition.content));
|
|
226
|
+
}, [definition]);
|
|
227
|
+
const applyEditor = (0, react.useCallback)((editor) => {
|
|
228
|
+
const updated = editor.toJSON();
|
|
229
|
+
setItems(flattenTree(updated.content));
|
|
230
|
+
onDefinitionChangeRef.current?.(updated);
|
|
231
|
+
}, []);
|
|
232
|
+
const handleItemAdd = (0, react.useCallback)((sectionId, item) => {
|
|
233
|
+
const editor = new _bluprynt_forms_core.FormDefinitionEditor(definitionRef.current);
|
|
234
|
+
const parentId = sectionId ?? void 0;
|
|
235
|
+
const id = editor.nextId();
|
|
236
|
+
if (item.type === "section") editor.addSection({
|
|
237
|
+
...item,
|
|
238
|
+
id
|
|
239
|
+
}, parentId);
|
|
240
|
+
else editor.addField({
|
|
241
|
+
...item,
|
|
242
|
+
id
|
|
243
|
+
}, parentId);
|
|
244
|
+
applyEditor(editor);
|
|
245
|
+
return id;
|
|
246
|
+
}, [applyEditor]);
|
|
247
|
+
const handleItemChange = (0, react.useCallback)((id, newItem) => {
|
|
248
|
+
const editor = new _bluprynt_forms_core.FormDefinitionEditor(definitionRef.current);
|
|
249
|
+
if (newItem.type === "section") {
|
|
250
|
+
const { id: _, type: __, content: ___, ...updates } = newItem;
|
|
251
|
+
editor.updateSection(id, updates);
|
|
252
|
+
} else {
|
|
253
|
+
const { id: _, type: __, ...updates } = newItem;
|
|
254
|
+
editor.updateField(id, updates);
|
|
255
|
+
}
|
|
256
|
+
applyEditor(editor);
|
|
257
|
+
}, [applyEditor]);
|
|
258
|
+
const handleItemRemove = (0, react.useCallback)((itemId) => {
|
|
259
|
+
const editor = new _bluprynt_forms_core.FormDefinitionEditor(definitionRef.current);
|
|
260
|
+
editor.removeItem(itemId);
|
|
261
|
+
applyEditor(editor);
|
|
262
|
+
}, [applyEditor]);
|
|
263
|
+
const handleDragStart = (event) => {
|
|
264
|
+
const { source } = event.operation;
|
|
265
|
+
if (!source) return;
|
|
266
|
+
const item = items.find(({ id }) => id === source.id);
|
|
267
|
+
if (!item) return;
|
|
268
|
+
initialDepth.current = item.depth;
|
|
269
|
+
dragSourceId.current = source.id;
|
|
270
|
+
setItems((prev) => {
|
|
271
|
+
sourceChildren.current = [];
|
|
272
|
+
const descendants = getDescendants(prev, source.id);
|
|
273
|
+
return prev.filter((i) => {
|
|
274
|
+
if (descendants.has(i.id)) {
|
|
275
|
+
sourceChildren.current = [...sourceChildren.current, i];
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
return true;
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
const handleDragOver = (event, manager) => {
|
|
283
|
+
const { source, target } = event.operation;
|
|
284
|
+
event.preventDefault();
|
|
285
|
+
if (source && target && source.id !== target.id) setItems((prev) => {
|
|
286
|
+
const offsetLeft = manager.dragOperation.transform.x;
|
|
287
|
+
const dragDepth = getDragDepth(offsetLeft, 40);
|
|
288
|
+
const projectedDepth = initialDepth.current + dragDepth;
|
|
289
|
+
const { depth, parentId } = getProjection(prev, target.id, projectedDepth, 3);
|
|
290
|
+
return (0, _dnd_kit_helpers.move)(prev, event).map((item) => item.id === source.id ? {
|
|
291
|
+
...item,
|
|
292
|
+
depth,
|
|
293
|
+
parentId
|
|
294
|
+
} : item);
|
|
295
|
+
});
|
|
296
|
+
};
|
|
297
|
+
const handleDragMove = (event, manager) => {
|
|
298
|
+
if (event.defaultPrevented) return;
|
|
299
|
+
const { source, target } = event.operation;
|
|
300
|
+
if (!source || !target) return;
|
|
301
|
+
const offsetLeft = manager.dragOperation.transform.x;
|
|
302
|
+
const dragDepth = getDragDepth(offsetLeft, 40);
|
|
303
|
+
const projectedDepth = initialDepth.current + dragDepth;
|
|
304
|
+
const { depth, parentId } = getProjection(items, source.id, projectedDepth, 3);
|
|
305
|
+
if (source.data?.depth !== depth || source.data?.parentId !== parentId) setItems((prev) => prev.map((item) => item.id === source.id ? {
|
|
306
|
+
...item,
|
|
307
|
+
depth,
|
|
308
|
+
parentId
|
|
309
|
+
} : item));
|
|
310
|
+
};
|
|
311
|
+
const handleDragEnd = (event) => {
|
|
312
|
+
if (event.canceled) {
|
|
313
|
+
setItems(flattenTree(definitionRef.current.content));
|
|
314
|
+
dragSourceId.current = null;
|
|
315
|
+
sourceChildren.current = [];
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const draggedId = dragSourceId.current;
|
|
319
|
+
if (draggedId === null) return;
|
|
320
|
+
const draggedItem = items.find((i) => i.id === draggedId);
|
|
321
|
+
if (!draggedItem) return;
|
|
322
|
+
const targetParentId = draggedItem.parentId;
|
|
323
|
+
const draggedFlatIndex = items.findIndex((i) => i.id === draggedId);
|
|
324
|
+
let siblingIndex = 0;
|
|
325
|
+
for (let i = 0; i < draggedFlatIndex; i++) {
|
|
326
|
+
const current = items[i];
|
|
327
|
+
if (current && current.parentId === targetParentId && current.type !== "add-placeholder") siblingIndex++;
|
|
328
|
+
}
|
|
329
|
+
const editor = new _bluprynt_forms_core.FormDefinitionEditor(definitionRef.current);
|
|
330
|
+
editor.moveItem(draggedId, targetParentId ?? void 0, siblingIndex);
|
|
331
|
+
applyEditor(editor);
|
|
332
|
+
dragSourceId.current = null;
|
|
333
|
+
sourceChildren.current = [];
|
|
334
|
+
};
|
|
335
|
+
return {
|
|
336
|
+
items,
|
|
337
|
+
handleItemAdd,
|
|
338
|
+
handleItemChange,
|
|
339
|
+
handleItemRemove,
|
|
340
|
+
handleDragStart,
|
|
341
|
+
handleDragOver,
|
|
342
|
+
handleDragMove,
|
|
343
|
+
handleDragEnd
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/builder.tsx
|
|
348
|
+
const FormBuilder = ({ definition, container: Container, section: SectionComponent, field: FieldComponent, addPlaceholder: AddPlaceholderComponent, selectedId, onDefinitionChange }) => {
|
|
349
|
+
const { items, handleItemAdd, handleItemChange, handleItemRemove, handleDragStart, handleDragOver, handleDragMove, handleDragEnd } = useFormBuilder(definition, onDefinitionChange);
|
|
350
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_dnd_kit_react.DragDropProvider, {
|
|
351
|
+
onDragStart: handleDragStart,
|
|
352
|
+
onDragOver: handleDragOver,
|
|
353
|
+
onDragMove: handleDragMove,
|
|
354
|
+
onDragEnd: handleDragEnd,
|
|
355
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Container, { children: items.map((item, index) => {
|
|
356
|
+
const onAdd = (value) => handleItemAdd(item.type === "section" ? item.id : item.parentId, value);
|
|
357
|
+
const onRemove = () => handleItemRemove(item.id);
|
|
358
|
+
const onChange = (value) => handleItemChange(value.id, value);
|
|
359
|
+
return isSection(item) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
|
|
360
|
+
id: item.id,
|
|
361
|
+
depth: item.depth,
|
|
362
|
+
index,
|
|
363
|
+
parentId: item.parentId,
|
|
364
|
+
item: item.item,
|
|
365
|
+
isSelected: selectedId === item.id,
|
|
366
|
+
component: SectionComponent,
|
|
367
|
+
onAdd,
|
|
368
|
+
onRemove,
|
|
369
|
+
onChange
|
|
370
|
+
}, item.id) : isField(item) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
371
|
+
id: item.id,
|
|
372
|
+
depth: item.depth,
|
|
373
|
+
index,
|
|
374
|
+
parentId: item.parentId,
|
|
375
|
+
type: item.type,
|
|
376
|
+
item: item.item,
|
|
377
|
+
isSelected: selectedId === item.id,
|
|
378
|
+
component: FieldComponent,
|
|
379
|
+
onAdd,
|
|
380
|
+
onRemove,
|
|
381
|
+
onChange
|
|
382
|
+
}, item.id) : isAddPlaceholder(item) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddPlaceholder, {
|
|
383
|
+
depth: item.depth,
|
|
384
|
+
parentId: item.parentId,
|
|
385
|
+
component: AddPlaceholderComponent,
|
|
386
|
+
onAdd,
|
|
387
|
+
onChange: handleItemChange
|
|
388
|
+
}, item.id) : null;
|
|
389
|
+
}) })
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
//#endregion
|
|
393
|
+
exports.FormBuilder = FormBuilder;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { FC, PropsWithChildren } from "react";
|
|
2
|
+
import { ContentItem, FieldContentItem, FieldType, FormDefinition, SectionContentItem } from "@bluprynt/forms-core";
|
|
3
|
+
|
|
4
|
+
//#region src/core/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A `ContentItem` without an `id` — used when adding new items (id is auto-generated).
|
|
7
|
+
*/
|
|
8
|
+
type NewContentItem = Omit<FieldContentItem, 'id'> | Omit<SectionContentItem, 'id'>;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/add-placeholder.d.ts
|
|
11
|
+
type AddPlaceholderRenderProps = {
|
|
12
|
+
depth: number;
|
|
13
|
+
parentId: number | null;
|
|
14
|
+
onAdd: (item: NewContentItem) => number;
|
|
15
|
+
onChange: (id: number, item: ContentItem) => void;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/field.d.ts
|
|
19
|
+
type FieldRenderProps = {
|
|
20
|
+
ref: (element: Element | null) => void;
|
|
21
|
+
handleRef: (element: Element | null) => void;
|
|
22
|
+
id: number;
|
|
23
|
+
depth: number;
|
|
24
|
+
index: number;
|
|
25
|
+
parentId: number | null;
|
|
26
|
+
type: FieldType;
|
|
27
|
+
item: FieldContentItem;
|
|
28
|
+
isDragging: boolean;
|
|
29
|
+
isSelected: boolean;
|
|
30
|
+
onAdd: (item: NewContentItem) => number;
|
|
31
|
+
onRemove: () => void;
|
|
32
|
+
onChange: (item: ContentItem) => void;
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/section.d.ts
|
|
36
|
+
type SectionRenderProps = {
|
|
37
|
+
ref: (element: Element | null) => void;
|
|
38
|
+
handleRef: (element: Element | null) => void;
|
|
39
|
+
id: number;
|
|
40
|
+
depth: number;
|
|
41
|
+
index: number;
|
|
42
|
+
parentId: number | null;
|
|
43
|
+
item: SectionContentItem;
|
|
44
|
+
isDragging: boolean;
|
|
45
|
+
isSelected: boolean;
|
|
46
|
+
onAdd: (item: NewContentItem) => number;
|
|
47
|
+
onRemove: () => void;
|
|
48
|
+
onChange: (item: ContentItem) => void;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/builder.d.ts
|
|
52
|
+
type FormBuilderProps = {
|
|
53
|
+
definition: FormDefinition;
|
|
54
|
+
container: FC<PropsWithChildren>;
|
|
55
|
+
section: FC<SectionRenderProps>;
|
|
56
|
+
field: FC<FieldRenderProps>;
|
|
57
|
+
addPlaceholder: FC<AddPlaceholderRenderProps>;
|
|
58
|
+
selectedId?: number | null;
|
|
59
|
+
onDefinitionChange?: (definition: FormDefinition) => void;
|
|
60
|
+
};
|
|
61
|
+
declare const FormBuilder: FC<FormBuilderProps>;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { type AddPlaceholderRenderProps, type FieldRenderProps, FormBuilder, type NewContentItem, type SectionRenderProps };
|
|
64
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/core/types.ts","../src/add-placeholder.tsx","../src/field.tsx","../src/section.tsx","../src/builder.tsx"],"mappings":";;;;;;;KAKY,cAAA,GAAiB,IAAA,CAAK,gBAAA,UAA0B,IAAA,CAAK,kBAAA;;;KCCrD,yBAAA;EACR,KAAA;EACA,QAAA;EACA,KAAA,GAAQ,IAAA,EAAM,cAAA;EACd,QAAA,GAAW,EAAA,UAAY,IAAA,EAAM,WAAA;AAAA;;;KCFrB,gBAAA;EACR,GAAA,GAAM,OAAA,EAAS,OAAA;EACf,SAAA,GAAY,OAAA,EAAS,OAAA;EACrB,EAAA;EACA,KAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA,EAAM,SAAA;EACN,IAAA,EAAM,gBAAA;EACN,UAAA;EACA,UAAA;EACA,KAAA,GAAQ,IAAA,EAAM,cAAA;EACd,QAAA;EACA,QAAA,GAAW,IAAA,EAAM,WAAA;AAAA;;;KCbT,kBAAA;EACR,GAAA,GAAM,OAAA,EAAS,OAAA;EACf,SAAA,GAAY,OAAA,EAAS,OAAA;EACrB,EAAA;EACA,KAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA,EAAM,kBAAA;EACN,UAAA;EACA,UAAA;EACA,KAAA,GAAQ,IAAA,EAAM,cAAA;EACd,QAAA;EACA,QAAA,GAAW,IAAA,EAAM,WAAA;AAAA;;;KCThB,gBAAA;EACD,UAAA,EAAY,cAAA;EACZ,SAAA,EAAW,EAAA,CAAG,iBAAA;EACd,OAAA,EAAS,EAAA,CAAG,kBAAA;EACZ,KAAA,EAAO,EAAA,CAAG,gBAAA;EACV,cAAA,EAAgB,EAAA,CAAG,yBAAA;EACnB,UAAA;EACA,kBAAA,IAAsB,UAAA,EAAY,cAAA;AAAA;AAAA,cAGzB,WAAA,EAAa,EAAA,CAAG,gBAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { FC, PropsWithChildren } from "react";
|
|
2
|
+
import { ContentItem, FieldContentItem, FieldType, FormDefinition, SectionContentItem } from "@bluprynt/forms-core";
|
|
3
|
+
|
|
4
|
+
//#region src/core/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A `ContentItem` without an `id` — used when adding new items (id is auto-generated).
|
|
7
|
+
*/
|
|
8
|
+
type NewContentItem = Omit<FieldContentItem, 'id'> | Omit<SectionContentItem, 'id'>;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/add-placeholder.d.ts
|
|
11
|
+
type AddPlaceholderRenderProps = {
|
|
12
|
+
depth: number;
|
|
13
|
+
parentId: number | null;
|
|
14
|
+
onAdd: (item: NewContentItem) => number;
|
|
15
|
+
onChange: (id: number, item: ContentItem) => void;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/field.d.ts
|
|
19
|
+
type FieldRenderProps = {
|
|
20
|
+
ref: (element: Element | null) => void;
|
|
21
|
+
handleRef: (element: Element | null) => void;
|
|
22
|
+
id: number;
|
|
23
|
+
depth: number;
|
|
24
|
+
index: number;
|
|
25
|
+
parentId: number | null;
|
|
26
|
+
type: FieldType;
|
|
27
|
+
item: FieldContentItem;
|
|
28
|
+
isDragging: boolean;
|
|
29
|
+
isSelected: boolean;
|
|
30
|
+
onAdd: (item: NewContentItem) => number;
|
|
31
|
+
onRemove: () => void;
|
|
32
|
+
onChange: (item: ContentItem) => void;
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/section.d.ts
|
|
36
|
+
type SectionRenderProps = {
|
|
37
|
+
ref: (element: Element | null) => void;
|
|
38
|
+
handleRef: (element: Element | null) => void;
|
|
39
|
+
id: number;
|
|
40
|
+
depth: number;
|
|
41
|
+
index: number;
|
|
42
|
+
parentId: number | null;
|
|
43
|
+
item: SectionContentItem;
|
|
44
|
+
isDragging: boolean;
|
|
45
|
+
isSelected: boolean;
|
|
46
|
+
onAdd: (item: NewContentItem) => number;
|
|
47
|
+
onRemove: () => void;
|
|
48
|
+
onChange: (item: ContentItem) => void;
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/builder.d.ts
|
|
52
|
+
type FormBuilderProps = {
|
|
53
|
+
definition: FormDefinition;
|
|
54
|
+
container: FC<PropsWithChildren>;
|
|
55
|
+
section: FC<SectionRenderProps>;
|
|
56
|
+
field: FC<FieldRenderProps>;
|
|
57
|
+
addPlaceholder: FC<AddPlaceholderRenderProps>;
|
|
58
|
+
selectedId?: number | null;
|
|
59
|
+
onDefinitionChange?: (definition: FormDefinition) => void;
|
|
60
|
+
};
|
|
61
|
+
declare const FormBuilder: FC<FormBuilderProps>;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { type AddPlaceholderRenderProps, type FieldRenderProps, FormBuilder, type NewContentItem, type SectionRenderProps };
|
|
64
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/types.ts","../src/add-placeholder.tsx","../src/field.tsx","../src/section.tsx","../src/builder.tsx"],"mappings":";;;;;;;KAKY,cAAA,GAAiB,IAAA,CAAK,gBAAA,UAA0B,IAAA,CAAK,kBAAA;;;KCCrD,yBAAA;EACR,KAAA;EACA,QAAA;EACA,KAAA,GAAQ,IAAA,EAAM,cAAA;EACd,QAAA,GAAW,EAAA,UAAY,IAAA,EAAM,WAAA;AAAA;;;KCFrB,gBAAA;EACR,GAAA,GAAM,OAAA,EAAS,OAAA;EACf,SAAA,GAAY,OAAA,EAAS,OAAA;EACrB,EAAA;EACA,KAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA,EAAM,SAAA;EACN,IAAA,EAAM,gBAAA;EACN,UAAA;EACA,UAAA;EACA,KAAA,GAAQ,IAAA,EAAM,cAAA;EACd,QAAA;EACA,QAAA,GAAW,IAAA,EAAM,WAAA;AAAA;;;KCbT,kBAAA;EACR,GAAA,GAAM,OAAA,EAAS,OAAA;EACf,SAAA,GAAY,OAAA,EAAS,OAAA;EACrB,EAAA;EACA,KAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA,EAAM,kBAAA;EACN,UAAA;EACA,UAAA;EACA,KAAA,GAAQ,IAAA,EAAM,cAAA;EACd,QAAA;EACA,QAAA,GAAW,IAAA,EAAM,WAAA;AAAA;;;KCThB,gBAAA;EACD,UAAA,EAAY,cAAA;EACZ,SAAA,EAAW,EAAA,CAAG,iBAAA;EACd,OAAA,EAAS,EAAA,CAAG,kBAAA;EACZ,KAAA,EAAO,EAAA,CAAG,gBAAA;EACV,cAAA,EAAgB,EAAA,CAAG,yBAAA;EACnB,UAAA;EACA,kBAAA,IAAsB,UAAA,EAAY,cAAA;AAAA;AAAA,cAGzB,WAAA,EAAa,EAAA,CAAG,gBAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { DragDropProvider } from "@dnd-kit/react";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useSortable } from "@dnd-kit/react/sortable";
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
+
import { FormDefinitionEditor } from "@bluprynt/forms-core";
|
|
6
|
+
import { move } from "@dnd-kit/helpers";
|
|
7
|
+
//#region src/add-placeholder.tsx
|
|
8
|
+
const AddPlaceholder = ({ depth, parentId, component: Component, onAdd, onChange }) => /* @__PURE__ */ jsx(Component, {
|
|
9
|
+
depth,
|
|
10
|
+
parentId,
|
|
11
|
+
onAdd,
|
|
12
|
+
onChange
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/core/types.ts
|
|
16
|
+
/**
|
|
17
|
+
* Type guard that narrows a `TreeItem` to a section (`type` is `'section'`, `item` is `SectionContentItem`).
|
|
18
|
+
*/
|
|
19
|
+
const isSection = (item) => item.type === "section";
|
|
20
|
+
/**
|
|
21
|
+
* Type guard that narrows a `TreeItem` to a field (`type` is not `'section'`, `item` is `FieldContentItem`).
|
|
22
|
+
*/
|
|
23
|
+
const isField = (item) => item.type !== "section" && item.type !== "add-placeholder";
|
|
24
|
+
/**
|
|
25
|
+
* Type guard that narrows a `TreeItem` to an "add" placeholder.
|
|
26
|
+
*/
|
|
27
|
+
const isAddPlaceholder = (item) => item.type === "add-placeholder";
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/core/constants.ts
|
|
30
|
+
const SORTABLE_CONFIG = {
|
|
31
|
+
alignment: {
|
|
32
|
+
x: "start",
|
|
33
|
+
y: "center"
|
|
34
|
+
},
|
|
35
|
+
transition: { idle: true }
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/field.tsx
|
|
39
|
+
const Field = ({ id, depth, index, parentId, type, item, isSelected, component: Component, onAdd, onRemove, onChange }) => {
|
|
40
|
+
const { ref, handleRef, isDragSource } = useSortable({
|
|
41
|
+
...SORTABLE_CONFIG,
|
|
42
|
+
id,
|
|
43
|
+
index,
|
|
44
|
+
data: {
|
|
45
|
+
depth,
|
|
46
|
+
parentId
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
50
|
+
ref,
|
|
51
|
+
handleRef,
|
|
52
|
+
id,
|
|
53
|
+
depth,
|
|
54
|
+
index,
|
|
55
|
+
parentId,
|
|
56
|
+
type,
|
|
57
|
+
item,
|
|
58
|
+
isDragging: isDragSource,
|
|
59
|
+
isSelected,
|
|
60
|
+
onAdd,
|
|
61
|
+
onRemove,
|
|
62
|
+
onChange
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/section.tsx
|
|
67
|
+
const Section = ({ id, depth, index, parentId, item, isSelected, component: Component, onAdd, onRemove, onChange }) => {
|
|
68
|
+
const { ref, handleRef, isDragSource } = useSortable({
|
|
69
|
+
...SORTABLE_CONFIG,
|
|
70
|
+
id,
|
|
71
|
+
index,
|
|
72
|
+
data: {
|
|
73
|
+
depth,
|
|
74
|
+
parentId
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
78
|
+
ref,
|
|
79
|
+
handleRef,
|
|
80
|
+
id,
|
|
81
|
+
depth,
|
|
82
|
+
index,
|
|
83
|
+
parentId,
|
|
84
|
+
item,
|
|
85
|
+
isDragging: isDragSource,
|
|
86
|
+
isSelected,
|
|
87
|
+
onAdd,
|
|
88
|
+
onRemove,
|
|
89
|
+
onChange
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/core/projection.ts
|
|
94
|
+
/**
|
|
95
|
+
* Converts a horizontal pixel offset into a depth level based on indentation width.
|
|
96
|
+
*/
|
|
97
|
+
const getDragDepth = (offset, indentationWidth) => Math.round(offset / indentationWidth);
|
|
98
|
+
/**
|
|
99
|
+
* Returns the maximum allowed nesting depth when dragging above a target item.
|
|
100
|
+
* Only sections can accept nested children.
|
|
101
|
+
*/
|
|
102
|
+
const getMaxDepth = (targetItem, previousItem) => {
|
|
103
|
+
if (!previousItem) return 0;
|
|
104
|
+
if (previousItem.type !== "section") return previousItem.depth;
|
|
105
|
+
return Math.min(targetItem.depth + 1, previousItem.depth + 1);
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Returns the minimum depth based on the next sibling item.
|
|
109
|
+
* If there is no next item, the minimum depth is 0 (root level).
|
|
110
|
+
*/
|
|
111
|
+
const getMinDepth = (nextItem) => nextItem ? nextItem.depth : 0;
|
|
112
|
+
/**
|
|
113
|
+
* Calculates the projected depth and parent for a dragged item relative to a target position.
|
|
114
|
+
* Clamps depth between min/max bounds and enforces `maxAllowedDepth`.
|
|
115
|
+
*/
|
|
116
|
+
const getProjection = (items, targetId, projectedDepth, maxAllowedDepth) => {
|
|
117
|
+
const targetItemIndex = items.findIndex(({ id }) => id === targetId);
|
|
118
|
+
const previousItem = items[targetItemIndex - 1];
|
|
119
|
+
const targetItem = items[targetItemIndex];
|
|
120
|
+
const nextItem = items[targetItemIndex + 1];
|
|
121
|
+
if (!targetItem) return {
|
|
122
|
+
depth: 0,
|
|
123
|
+
maxDepth: 0,
|
|
124
|
+
minDepth: 0,
|
|
125
|
+
parentId: null
|
|
126
|
+
};
|
|
127
|
+
const maxDepth = getMaxDepth(targetItem, previousItem);
|
|
128
|
+
const minDepth = getMinDepth(nextItem);
|
|
129
|
+
let depth = projectedDepth;
|
|
130
|
+
if (depth >= maxDepth) depth = maxDepth;
|
|
131
|
+
else if (depth < minDepth) depth = minDepth;
|
|
132
|
+
if (depth > maxAllowedDepth) depth = maxAllowedDepth;
|
|
133
|
+
const getParentId = () => {
|
|
134
|
+
if (depth === 0 || !previousItem) return null;
|
|
135
|
+
if (depth === previousItem.depth) return previousItem.parentId;
|
|
136
|
+
if (depth > previousItem.depth) {
|
|
137
|
+
if (previousItem.type !== "section") return previousItem.parentId;
|
|
138
|
+
return previousItem.id;
|
|
139
|
+
}
|
|
140
|
+
return items.slice(0, targetItemIndex).reverse().find((item) => item.depth === depth)?.parentId ?? null;
|
|
141
|
+
};
|
|
142
|
+
return {
|
|
143
|
+
depth,
|
|
144
|
+
maxDepth,
|
|
145
|
+
minDepth,
|
|
146
|
+
parentId: getParentId()
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/core/tree.ts
|
|
151
|
+
let placeholderIdCounter = -1e3;
|
|
152
|
+
/**
|
|
153
|
+
* Resets the placeholder id counter. Call before each full flatten to ensure stable ids per flatten pass.
|
|
154
|
+
*/
|
|
155
|
+
const resetPlaceholderIds = () => {
|
|
156
|
+
placeholderIdCounter = -1e3;
|
|
157
|
+
};
|
|
158
|
+
const nextPlaceholderId = () => --placeholderIdCounter;
|
|
159
|
+
/**
|
|
160
|
+
* Flattens a nested content tree into a flat list of items with depth and parent info.
|
|
161
|
+
* Each item preserves its position metadata for rendering in a sortable tree.
|
|
162
|
+
* Appends an "add-placeholder" at the end of each section and at the root level.
|
|
163
|
+
*/
|
|
164
|
+
const flattenTree = (items, parentId = null, depth = 0) => {
|
|
165
|
+
if (parentId === null) resetPlaceholderIds();
|
|
166
|
+
const result = items.reduce((result, current, index) => {
|
|
167
|
+
const isSection = current.type === "section";
|
|
168
|
+
result.push({
|
|
169
|
+
id: current.id,
|
|
170
|
+
type: current.type,
|
|
171
|
+
parentId,
|
|
172
|
+
depth,
|
|
173
|
+
index,
|
|
174
|
+
item: current
|
|
175
|
+
});
|
|
176
|
+
if (isSection) {
|
|
177
|
+
const children = current.content;
|
|
178
|
+
result.push(...flattenTree(children, current.id, depth + 1));
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}, []);
|
|
182
|
+
result.push({
|
|
183
|
+
id: nextPlaceholderId(),
|
|
184
|
+
type: "add-placeholder",
|
|
185
|
+
parentId,
|
|
186
|
+
depth,
|
|
187
|
+
index: items.length,
|
|
188
|
+
item: void 0
|
|
189
|
+
});
|
|
190
|
+
return result;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Collects all transitive descendant ids for a given parent from a flat item list.
|
|
194
|
+
*/
|
|
195
|
+
const getDescendants = (items, parentId) => {
|
|
196
|
+
return items.filter((item) => item.parentId === parentId).reduce((result, child) => {
|
|
197
|
+
result.add(child.id);
|
|
198
|
+
for (const descendant of getDescendants(items, child.id)) result.add(descendant);
|
|
199
|
+
return result;
|
|
200
|
+
}, /* @__PURE__ */ new Set());
|
|
201
|
+
};
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/use-form-builder.ts
|
|
204
|
+
/**
|
|
205
|
+
* Manages the form builder state: flattened tree items, drag-and-drop reordering,
|
|
206
|
+
* item editing, and removal. Uses {@link FormDefinitionEditor} for all definition mutations
|
|
207
|
+
* and notifies the consumer of structural changes via {@link onDefinitionChange}.
|
|
208
|
+
*
|
|
209
|
+
* @param definition - The current form definition to build upon.
|
|
210
|
+
* @param onDefinitionChange - Optional callback invoked with the updated `FormDefinition`
|
|
211
|
+
* whenever the tree structure, item content, or item order changes.
|
|
212
|
+
* @returns Flattened items and handlers for item/drag operations.
|
|
213
|
+
*/
|
|
214
|
+
const useFormBuilder = (definition, onDefinitionChange) => {
|
|
215
|
+
const [items, setItems] = useState(() => flattenTree(definition.content));
|
|
216
|
+
const initialDepth = useRef(0);
|
|
217
|
+
const sourceChildren = useRef([]);
|
|
218
|
+
const dragSourceId = useRef(null);
|
|
219
|
+
const definitionRef = useRef(definition);
|
|
220
|
+
definitionRef.current = definition;
|
|
221
|
+
const onDefinitionChangeRef = useRef(onDefinitionChange);
|
|
222
|
+
onDefinitionChangeRef.current = onDefinitionChange;
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
setItems(flattenTree(definition.content));
|
|
225
|
+
}, [definition]);
|
|
226
|
+
const applyEditor = useCallback((editor) => {
|
|
227
|
+
const updated = editor.toJSON();
|
|
228
|
+
setItems(flattenTree(updated.content));
|
|
229
|
+
onDefinitionChangeRef.current?.(updated);
|
|
230
|
+
}, []);
|
|
231
|
+
const handleItemAdd = useCallback((sectionId, item) => {
|
|
232
|
+
const editor = new FormDefinitionEditor(definitionRef.current);
|
|
233
|
+
const parentId = sectionId ?? void 0;
|
|
234
|
+
const id = editor.nextId();
|
|
235
|
+
if (item.type === "section") editor.addSection({
|
|
236
|
+
...item,
|
|
237
|
+
id
|
|
238
|
+
}, parentId);
|
|
239
|
+
else editor.addField({
|
|
240
|
+
...item,
|
|
241
|
+
id
|
|
242
|
+
}, parentId);
|
|
243
|
+
applyEditor(editor);
|
|
244
|
+
return id;
|
|
245
|
+
}, [applyEditor]);
|
|
246
|
+
const handleItemChange = useCallback((id, newItem) => {
|
|
247
|
+
const editor = new FormDefinitionEditor(definitionRef.current);
|
|
248
|
+
if (newItem.type === "section") {
|
|
249
|
+
const { id: _, type: __, content: ___, ...updates } = newItem;
|
|
250
|
+
editor.updateSection(id, updates);
|
|
251
|
+
} else {
|
|
252
|
+
const { id: _, type: __, ...updates } = newItem;
|
|
253
|
+
editor.updateField(id, updates);
|
|
254
|
+
}
|
|
255
|
+
applyEditor(editor);
|
|
256
|
+
}, [applyEditor]);
|
|
257
|
+
const handleItemRemove = useCallback((itemId) => {
|
|
258
|
+
const editor = new FormDefinitionEditor(definitionRef.current);
|
|
259
|
+
editor.removeItem(itemId);
|
|
260
|
+
applyEditor(editor);
|
|
261
|
+
}, [applyEditor]);
|
|
262
|
+
const handleDragStart = (event) => {
|
|
263
|
+
const { source } = event.operation;
|
|
264
|
+
if (!source) return;
|
|
265
|
+
const item = items.find(({ id }) => id === source.id);
|
|
266
|
+
if (!item) return;
|
|
267
|
+
initialDepth.current = item.depth;
|
|
268
|
+
dragSourceId.current = source.id;
|
|
269
|
+
setItems((prev) => {
|
|
270
|
+
sourceChildren.current = [];
|
|
271
|
+
const descendants = getDescendants(prev, source.id);
|
|
272
|
+
return prev.filter((i) => {
|
|
273
|
+
if (descendants.has(i.id)) {
|
|
274
|
+
sourceChildren.current = [...sourceChildren.current, i];
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
return true;
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
const handleDragOver = (event, manager) => {
|
|
282
|
+
const { source, target } = event.operation;
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
if (source && target && source.id !== target.id) setItems((prev) => {
|
|
285
|
+
const offsetLeft = manager.dragOperation.transform.x;
|
|
286
|
+
const dragDepth = getDragDepth(offsetLeft, 40);
|
|
287
|
+
const projectedDepth = initialDepth.current + dragDepth;
|
|
288
|
+
const { depth, parentId } = getProjection(prev, target.id, projectedDepth, 3);
|
|
289
|
+
return move(prev, event).map((item) => item.id === source.id ? {
|
|
290
|
+
...item,
|
|
291
|
+
depth,
|
|
292
|
+
parentId
|
|
293
|
+
} : item);
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
const handleDragMove = (event, manager) => {
|
|
297
|
+
if (event.defaultPrevented) return;
|
|
298
|
+
const { source, target } = event.operation;
|
|
299
|
+
if (!source || !target) return;
|
|
300
|
+
const offsetLeft = manager.dragOperation.transform.x;
|
|
301
|
+
const dragDepth = getDragDepth(offsetLeft, 40);
|
|
302
|
+
const projectedDepth = initialDepth.current + dragDepth;
|
|
303
|
+
const { depth, parentId } = getProjection(items, source.id, projectedDepth, 3);
|
|
304
|
+
if (source.data?.depth !== depth || source.data?.parentId !== parentId) setItems((prev) => prev.map((item) => item.id === source.id ? {
|
|
305
|
+
...item,
|
|
306
|
+
depth,
|
|
307
|
+
parentId
|
|
308
|
+
} : item));
|
|
309
|
+
};
|
|
310
|
+
const handleDragEnd = (event) => {
|
|
311
|
+
if (event.canceled) {
|
|
312
|
+
setItems(flattenTree(definitionRef.current.content));
|
|
313
|
+
dragSourceId.current = null;
|
|
314
|
+
sourceChildren.current = [];
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const draggedId = dragSourceId.current;
|
|
318
|
+
if (draggedId === null) return;
|
|
319
|
+
const draggedItem = items.find((i) => i.id === draggedId);
|
|
320
|
+
if (!draggedItem) return;
|
|
321
|
+
const targetParentId = draggedItem.parentId;
|
|
322
|
+
const draggedFlatIndex = items.findIndex((i) => i.id === draggedId);
|
|
323
|
+
let siblingIndex = 0;
|
|
324
|
+
for (let i = 0; i < draggedFlatIndex; i++) {
|
|
325
|
+
const current = items[i];
|
|
326
|
+
if (current && current.parentId === targetParentId && current.type !== "add-placeholder") siblingIndex++;
|
|
327
|
+
}
|
|
328
|
+
const editor = new FormDefinitionEditor(definitionRef.current);
|
|
329
|
+
editor.moveItem(draggedId, targetParentId ?? void 0, siblingIndex);
|
|
330
|
+
applyEditor(editor);
|
|
331
|
+
dragSourceId.current = null;
|
|
332
|
+
sourceChildren.current = [];
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
items,
|
|
336
|
+
handleItemAdd,
|
|
337
|
+
handleItemChange,
|
|
338
|
+
handleItemRemove,
|
|
339
|
+
handleDragStart,
|
|
340
|
+
handleDragOver,
|
|
341
|
+
handleDragMove,
|
|
342
|
+
handleDragEnd
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/builder.tsx
|
|
347
|
+
const FormBuilder = ({ definition, container: Container, section: SectionComponent, field: FieldComponent, addPlaceholder: AddPlaceholderComponent, selectedId, onDefinitionChange }) => {
|
|
348
|
+
const { items, handleItemAdd, handleItemChange, handleItemRemove, handleDragStart, handleDragOver, handleDragMove, handleDragEnd } = useFormBuilder(definition, onDefinitionChange);
|
|
349
|
+
return /* @__PURE__ */ jsx(DragDropProvider, {
|
|
350
|
+
onDragStart: handleDragStart,
|
|
351
|
+
onDragOver: handleDragOver,
|
|
352
|
+
onDragMove: handleDragMove,
|
|
353
|
+
onDragEnd: handleDragEnd,
|
|
354
|
+
children: /* @__PURE__ */ jsx(Container, { children: items.map((item, index) => {
|
|
355
|
+
const onAdd = (value) => handleItemAdd(item.type === "section" ? item.id : item.parentId, value);
|
|
356
|
+
const onRemove = () => handleItemRemove(item.id);
|
|
357
|
+
const onChange = (value) => handleItemChange(value.id, value);
|
|
358
|
+
return isSection(item) ? /* @__PURE__ */ jsx(Section, {
|
|
359
|
+
id: item.id,
|
|
360
|
+
depth: item.depth,
|
|
361
|
+
index,
|
|
362
|
+
parentId: item.parentId,
|
|
363
|
+
item: item.item,
|
|
364
|
+
isSelected: selectedId === item.id,
|
|
365
|
+
component: SectionComponent,
|
|
366
|
+
onAdd,
|
|
367
|
+
onRemove,
|
|
368
|
+
onChange
|
|
369
|
+
}, item.id) : isField(item) ? /* @__PURE__ */ jsx(Field, {
|
|
370
|
+
id: item.id,
|
|
371
|
+
depth: item.depth,
|
|
372
|
+
index,
|
|
373
|
+
parentId: item.parentId,
|
|
374
|
+
type: item.type,
|
|
375
|
+
item: item.item,
|
|
376
|
+
isSelected: selectedId === item.id,
|
|
377
|
+
component: FieldComponent,
|
|
378
|
+
onAdd,
|
|
379
|
+
onRemove,
|
|
380
|
+
onChange
|
|
381
|
+
}, item.id) : isAddPlaceholder(item) ? /* @__PURE__ */ jsx(AddPlaceholder, {
|
|
382
|
+
depth: item.depth,
|
|
383
|
+
parentId: item.parentId,
|
|
384
|
+
component: AddPlaceholderComponent,
|
|
385
|
+
onAdd,
|
|
386
|
+
onChange: handleItemChange
|
|
387
|
+
}, item.id) : null;
|
|
388
|
+
}) })
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
//#endregion
|
|
392
|
+
export { FormBuilder };
|
|
393
|
+
|
|
394
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/add-placeholder.tsx","../src/core/types.ts","../src/core/constants.ts","../src/field.tsx","../src/section.tsx","../src/core/projection.ts","../src/core/tree.ts","../src/use-form-builder.ts","../src/builder.tsx"],"sourcesContent":["import type { FC } from 'react'\n\nimport type { ContentItem } from '@bluprynt/forms-core'\n\nimport type { NewContentItem } from './core/types'\n\nexport type AddPlaceholderRenderProps = {\n depth: number\n parentId: number | null\n onAdd: (item: NewContentItem) => number\n onChange: (id: number, item: ContentItem) => void\n}\n\ntype AddPlaceholderProps = {\n depth: number\n parentId: number | null\n component: FC<AddPlaceholderRenderProps>\n onAdd: (item: NewContentItem) => number\n onChange: (id: number, item: ContentItem) => void\n}\n\nexport const AddPlaceholder: FC<AddPlaceholderProps> = ({ depth, parentId, component: Component, onAdd, onChange }) => (\n <Component depth={depth} parentId={parentId} onAdd={onAdd} onChange={onChange} />\n)\n","import type { ContentItem, ContentItemType, FieldContentItem, SectionContentItem } from '@bluprynt/forms-core'\n\n/**\n * A `ContentItem` without an `id` — used when adding new items (id is auto-generated).\n */\nexport type NewContentItem = Omit<FieldContentItem, 'id'> | Omit<SectionContentItem, 'id'>\n\n/**\n * Flat representation of a content item within the sortable tree.\n * Carries depth, parent reference, and the original `ContentItem` for rendering and reconstruction.\n */\nexport type TreeItem = {\n id: number\n type: ContentItemType | 'add-placeholder'\n parentId: number | null\n depth: number\n index: number\n collapsed?: boolean\n item: ContentItem | undefined\n onChange?: (item: ContentItem) => void\n}\n\n/**\n * Type guard that narrows a `TreeItem` to a section (`type` is `'section'`, `item` is `SectionContentItem`).\n */\nexport const isSection = (item: TreeItem): item is TreeItem & { type: 'section'; item: SectionContentItem } =>\n item.type === 'section'\n\n/**\n * Type guard that narrows a `TreeItem` to a field (`type` is not `'section'`, `item` is `FieldContentItem`).\n */\nexport const isField = (\n item: TreeItem,\n): item is TreeItem & { type: Exclude<ContentItemType, 'section'>; item: FieldContentItem } =>\n item.type !== 'section' && item.type !== 'add-placeholder'\n\n/**\n * Type guard that narrows a `TreeItem` to an \"add\" placeholder.\n */\nexport const isAddPlaceholder = (item: TreeItem): item is TreeItem & { type: 'add-placeholder'; item: undefined } =>\n item.type === 'add-placeholder'\n","export const INDENTATION = 40\nexport const MAX_DEPTH = 3\n\nexport const SORTABLE_CONFIG = {\n alignment: {\n x: 'start',\n y: 'center',\n },\n transition: {\n idle: true,\n },\n} as const\n","import type { FC } from 'react'\n\nimport type { ContentItem, FieldContentItem, FieldType } from '@bluprynt/forms-core'\nimport { useSortable } from '@dnd-kit/react/sortable'\n\nimport { SORTABLE_CONFIG } from './core/constants'\nimport type { NewContentItem } from './core/types'\n\nexport type FieldRenderProps = {\n ref: (element: Element | null) => void\n handleRef: (element: Element | null) => void\n id: number\n depth: number\n index: number\n parentId: number | null\n type: FieldType\n item: FieldContentItem\n isDragging: boolean\n isSelected: boolean\n onAdd: (item: NewContentItem) => number\n onRemove: () => void\n onChange: (item: ContentItem) => void\n}\n\ntype FieldProps = {\n id: number\n depth: number\n index: number\n parentId: number | null\n type: FieldType\n item: FieldContentItem\n isSelected: boolean\n component: FC<FieldRenderProps>\n onAdd: (item: NewContentItem) => number\n onRemove: () => void\n onChange: (item: ContentItem) => void\n}\n\nexport const Field: FC<FieldProps> = ({\n id,\n depth,\n index,\n parentId,\n type,\n item,\n isSelected,\n component: Component,\n onAdd,\n onRemove,\n onChange,\n}) => {\n const { ref, handleRef, isDragSource } = useSortable({\n ...SORTABLE_CONFIG,\n id,\n index,\n data: { depth, parentId },\n })\n\n return (\n <Component\n ref={ref}\n handleRef={handleRef}\n id={id}\n depth={depth}\n index={index}\n parentId={parentId}\n type={type}\n item={item}\n isDragging={isDragSource}\n isSelected={isSelected}\n onAdd={onAdd}\n onRemove={onRemove}\n onChange={onChange}\n />\n )\n}\n","import { type FC } from 'react'\n\nimport type { ContentItem, SectionContentItem } from '@bluprynt/forms-core'\nimport { useSortable } from '@dnd-kit/react/sortable'\n\nimport { SORTABLE_CONFIG } from './core/constants'\nimport type { NewContentItem } from './core/types'\n\nexport type SectionRenderProps = {\n ref: (element: Element | null) => void\n handleRef: (element: Element | null) => void\n id: number\n depth: number\n index: number\n parentId: number | null\n item: SectionContentItem\n isDragging: boolean\n isSelected: boolean\n onAdd: (item: NewContentItem) => number\n onRemove: () => void\n onChange: (item: ContentItem) => void\n}\n\ntype SectionProps = {\n id: number\n depth: number\n index: number\n parentId: number | null\n item: SectionContentItem\n isSelected: boolean\n component: FC<SectionRenderProps>\n onAdd: (item: NewContentItem) => number\n onRemove: () => void\n onChange: (item: ContentItem) => void\n}\n\nexport const Section: FC<SectionProps> = ({\n id,\n depth,\n index,\n parentId,\n item,\n isSelected,\n component: Component,\n onAdd,\n onRemove,\n onChange,\n}) => {\n const { ref, handleRef, isDragSource } = useSortable({\n ...SORTABLE_CONFIG,\n id,\n index,\n data: { depth, parentId },\n })\n\n return (\n <Component\n ref={ref}\n handleRef={handleRef}\n id={id}\n depth={depth}\n index={index}\n parentId={parentId}\n item={item}\n isDragging={isDragSource}\n isSelected={isSelected}\n onAdd={onAdd}\n onRemove={onRemove}\n onChange={onChange}\n />\n )\n}\n","import type { TreeItem } from './types'\n\n/**\n * Converts a horizontal pixel offset into a depth level based on indentation width.\n */\nexport const getDragDepth = (offset: number, indentationWidth: number): number => Math.round(offset / indentationWidth)\n\n/**\n * Returns the maximum allowed nesting depth when dragging above a target item.\n * Only sections can accept nested children.\n */\nconst getMaxDepth = (targetItem: TreeItem, previousItem: TreeItem | undefined): number => {\n if (!previousItem) return 0\n if (previousItem.type !== 'section') {\n return previousItem.depth\n }\n return Math.min(targetItem.depth + 1, previousItem.depth + 1)\n}\n\n/**\n * Returns the minimum depth based on the next sibling item.\n * If there is no next item, the minimum depth is 0 (root level).\n */\nconst getMinDepth = (nextItem: TreeItem | undefined): number => (nextItem ? nextItem.depth : 0)\n\n/**\n * Calculates the projected depth and parent for a dragged item relative to a target position.\n * Clamps depth between min/max bounds and enforces `maxAllowedDepth`.\n */\nexport const getProjection = (\n items: TreeItem[],\n targetId: number,\n projectedDepth: number,\n maxAllowedDepth: number,\n): { depth: number; maxDepth: number; minDepth: number; parentId: number | null } => {\n const targetItemIndex = items.findIndex(({ id }) => id === targetId)\n const previousItem = items[targetItemIndex - 1]\n const targetItem = items[targetItemIndex]\n const nextItem = items[targetItemIndex + 1]\n if (!targetItem) return { depth: 0, maxDepth: 0, minDepth: 0, parentId: null }\n\n const maxDepth = getMaxDepth(targetItem, previousItem)\n const minDepth = getMinDepth(nextItem)\n let depth = projectedDepth\n\n if (depth >= maxDepth) {\n depth = maxDepth\n } else if (depth < minDepth) {\n depth = minDepth\n }\n\n if (depth > maxAllowedDepth) {\n depth = maxAllowedDepth\n }\n\n const getParentId = (): number | null => {\n if (depth === 0 || !previousItem) {\n return null\n }\n\n if (depth === previousItem.depth) {\n return previousItem.parentId\n }\n\n if (depth > previousItem.depth) {\n if (previousItem.type !== 'section') {\n return previousItem.parentId\n }\n return previousItem.id\n }\n\n const newParent = items\n .slice(0, targetItemIndex)\n .reverse()\n .find((item) => item.depth === depth)?.parentId\n\n return newParent ?? null\n }\n\n return { depth, maxDepth, minDepth, parentId: getParentId() }\n}\n","import type { ContentItem } from '@bluprynt/forms-core'\n\nimport type { TreeItem } from './types'\n\nlet placeholderIdCounter = -1000\n\n/**\n * Resets the placeholder id counter. Call before each full flatten to ensure stable ids per flatten pass.\n */\nconst resetPlaceholderIds = () => {\n placeholderIdCounter = -1000\n}\n\nconst nextPlaceholderId = () => --placeholderIdCounter\n\n/**\n * Flattens a nested content tree into a flat list of items with depth and parent info.\n * Each item preserves its position metadata for rendering in a sortable tree.\n * Appends an \"add-placeholder\" at the end of each section and at the root level.\n */\nexport const flattenTree = (items: ContentItem[], parentId: number | null = null, depth = 0): TreeItem[] => {\n if (parentId === null) resetPlaceholderIds()\n\n const result = items.reduce<TreeItem[]>((result, current, index) => {\n const isSection = current.type === 'section'\n\n result.push({\n id: current.id,\n type: current.type,\n parentId,\n depth,\n index,\n item: current,\n })\n\n if (isSection) {\n const children = (current as { content: ContentItem[] }).content\n result.push(...flattenTree(children, current.id, depth + 1))\n }\n\n return result\n }, [])\n\n result.push({\n id: nextPlaceholderId(),\n type: 'add-placeholder',\n parentId,\n depth,\n index: items.length,\n item: undefined,\n })\n\n return result\n}\n\n/**\n * Collects all transitive descendant ids for a given parent from a flat item list.\n */\nexport const getDescendants = (items: TreeItem[], parentId: number): Set<number> => {\n const directChildren = items.filter((item) => item.parentId === parentId)\n\n return directChildren.reduce((result, child) => {\n result.add(child.id)\n for (const descendant of getDescendants(items, child.id)) {\n result.add(descendant)\n }\n return result\n }, new Set<number>())\n}\n","import { type ComponentProps, useCallback, useEffect, useRef, useState } from 'react'\n\nimport {\n type ContentItem,\n type FieldContentItem,\n type FormDefinition,\n FormDefinitionEditor,\n type SectionContentItem,\n} from '@bluprynt/forms-core'\nimport { move } from '@dnd-kit/helpers'\nimport { DragDropProvider } from '@dnd-kit/react'\n\nimport { INDENTATION, MAX_DEPTH } from './core/constants'\nimport { getDragDepth, getProjection } from './core/projection'\nimport { flattenTree, getDescendants } from './core/tree'\nimport type { NewContentItem, TreeItem } from './core/types'\n\n/**\n * Manages the form builder state: flattened tree items, drag-and-drop reordering,\n * item editing, and removal. Uses {@link FormDefinitionEditor} for all definition mutations\n * and notifies the consumer of structural changes via {@link onDefinitionChange}.\n *\n * @param definition - The current form definition to build upon.\n * @param onDefinitionChange - Optional callback invoked with the updated `FormDefinition`\n * whenever the tree structure, item content, or item order changes.\n * @returns Flattened items and handlers for item/drag operations.\n */\nexport const useFormBuilder = (\n definition: FormDefinition,\n onDefinitionChange?: (definition: FormDefinition) => void,\n): {\n items: TreeItem[]\n handleItemAdd: (sectionId: number | null, item: NewContentItem) => number\n handleItemChange: (id: number, newItem: ContentItem) => void\n handleItemRemove: (itemId: number) => void\n handleDragStart: ComponentProps<typeof DragDropProvider>['onDragStart']\n handleDragOver: ComponentProps<typeof DragDropProvider>['onDragOver']\n handleDragMove: ComponentProps<typeof DragDropProvider>['onDragMove']\n handleDragEnd: ComponentProps<typeof DragDropProvider>['onDragEnd']\n} => {\n const [items, setItems] = useState<TreeItem[]>(() => flattenTree(definition.content))\n const initialDepth = useRef(0)\n const sourceChildren = useRef<TreeItem[]>([])\n const dragSourceId = useRef<number | null>(null)\n\n const definitionRef = useRef(definition)\n definitionRef.current = definition\n\n const onDefinitionChangeRef = useRef(onDefinitionChange)\n onDefinitionChangeRef.current = onDefinitionChange\n\n // Re-sync items when definition changes externally (e.g. from field editor sheet)\n useEffect(() => {\n setItems(flattenTree(definition.content))\n }, [definition])\n\n const applyEditor = useCallback((editor: FormDefinitionEditor) => {\n const updated = editor.toJSON()\n setItems(flattenTree(updated.content))\n onDefinitionChangeRef.current?.(updated)\n }, [])\n\n const handleItemAdd = useCallback(\n (sectionId: number | null, item: NewContentItem): number => {\n const editor = new FormDefinitionEditor(definitionRef.current)\n const parentId = sectionId ?? undefined\n const id = editor.nextId()\n\n if (item.type === 'section') editor.addSection({ ...item, id }, parentId)\n else editor.addField({ ...item, id } as FieldContentItem, parentId)\n\n applyEditor(editor)\n return id\n },\n [applyEditor],\n )\n\n const handleItemChange = useCallback(\n (id: number, newItem: ContentItem) => {\n const editor = new FormDefinitionEditor(definitionRef.current)\n\n if (newItem.type === 'section') {\n const { id: _, type: __, content: ___, ...updates } = newItem as SectionContentItem\n editor.updateSection(id, updates)\n } else {\n const { id: _, type: __, ...updates } = newItem as FieldContentItem\n editor.updateField(id, updates)\n }\n\n applyEditor(editor)\n },\n [applyEditor],\n )\n\n const handleItemRemove = useCallback(\n (itemId: number) => {\n const editor = new FormDefinitionEditor(definitionRef.current)\n editor.removeItem(itemId)\n applyEditor(editor)\n },\n [applyEditor],\n )\n\n const handleDragStart: ComponentProps<typeof DragDropProvider>['onDragStart'] = (event) => {\n const { source } = event.operation\n if (!source) return\n\n const item = items.find(({ id }) => id === source.id)\n if (!item) return\n\n initialDepth.current = item.depth\n dragSourceId.current = source.id as number\n\n setItems((prev) => {\n sourceChildren.current = []\n const descendants = getDescendants(prev, source.id as number)\n\n return prev.filter((i) => {\n if (descendants.has(i.id)) {\n sourceChildren.current = [...sourceChildren.current, i]\n return false\n }\n return true\n })\n })\n }\n\n const handleDragOver: ComponentProps<typeof DragDropProvider>['onDragOver'] = (event, manager) => {\n const { source, target } = event.operation\n event.preventDefault()\n\n if (source && target && source.id !== target.id) {\n setItems((prev) => {\n const offsetLeft = manager.dragOperation.transform.x\n const dragDepth = getDragDepth(offsetLeft, INDENTATION)\n const projectedDepth = initialDepth.current + dragDepth\n\n const { depth, parentId } = getProjection(prev, target.id as number, projectedDepth, MAX_DEPTH)\n\n const sorted = move(prev, event)\n return sorted.map((item) => (item.id === source.id ? { ...item, depth, parentId } : item))\n })\n }\n }\n\n const handleDragMove: ComponentProps<typeof DragDropProvider>['onDragMove'] = (event, manager) => {\n if (event.defaultPrevented) return\n\n const { source, target } = event.operation\n if (!source || !target) return\n\n const offsetLeft = manager.dragOperation.transform.x\n const dragDepth = getDragDepth(offsetLeft, INDENTATION)\n const projectedDepth = initialDepth.current + dragDepth\n\n const { depth, parentId } = getProjection(items, source.id as number, projectedDepth, MAX_DEPTH)\n\n if (\n (source.data as { depth: number })?.depth !== depth ||\n (source.data as { parentId: number | null })?.parentId !== parentId\n ) {\n setItems((prev) => prev.map((item) => (item.id === source.id ? { ...item, depth, parentId } : item)))\n }\n }\n\n const handleDragEnd: ComponentProps<typeof DragDropProvider>['onDragEnd'] = (event) => {\n if (event.canceled) {\n setItems(flattenTree(definitionRef.current.content))\n dragSourceId.current = null\n sourceChildren.current = []\n return\n }\n\n const draggedId = dragSourceId.current\n if (draggedId === null) return\n\n const draggedItem = items.find((i) => i.id === draggedId)\n if (!draggedItem) return\n\n // Compute sibling index in the new parent\n const targetParentId = draggedItem.parentId\n const draggedFlatIndex = items.findIndex((i) => i.id === draggedId)\n let siblingIndex = 0\n for (let i = 0; i < draggedFlatIndex; i++) {\n const current = items[i]\n if (current && current.parentId === targetParentId && current.type !== 'add-placeholder') {\n siblingIndex++\n }\n }\n\n const editor = new FormDefinitionEditor(definitionRef.current)\n editor.moveItem(draggedId, targetParentId ?? undefined, siblingIndex)\n applyEditor(editor)\n\n dragSourceId.current = null\n sourceChildren.current = []\n }\n\n return {\n items,\n handleItemAdd,\n handleItemChange,\n handleItemRemove,\n handleDragStart,\n handleDragOver,\n handleDragMove,\n handleDragEnd,\n } as const\n}\n","import type { FC, PropsWithChildren } from 'react'\n\nimport type { ContentItem, FormDefinition } from '@bluprynt/forms-core'\nimport { DragDropProvider } from '@dnd-kit/react'\n\nimport { AddPlaceholder, type AddPlaceholderRenderProps } from './add-placeholder'\nimport { isAddPlaceholder, isField, isSection, NewContentItem } from './core/types'\nimport { Field, type FieldRenderProps } from './field'\nimport { Section, type SectionRenderProps } from './section'\nimport { useFormBuilder } from './use-form-builder'\n\ntype FormBuilderProps = {\n definition: FormDefinition\n container: FC<PropsWithChildren>\n section: FC<SectionRenderProps>\n field: FC<FieldRenderProps>\n addPlaceholder: FC<AddPlaceholderRenderProps>\n selectedId?: number | null\n onDefinitionChange?: (definition: FormDefinition) => void\n}\n\nexport const FormBuilder: FC<FormBuilderProps> = ({\n definition,\n container: Container,\n section: SectionComponent,\n field: FieldComponent,\n addPlaceholder: AddPlaceholderComponent,\n selectedId,\n onDefinitionChange,\n}) => {\n const {\n items,\n handleItemAdd,\n handleItemChange,\n handleItemRemove,\n handleDragStart,\n handleDragOver,\n handleDragMove,\n handleDragEnd,\n } = useFormBuilder(definition, onDefinitionChange)\n\n return (\n <DragDropProvider\n onDragStart={handleDragStart}\n onDragOver={handleDragOver}\n onDragMove={handleDragMove}\n onDragEnd={handleDragEnd}>\n <Container>\n {items.map((item, index) => {\n const onAdd = (value: NewContentItem) =>\n handleItemAdd(item.type === 'section' ? item.id : item.parentId, value)\n const onRemove = () => handleItemRemove(item.id)\n const onChange = (value: ContentItem) => handleItemChange(value.id, value)\n\n return isSection(item) ? (\n <Section\n key={item.id}\n id={item.id}\n depth={item.depth}\n index={index}\n parentId={item.parentId}\n item={item.item}\n isSelected={selectedId === item.id}\n component={SectionComponent}\n onAdd={onAdd}\n onRemove={onRemove}\n onChange={onChange}\n />\n ) : isField(item) ? (\n <Field\n key={item.id}\n id={item.id}\n depth={item.depth}\n index={index}\n parentId={item.parentId}\n type={item.type}\n item={item.item}\n isSelected={selectedId === item.id}\n component={FieldComponent}\n onAdd={onAdd}\n onRemove={onRemove}\n onChange={onChange}\n />\n ) : isAddPlaceholder(item) ? (\n <AddPlaceholder\n key={item.id}\n depth={item.depth}\n parentId={item.parentId}\n component={AddPlaceholderComponent}\n onAdd={onAdd}\n onChange={handleItemChange}\n />\n ) : null\n })}\n </Container>\n </DragDropProvider>\n )\n}\n"],"mappings":";;;;;;;AAqBA,MAAa,kBAA2C,EAAE,OAAO,UAAU,WAAW,WAAW,OAAO,eACpG,oBAAC,WAAD;CAAkB;CAAiB;CAAiB;CAAiB;CAAY,CAAA;;;;;;ACGrF,MAAa,aAAa,SACtB,KAAK,SAAS;;;;AAKlB,MAAa,WACT,SAEA,KAAK,SAAS,aAAa,KAAK,SAAS;;;;AAK7C,MAAa,oBAAoB,SAC7B,KAAK,SAAS;;;ACrClB,MAAa,kBAAkB;CAC3B,WAAW;EACP,GAAG;EACH,GAAG;EACN;CACD,YAAY,EACR,MAAM,MACT;CACJ;;;AC2BD,MAAa,SAAyB,EAClC,IACA,OACA,OACA,UACA,MACA,MACA,YACA,WAAW,WACX,OACA,UACA,eACE;CACF,MAAM,EAAE,KAAK,WAAW,iBAAiB,YAAY;EACjD,GAAG;EACH;EACA;EACA,MAAM;GAAE;GAAO;GAAU;EAC5B,CAAC;AAEF,QACI,oBAAC,WAAD;EACS;EACM;EACP;EACG;EACA;EACG;EACJ;EACA;EACN,YAAY;EACA;EACL;EACG;EACA;EACZ,CAAA;;;;ACrCV,MAAa,WAA6B,EACtC,IACA,OACA,OACA,UACA,MACA,YACA,WAAW,WACX,OACA,UACA,eACE;CACF,MAAM,EAAE,KAAK,WAAW,iBAAiB,YAAY;EACjD,GAAG;EACH;EACA;EACA,MAAM;GAAE;GAAO;GAAU;EAC5B,CAAC;AAEF,QACI,oBAAC,WAAD;EACS;EACM;EACP;EACG;EACA;EACG;EACJ;EACN,YAAY;EACA;EACL;EACG;EACA;EACZ,CAAA;;;;;;;AChEV,MAAa,gBAAgB,QAAgB,qBAAqC,KAAK,MAAM,SAAS,iBAAiB;;;;;AAMvH,MAAM,eAAe,YAAsB,iBAA+C;AACtF,KAAI,CAAC,aAAc,QAAO;AAC1B,KAAI,aAAa,SAAS,UACtB,QAAO,aAAa;AAExB,QAAO,KAAK,IAAI,WAAW,QAAQ,GAAG,aAAa,QAAQ,EAAE;;;;;;AAOjE,MAAM,eAAe,aAA4C,WAAW,SAAS,QAAQ;;;;;AAM7F,MAAa,iBACT,OACA,UACA,gBACA,oBACiF;CACjF,MAAM,kBAAkB,MAAM,WAAW,EAAE,SAAS,OAAO,SAAS;CACpE,MAAM,eAAe,MAAM,kBAAkB;CAC7C,MAAM,aAAa,MAAM;CACzB,MAAM,WAAW,MAAM,kBAAkB;AACzC,KAAI,CAAC,WAAY,QAAO;EAAE,OAAO;EAAG,UAAU;EAAG,UAAU;EAAG,UAAU;EAAM;CAE9E,MAAM,WAAW,YAAY,YAAY,aAAa;CACtD,MAAM,WAAW,YAAY,SAAS;CACtC,IAAI,QAAQ;AAEZ,KAAI,SAAS,SACT,SAAQ;UACD,QAAQ,SACf,SAAQ;AAGZ,KAAI,QAAQ,gBACR,SAAQ;CAGZ,MAAM,oBAAmC;AACrC,MAAI,UAAU,KAAK,CAAC,aAChB,QAAO;AAGX,MAAI,UAAU,aAAa,MACvB,QAAO,aAAa;AAGxB,MAAI,QAAQ,aAAa,OAAO;AAC5B,OAAI,aAAa,SAAS,UACtB,QAAO,aAAa;AAExB,UAAO,aAAa;;AAQxB,SALkB,MACb,MAAM,GAAG,gBAAgB,CACzB,SAAS,CACT,MAAM,SAAS,KAAK,UAAU,MAAM,EAAE,YAEvB;;AAGxB,QAAO;EAAE;EAAO;EAAU;EAAU,UAAU,aAAa;EAAE;;;;AC3EjE,IAAI,uBAAuB;;;;AAK3B,MAAM,4BAA4B;AAC9B,wBAAuB;;AAG3B,MAAM,0BAA0B,EAAE;;;;;;AAOlC,MAAa,eAAe,OAAsB,WAA0B,MAAM,QAAQ,MAAkB;AACxG,KAAI,aAAa,KAAM,sBAAqB;CAE5C,MAAM,SAAS,MAAM,QAAoB,QAAQ,SAAS,UAAU;EAChE,MAAM,YAAY,QAAQ,SAAS;AAEnC,SAAO,KAAK;GACR,IAAI,QAAQ;GACZ,MAAM,QAAQ;GACd;GACA;GACA;GACA,MAAM;GACT,CAAC;AAEF,MAAI,WAAW;GACX,MAAM,WAAY,QAAuC;AACzD,UAAO,KAAK,GAAG,YAAY,UAAU,QAAQ,IAAI,QAAQ,EAAE,CAAC;;AAGhE,SAAO;IACR,EAAE,CAAC;AAEN,QAAO,KAAK;EACR,IAAI,mBAAmB;EACvB,MAAM;EACN;EACA;EACA,OAAO,MAAM;EACb,MAAM,KAAA;EACT,CAAC;AAEF,QAAO;;;;;AAMX,MAAa,kBAAkB,OAAmB,aAAkC;AAGhF,QAFuB,MAAM,QAAQ,SAAS,KAAK,aAAa,SAAS,CAEnD,QAAQ,QAAQ,UAAU;AAC5C,SAAO,IAAI,MAAM,GAAG;AACpB,OAAK,MAAM,cAAc,eAAe,OAAO,MAAM,GAAG,CACpD,QAAO,IAAI,WAAW;AAE1B,SAAO;oBACR,IAAI,KAAa,CAAC;;;;;;;;;;;;;;ACxCzB,MAAa,kBACT,YACA,uBAUC;CACD,MAAM,CAAC,OAAO,YAAY,eAA2B,YAAY,WAAW,QAAQ,CAAC;CACrF,MAAM,eAAe,OAAO,EAAE;CAC9B,MAAM,iBAAiB,OAAmB,EAAE,CAAC;CAC7C,MAAM,eAAe,OAAsB,KAAK;CAEhD,MAAM,gBAAgB,OAAO,WAAW;AACxC,eAAc,UAAU;CAExB,MAAM,wBAAwB,OAAO,mBAAmB;AACxD,uBAAsB,UAAU;AAGhC,iBAAgB;AACZ,WAAS,YAAY,WAAW,QAAQ,CAAC;IAC1C,CAAC,WAAW,CAAC;CAEhB,MAAM,cAAc,aAAa,WAAiC;EAC9D,MAAM,UAAU,OAAO,QAAQ;AAC/B,WAAS,YAAY,QAAQ,QAAQ,CAAC;AACtC,wBAAsB,UAAU,QAAQ;IACzC,EAAE,CAAC;CAEN,MAAM,gBAAgB,aACjB,WAA0B,SAAiC;EACxD,MAAM,SAAS,IAAI,qBAAqB,cAAc,QAAQ;EAC9D,MAAM,WAAW,aAAa,KAAA;EAC9B,MAAM,KAAK,OAAO,QAAQ;AAE1B,MAAI,KAAK,SAAS,UAAW,QAAO,WAAW;GAAE,GAAG;GAAM;GAAI,EAAE,SAAS;MACpE,QAAO,SAAS;GAAE,GAAG;GAAM;GAAI,EAAsB,SAAS;AAEnE,cAAY,OAAO;AACnB,SAAO;IAEX,CAAC,YAAY,CAChB;CAED,MAAM,mBAAmB,aACpB,IAAY,YAAyB;EAClC,MAAM,SAAS,IAAI,qBAAqB,cAAc,QAAQ;AAE9D,MAAI,QAAQ,SAAS,WAAW;GAC5B,MAAM,EAAE,IAAI,GAAG,MAAM,IAAI,SAAS,KAAK,GAAG,YAAY;AACtD,UAAO,cAAc,IAAI,QAAQ;SAC9B;GACH,MAAM,EAAE,IAAI,GAAG,MAAM,IAAI,GAAG,YAAY;AACxC,UAAO,YAAY,IAAI,QAAQ;;AAGnC,cAAY,OAAO;IAEvB,CAAC,YAAY,CAChB;CAED,MAAM,mBAAmB,aACpB,WAAmB;EAChB,MAAM,SAAS,IAAI,qBAAqB,cAAc,QAAQ;AAC9D,SAAO,WAAW,OAAO;AACzB,cAAY,OAAO;IAEvB,CAAC,YAAY,CAChB;CAED,MAAM,mBAA2E,UAAU;EACvF,MAAM,EAAE,WAAW,MAAM;AACzB,MAAI,CAAC,OAAQ;EAEb,MAAM,OAAO,MAAM,MAAM,EAAE,SAAS,OAAO,OAAO,GAAG;AACrD,MAAI,CAAC,KAAM;AAEX,eAAa,UAAU,KAAK;AAC5B,eAAa,UAAU,OAAO;AAE9B,YAAU,SAAS;AACf,kBAAe,UAAU,EAAE;GAC3B,MAAM,cAAc,eAAe,MAAM,OAAO,GAAa;AAE7D,UAAO,KAAK,QAAQ,MAAM;AACtB,QAAI,YAAY,IAAI,EAAE,GAAG,EAAE;AACvB,oBAAe,UAAU,CAAC,GAAG,eAAe,SAAS,EAAE;AACvD,YAAO;;AAEX,WAAO;KACT;IACJ;;CAGN,MAAM,kBAAyE,OAAO,YAAY;EAC9F,MAAM,EAAE,QAAQ,WAAW,MAAM;AACjC,QAAM,gBAAgB;AAEtB,MAAI,UAAU,UAAU,OAAO,OAAO,OAAO,GACzC,WAAU,SAAS;GACf,MAAM,aAAa,QAAQ,cAAc,UAAU;GACnD,MAAM,YAAY,aAAa,YAAA,GAAwB;GACvD,MAAM,iBAAiB,aAAa,UAAU;GAE9C,MAAM,EAAE,OAAO,aAAa,cAAc,MAAM,OAAO,IAAc,gBAAA,EAA0B;AAG/F,UADe,KAAK,MAAM,MAAM,CAClB,KAAK,SAAU,KAAK,OAAO,OAAO,KAAK;IAAE,GAAG;IAAM;IAAO;IAAU,GAAG,KAAM;IAC5F;;CAIV,MAAM,kBAAyE,OAAO,YAAY;AAC9F,MAAI,MAAM,iBAAkB;EAE5B,MAAM,EAAE,QAAQ,WAAW,MAAM;AACjC,MAAI,CAAC,UAAU,CAAC,OAAQ;EAExB,MAAM,aAAa,QAAQ,cAAc,UAAU;EACnD,MAAM,YAAY,aAAa,YAAA,GAAwB;EACvD,MAAM,iBAAiB,aAAa,UAAU;EAE9C,MAAM,EAAE,OAAO,aAAa,cAAc,OAAO,OAAO,IAAc,gBAAA,EAA0B;AAEhG,MACK,OAAO,MAA4B,UAAU,SAC7C,OAAO,MAAsC,aAAa,SAE3D,WAAU,SAAS,KAAK,KAAK,SAAU,KAAK,OAAO,OAAO,KAAK;GAAE,GAAG;GAAM;GAAO;GAAU,GAAG,KAAM,CAAC;;CAI7G,MAAM,iBAAuE,UAAU;AACnF,MAAI,MAAM,UAAU;AAChB,YAAS,YAAY,cAAc,QAAQ,QAAQ,CAAC;AACpD,gBAAa,UAAU;AACvB,kBAAe,UAAU,EAAE;AAC3B;;EAGJ,MAAM,YAAY,aAAa;AAC/B,MAAI,cAAc,KAAM;EAExB,MAAM,cAAc,MAAM,MAAM,MAAM,EAAE,OAAO,UAAU;AACzD,MAAI,CAAC,YAAa;EAGlB,MAAM,iBAAiB,YAAY;EACnC,MAAM,mBAAmB,MAAM,WAAW,MAAM,EAAE,OAAO,UAAU;EACnE,IAAI,eAAe;AACnB,OAAK,IAAI,IAAI,GAAG,IAAI,kBAAkB,KAAK;GACvC,MAAM,UAAU,MAAM;AACtB,OAAI,WAAW,QAAQ,aAAa,kBAAkB,QAAQ,SAAS,kBACnE;;EAIR,MAAM,SAAS,IAAI,qBAAqB,cAAc,QAAQ;AAC9D,SAAO,SAAS,WAAW,kBAAkB,KAAA,GAAW,aAAa;AACrE,cAAY,OAAO;AAEnB,eAAa,UAAU;AACvB,iBAAe,UAAU,EAAE;;AAG/B,QAAO;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACH;;;;AC1LL,MAAa,eAAqC,EAC9C,YACA,WAAW,WACX,SAAS,kBACT,OAAO,gBACP,gBAAgB,yBAChB,YACA,yBACE;CACF,MAAM,EACF,OACA,eACA,kBACA,kBACA,iBACA,gBACA,gBACA,kBACA,eAAe,YAAY,mBAAmB;AAElD,QACI,oBAAC,kBAAD;EACI,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,WAAW;YACX,oBAAC,WAAD,EAAA,UACK,MAAM,KAAK,MAAM,UAAU;GACxB,MAAM,SAAS,UACX,cAAc,KAAK,SAAS,YAAY,KAAK,KAAK,KAAK,UAAU,MAAM;GAC3E,MAAM,iBAAiB,iBAAiB,KAAK,GAAG;GAChD,MAAM,YAAY,UAAuB,iBAAiB,MAAM,IAAI,MAAM;AAE1E,UAAO,UAAU,KAAK,GAClB,oBAAC,SAAD;IAEI,IAAI,KAAK;IACT,OAAO,KAAK;IACL;IACP,UAAU,KAAK;IACf,MAAM,KAAK;IACX,YAAY,eAAe,KAAK;IAChC,WAAW;IACJ;IACG;IACA;IACZ,EAXO,KAAK,GAWZ,GACF,QAAQ,KAAK,GACb,oBAAC,OAAD;IAEI,IAAI,KAAK;IACT,OAAO,KAAK;IACL;IACP,UAAU,KAAK;IACf,MAAM,KAAK;IACX,MAAM,KAAK;IACX,YAAY,eAAe,KAAK;IAChC,WAAW;IACJ;IACG;IACA;IACZ,EAZO,KAAK,GAYZ,GACF,iBAAiB,KAAK,GACtB,oBAAC,gBAAD;IAEI,OAAO,KAAK;IACZ,UAAU,KAAK;IACf,WAAW;IACJ;IACP,UAAU;IACZ,EANO,KAAK,GAMZ,GACF;IACN,EACM,CAAA;EACG,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bluprynt/forms-builder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/blupryntco/forms-engine.git",
|
|
7
|
+
"directory": "packages/builder"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://blupryntco.github.io/forms-engine/",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.mjs",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@dnd-kit/abstract": "^0.3.0",
|
|
26
|
+
"@dnd-kit/dom": "^0.3.0",
|
|
27
|
+
"@dnd-kit/helpers": "^0.3.0",
|
|
28
|
+
"@dnd-kit/react": "^0.3.0",
|
|
29
|
+
"@dnd-kit/state": "^0.3.0",
|
|
30
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
31
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
32
|
+
"@bluprynt/forms-core": "1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@dnd-kit/abstract": "0.3.2",
|
|
36
|
+
"@dnd-kit/dom": "0.3.2",
|
|
37
|
+
"@dnd-kit/helpers": "0.3.2",
|
|
38
|
+
"@dnd-kit/react": "0.3.2",
|
|
39
|
+
"@dnd-kit/state": "0.3.2",
|
|
40
|
+
"@testing-library/react": "16.3.2",
|
|
41
|
+
"@types/jest": "29.5.14",
|
|
42
|
+
"@types/react": "19.1.8",
|
|
43
|
+
"@types/react-dom": "19.1.6",
|
|
44
|
+
"jest": "29.7.0",
|
|
45
|
+
"jest-environment-jsdom": "30.3.0",
|
|
46
|
+
"react": "19.1.0",
|
|
47
|
+
"react-dom": "19.1.0",
|
|
48
|
+
"ts-jest": "29.4.6",
|
|
49
|
+
"tsdown": "0.21.1",
|
|
50
|
+
"typescript": "5.9.3",
|
|
51
|
+
"@bluprynt/forms-core": "1.0.0",
|
|
52
|
+
"@repo/typescript-config": "0.0.0"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsdown",
|
|
56
|
+
"dev": "tsdown --watch",
|
|
57
|
+
"lint": "biome check .",
|
|
58
|
+
"check-types": "tsc --noEmit",
|
|
59
|
+
"test": "jest",
|
|
60
|
+
"test:watch": "jest --watch",
|
|
61
|
+
"test:cov": "jest --coverage"
|
|
62
|
+
}
|
|
63
|
+
}
|