@jbpark/live-editor 4.6.1 → 5.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.ko.md +2 -2
- package/README.md +2 -2
- package/dist/{boundary-BLB80tyw.js → boundary-6_nTsWxP.js} +2 -2
- package/dist/{boundary-BLB80tyw.js.map → boundary-6_nTsWxP.js.map} +1 -1
- package/dist/{compile-Dxas-D05.js → compile-DwOyWrmI.js} +8 -7
- package/dist/compile-DwOyWrmI.js.map +1 -0
- package/dist/constants-D-XKuEY2.js +58 -0
- package/dist/constants-D-XKuEY2.js.map +1 -0
- package/dist/{context-B8MzP7_p.js → context-DDR1xVKo.js} +3 -3
- package/dist/{context-B8MzP7_p.js.map → context-DDR1xVKo.js.map} +1 -1
- package/dist/{core-Bgfek364.js → core-CKazr4tT.js} +2 -2
- package/dist/core-CKazr4tT.js.map +1 -0
- package/dist/{detect-typescript-Dr_oLniM.js → detect-typescript-CoAO-QUJ.js} +2 -2
- package/dist/{detect-typescript-Dr_oLniM.js.map → detect-typescript-CoAO-QUJ.js.map} +1 -1
- package/dist/dnd/index.d.ts +2 -2
- package/dist/dnd/index.js +2 -2
- package/dist/{dnd-V8NAxd_R.js → dnd-BlC1gqUy.js} +2558 -867
- package/dist/dnd-BlC1gqUy.js.map +1 -0
- package/dist/document-BQhb7YR8.d.ts +17 -0
- package/dist/{document-CHO215iP.js → document-DcwIEHRV.js} +189 -104
- package/dist/document-DcwIEHRV.js.map +1 -0
- package/dist/editor/index.js +2 -2
- package/dist/{editor-CMElfHer.js → editor-Bty5XzpV.js} +2 -2
- package/dist/{editor-CMElfHer.js.map → editor-Bty5XzpV.js.map} +1 -1
- package/dist/error/index.d.ts +1 -1
- package/dist/error/index.js +2 -2
- package/dist/{error-CN_GCnXB.js → error-DyYx2NQ2.js} +17 -31
- package/dist/error-DyYx2NQ2.js.map +1 -0
- package/dist/{index-CiWB0zFb.d.ts → index-0VB5C3Jk.d.ts} +28 -50
- package/dist/{index-DpDU1IAw.d.ts → index-92L7F440.d.ts} +2 -2
- package/dist/index-C8h8TcIl.d.ts +15 -0
- package/dist/{index-DQkL37eL.d.ts → index-Cl79M7ZJ.d.ts} +6 -4
- package/dist/{index-CRwEhJ7w.d.ts → index-kKUjo9lr.d.ts} +2 -2
- package/dist/{index-DrWtSKXu.d.ts → index-tAiuHZsC.d.ts} +2 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/{messages-CSrMI4Rm.js → messages-Bt5WXKTE.js} +5 -2
- package/dist/messages-Bt5WXKTE.js.map +1 -0
- package/dist/{messages-IBPs4TkZ.d.ts → messages-Yb3b7YJt.d.ts} +5 -2
- package/dist/preview/index.d.ts +1 -1
- package/dist/preview/index.js +2 -2
- package/dist/{preview-CtyG5UbH.js → preview-DC0pyOdu.js} +4 -4
- package/dist/preview-DC0pyOdu.js.map +1 -0
- package/dist/provider/index.d.ts +2 -2
- package/dist/provider/index.js +2 -2
- package/dist/{sections-jYuqS4KB.js → sections-4uirsXRU.js} +4 -7
- package/dist/sections-4uirsXRU.js.map +1 -0
- package/dist/style.css +776 -346
- package/dist/{validate-DB59Z_Nx.d.ts → types-Czps1Txy.d.ts} +12 -28
- package/dist/update-2PJbwoS8.d.ts +68 -0
- package/dist/{items-BXXtMUNI.js → update-DNInQSM7.js} +143 -392
- package/dist/update-DNInQSM7.js.map +1 -0
- package/dist/{use-dynamic-tailwind-BP1toJkW.js → use-dynamic-tailwind-CSYKXDmX.js} +301 -273
- package/dist/use-dynamic-tailwind-CSYKXDmX.js.map +1 -0
- package/dist/utils/ast/index.d.ts +47 -4
- package/dist/utils/ast/index.js +4 -64
- package/dist/utils/ast/index.js.map +1 -1
- package/dist/utils/index.d.ts +13 -32
- package/dist/utils/index.js +4 -26
- package/dist/validate-CZz30Pnb.js +122 -0
- package/dist/validate-CZz30Pnb.js.map +1 -0
- package/dist/validate-CiA1LF6S.d.ts +33 -0
- package/package.json +9 -9
- package/dist/compile-Dxas-D05.js.map +0 -1
- package/dist/constants-DU17Tdwo.js +0 -1459
- package/dist/constants-DU17Tdwo.js.map +0 -1
- package/dist/core-Bgfek364.js.map +0 -1
- package/dist/dnd-V8NAxd_R.js.map +0 -1
- package/dist/document-CHO215iP.js.map +0 -1
- package/dist/document-DqRSCtsx.d.ts +0 -1614
- package/dist/error-CN_GCnXB.js.map +0 -1
- package/dist/index-C_PfR4X9.d.ts +0 -734
- package/dist/items-BXXtMUNI.js.map +0 -1
- package/dist/messages-CSrMI4Rm.js.map +0 -1
- package/dist/preview-CtyG5UbH.js.map +0 -1
- package/dist/sections-jYuqS4KB.js.map +0 -1
- package/dist/use-dynamic-tailwind-BP1toJkW.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/validate-iLo8IONY.js +0 -64
- package/dist/validate-iLo8IONY.js.map +0 -1
|
@@ -1,24 +1,1434 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as validateBindingValue } from "./validate-
|
|
4
|
-
import { i as useLiveMessages } from "./messages-
|
|
1
|
+
import { C as isJSXElement, D as isJSXIdentifier, E as isJSXFragment, I as isSpreadElement, L as isStringLiteral, N as isObjectExpression, O as isJSXOpeningElement, P as isObjectProperty, S as isJSXAttribute, a as inspectDocument, at as stringLiteral, b as isExpression, ct as traverseFast, d as applyEdits, dt as parseExpression, k as isJSXSpreadAttribute, l as fillIdsFrom, n as fillSectionIds, ot as templateElement, p as nanoid, st as templateLiteral, t as createSectionPreviewCache, u as replaceIds, x as isIdentifier } from "./document-DcwIEHRV.js";
|
|
2
|
+
import { r as DATA_ATTR, s as RESERVED_BINDING_PROPERTIES, t as BINDING_PROP } from "./constants-D-XKuEY2.js";
|
|
3
|
+
import { n as validateBindingValue } from "./validate-CZz30Pnb.js";
|
|
4
|
+
import { i as useLiveMessages } from "./messages-Bt5WXKTE.js";
|
|
5
5
|
import { i as usePreview } from "./states-Ci1AvoQ9.js";
|
|
6
|
-
import {
|
|
6
|
+
import { _ as parseArrayExpression, a as getChildrenSignatures, b as generateCode, c as removeIndices, d as getStructuredValue, f as readNodeBindings, g as isLosslesslyEvaluable, h as extractObjectProperties, i as valueToExpression, l as findEditableChildren, m as extractNodeValue, n as updateAll, o as extract, p as canLosslesslyEvaluateSource, r as createNodeFromValue, s as moveSelectedIndices, u as getCurrentValue, v as parseValue } from "./update-DNInQSM7.js";
|
|
7
7
|
import { t as cn } from "./cn-CAnad2vL.js";
|
|
8
|
-
import {
|
|
9
|
-
import { a as
|
|
10
|
-
import {
|
|
11
|
-
import { n as
|
|
12
|
-
import { t as Core } from "./core-
|
|
13
|
-
import { Children, createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { r as preloadScripts } from "./compile-DwOyWrmI.js";
|
|
9
|
+
import { a as replaceSections, i as generateSection, r as extractSections } from "./sections-4uirsXRU.js";
|
|
10
|
+
import { n as Error$1, t as ErrorBoundary } from "./boundary-6_nTsWxP.js";
|
|
11
|
+
import { i as Frame, n as useCompiledModule, t as useDynamicTailwind } from "./use-dynamic-tailwind-CSYKXDmX.js";
|
|
12
|
+
import { t as Core } from "./core-CKazr4tT.js";
|
|
13
|
+
import { Children, createContext, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
14
14
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
15
|
import { DndContext, DragOverlay, KeyboardSensor, MouseSensor, PointerSensor, TouchSensor, closestCenter, useDndContext, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
16
16
|
import { restrictToParentElement, restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
17
17
|
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { useDebounce, useElementPosition, useKeyPress, useLatest, useMultiSelect, usePendingCommit, useResponsiveSize, useSet, useStableValue } from "@jbpark/use-hooks";
|
|
19
|
+
import { Button, Card, Checkbox, ColorPicker, DatePicker, Drawer, Input, RichTextEditor, Select, Space, Splitter, TimePicker, Toast, Typography } from "@jbpark/ui-kit";
|
|
20
20
|
import { CSS as CSS$1 } from "@dnd-kit/utilities";
|
|
21
21
|
import { ArrowDown, ArrowUp, ChevronDown, ChevronRight, ChevronUp, Copy, Crosshair, GripVertical, LayoutGrid, Plus, Trash, X } from "lucide-react";
|
|
22
|
+
//#region src/constants/palette-sections.ts
|
|
23
|
+
const PALETTE_SECTIONS = [
|
|
24
|
+
{
|
|
25
|
+
id: "hero",
|
|
26
|
+
name: "Hero",
|
|
27
|
+
code: `
|
|
28
|
+
<section
|
|
29
|
+
data-name="Hero"
|
|
30
|
+
className={cn(
|
|
31
|
+
'text-center text-white',
|
|
32
|
+
//
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<ui.Space
|
|
36
|
+
data-id=""
|
|
37
|
+
data-binding={[
|
|
38
|
+
{
|
|
39
|
+
label: 'Background Style',
|
|
40
|
+
property: 'style',
|
|
41
|
+
type: 'object',
|
|
42
|
+
// Types the keys inside the object value; a leaf takes the same
|
|
43
|
+
// fields as a top-level binding.
|
|
44
|
+
render: {
|
|
45
|
+
backgroundColor: { type: 'color', label: 'Background Color' },
|
|
46
|
+
},
|
|
47
|
+
// Not a library field, so it arrives as meta.group.
|
|
48
|
+
group: 'layout',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'Content Spacing',
|
|
52
|
+
property: 'size',
|
|
53
|
+
type: 'number',
|
|
54
|
+
// Constraints the library validates with or without a widget.
|
|
55
|
+
min: 0,
|
|
56
|
+
max: 40,
|
|
57
|
+
// Presentation only: the built-in panel ignores it and shows a
|
|
58
|
+
// number input; a custom panel can switch on widget.type.
|
|
59
|
+
widget: { type: 'slider', step: 4, unit: 'px' },
|
|
60
|
+
group: 'layout',
|
|
61
|
+
},
|
|
62
|
+
]}
|
|
63
|
+
style={{
|
|
64
|
+
backgroundColor: '#6366f1',
|
|
65
|
+
}}
|
|
66
|
+
orientation="vertical"
|
|
67
|
+
size={16}
|
|
68
|
+
align="center"
|
|
69
|
+
className={cn(
|
|
70
|
+
'px-5 py-15',
|
|
71
|
+
//
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
<h1
|
|
75
|
+
data-id=""
|
|
76
|
+
data-binding={[
|
|
77
|
+
{
|
|
78
|
+
label: 'Title',
|
|
79
|
+
property: 'innerText',
|
|
80
|
+
type: 'string',
|
|
81
|
+
required: true,
|
|
82
|
+
},
|
|
83
|
+
]}
|
|
84
|
+
className={cn(
|
|
85
|
+
'text-5xl font-bold',
|
|
86
|
+
//
|
|
87
|
+
)}
|
|
88
|
+
>
|
|
89
|
+
Welcome to Live Editor
|
|
90
|
+
</h1>
|
|
91
|
+
<p
|
|
92
|
+
data-id=""
|
|
93
|
+
data-binding={[
|
|
94
|
+
{
|
|
95
|
+
label: 'Description',
|
|
96
|
+
type: 'richtext',
|
|
97
|
+
},
|
|
98
|
+
]}
|
|
99
|
+
className={cn(
|
|
100
|
+
'text-xl',
|
|
101
|
+
//
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
Build <strong>dynamic components</strong> with drag & drop
|
|
105
|
+
</p>
|
|
106
|
+
<ui.Button
|
|
107
|
+
data-id=""
|
|
108
|
+
data-binding={[
|
|
109
|
+
{
|
|
110
|
+
label: 'Button Text',
|
|
111
|
+
property: 'innerText',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
label: 'Button Icon',
|
|
115
|
+
property: 'icon',
|
|
116
|
+
type: 'jsx',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: 'Button Size',
|
|
120
|
+
property: 'size',
|
|
121
|
+
options: [
|
|
122
|
+
{ label: 'Small', value: 'small' },
|
|
123
|
+
{ label: 'Middle', value: 'middle' },
|
|
124
|
+
{ label: 'Large', value: 'large' },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: 'Button Variant',
|
|
129
|
+
property: 'variant',
|
|
130
|
+
options: [
|
|
131
|
+
{ label: 'Solid', value: 'solid' },
|
|
132
|
+
{ label: 'Outlined', value: 'outlined' },
|
|
133
|
+
{ label: 'Filled', value: 'filled' },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
]}
|
|
137
|
+
icon={<span>🚀</span>}
|
|
138
|
+
size="large"
|
|
139
|
+
variant="solid"
|
|
140
|
+
className={cn(
|
|
141
|
+
'bg-white text-indigo-500',
|
|
142
|
+
'hover:bg-gray-50',
|
|
143
|
+
//
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
Get Started
|
|
147
|
+
</ui.Button>
|
|
148
|
+
</ui.Space>
|
|
149
|
+
</section>
|
|
150
|
+
`
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "features",
|
|
154
|
+
name: "Features",
|
|
155
|
+
code: `
|
|
156
|
+
<section
|
|
157
|
+
data-name="Features"
|
|
158
|
+
className={cn(
|
|
159
|
+
'px-5 py-10',
|
|
160
|
+
//
|
|
161
|
+
)}
|
|
162
|
+
>
|
|
163
|
+
<ui.Space
|
|
164
|
+
data-id=""
|
|
165
|
+
data-binding={[
|
|
166
|
+
{
|
|
167
|
+
label: 'Feature Cards',
|
|
168
|
+
property: 'children',
|
|
169
|
+
},
|
|
170
|
+
]}
|
|
171
|
+
className={cn(
|
|
172
|
+
'mx-auto max-w-7xl',
|
|
173
|
+
//
|
|
174
|
+
)}
|
|
175
|
+
size="large"
|
|
176
|
+
wrap
|
|
177
|
+
>
|
|
178
|
+
<ui.Card
|
|
179
|
+
className={cn(
|
|
180
|
+
'min-w-70 flex-1 text-center',
|
|
181
|
+
//
|
|
182
|
+
)}
|
|
183
|
+
>
|
|
184
|
+
<ui.Space orientation="vertical" size="middle" align="center">
|
|
185
|
+
<div
|
|
186
|
+
data-id=""
|
|
187
|
+
data-binding={[
|
|
188
|
+
{
|
|
189
|
+
label: 'Icon',
|
|
190
|
+
property: 'innerText',
|
|
191
|
+
},
|
|
192
|
+
]}
|
|
193
|
+
className={cn(
|
|
194
|
+
'text-5xl',
|
|
195
|
+
//
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
🚀
|
|
199
|
+
</div>
|
|
200
|
+
<h3
|
|
201
|
+
data-id=""
|
|
202
|
+
data-binding={[
|
|
203
|
+
{
|
|
204
|
+
label: 'Title',
|
|
205
|
+
property: 'innerText',
|
|
206
|
+
},
|
|
207
|
+
]}
|
|
208
|
+
className={cn(
|
|
209
|
+
'text-2xl text-gray-800',
|
|
210
|
+
//
|
|
211
|
+
)}
|
|
212
|
+
>
|
|
213
|
+
Feature 1
|
|
214
|
+
</h3>
|
|
215
|
+
<p
|
|
216
|
+
data-id=""
|
|
217
|
+
data-binding={[
|
|
218
|
+
{
|
|
219
|
+
label: 'Description',
|
|
220
|
+
property: 'innerText',
|
|
221
|
+
},
|
|
222
|
+
]}
|
|
223
|
+
className={cn(
|
|
224
|
+
'text-gray-600',
|
|
225
|
+
//
|
|
226
|
+
)}
|
|
227
|
+
>
|
|
228
|
+
Amazing feature description
|
|
229
|
+
</p>
|
|
230
|
+
</ui.Space>
|
|
231
|
+
</ui.Card>
|
|
232
|
+
<ui.Card
|
|
233
|
+
className={cn(
|
|
234
|
+
'min-w-70 flex-1 text-center',
|
|
235
|
+
//
|
|
236
|
+
)}
|
|
237
|
+
>
|
|
238
|
+
<ui.Space orientation="vertical" size="middle" align="center">
|
|
239
|
+
<div
|
|
240
|
+
data-id=""
|
|
241
|
+
data-binding={[
|
|
242
|
+
{
|
|
243
|
+
label: 'Icon',
|
|
244
|
+
property: 'innerText',
|
|
245
|
+
},
|
|
246
|
+
]}
|
|
247
|
+
className={cn(
|
|
248
|
+
'text-5xl',
|
|
249
|
+
//
|
|
250
|
+
)}
|
|
251
|
+
>
|
|
252
|
+
⚡
|
|
253
|
+
</div>
|
|
254
|
+
<h3
|
|
255
|
+
data-id=""
|
|
256
|
+
data-binding={[
|
|
257
|
+
{
|
|
258
|
+
label: 'Title',
|
|
259
|
+
property: 'innerText',
|
|
260
|
+
},
|
|
261
|
+
]}
|
|
262
|
+
className={cn(
|
|
263
|
+
'text-2xl text-gray-800',
|
|
264
|
+
//
|
|
265
|
+
)}
|
|
266
|
+
>
|
|
267
|
+
Feature 2
|
|
268
|
+
</h3>
|
|
269
|
+
<p
|
|
270
|
+
data-id=""
|
|
271
|
+
data-binding={[
|
|
272
|
+
{
|
|
273
|
+
label: 'Description',
|
|
274
|
+
property: 'innerText',
|
|
275
|
+
},
|
|
276
|
+
]}
|
|
277
|
+
className={cn(
|
|
278
|
+
'text-gray-600',
|
|
279
|
+
//
|
|
280
|
+
)}
|
|
281
|
+
>
|
|
282
|
+
Another great feature
|
|
283
|
+
</p>
|
|
284
|
+
</ui.Space>
|
|
285
|
+
</ui.Card>
|
|
286
|
+
<ui.Card
|
|
287
|
+
className={cn(
|
|
288
|
+
'min-w-70 flex-1 text-center',
|
|
289
|
+
//
|
|
290
|
+
)}
|
|
291
|
+
>
|
|
292
|
+
<ui.Space orientation="vertical" size="middle" align="center">
|
|
293
|
+
<div
|
|
294
|
+
data-id=""
|
|
295
|
+
data-binding={[
|
|
296
|
+
{
|
|
297
|
+
label: 'Icon',
|
|
298
|
+
property: 'innerText',
|
|
299
|
+
},
|
|
300
|
+
]}
|
|
301
|
+
className={cn(
|
|
302
|
+
'text-5xl',
|
|
303
|
+
//
|
|
304
|
+
)}
|
|
305
|
+
>
|
|
306
|
+
✨
|
|
307
|
+
</div>
|
|
308
|
+
<h3
|
|
309
|
+
data-id=""
|
|
310
|
+
data-binding={[
|
|
311
|
+
{
|
|
312
|
+
label: 'Title',
|
|
313
|
+
property: 'innerText',
|
|
314
|
+
},
|
|
315
|
+
]}
|
|
316
|
+
className={cn(
|
|
317
|
+
'text-2xl text-gray-800',
|
|
318
|
+
//
|
|
319
|
+
)}
|
|
320
|
+
>
|
|
321
|
+
Feature 3
|
|
322
|
+
</h3>
|
|
323
|
+
<p
|
|
324
|
+
data-id=""
|
|
325
|
+
data-binding={[
|
|
326
|
+
{
|
|
327
|
+
label: 'Description',
|
|
328
|
+
property: 'innerText',
|
|
329
|
+
},
|
|
330
|
+
]}
|
|
331
|
+
className={cn(
|
|
332
|
+
'text-gray-600',
|
|
333
|
+
//
|
|
334
|
+
)}
|
|
335
|
+
>
|
|
336
|
+
One more awesome thing
|
|
337
|
+
</p>
|
|
338
|
+
</ui.Space>
|
|
339
|
+
</ui.Card>
|
|
340
|
+
</ui.Space>
|
|
341
|
+
</section>
|
|
342
|
+
`
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: "about",
|
|
346
|
+
name: "About",
|
|
347
|
+
code: `
|
|
348
|
+
<section
|
|
349
|
+
data-name="About"
|
|
350
|
+
className={cn(
|
|
351
|
+
'mx-auto max-w-3xl px-5 py-10',
|
|
352
|
+
//
|
|
353
|
+
)}
|
|
354
|
+
>
|
|
355
|
+
<ui.Typography.Title
|
|
356
|
+
data-id=""
|
|
357
|
+
data-binding={[{ label: 'Title', property: 'innerText' }]}
|
|
358
|
+
level={2}
|
|
359
|
+
className={cn(
|
|
360
|
+
'mb-5 text-4xl text-gray-800',
|
|
361
|
+
//
|
|
362
|
+
)}
|
|
363
|
+
>
|
|
364
|
+
About Live Editor
|
|
365
|
+
</ui.Typography.Title>
|
|
366
|
+
<ui.Typography.Paragraph
|
|
367
|
+
data-id=""
|
|
368
|
+
data-binding={[
|
|
369
|
+
{
|
|
370
|
+
label: 'Description 1',
|
|
371
|
+
type: 'richtext',
|
|
372
|
+
},
|
|
373
|
+
]}
|
|
374
|
+
className={cn(
|
|
375
|
+
'mb-4 text-lg leading-relaxed text-gray-600',
|
|
376
|
+
//
|
|
377
|
+
)}
|
|
378
|
+
>
|
|
379
|
+
Live Editor is a powerful visual development tool that enables you to
|
|
380
|
+
create stunning web interfaces through an intuitive drag-and-drop
|
|
381
|
+
experience. Built with modern web technologies, it streamlines your
|
|
382
|
+
workflow and accelerates development cycles.
|
|
383
|
+
</ui.Typography.Paragraph>
|
|
384
|
+
<ui.Typography.Paragraph
|
|
385
|
+
data-id=""
|
|
386
|
+
data-binding={[
|
|
387
|
+
{
|
|
388
|
+
label: 'Description 2',
|
|
389
|
+
type: 'richtext',
|
|
390
|
+
},
|
|
391
|
+
]}
|
|
392
|
+
className={cn(
|
|
393
|
+
'text-lg leading-relaxed text-gray-600',
|
|
394
|
+
//
|
|
395
|
+
)}
|
|
396
|
+
>
|
|
397
|
+
Whether you're a seasoned developer or just starting out, Live
|
|
398
|
+
Editor provides the flexibility and control you need to bring your
|
|
399
|
+
creative vision to life. Focus on what matters most - building great
|
|
400
|
+
products.
|
|
401
|
+
</ui.Typography.Paragraph>
|
|
402
|
+
</section>
|
|
403
|
+
`
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "cta",
|
|
407
|
+
name: "CTA",
|
|
408
|
+
code: `
|
|
409
|
+
<section
|
|
410
|
+
data-name="CTA"
|
|
411
|
+
className={cn(
|
|
412
|
+
'bg-gray-50 px-5 py-12 text-center',
|
|
413
|
+
//
|
|
414
|
+
)}
|
|
415
|
+
>
|
|
416
|
+
<div
|
|
417
|
+
className={cn(
|
|
418
|
+
'mx-auto max-w-xl',
|
|
419
|
+
//
|
|
420
|
+
)}
|
|
421
|
+
>
|
|
422
|
+
<ui.Typography.Title
|
|
423
|
+
data-id=""
|
|
424
|
+
data-binding={[{ label: 'Title', property: 'innerText' }]}
|
|
425
|
+
level={2}
|
|
426
|
+
className={cn(
|
|
427
|
+
'mb-4 text-3xl text-gray-800',
|
|
428
|
+
//
|
|
429
|
+
)}
|
|
430
|
+
>
|
|
431
|
+
Ready to get started?
|
|
432
|
+
</ui.Typography.Title>
|
|
433
|
+
<ui.Typography.Paragraph
|
|
434
|
+
data-id=""
|
|
435
|
+
data-binding={[
|
|
436
|
+
{
|
|
437
|
+
label: 'Description',
|
|
438
|
+
property: 'innerText',
|
|
439
|
+
},
|
|
440
|
+
]}
|
|
441
|
+
className={cn(
|
|
442
|
+
'mb-6 text-lg text-gray-600',
|
|
443
|
+
//
|
|
444
|
+
)}
|
|
445
|
+
>
|
|
446
|
+
Start exploring what Live Editor can do for your projects
|
|
447
|
+
</ui.Typography.Paragraph>
|
|
448
|
+
<ui.Space
|
|
449
|
+
data-id=""
|
|
450
|
+
data-binding={[{ label: 'Buttons', property: 'children' }]}
|
|
451
|
+
className={cn(
|
|
452
|
+
'flex flex-wrap justify-center gap-4',
|
|
453
|
+
//
|
|
454
|
+
)}
|
|
455
|
+
>
|
|
456
|
+
<ui.Button
|
|
457
|
+
data-id=""
|
|
458
|
+
data-binding={[
|
|
459
|
+
{
|
|
460
|
+
label: 'Button 1 Text',
|
|
461
|
+
property: 'innerText',
|
|
462
|
+
},
|
|
463
|
+
]}
|
|
464
|
+
size="large"
|
|
465
|
+
className={cn(
|
|
466
|
+
'bg-indigo-500 px-8 text-base text-white',
|
|
467
|
+
'hover:bg-indigo-600',
|
|
468
|
+
//
|
|
469
|
+
)}
|
|
470
|
+
>
|
|
471
|
+
View Documentation
|
|
472
|
+
</ui.Button>
|
|
473
|
+
<ui.Button
|
|
474
|
+
data-id=""
|
|
475
|
+
data-binding={[
|
|
476
|
+
{
|
|
477
|
+
label: 'Button 2 Text',
|
|
478
|
+
property: 'innerText',
|
|
479
|
+
},
|
|
480
|
+
]}
|
|
481
|
+
variant="outlined"
|
|
482
|
+
size="large"
|
|
483
|
+
className={cn(
|
|
484
|
+
'border-2 border-indigo-500 px-8 text-base text-indigo-500',
|
|
485
|
+
'hover:bg-indigo-50',
|
|
486
|
+
//
|
|
487
|
+
)}
|
|
488
|
+
>
|
|
489
|
+
View Examples
|
|
490
|
+
</ui.Button>
|
|
491
|
+
</ui.Space>
|
|
492
|
+
</div>
|
|
493
|
+
</section>
|
|
494
|
+
`
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: "getting-started",
|
|
498
|
+
name: "Getting Started",
|
|
499
|
+
code: `
|
|
500
|
+
<section
|
|
501
|
+
data-name="Getting Started"
|
|
502
|
+
className={cn(
|
|
503
|
+
'px-5 py-16',
|
|
504
|
+
//
|
|
505
|
+
)}
|
|
506
|
+
>
|
|
507
|
+
<div
|
|
508
|
+
className={cn(
|
|
509
|
+
'mx-auto max-w-5xl',
|
|
510
|
+
//
|
|
511
|
+
)}
|
|
512
|
+
>
|
|
513
|
+
<ui.Typography.Title
|
|
514
|
+
data-id=""
|
|
515
|
+
data-binding={[{ label: 'Title', property: 'innerText' }]}
|
|
516
|
+
level={2}
|
|
517
|
+
className={cn(
|
|
518
|
+
'mb-12 text-center text-4xl font-bold text-gray-800',
|
|
519
|
+
//
|
|
520
|
+
)}
|
|
521
|
+
>
|
|
522
|
+
How to Get Started
|
|
523
|
+
</ui.Typography.Title>
|
|
524
|
+
<div
|
|
525
|
+
className={cn(
|
|
526
|
+
'grid gap-8 md:grid-cols-3',
|
|
527
|
+
//
|
|
528
|
+
)}
|
|
529
|
+
>
|
|
530
|
+
<ui.Card
|
|
531
|
+
data-id=""
|
|
532
|
+
data-binding={[
|
|
533
|
+
{
|
|
534
|
+
label: 'Card 1 Content',
|
|
535
|
+
property: 'children',
|
|
536
|
+
},
|
|
537
|
+
]}
|
|
538
|
+
className={cn(
|
|
539
|
+
'rounded-lg border-2 border-gray-200 bg-white p-8',
|
|
540
|
+
//
|
|
541
|
+
)}
|
|
542
|
+
>
|
|
543
|
+
<ui.Typography.Title
|
|
544
|
+
data-id=""
|
|
545
|
+
data-binding={[
|
|
546
|
+
{
|
|
547
|
+
label: 'Card 1 Title',
|
|
548
|
+
property: 'innerText',
|
|
549
|
+
},
|
|
550
|
+
]}
|
|
551
|
+
level={3}
|
|
552
|
+
className={cn(
|
|
553
|
+
'mb-2 text-2xl font-bold text-gray-800',
|
|
554
|
+
//
|
|
555
|
+
)}
|
|
556
|
+
>
|
|
557
|
+
Explore
|
|
558
|
+
</ui.Typography.Title>
|
|
559
|
+
<ui.Typography.Paragraph
|
|
560
|
+
data-id=""
|
|
561
|
+
data-binding={[
|
|
562
|
+
{
|
|
563
|
+
label: 'Card 1 Icon',
|
|
564
|
+
property: 'innerText',
|
|
565
|
+
},
|
|
566
|
+
]}
|
|
567
|
+
className={cn(
|
|
568
|
+
'mb-6 text-4xl font-bold text-indigo-600',
|
|
569
|
+
//
|
|
570
|
+
)}
|
|
571
|
+
>
|
|
572
|
+
📚
|
|
573
|
+
</ui.Typography.Paragraph>
|
|
574
|
+
<ul
|
|
575
|
+
className={cn(
|
|
576
|
+
'mb-8 space-y-3 text-gray-600',
|
|
577
|
+
//
|
|
578
|
+
)}
|
|
579
|
+
>
|
|
580
|
+
<li>✓ Read documentation</li>
|
|
581
|
+
<li>✓ Browse examples</li>
|
|
582
|
+
<li>✓ Try the demo</li>
|
|
583
|
+
</ul>
|
|
584
|
+
<ui.Button
|
|
585
|
+
data-id=""
|
|
586
|
+
data-binding={[
|
|
587
|
+
{
|
|
588
|
+
label: 'Card 1 Button',
|
|
589
|
+
property: 'innerText',
|
|
590
|
+
},
|
|
591
|
+
]}
|
|
592
|
+
variant="outlined"
|
|
593
|
+
size="large"
|
|
594
|
+
block
|
|
595
|
+
className={cn(
|
|
596
|
+
'border-2 border-indigo-500 text-indigo-500',
|
|
597
|
+
'hover:bg-indigo-50',
|
|
598
|
+
//
|
|
599
|
+
)}
|
|
600
|
+
>
|
|
601
|
+
View Docs
|
|
602
|
+
</ui.Button>
|
|
603
|
+
</ui.Card>
|
|
604
|
+
<ui.Card
|
|
605
|
+
data-id=""
|
|
606
|
+
data-binding={[
|
|
607
|
+
{
|
|
608
|
+
label: 'Card 2 Content',
|
|
609
|
+
property: 'children',
|
|
610
|
+
},
|
|
611
|
+
]}
|
|
612
|
+
className={cn(
|
|
613
|
+
'relative rounded-lg border-2 border-indigo-500 bg-white p-8',
|
|
614
|
+
//
|
|
615
|
+
)}
|
|
616
|
+
>
|
|
617
|
+
<div
|
|
618
|
+
className={cn(
|
|
619
|
+
'absolute -top-4 left-1/2 -translate-x-1/2',
|
|
620
|
+
'rounded-full bg-indigo-500 px-4 py-1 text-sm text-white',
|
|
621
|
+
//
|
|
622
|
+
)}
|
|
623
|
+
>
|
|
624
|
+
Recommended
|
|
625
|
+
</div>
|
|
626
|
+
<ui.Typography.Title
|
|
627
|
+
data-id=""
|
|
628
|
+
data-binding={[
|
|
629
|
+
{
|
|
630
|
+
label: 'Card 2 Title',
|
|
631
|
+
property: 'innerText',
|
|
632
|
+
},
|
|
633
|
+
]}
|
|
634
|
+
level={3}
|
|
635
|
+
className={cn(
|
|
636
|
+
'mb-2 text-2xl font-bold text-gray-800',
|
|
637
|
+
//
|
|
638
|
+
)}
|
|
639
|
+
>
|
|
640
|
+
Practice
|
|
641
|
+
</ui.Typography.Title>
|
|
642
|
+
<ui.Typography.Paragraph
|
|
643
|
+
data-id=""
|
|
644
|
+
data-binding={[
|
|
645
|
+
{
|
|
646
|
+
label: 'Card 2 Icon',
|
|
647
|
+
property: 'innerText',
|
|
648
|
+
},
|
|
649
|
+
]}
|
|
650
|
+
className={cn(
|
|
651
|
+
'mb-6 text-4xl font-bold text-indigo-600',
|
|
652
|
+
//
|
|
653
|
+
)}
|
|
654
|
+
>
|
|
655
|
+
💻
|
|
656
|
+
</ui.Typography.Paragraph>
|
|
657
|
+
<ul
|
|
658
|
+
className={cn(
|
|
659
|
+
'mb-8 space-y-3 text-gray-600',
|
|
660
|
+
//
|
|
661
|
+
)}
|
|
662
|
+
>
|
|
663
|
+
<li>✓ Interactive playground</li>
|
|
664
|
+
<li>✓ Experiment freely</li>
|
|
665
|
+
<li>✓ Build your own</li>
|
|
666
|
+
</ul>
|
|
667
|
+
<ui.Button
|
|
668
|
+
data-id=""
|
|
669
|
+
data-binding={[
|
|
670
|
+
{
|
|
671
|
+
label: 'Card 2 Button',
|
|
672
|
+
property: 'innerText',
|
|
673
|
+
},
|
|
674
|
+
]}
|
|
675
|
+
size="large"
|
|
676
|
+
block
|
|
677
|
+
className={cn(
|
|
678
|
+
'bg-indigo-500 text-white',
|
|
679
|
+
'hover:bg-indigo-600',
|
|
680
|
+
//
|
|
681
|
+
)}
|
|
682
|
+
>
|
|
683
|
+
Start Building
|
|
684
|
+
</ui.Button>
|
|
685
|
+
</ui.Card>
|
|
686
|
+
<ui.Card
|
|
687
|
+
data-id=""
|
|
688
|
+
data-binding={[
|
|
689
|
+
{
|
|
690
|
+
label: 'Card 3 Content',
|
|
691
|
+
property: 'children',
|
|
692
|
+
},
|
|
693
|
+
]}
|
|
694
|
+
className={cn(
|
|
695
|
+
'rounded-lg border-2 border-gray-200 bg-white p-8',
|
|
696
|
+
//
|
|
697
|
+
)}
|
|
698
|
+
>
|
|
699
|
+
<ui.Typography.Title
|
|
700
|
+
data-id=""
|
|
701
|
+
data-binding={[
|
|
702
|
+
{
|
|
703
|
+
label: 'Card 3 Title',
|
|
704
|
+
property: 'innerText',
|
|
705
|
+
},
|
|
706
|
+
]}
|
|
707
|
+
level={3}
|
|
708
|
+
className={cn(
|
|
709
|
+
'mb-2 text-2xl font-bold text-gray-800',
|
|
710
|
+
//
|
|
711
|
+
)}
|
|
712
|
+
>
|
|
713
|
+
Contribute
|
|
714
|
+
</ui.Typography.Title>
|
|
715
|
+
<ui.Typography.Paragraph
|
|
716
|
+
data-id=""
|
|
717
|
+
data-binding={[
|
|
718
|
+
{
|
|
719
|
+
label: 'Card 3 Icon',
|
|
720
|
+
property: 'innerText',
|
|
721
|
+
},
|
|
722
|
+
]}
|
|
723
|
+
className={cn(
|
|
724
|
+
'mb-6 text-4xl font-bold text-indigo-600',
|
|
725
|
+
//
|
|
726
|
+
)}
|
|
727
|
+
>
|
|
728
|
+
🤝
|
|
729
|
+
</ui.Typography.Paragraph>
|
|
730
|
+
<ul
|
|
731
|
+
className={cn(
|
|
732
|
+
'mb-8 space-y-3 text-gray-600',
|
|
733
|
+
//
|
|
734
|
+
)}
|
|
735
|
+
>
|
|
736
|
+
<li>✓ Report issues</li>
|
|
737
|
+
<li>✓ Suggest features</li>
|
|
738
|
+
<li>✓ Submit pull requests</li>
|
|
739
|
+
</ul>
|
|
740
|
+
<ui.Button
|
|
741
|
+
data-id=""
|
|
742
|
+
data-binding={[
|
|
743
|
+
{
|
|
744
|
+
label: 'Card 3 Button',
|
|
745
|
+
property: 'innerText',
|
|
746
|
+
},
|
|
747
|
+
]}
|
|
748
|
+
variant="outlined"
|
|
749
|
+
size="large"
|
|
750
|
+
block
|
|
751
|
+
className={cn(
|
|
752
|
+
'border-2 border-indigo-500 text-indigo-500',
|
|
753
|
+
'hover:bg-indigo-50',
|
|
754
|
+
//
|
|
755
|
+
)}
|
|
756
|
+
>
|
|
757
|
+
View on GitHub
|
|
758
|
+
</ui.Button>
|
|
759
|
+
</ui.Card>
|
|
760
|
+
</div>
|
|
761
|
+
</div>
|
|
762
|
+
</section>
|
|
763
|
+
`
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
id: "key-features",
|
|
767
|
+
name: "Key Features",
|
|
768
|
+
code: `
|
|
769
|
+
<section
|
|
770
|
+
data-name="Key Features"
|
|
771
|
+
className={cn(
|
|
772
|
+
'bg-indigo-50 px-5 py-16',
|
|
773
|
+
//
|
|
774
|
+
)}
|
|
775
|
+
>
|
|
776
|
+
<div
|
|
777
|
+
className={cn(
|
|
778
|
+
'mx-auto max-w-6xl',
|
|
779
|
+
//
|
|
780
|
+
)}
|
|
781
|
+
>
|
|
782
|
+
<h2
|
|
783
|
+
data-id=""
|
|
784
|
+
data-binding={[{ label: 'Title', property: 'innerText' }]}
|
|
785
|
+
className={cn(
|
|
786
|
+
'mb-12 text-center text-4xl font-bold text-gray-800',
|
|
787
|
+
//
|
|
788
|
+
)}
|
|
789
|
+
>
|
|
790
|
+
Key Features
|
|
791
|
+
</h2>
|
|
792
|
+
<div
|
|
793
|
+
className={cn(
|
|
794
|
+
'grid gap-8 md:grid-cols-2',
|
|
795
|
+
//
|
|
796
|
+
)}
|
|
797
|
+
>
|
|
798
|
+
<div
|
|
799
|
+
className={cn(
|
|
800
|
+
'rounded-lg bg-white p-8 shadow-md',
|
|
801
|
+
//
|
|
802
|
+
)}
|
|
803
|
+
>
|
|
804
|
+
<div
|
|
805
|
+
className={cn(
|
|
806
|
+
'mb-4 text-4xl',
|
|
807
|
+
//
|
|
808
|
+
)}
|
|
809
|
+
>
|
|
810
|
+
🎨
|
|
811
|
+
</div>
|
|
812
|
+
<h3
|
|
813
|
+
className={cn(
|
|
814
|
+
'mb-4 text-2xl font-bold text-gray-800',
|
|
815
|
+
//
|
|
816
|
+
)}
|
|
817
|
+
>
|
|
818
|
+
Visual Editing
|
|
819
|
+
</h3>
|
|
820
|
+
<p
|
|
821
|
+
className={cn(
|
|
822
|
+
'text-gray-600',
|
|
823
|
+
//
|
|
824
|
+
)}
|
|
825
|
+
>
|
|
826
|
+
See your changes in real-time as you build. Live Editor provides
|
|
827
|
+
instant visual feedback, making it easier to understand how code
|
|
828
|
+
affects your interface.
|
|
829
|
+
</p>
|
|
830
|
+
</div>
|
|
831
|
+
<div
|
|
832
|
+
className={cn(
|
|
833
|
+
'rounded-lg bg-white p-8 shadow-md',
|
|
834
|
+
//
|
|
835
|
+
)}
|
|
836
|
+
>
|
|
837
|
+
<div
|
|
838
|
+
className={cn(
|
|
839
|
+
'mb-4 text-4xl',
|
|
840
|
+
//
|
|
841
|
+
)}
|
|
842
|
+
>
|
|
843
|
+
🧩
|
|
844
|
+
</div>
|
|
845
|
+
<h3
|
|
846
|
+
className={cn(
|
|
847
|
+
'mb-4 text-2xl font-bold text-gray-800',
|
|
848
|
+
//
|
|
849
|
+
)}
|
|
850
|
+
>
|
|
851
|
+
Component-Based
|
|
852
|
+
</h3>
|
|
853
|
+
<p
|
|
854
|
+
className={cn(
|
|
855
|
+
'text-gray-600',
|
|
856
|
+
//
|
|
857
|
+
)}
|
|
858
|
+
>
|
|
859
|
+
Build with reusable components that you can drag, drop, and
|
|
860
|
+
customize. Learn modern development patterns while creating your
|
|
861
|
+
interface.
|
|
862
|
+
</p>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
</div>
|
|
866
|
+
</section>
|
|
867
|
+
`
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
id: "stats",
|
|
871
|
+
name: "Stats",
|
|
872
|
+
code: `
|
|
873
|
+
<section
|
|
874
|
+
data-name="Stats"
|
|
875
|
+
className={cn(
|
|
876
|
+
'bg-indigo-600 text-white',
|
|
877
|
+
//
|
|
878
|
+
)}
|
|
879
|
+
>
|
|
880
|
+
<ui.Marquees
|
|
881
|
+
data-id=""
|
|
882
|
+
data-binding={[
|
|
883
|
+
{ label: 'Stats Items', property: 'items' },
|
|
884
|
+
{
|
|
885
|
+
label: 'Scroll Speed',
|
|
886
|
+
property: 'speed',
|
|
887
|
+
type: 'number',
|
|
888
|
+
min: 20,
|
|
889
|
+
max: 300,
|
|
890
|
+
// The bare-string form; it arrives as { type: 'slider' }.
|
|
891
|
+
widget: 'slider',
|
|
892
|
+
},
|
|
893
|
+
{ label: 'Pause On Hover', property: 'pauseOnHover', type: 'boolean' },
|
|
894
|
+
]}
|
|
895
|
+
speed={100}
|
|
896
|
+
pauseOnHover={true}
|
|
897
|
+
autoFill={true}
|
|
898
|
+
className="px-5 py-16"
|
|
899
|
+
items={[
|
|
900
|
+
{
|
|
901
|
+
key: 'stats-row',
|
|
902
|
+
children: (
|
|
903
|
+
<div
|
|
904
|
+
data-id=""
|
|
905
|
+
data-binding={[
|
|
906
|
+
{
|
|
907
|
+
label: 'Stats Cards',
|
|
908
|
+
property: 'children',
|
|
909
|
+
},
|
|
910
|
+
]}
|
|
911
|
+
className={cn('flex gap-20')}
|
|
912
|
+
>
|
|
913
|
+
<div
|
|
914
|
+
className={cn(
|
|
915
|
+
'min-w-60 text-center',
|
|
916
|
+
//
|
|
917
|
+
)}
|
|
918
|
+
>
|
|
919
|
+
<p
|
|
920
|
+
data-id=""
|
|
921
|
+
data-binding={[
|
|
922
|
+
{
|
|
923
|
+
label: 'Title',
|
|
924
|
+
property: 'innerText',
|
|
925
|
+
},
|
|
926
|
+
]}
|
|
927
|
+
className={cn(
|
|
928
|
+
'mb-2 text-5xl font-bold',
|
|
929
|
+
//
|
|
930
|
+
)}
|
|
931
|
+
>
|
|
932
|
+
Open
|
|
933
|
+
</p>
|
|
934
|
+
<p
|
|
935
|
+
data-id=""
|
|
936
|
+
data-binding={[
|
|
937
|
+
{
|
|
938
|
+
label: 'Description',
|
|
939
|
+
property: 'innerText',
|
|
940
|
+
},
|
|
941
|
+
]}
|
|
942
|
+
className={cn(
|
|
943
|
+
'text-indigo-200',
|
|
944
|
+
//
|
|
945
|
+
)}
|
|
946
|
+
>
|
|
947
|
+
Source Project
|
|
948
|
+
</p>
|
|
949
|
+
</div>
|
|
950
|
+
<div
|
|
951
|
+
className={cn(
|
|
952
|
+
'min-w-60 text-center',
|
|
953
|
+
//
|
|
954
|
+
)}
|
|
955
|
+
>
|
|
956
|
+
<p
|
|
957
|
+
data-id=""
|
|
958
|
+
data-binding={[
|
|
959
|
+
{
|
|
960
|
+
label: 'Title',
|
|
961
|
+
property: 'innerText',
|
|
962
|
+
},
|
|
963
|
+
]}
|
|
964
|
+
className={cn(
|
|
965
|
+
'mb-2 text-5xl font-bold',
|
|
966
|
+
//
|
|
967
|
+
)}
|
|
968
|
+
>
|
|
969
|
+
Free
|
|
970
|
+
</p>
|
|
971
|
+
<p
|
|
972
|
+
data-id=""
|
|
973
|
+
data-binding={[
|
|
974
|
+
{
|
|
975
|
+
label: 'Description',
|
|
976
|
+
property: 'innerText',
|
|
977
|
+
},
|
|
978
|
+
]}
|
|
979
|
+
className={cn(
|
|
980
|
+
'text-indigo-200',
|
|
981
|
+
//
|
|
982
|
+
)}
|
|
983
|
+
>
|
|
984
|
+
For Everyone
|
|
985
|
+
</p>
|
|
986
|
+
</div>
|
|
987
|
+
<div
|
|
988
|
+
className={cn(
|
|
989
|
+
'min-w-60 text-center',
|
|
990
|
+
//
|
|
991
|
+
)}
|
|
992
|
+
>
|
|
993
|
+
<p
|
|
994
|
+
data-id=""
|
|
995
|
+
data-binding={[
|
|
996
|
+
{
|
|
997
|
+
label: 'Title',
|
|
998
|
+
property: 'innerText',
|
|
999
|
+
},
|
|
1000
|
+
]}
|
|
1001
|
+
className={cn(
|
|
1002
|
+
'mb-2 text-5xl font-bold',
|
|
1003
|
+
//
|
|
1004
|
+
)}
|
|
1005
|
+
>
|
|
1006
|
+
Active
|
|
1007
|
+
</p>
|
|
1008
|
+
<p
|
|
1009
|
+
data-id=""
|
|
1010
|
+
data-binding={[
|
|
1011
|
+
{
|
|
1012
|
+
label: 'Description',
|
|
1013
|
+
property: 'innerText',
|
|
1014
|
+
},
|
|
1015
|
+
]}
|
|
1016
|
+
className={cn(
|
|
1017
|
+
'text-indigo-200',
|
|
1018
|
+
//
|
|
1019
|
+
)}
|
|
1020
|
+
>
|
|
1021
|
+
Community
|
|
1022
|
+
</p>
|
|
1023
|
+
</div>
|
|
1024
|
+
<div
|
|
1025
|
+
className={cn(
|
|
1026
|
+
'min-w-60 text-center',
|
|
1027
|
+
//
|
|
1028
|
+
)}
|
|
1029
|
+
>
|
|
1030
|
+
<p
|
|
1031
|
+
data-id=""
|
|
1032
|
+
data-binding={[
|
|
1033
|
+
{
|
|
1034
|
+
label: 'Title',
|
|
1035
|
+
property: 'innerText',
|
|
1036
|
+
},
|
|
1037
|
+
]}
|
|
1038
|
+
className={cn(
|
|
1039
|
+
'mb-2 text-5xl font-bold',
|
|
1040
|
+
//
|
|
1041
|
+
)}
|
|
1042
|
+
>
|
|
1043
|
+
Learn
|
|
1044
|
+
</p>
|
|
1045
|
+
<p
|
|
1046
|
+
data-id=""
|
|
1047
|
+
data-binding={[
|
|
1048
|
+
{
|
|
1049
|
+
label: 'Description',
|
|
1050
|
+
property: 'innerText',
|
|
1051
|
+
},
|
|
1052
|
+
]}
|
|
1053
|
+
className={cn(
|
|
1054
|
+
'text-indigo-200',
|
|
1055
|
+
//
|
|
1056
|
+
)}
|
|
1057
|
+
>
|
|
1058
|
+
By Doing
|
|
1059
|
+
</p>
|
|
1060
|
+
</div>
|
|
1061
|
+
</div>
|
|
1062
|
+
),
|
|
1063
|
+
},
|
|
1064
|
+
]}
|
|
1065
|
+
/>
|
|
1066
|
+
</section>
|
|
1067
|
+
`
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
id: "roadmap",
|
|
1071
|
+
name: "Roadmap",
|
|
1072
|
+
code: `
|
|
1073
|
+
<section
|
|
1074
|
+
data-name="Roadmap"
|
|
1075
|
+
className={cn(
|
|
1076
|
+
'px-5 py-16',
|
|
1077
|
+
//
|
|
1078
|
+
)}
|
|
1079
|
+
>
|
|
1080
|
+
<div
|
|
1081
|
+
className={cn(
|
|
1082
|
+
'mx-auto max-w-3xl',
|
|
1083
|
+
//
|
|
1084
|
+
)}
|
|
1085
|
+
>
|
|
1086
|
+
<h2
|
|
1087
|
+
data-id=""
|
|
1088
|
+
data-binding={[
|
|
1089
|
+
{ label: 'Title', property: 'innerText', required: true },
|
|
1090
|
+
]}
|
|
1091
|
+
className={cn(
|
|
1092
|
+
'mb-8 text-center text-4xl font-bold text-gray-800',
|
|
1093
|
+
//
|
|
1094
|
+
)}
|
|
1095
|
+
>
|
|
1096
|
+
Roadmap
|
|
1097
|
+
</h2>
|
|
1098
|
+
<ui.List
|
|
1099
|
+
data-id=""
|
|
1100
|
+
data-binding={[
|
|
1101
|
+
{
|
|
1102
|
+
label: 'Milestones',
|
|
1103
|
+
property: 'data',
|
|
1104
|
+
type: 'array',
|
|
1105
|
+
// One leaf per item key. Each leaf takes the same fields as a
|
|
1106
|
+
// top-level binding, and label falls back to the key.
|
|
1107
|
+
render: {
|
|
1108
|
+
title: {
|
|
1109
|
+
type: 'string',
|
|
1110
|
+
label: 'Title',
|
|
1111
|
+
required: true,
|
|
1112
|
+
},
|
|
1113
|
+
date: { type: 'date', label: 'Release Date', required: true },
|
|
1114
|
+
status: {
|
|
1115
|
+
type: 'string',
|
|
1116
|
+
label: 'Status',
|
|
1117
|
+
options: [
|
|
1118
|
+
{ label: 'Planned', value: 'planned' },
|
|
1119
|
+
{ label: 'In Progress', value: 'in-progress' },
|
|
1120
|
+
{ label: 'Shipped', value: 'shipped' },
|
|
1121
|
+
],
|
|
1122
|
+
},
|
|
1123
|
+
progress: {
|
|
1124
|
+
type: 'number',
|
|
1125
|
+
label: 'Progress',
|
|
1126
|
+
min: 0,
|
|
1127
|
+
max: 100,
|
|
1128
|
+
widget: { type: 'slider', step: 10, unit: '%' },
|
|
1129
|
+
},
|
|
1130
|
+
color: { type: 'color', label: 'Accent Color' },
|
|
1131
|
+
highlight: { type: 'boolean', label: 'Highlight' },
|
|
1132
|
+
// An object-valued key: its own render map types the keys
|
|
1133
|
+
// inside it, one level further down.
|
|
1134
|
+
link: {
|
|
1135
|
+
type: 'object',
|
|
1136
|
+
label: 'Link',
|
|
1137
|
+
render: {
|
|
1138
|
+
text: { type: 'string', label: 'Link Text' },
|
|
1139
|
+
href: {
|
|
1140
|
+
type: 'url',
|
|
1141
|
+
label: 'Link URL',
|
|
1142
|
+
pattern: '^https://',
|
|
1143
|
+
// Not a library field, so it arrives as meta.hint.
|
|
1144
|
+
hint: 'Must use https',
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
]}
|
|
1151
|
+
data={[
|
|
1152
|
+
{
|
|
1153
|
+
title: 'Visual editing',
|
|
1154
|
+
date: '2026-03-02',
|
|
1155
|
+
status: 'shipped',
|
|
1156
|
+
progress: 100,
|
|
1157
|
+
color: '#22c55e',
|
|
1158
|
+
highlight: false,
|
|
1159
|
+
link: { text: 'Release notes', href: 'https://github.com' },
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
title: 'Custom panels',
|
|
1163
|
+
date: '2026-09-26',
|
|
1164
|
+
status: 'in-progress',
|
|
1165
|
+
progress: 60,
|
|
1166
|
+
color: '#6366f1',
|
|
1167
|
+
highlight: true,
|
|
1168
|
+
link: { text: 'Guide', href: 'https://github.com' },
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
title: 'Collaboration',
|
|
1172
|
+
date: '2027-01-15',
|
|
1173
|
+
status: 'planned',
|
|
1174
|
+
progress: 0,
|
|
1175
|
+
color: '#f59e0b',
|
|
1176
|
+
highlight: false,
|
|
1177
|
+
link: { text: 'Discussion', href: 'https://github.com' },
|
|
1178
|
+
},
|
|
1179
|
+
]}
|
|
1180
|
+
itemKey={item => item.title}
|
|
1181
|
+
renderItem={item => (
|
|
1182
|
+
<div
|
|
1183
|
+
className={cn(
|
|
1184
|
+
'flex items-center gap-4 border-b border-gray-100 py-4',
|
|
1185
|
+
item.highlight && 'rounded-lg bg-indigo-50 px-4',
|
|
1186
|
+
)}
|
|
1187
|
+
>
|
|
1188
|
+
<span
|
|
1189
|
+
className="size-3 shrink-0 rounded-full"
|
|
1190
|
+
style={{ backgroundColor: item.color }}
|
|
1191
|
+
/>
|
|
1192
|
+
<div className="min-w-0 flex-1">
|
|
1193
|
+
<p className="font-semibold text-gray-800">{item.title}</p>
|
|
1194
|
+
<p className="text-sm text-gray-500">
|
|
1195
|
+
{item.date} · {item.status}
|
|
1196
|
+
</p>
|
|
1197
|
+
<ui.Progress value={item.progress} className="mt-2" />
|
|
1198
|
+
</div>
|
|
1199
|
+
<a
|
|
1200
|
+
href={item.link.href}
|
|
1201
|
+
className="text-sm text-indigo-500 hover:underline"
|
|
1202
|
+
>
|
|
1203
|
+
{item.link.text}
|
|
1204
|
+
</a>
|
|
1205
|
+
</div>
|
|
1206
|
+
)}
|
|
1207
|
+
/>
|
|
1208
|
+
<p
|
|
1209
|
+
className={cn(
|
|
1210
|
+
'mt-6 text-center text-sm text-gray-500',
|
|
1211
|
+
//
|
|
1212
|
+
)}
|
|
1213
|
+
>
|
|
1214
|
+
Last updated{' '}
|
|
1215
|
+
<time
|
|
1216
|
+
data-id=""
|
|
1217
|
+
data-binding={[
|
|
1218
|
+
{
|
|
1219
|
+
label: 'Updated On',
|
|
1220
|
+
property: 'dateTime',
|
|
1221
|
+
type: 'date',
|
|
1222
|
+
required: true,
|
|
1223
|
+
},
|
|
1224
|
+
{ label: 'Updated Text', property: 'innerText' },
|
|
1225
|
+
]}
|
|
1226
|
+
dateTime="2026-09-26"
|
|
1227
|
+
>
|
|
1228
|
+
September 26, 2026
|
|
1229
|
+
</time>
|
|
1230
|
+
{' · '}
|
|
1231
|
+
<a
|
|
1232
|
+
data-id=""
|
|
1233
|
+
data-binding={[
|
|
1234
|
+
{ label: 'Link Text', property: 'innerText' },
|
|
1235
|
+
{
|
|
1236
|
+
label: 'Link URL',
|
|
1237
|
+
property: 'href',
|
|
1238
|
+
type: 'url',
|
|
1239
|
+
required: true,
|
|
1240
|
+
pattern: '^https://',
|
|
1241
|
+
},
|
|
1242
|
+
]}
|
|
1243
|
+
href="https://github.com/pjb0811/live-editor/releases"
|
|
1244
|
+
className="text-indigo-500 hover:underline"
|
|
1245
|
+
>
|
|
1246
|
+
All releases
|
|
1247
|
+
</a>
|
|
1248
|
+
</p>
|
|
1249
|
+
</div>
|
|
1250
|
+
</section>
|
|
1251
|
+
`
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
id: "faq",
|
|
1255
|
+
name: "FAQ",
|
|
1256
|
+
code: `
|
|
1257
|
+
<section
|
|
1258
|
+
data-name="FAQ"
|
|
1259
|
+
className={cn(
|
|
1260
|
+
'px-5 py-16',
|
|
1261
|
+
//
|
|
1262
|
+
)}
|
|
1263
|
+
>
|
|
1264
|
+
<div
|
|
1265
|
+
className={cn(
|
|
1266
|
+
'mx-auto max-w-3xl',
|
|
1267
|
+
//
|
|
1268
|
+
)}
|
|
1269
|
+
>
|
|
1270
|
+
<ui.Typography.Title
|
|
1271
|
+
data-id=""
|
|
1272
|
+
data-binding={[{ label: 'Title', property: 'innerText' }]}
|
|
1273
|
+
level={2}
|
|
1274
|
+
className={cn(
|
|
1275
|
+
'mb-12 text-center text-4xl font-bold text-gray-800',
|
|
1276
|
+
//
|
|
1277
|
+
)}
|
|
1278
|
+
>
|
|
1279
|
+
Frequently Asked Questions
|
|
1280
|
+
</ui.Typography.Title>
|
|
1281
|
+
<ui.Collapse
|
|
1282
|
+
data-id=""
|
|
1283
|
+
data-binding={[{ label: 'FAQ Items', property: 'items' }]}
|
|
1284
|
+
items={[
|
|
1285
|
+
{
|
|
1286
|
+
key: '1',
|
|
1287
|
+
label: (
|
|
1288
|
+
<ui.Typography.Text
|
|
1289
|
+
data-id=""
|
|
1290
|
+
data-binding={[
|
|
1291
|
+
{
|
|
1292
|
+
label: 'Question 1',
|
|
1293
|
+
property: 'innerText',
|
|
1294
|
+
},
|
|
1295
|
+
]}
|
|
1296
|
+
className={cn('text-lg font-semibold text-gray-800')}
|
|
1297
|
+
>
|
|
1298
|
+
What is Live Editor?
|
|
1299
|
+
</ui.Typography.Text>
|
|
1300
|
+
),
|
|
1301
|
+
children: (
|
|
1302
|
+
<ui.Typography.Paragraph
|
|
1303
|
+
data-id=""
|
|
1304
|
+
data-binding={[
|
|
1305
|
+
{
|
|
1306
|
+
label: 'Answer 1',
|
|
1307
|
+
type: 'richtext',
|
|
1308
|
+
},
|
|
1309
|
+
]}
|
|
1310
|
+
className={cn('text-gray-600')}
|
|
1311
|
+
>
|
|
1312
|
+
Live Editor is an open-source tool for building web
|
|
1313
|
+
interfaces visually. It helps you learn web development by
|
|
1314
|
+
providing instant feedback as you build components.
|
|
1315
|
+
</ui.Typography.Paragraph>
|
|
1316
|
+
),
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
key: '2',
|
|
1320
|
+
label: (
|
|
1321
|
+
<ui.Typography.Text
|
|
1322
|
+
data-id=""
|
|
1323
|
+
data-binding={[
|
|
1324
|
+
{
|
|
1325
|
+
label: 'Question 2',
|
|
1326
|
+
property: 'innerText',
|
|
1327
|
+
},
|
|
1328
|
+
]}
|
|
1329
|
+
className={cn('text-lg font-semibold text-gray-800')}
|
|
1330
|
+
>
|
|
1331
|
+
Do I need coding experience?
|
|
1332
|
+
</ui.Typography.Text>
|
|
1333
|
+
),
|
|
1334
|
+
children: (
|
|
1335
|
+
<ui.Typography.Paragraph
|
|
1336
|
+
data-id=""
|
|
1337
|
+
data-binding={[
|
|
1338
|
+
{
|
|
1339
|
+
label: 'Answer 2',
|
|
1340
|
+
type: 'richtext',
|
|
1341
|
+
},
|
|
1342
|
+
]}
|
|
1343
|
+
className={cn('text-gray-600')}
|
|
1344
|
+
>
|
|
1345
|
+
No! Live Editor is designed for all skill levels. Beginners
|
|
1346
|
+
can learn through visual editing, while experienced
|
|
1347
|
+
developers can work directly with code.
|
|
1348
|
+
</ui.Typography.Paragraph>
|
|
1349
|
+
),
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
key: '3',
|
|
1353
|
+
label: (
|
|
1354
|
+
<ui.Typography.Text
|
|
1355
|
+
data-id=""
|
|
1356
|
+
data-binding={[
|
|
1357
|
+
{
|
|
1358
|
+
label: 'Question 3',
|
|
1359
|
+
property: 'innerText',
|
|
1360
|
+
},
|
|
1361
|
+
]}
|
|
1362
|
+
className={cn('text-lg font-semibold text-gray-800')}
|
|
1363
|
+
>
|
|
1364
|
+
Is it really free?
|
|
1365
|
+
</ui.Typography.Text>
|
|
1366
|
+
),
|
|
1367
|
+
children: (
|
|
1368
|
+
<ui.Typography.Paragraph
|
|
1369
|
+
data-id=""
|
|
1370
|
+
data-binding={[
|
|
1371
|
+
{
|
|
1372
|
+
label: 'Answer 3',
|
|
1373
|
+
type: 'richtext',
|
|
1374
|
+
},
|
|
1375
|
+
]}
|
|
1376
|
+
className={cn('text-gray-600')}
|
|
1377
|
+
>
|
|
1378
|
+
Yes, completely free! Live Editor is an open-source project
|
|
1379
|
+
with no hidden costs or premium tiers. All features are
|
|
1380
|
+
available to everyone.
|
|
1381
|
+
</ui.Typography.Paragraph>
|
|
1382
|
+
),
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
key: '4',
|
|
1386
|
+
label: (
|
|
1387
|
+
<ui.Typography.Text
|
|
1388
|
+
data-id=""
|
|
1389
|
+
data-binding={[
|
|
1390
|
+
{
|
|
1391
|
+
label: 'Question 4',
|
|
1392
|
+
property: 'innerText',
|
|
1393
|
+
},
|
|
1394
|
+
]}
|
|
1395
|
+
className={cn('text-lg font-semibold text-gray-800')}
|
|
1396
|
+
>
|
|
1397
|
+
How can I contribute?
|
|
1398
|
+
</ui.Typography.Text>
|
|
1399
|
+
),
|
|
1400
|
+
children: (
|
|
1401
|
+
<ui.Typography.Paragraph
|
|
1402
|
+
data-id=""
|
|
1403
|
+
data-binding={[
|
|
1404
|
+
{
|
|
1405
|
+
label: 'Answer 4',
|
|
1406
|
+
type: 'richtext',
|
|
1407
|
+
},
|
|
1408
|
+
]}
|
|
1409
|
+
className={cn('text-gray-600')}
|
|
1410
|
+
>
|
|
1411
|
+
You can contribute by reporting bugs, suggesting features,
|
|
1412
|
+
sharing your projects, or contributing code. Check our
|
|
1413
|
+
GitHub repository for more details.
|
|
1414
|
+
</ui.Typography.Paragraph>
|
|
1415
|
+
),
|
|
1416
|
+
},
|
|
1417
|
+
]}
|
|
1418
|
+
/>
|
|
1419
|
+
</div>
|
|
1420
|
+
</section>
|
|
1421
|
+
`
|
|
1422
|
+
}
|
|
1423
|
+
];
|
|
1424
|
+
//#endregion
|
|
1425
|
+
//#region src/components/dnd/canvas/canvas-notice.tsx
|
|
1426
|
+
const CanvasNotice = ({ title, detail }) => /* @__PURE__ */ jsxs(Space, {
|
|
1427
|
+
orientation: "vertical",
|
|
1428
|
+
align: "center",
|
|
1429
|
+
children: [/* @__PURE__ */ jsx(Typography.Paragraph, { children: title }), /* @__PURE__ */ jsx(Typography.Text, { children: detail })]
|
|
1430
|
+
});
|
|
1431
|
+
//#endregion
|
|
22
1432
|
//#region src/components/dnd/palette/palette-drag.ts
|
|
23
1433
|
const PALETTE_SECTION = "palette-section";
|
|
24
1434
|
const paletteDragData = (section) => ({
|
|
@@ -220,6 +1630,10 @@ const arePropsEqual = (prev, next) => {
|
|
|
220
1630
|
};
|
|
221
1631
|
var renderer_default = memo(Renderer, arePropsEqual);
|
|
222
1632
|
//#endregion
|
|
1633
|
+
//#region src/components/dnd/canvas/section-handlers.ts
|
|
1634
|
+
const SectionHandlersContext = createContext(null);
|
|
1635
|
+
const useSectionHandlers = () => useContext(SectionHandlersContext);
|
|
1636
|
+
//#endregion
|
|
223
1637
|
//#region src/components/dnd/canvas/sortable.tsx
|
|
224
1638
|
const NAVIGATION_KEYS = {
|
|
225
1639
|
ArrowUp: "previous",
|
|
@@ -227,7 +1641,9 @@ const NAVIGATION_KEYS = {
|
|
|
227
1641
|
Home: "first",
|
|
228
1642
|
End: "last"
|
|
229
1643
|
};
|
|
230
|
-
const Sortable = ({ id, children, selected, onClick, onDelete
|
|
1644
|
+
const Sortable = ({ id, children, selected, onClick, onDelete, onCopy, onMoveUp, onMoveDown, canMoveUp = false, canMoveDown = false }) => {
|
|
1645
|
+
const handlers = useSectionHandlers();
|
|
1646
|
+
const inspecting = handlers?.inspecting ?? false;
|
|
231
1647
|
const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging, isOver, active } = useSortable({
|
|
232
1648
|
id,
|
|
233
1649
|
disabled: inspecting
|
|
@@ -249,28 +1665,20 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
|
|
|
249
1665
|
return;
|
|
250
1666
|
}
|
|
251
1667
|
const to = NAVIGATION_KEYS[e.key];
|
|
252
|
-
if (to &&
|
|
1668
|
+
if (to && handlers) {
|
|
253
1669
|
e.preventDefault();
|
|
254
|
-
onNavigate(to);
|
|
1670
|
+
handlers.onNavigate(id, to);
|
|
255
1671
|
return;
|
|
256
1672
|
}
|
|
257
|
-
if ((e.key === "Delete" || e.key === "Backspace") &&
|
|
1673
|
+
if ((e.key === "Delete" || e.key === "Backspace") && handlers) {
|
|
258
1674
|
e.preventDefault();
|
|
259
|
-
onDeleteKey();
|
|
1675
|
+
handlers.onDeleteKey(id);
|
|
260
1676
|
}
|
|
261
1677
|
};
|
|
262
1678
|
const setRefs = (node) => {
|
|
263
1679
|
setNodeRef(node);
|
|
264
1680
|
setActivatorNodeRef(node);
|
|
265
|
-
nodeRef
|
|
266
|
-
};
|
|
267
|
-
const onDelete = (e) => {
|
|
268
|
-
e.stopPropagation();
|
|
269
|
-
_onDelete?.(id);
|
|
270
|
-
};
|
|
271
|
-
const onCopy = (e) => {
|
|
272
|
-
e.stopPropagation();
|
|
273
|
-
_onCopy?.(id);
|
|
1681
|
+
handlers?.nodeRef(id)(node);
|
|
274
1682
|
};
|
|
275
1683
|
const press = (action) => (e) => {
|
|
276
1684
|
e.stopPropagation();
|
|
@@ -287,11 +1695,11 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
|
|
|
287
1695
|
children: [
|
|
288
1696
|
/* @__PURE__ */ jsx("div", {
|
|
289
1697
|
className: cn("absolute inset-0 z-50"),
|
|
290
|
-
onPointerMove: inspecting ? (e) => onInspectMove
|
|
291
|
-
onPointerLeave: inspecting ? onInspectLeave : void 0,
|
|
1698
|
+
onPointerMove: inspecting ? (e) => handlers?.onInspectMove(e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY) : void 0,
|
|
1699
|
+
onPointerLeave: inspecting ? handlers?.onInspectLeave : void 0,
|
|
292
1700
|
onClick: inspecting ? (e) => {
|
|
293
1701
|
e.stopPropagation();
|
|
294
|
-
onInspectPick
|
|
1702
|
+
handlers?.onInspectPick(id, e.currentTarget.parentElement, e.currentTarget, e.clientX, e.clientY);
|
|
295
1703
|
} : void 0
|
|
296
1704
|
}),
|
|
297
1705
|
children,
|
|
@@ -312,13 +1720,13 @@ const Sortable = ({ id, children, selected, onClick, onDelete: _onDelete, onCopy
|
|
|
312
1720
|
}),
|
|
313
1721
|
/* @__PURE__ */ jsx(Button, {
|
|
314
1722
|
icon: /* @__PURE__ */ jsx(Copy, {}),
|
|
315
|
-
onClick: onCopy,
|
|
1723
|
+
onClick: press(() => onCopy?.(id)),
|
|
316
1724
|
"aria-label": messages.section.duplicate
|
|
317
1725
|
}),
|
|
318
1726
|
/* @__PURE__ */ jsx(Button, {
|
|
319
1727
|
danger: true,
|
|
320
1728
|
icon: /* @__PURE__ */ jsx(Trash, {}),
|
|
321
|
-
onClick: onDelete,
|
|
1729
|
+
onClick: press(() => onDelete?.(id)),
|
|
322
1730
|
"aria-label": messages.section.delete
|
|
323
1731
|
})
|
|
324
1732
|
]
|
|
@@ -427,6 +1835,9 @@ const useDndPalette = () => useDndRegions("useDndPalette()").palette;
|
|
|
427
1835
|
const useDndPanel = () => useDndRegions("useDndPanel()").panel;
|
|
428
1836
|
const useDndLayout = () => useDndRegions("useDndLayout()");
|
|
429
1837
|
//#endregion
|
|
1838
|
+
//#region src/components/dnd/panel/element-selection.ts
|
|
1839
|
+
const ElementSelectionContext = createContext({ ids: /* @__PURE__ */ new Set() });
|
|
1840
|
+
//#endregion
|
|
430
1841
|
//#region src/components/dnd/panel-binding.ts
|
|
431
1842
|
const readAttribute = (node, name) => node.dataAttributes.find((attribute) => attribute.name === name)?.value;
|
|
432
1843
|
const canEditBindingValue = (node, binding) => {
|
|
@@ -446,7 +1857,16 @@ const toBindingFields = (binding) => ({
|
|
|
446
1857
|
min: binding.min,
|
|
447
1858
|
max: binding.max,
|
|
448
1859
|
pattern: binding.pattern,
|
|
1860
|
+
patternMessage: binding.patternMessage,
|
|
449
1861
|
required: binding.required,
|
|
1862
|
+
minLength: binding.minLength,
|
|
1863
|
+
maxLength: binding.maxLength,
|
|
1864
|
+
step: binding.step,
|
|
1865
|
+
minItems: binding.minItems,
|
|
1866
|
+
maxItems: binding.maxItems,
|
|
1867
|
+
minDate: binding.minDate,
|
|
1868
|
+
maxDate: binding.maxDate,
|
|
1869
|
+
protocols: binding.protocols,
|
|
450
1870
|
meta: binding.meta
|
|
451
1871
|
});
|
|
452
1872
|
const resolveRenderEntry = (render, key) => {
|
|
@@ -504,8 +1924,38 @@ const withPanelCommit = (bindings, commit) => bindings.map((binding) => ({
|
|
|
504
1924
|
})
|
|
505
1925
|
}));
|
|
506
1926
|
//#endregion
|
|
507
|
-
//#region src/components/dnd/panel/
|
|
508
|
-
const
|
|
1927
|
+
//#region src/components/dnd/panel/row-controls.tsx
|
|
1928
|
+
const RowControls = ({ index, count, labels, disabled = false, deleteDisabled = false, onMove, onDelete }) => /* @__PURE__ */ jsxs("div", {
|
|
1929
|
+
className: "flex space-x-1",
|
|
1930
|
+
children: [
|
|
1931
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1932
|
+
size: "small",
|
|
1933
|
+
icon: /* @__PURE__ */ jsx(ArrowUp, {}),
|
|
1934
|
+
"aria-label": labels.moveUp,
|
|
1935
|
+
disabled: disabled || index === 0,
|
|
1936
|
+
onClick: () => onMove(index - 1)
|
|
1937
|
+
}),
|
|
1938
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1939
|
+
size: "small",
|
|
1940
|
+
icon: /* @__PURE__ */ jsx(ArrowDown, {}),
|
|
1941
|
+
"aria-label": labels.moveDown,
|
|
1942
|
+
disabled: disabled || index === count - 1,
|
|
1943
|
+
onClick: () => onMove(index + 1)
|
|
1944
|
+
}),
|
|
1945
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1946
|
+
danger: true,
|
|
1947
|
+
size: "small",
|
|
1948
|
+
icon: /* @__PURE__ */ jsx(X, {}),
|
|
1949
|
+
"aria-label": labels.delete,
|
|
1950
|
+
title: labels.delete,
|
|
1951
|
+
disabled: disabled || deleteDisabled,
|
|
1952
|
+
onClick: onDelete
|
|
1953
|
+
})
|
|
1954
|
+
]
|
|
1955
|
+
});
|
|
1956
|
+
//#endregion
|
|
1957
|
+
//#region src/components/dnd/panel/selection/bulk-actions-bar.tsx
|
|
1958
|
+
const BulkActionsBar = ({ count, disabled = false, duplicateDisabled = false, deleteDisabled = false, onDuplicate, onMoveUp, onMoveDown, onDelete, onClear }) => {
|
|
509
1959
|
const messages = useLiveMessages();
|
|
510
1960
|
if (count === 0) return null;
|
|
511
1961
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -520,7 +1970,7 @@ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMove
|
|
|
520
1970
|
size: "small",
|
|
521
1971
|
icon: /* @__PURE__ */ jsx(Copy, {}),
|
|
522
1972
|
title: messages.selection.duplicate,
|
|
523
|
-
disabled,
|
|
1973
|
+
disabled: disabled || duplicateDisabled,
|
|
524
1974
|
onClick: onDuplicate
|
|
525
1975
|
}),
|
|
526
1976
|
/* @__PURE__ */ jsx(Button, {
|
|
@@ -542,7 +1992,7 @@ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMove
|
|
|
542
1992
|
size: "small",
|
|
543
1993
|
icon: /* @__PURE__ */ jsx(X, {}),
|
|
544
1994
|
title: messages.selection.delete,
|
|
545
|
-
disabled,
|
|
1995
|
+
disabled: disabled || deleteDisabled,
|
|
546
1996
|
onClick: onDelete
|
|
547
1997
|
}),
|
|
548
1998
|
/* @__PURE__ */ jsx(Button, {
|
|
@@ -555,36 +2005,33 @@ const BulkActionsBar = ({ count, disabled = false, onDuplicate, onMoveUp, onMove
|
|
|
555
2005
|
});
|
|
556
2006
|
};
|
|
557
2007
|
//#endregion
|
|
558
|
-
//#region src/components/dnd/panel/
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
return /* @__PURE__ */ jsx(
|
|
563
|
-
className: "
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
className: "block text-xs font-semibold text-gray-700",
|
|
570
|
-
children: [binding.label, /* @__PURE__ */ jsxs("span", {
|
|
571
|
-
className: "ml-1 text-gray-400",
|
|
572
|
-
children: [
|
|
573
|
-
"(",
|
|
574
|
-
binding.property,
|
|
575
|
-
")"
|
|
576
|
-
]
|
|
577
|
-
})]
|
|
578
|
-
}), /* @__PURE__ */ jsx(Field, {
|
|
579
|
-
binding,
|
|
580
|
-
onNodeChange: onChange
|
|
581
|
-
})]
|
|
582
|
-
}, `${binding.id}-${binding.property}`))
|
|
583
|
-
})
|
|
2008
|
+
//#region src/components/dnd/panel/selection/select-all-checkbox.tsx
|
|
2009
|
+
const SelectAllCheckbox = ({ total, selected, disabled = false, onSelectAll, onClear }) => {
|
|
2010
|
+
const messages = useLiveMessages();
|
|
2011
|
+
const isAllSelected = total > 0 && selected >= total;
|
|
2012
|
+
return /* @__PURE__ */ jsx(Checkbox, {
|
|
2013
|
+
className: "text-xs",
|
|
2014
|
+
checked: isAllSelected,
|
|
2015
|
+
indeterminate: selected > 0 && !isAllSelected,
|
|
2016
|
+
disabled,
|
|
2017
|
+
onChange: () => isAllSelected ? onClear() : onSelectAll(),
|
|
2018
|
+
children: messages.selection.selectAll
|
|
584
2019
|
});
|
|
585
2020
|
};
|
|
586
2021
|
//#endregion
|
|
587
|
-
//#region src/components/dnd/panel/
|
|
2022
|
+
//#region src/components/dnd/panel/selection/selection-checkbox.tsx
|
|
2023
|
+
const SelectionCheckbox = ({ checked, disabled = false, onToggle }) => /* @__PURE__ */ jsx("div", {
|
|
2024
|
+
onClick: (e) => {
|
|
2025
|
+
if (!disabled) onToggle(e.shiftKey);
|
|
2026
|
+
},
|
|
2027
|
+
className: "inline-flex",
|
|
2028
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
2029
|
+
checked,
|
|
2030
|
+
disabled
|
|
2031
|
+
})
|
|
2032
|
+
});
|
|
2033
|
+
//#endregion
|
|
2034
|
+
//#region src/components/dnd/panel/selection/use-structural-selection.ts
|
|
588
2035
|
const selectionAfter = (command, selected, count) => {
|
|
589
2036
|
switch (command.type) {
|
|
590
2037
|
case "append":
|
|
@@ -618,7 +2065,7 @@ const useStructuralSelection = (count, revision) => {
|
|
|
618
2065
|
};
|
|
619
2066
|
};
|
|
620
2067
|
//#endregion
|
|
621
|
-
//#region src/components/dnd/panel/use-dnd-children.ts
|
|
2068
|
+
//#region src/components/dnd/panel/children/use-dnd-children.ts
|
|
622
2069
|
const producedBy = (expected, action) => {
|
|
623
2070
|
if (action.type === "move-selected") {
|
|
624
2071
|
const moved = JSON.stringify(moveSelectedIndices(expected, new Set(action.indices), action.direction).items);
|
|
@@ -675,7 +2122,11 @@ const useDndChildren = (items, { onChange } = {}) => {
|
|
|
675
2122
|
};
|
|
676
2123
|
};
|
|
677
2124
|
//#endregion
|
|
678
|
-
//#region src/components/dnd/panel/children.tsx
|
|
2125
|
+
//#region src/components/dnd/panel/children/children.tsx
|
|
2126
|
+
const bindingsOf = (node, onChange) => {
|
|
2127
|
+
const source = resolvePanelBindings(node);
|
|
2128
|
+
return source ? withPanelCommit(source.bindings, onChange) : [];
|
|
2129
|
+
};
|
|
679
2130
|
const Children$1 = ({ value, onChange, onNodeChange }) => {
|
|
680
2131
|
const items = useMemo(() => Array.isArray(value) ? value : [], [value]);
|
|
681
2132
|
const { selection, actions } = useDndChildren(items, { onChange });
|
|
@@ -704,6 +2155,12 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
|
|
|
704
2155
|
children: messages.children.add
|
|
705
2156
|
})]
|
|
706
2157
|
}),
|
|
2158
|
+
items.length > 0 && /* @__PURE__ */ jsx(SelectAllCheckbox, {
|
|
2159
|
+
total: items.length,
|
|
2160
|
+
selected: selection.selected.size,
|
|
2161
|
+
onSelectAll: () => selection.replace(new Set(items.map((_, index) => index))),
|
|
2162
|
+
onClear: selection.clear
|
|
2163
|
+
}),
|
|
707
2164
|
/* @__PURE__ */ jsx(BulkActionsBar, {
|
|
708
2165
|
count: selection.selected.size,
|
|
709
2166
|
onDuplicate: actions.duplicateSelected,
|
|
@@ -719,43 +2176,19 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
|
|
|
719
2176
|
className: "flex items-center justify-between",
|
|
720
2177
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
721
2178
|
className: "flex items-center space-x-2",
|
|
722
|
-
children: [/* @__PURE__ */ jsx(
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
726
|
-
checked: selection.isSelected(itemIndex),
|
|
727
|
-
onChange: () => {}
|
|
728
|
-
})
|
|
2179
|
+
children: [/* @__PURE__ */ jsx(SelectionCheckbox, {
|
|
2180
|
+
checked: selection.isSelected(itemIndex),
|
|
2181
|
+
onToggle: (shiftKey) => selection.toggle(itemIndex, shiftKey)
|
|
729
2182
|
}), /* @__PURE__ */ jsx("div", {
|
|
730
2183
|
className: "text-xs font-medium text-green-800",
|
|
731
2184
|
children: messages.children.child(itemIndex + 1, item.tagName || "fragment")
|
|
732
2185
|
})]
|
|
733
|
-
}), /* @__PURE__ */
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
"aria-label": messages.children.moveUp,
|
|
740
|
-
disabled: itemIndex === 0,
|
|
741
|
-
onClick: () => actions.move(itemIndex, itemIndex - 1)
|
|
742
|
-
}),
|
|
743
|
-
/* @__PURE__ */ jsx(Button, {
|
|
744
|
-
size: "small",
|
|
745
|
-
icon: /* @__PURE__ */ jsx(ArrowDown, {}),
|
|
746
|
-
"aria-label": messages.children.moveDown,
|
|
747
|
-
disabled: itemIndex === items.length - 1,
|
|
748
|
-
onClick: () => actions.move(itemIndex, itemIndex + 1)
|
|
749
|
-
}),
|
|
750
|
-
/* @__PURE__ */ jsx(Button, {
|
|
751
|
-
"aria-label": messages.children.delete,
|
|
752
|
-
title: messages.children.delete,
|
|
753
|
-
danger: true,
|
|
754
|
-
size: "small",
|
|
755
|
-
icon: /* @__PURE__ */ jsx(X, {}),
|
|
756
|
-
onClick: () => actions.remove(itemIndex)
|
|
757
|
-
})
|
|
758
|
-
]
|
|
2186
|
+
}), /* @__PURE__ */ jsx(RowControls, {
|
|
2187
|
+
index: itemIndex,
|
|
2188
|
+
count: items.length,
|
|
2189
|
+
labels: messages.children,
|
|
2190
|
+
onMove: (toIndex) => actions.move(itemIndex, toIndex),
|
|
2191
|
+
onDelete: () => actions.remove(itemIndex)
|
|
759
2192
|
})]
|
|
760
2193
|
}),
|
|
761
2194
|
editableChildrenMap.has(itemIndex) && /* @__PURE__ */ jsxs("div", {
|
|
@@ -765,16 +2198,15 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
|
|
|
765
2198
|
children: messages.children.editableBindings
|
|
766
2199
|
}), editableChildrenMap.get(itemIndex).map((editableNode, idx) => {
|
|
767
2200
|
const nodeId = editableNode.dataAttributes.find((a) => a.name === "data-id")?.value;
|
|
768
|
-
return /* @__PURE__ */
|
|
769
|
-
|
|
2201
|
+
return /* @__PURE__ */ jsx(ElementFields, {
|
|
2202
|
+
id: nodeId ?? void 0,
|
|
770
2203
|
className: "rounded border border-green-100 bg-white p-2\n data-[picked]:ring-2 data-[picked]:ring-blue-400",
|
|
771
|
-
|
|
2204
|
+
header: /* @__PURE__ */ jsx("div", {
|
|
772
2205
|
className: "mb-1 text-xs text-green-600",
|
|
773
2206
|
children: editableNode.tagName
|
|
774
|
-
}),
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
})]
|
|
2207
|
+
}),
|
|
2208
|
+
bindings: bindingsOf(editableNode, onNodeChange),
|
|
2209
|
+
onNodeChange
|
|
778
2210
|
}, `editable-${nodeId || idx}`);
|
|
779
2211
|
})]
|
|
780
2212
|
}),
|
|
@@ -783,12 +2215,10 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
|
|
|
783
2215
|
children: [/* @__PURE__ */ jsx("div", {
|
|
784
2216
|
className: "text-xs font-medium text-green-700",
|
|
785
2217
|
children: messages.children.childNodes
|
|
786
|
-
}), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx(
|
|
2218
|
+
}), item.children.map((node, nodeIndex) => /* @__PURE__ */ jsx(ElementFields, {
|
|
787
2219
|
className: "ml-2 rounded border border-green-100 bg-white p-2",
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
onChange: onNodeChange
|
|
791
|
-
})
|
|
2220
|
+
bindings: bindingsOf(node, onNodeChange),
|
|
2221
|
+
onNodeChange
|
|
792
2222
|
}, `node-${itemIndex}-${nodeIndex}`))]
|
|
793
2223
|
})
|
|
794
2224
|
]
|
|
@@ -797,7 +2227,70 @@ const Children$1 = ({ value, onChange, onNodeChange }) => {
|
|
|
797
2227
|
});
|
|
798
2228
|
};
|
|
799
2229
|
//#endregion
|
|
800
|
-
//#region src/components/dnd/panel/field
|
|
2230
|
+
//#region src/components/dnd/panel/fields/color-picker-field.tsx
|
|
2231
|
+
const COLOR_COMMIT_DELAY = 75;
|
|
2232
|
+
const normalizeToHex = (value) => {
|
|
2233
|
+
const trimmed = value.trim();
|
|
2234
|
+
if (/^#([0-9A-Fa-f]{3}){1,2}$/.test(trimmed)) return trimmed;
|
|
2235
|
+
return "#000000";
|
|
2236
|
+
};
|
|
2237
|
+
const ColorPickerField = ({ value: source, onChange, label }) => {
|
|
2238
|
+
const value = normalizeToHex(source);
|
|
2239
|
+
const [liveValue, setLiveValue] = useState(value);
|
|
2240
|
+
const [prevValue, setPrevValue] = useState(value);
|
|
2241
|
+
const lastCommittedRef = useRef(value);
|
|
2242
|
+
if (value !== prevValue) {
|
|
2243
|
+
setPrevValue(value);
|
|
2244
|
+
setLiveValue(value);
|
|
2245
|
+
}
|
|
2246
|
+
useEffect(() => {
|
|
2247
|
+
lastCommittedRef.current = value;
|
|
2248
|
+
}, [value]);
|
|
2249
|
+
const commit = (next) => {
|
|
2250
|
+
if (next === lastCommittedRef.current) return;
|
|
2251
|
+
lastCommittedRef.current = next;
|
|
2252
|
+
onChange(next);
|
|
2253
|
+
};
|
|
2254
|
+
const debouncedCommit = useDebounce(() => commit(liveValue), {
|
|
2255
|
+
delay: COLOR_COMMIT_DELAY,
|
|
2256
|
+
autoInvoke: false
|
|
2257
|
+
});
|
|
2258
|
+
return /* @__PURE__ */ jsx(ColorPicker, {
|
|
2259
|
+
"aria-label": label,
|
|
2260
|
+
showText: true,
|
|
2261
|
+
value: liveValue,
|
|
2262
|
+
onChange: (next) => {
|
|
2263
|
+
setLiveValue(next);
|
|
2264
|
+
debouncedCommit();
|
|
2265
|
+
},
|
|
2266
|
+
onOpenChange: (open) => {
|
|
2267
|
+
if (!open) commit(liveValue);
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
};
|
|
2271
|
+
//#endregion
|
|
2272
|
+
//#region src/components/dnd/panel/fields/field-ids.ts
|
|
2273
|
+
const FieldIdsContext = createContext(null);
|
|
2274
|
+
const useFieldIds = () => {
|
|
2275
|
+
const labelledId = useContext(FieldIdsContext);
|
|
2276
|
+
const fallbackId = useId();
|
|
2277
|
+
const controlId = labelledId ?? fallbackId;
|
|
2278
|
+
return {
|
|
2279
|
+
controlId,
|
|
2280
|
+
errorId: `${controlId}-error`,
|
|
2281
|
+
isLabelled: labelledId !== null
|
|
2282
|
+
};
|
|
2283
|
+
};
|
|
2284
|
+
const LABELABLE_KINDS = /* @__PURE__ */ new Set([
|
|
2285
|
+
"text",
|
|
2286
|
+
"number",
|
|
2287
|
+
"url",
|
|
2288
|
+
"email",
|
|
2289
|
+
"tel",
|
|
2290
|
+
"time"
|
|
2291
|
+
]);
|
|
2292
|
+
//#endregion
|
|
2293
|
+
//#region src/components/dnd/panel/fields/field-kind.ts
|
|
801
2294
|
const isColorProperty = (property) => property.toLowerCase().includes("color");
|
|
802
2295
|
const getFieldKind = (binding) => {
|
|
803
2296
|
const { property, type, value } = binding;
|
|
@@ -812,13 +2305,63 @@ const getFieldKind = (binding) => {
|
|
|
812
2305
|
if (binding.options && Array.isArray(binding.options)) return "select";
|
|
813
2306
|
if (type === "color" || isColorProperty(property)) return "color";
|
|
814
2307
|
if (type === "date") return "date";
|
|
2308
|
+
if (type === "time") return "time";
|
|
2309
|
+
if (type === "datetime") return "datetime";
|
|
815
2310
|
if (type === "url") return "url";
|
|
2311
|
+
if (type === "email") return "email";
|
|
2312
|
+
if (type === "tel") return "tel";
|
|
816
2313
|
if (type === "number" || typeof value === "number") return "number";
|
|
817
2314
|
return "text";
|
|
818
2315
|
};
|
|
819
2316
|
const isStructuralFieldKind = (kind) => kind === "items" || kind === "children";
|
|
820
2317
|
//#endregion
|
|
821
|
-
//#region src/components/dnd/panel/
|
|
2318
|
+
//#region src/components/dnd/panel/fields/jsx-editor-field.tsx
|
|
2319
|
+
const JSXEditorField = ({ value, isHTML, onSave }) => {
|
|
2320
|
+
const selectionRef = useRef(null);
|
|
2321
|
+
const focusedRef = useRef(false);
|
|
2322
|
+
const handleUpdate = useCallback((update) => {
|
|
2323
|
+
selectionRef.current = update.state.selection;
|
|
2324
|
+
}, []);
|
|
2325
|
+
const handleCreateEditor = useCallback((view) => {
|
|
2326
|
+
if (selectionRef.current) view.dispatch({ selection: selectionRef.current });
|
|
2327
|
+
if (focusedRef.current) view.focus();
|
|
2328
|
+
}, []);
|
|
2329
|
+
const handleFocus = useCallback(() => {
|
|
2330
|
+
focusedRef.current = true;
|
|
2331
|
+
}, []);
|
|
2332
|
+
const handleBlur = useCallback((event) => {
|
|
2333
|
+
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) focusedRef.current = false;
|
|
2334
|
+
}, []);
|
|
2335
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2336
|
+
onFocusCapture: handleFocus,
|
|
2337
|
+
onBlurCapture: handleBlur,
|
|
2338
|
+
children: /* @__PURE__ */ jsx(Core, {
|
|
2339
|
+
value,
|
|
2340
|
+
height: "150px",
|
|
2341
|
+
fragment: !isHTML,
|
|
2342
|
+
raw: isHTML,
|
|
2343
|
+
onSave,
|
|
2344
|
+
onUpdate: handleUpdate,
|
|
2345
|
+
onCreateEditor: handleCreateEditor
|
|
2346
|
+
})
|
|
2347
|
+
});
|
|
2348
|
+
};
|
|
2349
|
+
//#endregion
|
|
2350
|
+
//#region src/components/dnd/panel/fields/labeled-field.tsx
|
|
2351
|
+
const LabeledField = ({ binding, label, labelClassName, children }) => {
|
|
2352
|
+
const controlId = useId();
|
|
2353
|
+
const isLabelable = LABELABLE_KINDS.has(getFieldKind(binding));
|
|
2354
|
+
return /* @__PURE__ */ jsxs(FieldIdsContext.Provider, {
|
|
2355
|
+
value: isLabelable ? controlId : null,
|
|
2356
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
2357
|
+
htmlFor: isLabelable ? controlId : void 0,
|
|
2358
|
+
className: labelClassName,
|
|
2359
|
+
children: label
|
|
2360
|
+
}), children]
|
|
2361
|
+
});
|
|
2362
|
+
};
|
|
2363
|
+
//#endregion
|
|
2364
|
+
//#region src/components/dnd/panel/items/sortable-items.tsx
|
|
822
2365
|
const label = (messages, items, id) => {
|
|
823
2366
|
const index = items.findIndex((item) => item.id === id);
|
|
824
2367
|
return index >= 0 ? messages.items.item(index + 1) : String(id);
|
|
@@ -900,19 +2443,334 @@ const ItemCard = ({ id, title, disabled, expanded, onToggle, checkbox, controls,
|
|
|
900
2443
|
});
|
|
901
2444
|
};
|
|
902
2445
|
//#endregion
|
|
903
|
-
//#region src/
|
|
904
|
-
const
|
|
905
|
-
let
|
|
2446
|
+
//#region src/utils/ast/array-source.ts
|
|
2447
|
+
const commaIn = (source, start, end) => {
|
|
2448
|
+
let comma = null;
|
|
2449
|
+
for (let at = start; at < end; at++) {
|
|
2450
|
+
if (/\s/.test(source[at])) continue;
|
|
2451
|
+
if (source.startsWith("/*", at)) {
|
|
2452
|
+
const close = source.indexOf("*/", at + 2);
|
|
2453
|
+
if (close < 0 || close + 2 > end) return null;
|
|
2454
|
+
at = close + 1;
|
|
2455
|
+
} else if (source.startsWith("//", at)) {
|
|
2456
|
+
const newline = source.indexOf("\n", at + 2);
|
|
2457
|
+
at = newline < 0 ? end : newline;
|
|
2458
|
+
} else if (source[at] === "," && comma === null) comma = at;
|
|
2459
|
+
else return null;
|
|
2460
|
+
}
|
|
2461
|
+
return comma;
|
|
2462
|
+
};
|
|
2463
|
+
const denseArraySource = (source) => {
|
|
2464
|
+
const array = parseArrayExpression(source);
|
|
2465
|
+
if (!array || array.elements.some((node) => !isExpression(node) || node.extra?.parenthesized)) return null;
|
|
2466
|
+
const elements = array.elements;
|
|
2467
|
+
const commas = elements.slice(0, -1).map((node, index) => commaIn(source, node.end, elements[index + 1].start));
|
|
2468
|
+
if (commas.some((comma) => comma === null)) return null;
|
|
2469
|
+
const last = elements.at(-1);
|
|
906
2470
|
return {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
2471
|
+
array,
|
|
2472
|
+
elements,
|
|
2473
|
+
commas,
|
|
2474
|
+
trailing: last ? commaIn(source, last.end, array.end - 1) : null
|
|
2475
|
+
};
|
|
2476
|
+
};
|
|
2477
|
+
const canStructurallyEditArray = (source) => denseArraySource(source) !== null;
|
|
2478
|
+
const validArrayIndices = (indices, length) => [...indices].every((index) => Number.isInteger(index) && index >= 0 && index < length);
|
|
2479
|
+
const reorderArraySource = (source, elements, next) => applyEdits(source, elements.map((node, index) => ({
|
|
2480
|
+
start: node.start,
|
|
2481
|
+
end: node.end,
|
|
2482
|
+
content: source.slice(next[index].start, next[index].end)
|
|
2483
|
+
})));
|
|
2484
|
+
const removeArraySource = (source, data, indices) => {
|
|
2485
|
+
const edits = [];
|
|
2486
|
+
const removedCommas = /* @__PURE__ */ new Set();
|
|
2487
|
+
const { elements, commas, trailing } = data;
|
|
2488
|
+
for (let index = 0; index < elements.length; index++) {
|
|
2489
|
+
if (!indices.has(index)) continue;
|
|
2490
|
+
const start = index;
|
|
2491
|
+
while (index + 1 < elements.length && indices.has(index + 1)) index++;
|
|
2492
|
+
for (let current = start; current <= index; current++) {
|
|
2493
|
+
const node = elements[current];
|
|
2494
|
+
edits.push({
|
|
2495
|
+
start: node.start,
|
|
2496
|
+
end: node.end,
|
|
2497
|
+
content: ""
|
|
2498
|
+
});
|
|
2499
|
+
if (current < elements.length - 1) removedCommas.add(commas[current]);
|
|
2500
|
+
}
|
|
2501
|
+
if (index === elements.length - 1) {
|
|
2502
|
+
if (trailing !== null) removedCommas.add(trailing);
|
|
2503
|
+
else if (start > 0) removedCommas.add(commas[start - 1]);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
for (const comma of removedCommas) edits.push({
|
|
2507
|
+
start: comma,
|
|
2508
|
+
end: comma + 1,
|
|
2509
|
+
content: ""
|
|
2510
|
+
});
|
|
2511
|
+
return applyEdits(source, edits);
|
|
2512
|
+
};
|
|
2513
|
+
const appendArraySource = (source, data, copies) => {
|
|
2514
|
+
const last = data.elements.at(-1);
|
|
2515
|
+
if (!last || copies.length === 0) return source;
|
|
2516
|
+
return applyEdits(source, [{
|
|
2517
|
+
start: last.end,
|
|
2518
|
+
end: last.end,
|
|
2519
|
+
content: `, ${copies.join(", ")}`
|
|
2520
|
+
}]);
|
|
2521
|
+
};
|
|
2522
|
+
//#endregion
|
|
2523
|
+
//#region src/utils/ast/items.ts
|
|
2524
|
+
const elementsOf = (code) => parseArrayExpression(code)?.elements ?? null;
|
|
2525
|
+
const kindOf = (element) => isObjectExpression(element) ? "object" : "primitive";
|
|
2526
|
+
const resolveRenderLeaf = (render, key) => {
|
|
2527
|
+
const leaf = render?.[key];
|
|
2528
|
+
return leaf && "type" in leaf ? leaf : null;
|
|
2529
|
+
};
|
|
2530
|
+
const toTemplateRaw = (value) => {
|
|
2531
|
+
return value.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${").replace(/\r/g, "\\r");
|
|
2532
|
+
};
|
|
2533
|
+
const buildPropertyValue = (value, declaredType, renderLeaf, current) => {
|
|
2534
|
+
if (renderLeaf?.property === BINDING_PROP.INNER_HTML) {
|
|
2535
|
+
const raw = String(value);
|
|
2536
|
+
return templateLiteral([templateElement({
|
|
2537
|
+
raw: toTemplateRaw(raw),
|
|
2538
|
+
cooked: raw
|
|
2539
|
+
}, true)], []);
|
|
2540
|
+
}
|
|
2541
|
+
if (renderLeaf?.type === "jsx" || isJSXElement(current) || isJSXFragment(current)) {
|
|
2542
|
+
const trimmed = String(value).trim();
|
|
2543
|
+
if (!trimmed.startsWith("<")) return stringLiteral(trimmed);
|
|
2544
|
+
try {
|
|
2545
|
+
return parseExpression(trimmed, { plugins: ["jsx", "typescript"] });
|
|
2546
|
+
} catch {
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
if (declaredType === "array" || declaredType === "object") {
|
|
2551
|
+
if (typeof value === "string") try {
|
|
2552
|
+
return parseExpression(value, { plugins: ["jsx", "typescript"] });
|
|
2553
|
+
} catch {
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
if (!isLosslesslyEvaluable(current)) return;
|
|
2557
|
+
return valueToExpression(value) ?? void 0;
|
|
2558
|
+
}
|
|
2559
|
+
return createNodeFromValue(declaredType, parseValue(value)) ?? void 0;
|
|
2560
|
+
};
|
|
2561
|
+
const updateArrayItemProperty = (code, index, key, value, render) => {
|
|
2562
|
+
const elements = elementsOf(code);
|
|
2563
|
+
const element = elements?.[index];
|
|
2564
|
+
if (!elements || !isObjectExpression(element)) return null;
|
|
2565
|
+
const target = element.properties.find((property) => isObjectProperty(property) && isIdentifier(property.key) && property.key.name === key);
|
|
2566
|
+
if (!target) return null;
|
|
2567
|
+
const declaredType = extractObjectProperties(element)[key]?.type ?? "string";
|
|
2568
|
+
const nextValue = buildPropertyValue(value, declaredType, resolveRenderLeaf(render, key), target.value);
|
|
2569
|
+
if (!nextValue) return null;
|
|
2570
|
+
let serialized = typeof value === "string" && (declaredType === "array" || declaredType === "object" || isJSXElement(nextValue) || isJSXFragment(nextValue)) ? value.trim() : generateCode(nextValue);
|
|
2571
|
+
if (nextValue.trailingComments?.some((comment) => comment.type === "CommentLine")) serialized += "\n";
|
|
2572
|
+
const content = target.shorthand ? `${key}: ${serialized}` : serialized;
|
|
2573
|
+
return applyEdits(code, [{
|
|
2574
|
+
start: target.value.start,
|
|
2575
|
+
end: target.value.end,
|
|
2576
|
+
content
|
|
2577
|
+
}]);
|
|
2578
|
+
};
|
|
2579
|
+
const updateArrayItemValue = (code, index, value) => {
|
|
2580
|
+
const elements = elementsOf(code);
|
|
2581
|
+
const element = elements?.[index];
|
|
2582
|
+
if (!elements || !isExpression(element)) return null;
|
|
2583
|
+
const nextValue = createNodeFromValue(extractNodeValue(element).type, parseValue(value));
|
|
2584
|
+
if (!nextValue) return null;
|
|
2585
|
+
return applyEdits(code, [{
|
|
2586
|
+
start: element.start,
|
|
2587
|
+
end: element.end,
|
|
2588
|
+
content: generateCode(nextValue)
|
|
2589
|
+
}]);
|
|
2590
|
+
};
|
|
2591
|
+
const moveArrayItem = (code, from, to) => {
|
|
2592
|
+
const data = denseArraySource(code);
|
|
2593
|
+
if (!data || !validArrayIndices([from], data.elements.length) || !Number.isInteger(to)) return null;
|
|
2594
|
+
const next = [...data.elements];
|
|
2595
|
+
const [moved] = next.splice(from, 1);
|
|
2596
|
+
next.splice(to, 0, moved);
|
|
2597
|
+
return reorderArraySource(code, data.elements, next);
|
|
2598
|
+
};
|
|
2599
|
+
const moveArrayItems = (code, indices, direction) => {
|
|
2600
|
+
const data = denseArraySource(code);
|
|
2601
|
+
if (!data || !validArrayIndices(indices, data.elements.length) || !["up", "down"].includes(direction)) return null;
|
|
2602
|
+
const next = moveSelectedIndices(data.elements, indices, direction);
|
|
2603
|
+
return {
|
|
2604
|
+
code: reorderArraySource(code, data.elements, next.items),
|
|
2605
|
+
indices: next.indices
|
|
2606
|
+
};
|
|
2607
|
+
};
|
|
2608
|
+
const removeArrayItems = (code, indices, kind) => {
|
|
2609
|
+
const data = denseArraySource(code);
|
|
2610
|
+
if (!data || !validArrayIndices(indices, data.elements.length)) return null;
|
|
2611
|
+
const remaining = removeIndices(data.elements, indices);
|
|
2612
|
+
if (!(kind === void 0 ? remaining : remaining.filter((node) => kindOf(node) === kind)).length) return null;
|
|
2613
|
+
return removeArraySource(code, data, indices);
|
|
2614
|
+
};
|
|
2615
|
+
const copyItem = (code, element, generateId, usedIds) => {
|
|
2616
|
+
const edits = [];
|
|
2617
|
+
let unsupported = false;
|
|
2618
|
+
const reserveId = (id) => {
|
|
2619
|
+
if (usedIds.has(id)) unsupported = true;
|
|
2620
|
+
usedIds.add(id);
|
|
2621
|
+
return JSON.stringify(id);
|
|
2622
|
+
};
|
|
2623
|
+
traverseFast(element, (node) => {
|
|
2624
|
+
if (!isJSXOpeningElement(node)) return;
|
|
2625
|
+
const ids = node.attributes.filter((attr) => isJSXAttribute(attr) && isJSXIdentifier(attr.name, { name: "data-id" }));
|
|
2626
|
+
const attr = ids[0];
|
|
2627
|
+
if (ids.length > 1 || attr && (!isStringLiteral(attr.value) || node.attributes.slice(node.attributes.indexOf(attr) + 1).some((a) => isJSXSpreadAttribute(a)))) {
|
|
2628
|
+
unsupported = true;
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
if (attr && isStringLiteral(attr.value)) edits.push({
|
|
2632
|
+
start: attr.value.start,
|
|
2633
|
+
end: attr.value.end,
|
|
2634
|
+
content: reserveId(generateId())
|
|
2635
|
+
});
|
|
2636
|
+
});
|
|
2637
|
+
if (isObjectExpression(element)) {
|
|
2638
|
+
const keys = element.properties.filter((prop) => isObjectProperty(prop) && !prop.computed && (isIdentifier(prop.key, { name: "key" }) || isStringLiteral(prop.key, { value: "key" })));
|
|
2639
|
+
const key = keys[0];
|
|
2640
|
+
if (keys.length > 1 || key && (!isStringLiteral(key.value) || element.properties.slice(element.properties.indexOf(key) + 1).some((prop) => isSpreadElement(prop) || "computed" in prop && prop.computed))) return null;
|
|
2641
|
+
if (key && isStringLiteral(key.value)) edits.push({
|
|
2642
|
+
start: key.value.start,
|
|
2643
|
+
end: key.value.end,
|
|
2644
|
+
content: reserveId(`${key.value.value}-${generateId()}`)
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
if (unsupported) return null;
|
|
2648
|
+
return applyEdits(code.slice(element.start, element.end), edits.map((edit) => ({
|
|
2649
|
+
...edit,
|
|
2650
|
+
start: edit.start - element.start,
|
|
2651
|
+
end: edit.end - element.start
|
|
2652
|
+
})));
|
|
2653
|
+
};
|
|
2654
|
+
const duplicateArrayItems = (code, indices, generateId = () => nanoid(6)) => {
|
|
2655
|
+
const data = denseArraySource(code);
|
|
2656
|
+
if (!data || !indices.size || !validArrayIndices(indices, data.elements.length)) return null;
|
|
2657
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
2658
|
+
traverseFast(data.array, (node) => {
|
|
2659
|
+
if (isJSXAttribute(node) && isJSXIdentifier(node.name, { name: "data-id" }) && isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
2660
|
+
else if (isObjectProperty(node) && !node.computed && (isIdentifier(node.key, { name: "key" }) || isStringLiteral(node.key, { value: "key" })) && isStringLiteral(node.value)) usedIds.add(node.value.value);
|
|
2661
|
+
});
|
|
2662
|
+
const copies = [...indices].sort((a, b) => a - b).map((index) => copyItem(code, data.elements[index], generateId, usedIds));
|
|
2663
|
+
return copies.every((copy) => copy !== null) ? appendArraySource(code, data, copies) : null;
|
|
2664
|
+
};
|
|
2665
|
+
const appendArrayItem = (code, kind, generateId = () => nanoid(6)) => {
|
|
2666
|
+
const index = denseArraySource(code)?.elements.findIndex((node) => kindOf(node) === kind) ?? -1;
|
|
2667
|
+
return index < 0 ? null : duplicateArrayItems(code, /* @__PURE__ */ new Set([index]), generateId);
|
|
2668
|
+
};
|
|
2669
|
+
//#endregion
|
|
2670
|
+
//#region src/components/dnd/panel/items/item-bindings.ts
|
|
2671
|
+
const isPrimitiveItem = (item) => !("editableProperties" in item);
|
|
2672
|
+
const toItemBindings = (item, { id, value, render, onNodeChange, onValueChange, onPropertyChange }) => {
|
|
2673
|
+
if (isPrimitiveItem(item)) return {
|
|
2674
|
+
id,
|
|
2675
|
+
index: item.index,
|
|
2676
|
+
elementIndex: item.elementIndex,
|
|
2677
|
+
properties: [],
|
|
2678
|
+
nested: [],
|
|
2679
|
+
value: {
|
|
2680
|
+
id: `primitive-${item.id}`,
|
|
2681
|
+
label: `item-${item.index}`,
|
|
2682
|
+
property: item.type,
|
|
2683
|
+
value: item.value ?? "",
|
|
2684
|
+
rawValue: item.type === "unknown" ? item.source : String(item.value ?? ""),
|
|
2685
|
+
canEditValue: canLosslesslyEvaluateSource(item.source),
|
|
2686
|
+
onChange: onValueChange
|
|
2687
|
+
}
|
|
2688
|
+
};
|
|
2689
|
+
const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
|
|
2690
|
+
const binding = resolveRenderEntry(render, key);
|
|
2691
|
+
const propertySource = value.slice(prop.astNode.start, prop.astNode.end);
|
|
2692
|
+
return {
|
|
2693
|
+
...toBindingFields(binding),
|
|
2694
|
+
id: `item-${id}-${key}`,
|
|
2695
|
+
meta: {
|
|
2696
|
+
...binding.meta,
|
|
2697
|
+
valueType: prop.type
|
|
2698
|
+
},
|
|
2699
|
+
value: parseValue(String(prop.value)),
|
|
2700
|
+
rawValue: prop.type === "unknown" ? propertySource : String(prop.value),
|
|
2701
|
+
canEditValue: canLosslesslyEvaluateSource(propertySource),
|
|
2702
|
+
onChange: (next) => onPropertyChange(key, next)
|
|
2703
|
+
};
|
|
2704
|
+
});
|
|
2705
|
+
const nested = [...Object.entries(item.jsxBindings).map(([property, nodes]) => ({
|
|
2706
|
+
property,
|
|
2707
|
+
elements: nodes.flatMap((node) => {
|
|
2708
|
+
const source = resolvePanelBindings(node);
|
|
2709
|
+
if (!source) return [];
|
|
2710
|
+
return [{
|
|
2711
|
+
id: source.id,
|
|
2712
|
+
tagName: source.tagName,
|
|
2713
|
+
bindings: withPanelCommit(source.bindings, onNodeChange)
|
|
2714
|
+
}];
|
|
2715
|
+
})
|
|
2716
|
+
})), ...Object.entries(item.jsxFallbacks).map(([property, code]) => ({
|
|
2717
|
+
property,
|
|
2718
|
+
elements: [],
|
|
2719
|
+
fallback: {
|
|
2720
|
+
id: `item-${id}-${property}-jsx`,
|
|
2721
|
+
label: property,
|
|
2722
|
+
property,
|
|
2723
|
+
type: "jsx",
|
|
2724
|
+
value: code,
|
|
2725
|
+
rawValue: code,
|
|
2726
|
+
canEditValue: true,
|
|
2727
|
+
onChange: (next) => onPropertyChange(property, next)
|
|
913
2728
|
}
|
|
2729
|
+
}))];
|
|
2730
|
+
return {
|
|
2731
|
+
id,
|
|
2732
|
+
index: item.index,
|
|
2733
|
+
elementIndex: item.elementIndex,
|
|
2734
|
+
properties,
|
|
2735
|
+
nested
|
|
914
2736
|
};
|
|
915
2737
|
};
|
|
2738
|
+
//#endregion
|
|
2739
|
+
//#region src/components/dnd/panel/items/item-identity.ts
|
|
2740
|
+
const createIdentityState = (value, kind, items, ids = items.map((item) => item.id)) => ({
|
|
2741
|
+
value,
|
|
2742
|
+
kind,
|
|
2743
|
+
ids,
|
|
2744
|
+
signatures: items.map((item) => item.source)
|
|
2745
|
+
});
|
|
2746
|
+
const reconcileIdentityState = (current, value, kind, items) => {
|
|
2747
|
+
if (current && current.value === value && current.kind === kind && current.ids.length === items.length) return current;
|
|
2748
|
+
if (!current || current.kind !== kind) return createIdentityState(value, kind, items);
|
|
2749
|
+
const previousCounts = /* @__PURE__ */ new Map();
|
|
2750
|
+
const nextCounts = /* @__PURE__ */ new Map();
|
|
2751
|
+
current.signatures.forEach((signature) => {
|
|
2752
|
+
previousCounts.set(signature, (previousCounts.get(signature) ?? 0) + 1);
|
|
2753
|
+
});
|
|
2754
|
+
items.forEach((item) => {
|
|
2755
|
+
nextCounts.set(item.source, (nextCounts.get(item.source) ?? 0) + 1);
|
|
2756
|
+
});
|
|
2757
|
+
const ids = items.map((item) => {
|
|
2758
|
+
const previousIndex = current.signatures.indexOf(item.source);
|
|
2759
|
+
return previousIndex >= 0 && previousCounts.get(item.source) === 1 && nextCounts.get(item.source) === 1 ? current.ids[previousIndex] : item.id;
|
|
2760
|
+
});
|
|
2761
|
+
return createIdentityState(value, kind, items, ids);
|
|
2762
|
+
};
|
|
2763
|
+
const moveId = (ids, from, to) => {
|
|
2764
|
+
const next = [...ids];
|
|
2765
|
+
const [moved] = next.splice(from, 1);
|
|
2766
|
+
next.splice(to, 0, moved);
|
|
2767
|
+
return next;
|
|
2768
|
+
};
|
|
2769
|
+
const removeIds = (ids, indices) => {
|
|
2770
|
+
return ids.filter((_, index) => !indices.has(index));
|
|
2771
|
+
};
|
|
2772
|
+
//#endregion
|
|
2773
|
+
//#region src/components/dnd/panel/items/items-source.ts
|
|
916
2774
|
const bindingsInJSX = (source, bindingOptions) => {
|
|
917
2775
|
const nodes = extract(source, bindingOptions);
|
|
918
2776
|
const container = nodes.find((n) => n.bindings?.some((b) => b.property === "children"));
|
|
@@ -978,49 +2836,29 @@ const parseSource = (value, bindingOptions) => {
|
|
|
978
2836
|
parseError: false
|
|
979
2837
|
};
|
|
980
2838
|
};
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
});
|
|
995
|
-
items.forEach((item) => {
|
|
996
|
-
nextCounts.set(item.source, (nextCounts.get(item.source) ?? 0) + 1);
|
|
997
|
-
});
|
|
998
|
-
const ids = items.map((item) => {
|
|
999
|
-
const previousIndex = current.signatures.indexOf(item.source);
|
|
1000
|
-
return previousIndex >= 0 && previousCounts.get(item.source) === 1 && nextCounts.get(item.source) === 1 ? current.ids[previousIndex] : item.id;
|
|
1001
|
-
});
|
|
1002
|
-
return createIdentityState(value, kind, items, ids);
|
|
1003
|
-
};
|
|
1004
|
-
const moveId = (ids, from, to) => {
|
|
1005
|
-
const next = [...ids];
|
|
1006
|
-
const [moved] = next.splice(from, 1);
|
|
1007
|
-
next.splice(to, 0, moved);
|
|
1008
|
-
return next;
|
|
1009
|
-
};
|
|
1010
|
-
const removeIds = (ids, indices) => {
|
|
1011
|
-
return ids.filter((_, index) => !indices.has(index));
|
|
2839
|
+
//#endregion
|
|
2840
|
+
//#region src/components/dnd/panel/items/use-item-expansion.ts
|
|
2841
|
+
const useItemExpansion = (ids) => {
|
|
2842
|
+
const [collapsed, { toggle, set }] = useSet();
|
|
2843
|
+
return {
|
|
2844
|
+
expandedIds: ids.filter((id) => !collapsed.has(id)),
|
|
2845
|
+
isExpanded: (id) => !collapsed.has(id),
|
|
2846
|
+
toggle: (id) => toggle(id),
|
|
2847
|
+
setExpanded: (expandedIds) => {
|
|
2848
|
+
const expanded = new Set(expandedIds);
|
|
2849
|
+
set(ids.filter((id) => !expanded.has(id)));
|
|
2850
|
+
}
|
|
2851
|
+
};
|
|
1012
2852
|
};
|
|
1013
|
-
|
|
2853
|
+
//#endregion
|
|
2854
|
+
//#region src/components/dnd/panel/items/use-dnd-items.ts
|
|
2855
|
+
const useDndItems = (value, { render, onChange, onNodeChange, minItems, maxItems } = {}) => {
|
|
1014
2856
|
const { reportError, bindingOptions } = useDndEditOptions();
|
|
1015
2857
|
const messages = useLiveMessages();
|
|
1016
2858
|
const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value, bindingOptions), [value, bindingOptions]);
|
|
1017
2859
|
const canEditStructure = useMemo(() => canStructurallyEditArray(value), [value]);
|
|
1018
|
-
const reportErrorRef =
|
|
1019
|
-
const messagesRef =
|
|
1020
|
-
useEffect(() => {
|
|
1021
|
-
reportErrorRef.current = reportError;
|
|
1022
|
-
messagesRef.current = messages;
|
|
1023
|
-
});
|
|
2860
|
+
const reportErrorRef = useLatest(reportError);
|
|
2861
|
+
const messagesRef = useLatest(messages);
|
|
1024
2862
|
useEffect(() => {
|
|
1025
2863
|
if (parseError) reportErrorRef.current({
|
|
1026
2864
|
type: "parse",
|
|
@@ -1028,7 +2866,11 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
|
|
|
1028
2866
|
title: messagesRef.current.editErrors.itemsParseFailed,
|
|
1029
2867
|
description: messagesRef.current.editErrors.checkConsole
|
|
1030
2868
|
});
|
|
1031
|
-
}, [
|
|
2869
|
+
}, [
|
|
2870
|
+
parseError,
|
|
2871
|
+
reportErrorRef,
|
|
2872
|
+
messagesRef
|
|
2873
|
+
]);
|
|
1032
2874
|
const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
|
|
1033
2875
|
const isPrimitive = kind === "primitive";
|
|
1034
2876
|
const rawItems = isPrimitive ? primitiveItems : objectItems;
|
|
@@ -1039,15 +2881,11 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
|
|
|
1039
2881
|
rawItems,
|
|
1040
2882
|
value
|
|
1041
2883
|
]);
|
|
1042
|
-
const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
|
|
1043
2884
|
const { selection, record } = useStructuralSelection(isPrimitive ? primitiveItems.length : objectItems.length, value);
|
|
1044
|
-
const
|
|
1045
|
-
useEffect(() => {
|
|
1046
|
-
pending.clear();
|
|
1047
|
-
});
|
|
2885
|
+
const pending = usePendingCommit(value);
|
|
1048
2886
|
const latest = () => {
|
|
1049
2887
|
const last = pending.read();
|
|
1050
|
-
if (last
|
|
2888
|
+
if (!last) return {
|
|
1051
2889
|
value,
|
|
1052
2890
|
items: rawItems,
|
|
1053
2891
|
ids: identity.ids,
|
|
@@ -1080,7 +2918,6 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
|
|
|
1080
2918
|
const nextItems = kind === "primitive" ? parsed.primitiveItems : parsed.objectItems;
|
|
1081
2919
|
const ids = nextIds ?? snapshot.ids;
|
|
1082
2920
|
pending.write({
|
|
1083
|
-
from: value,
|
|
1084
2921
|
value: next,
|
|
1085
2922
|
ids,
|
|
1086
2923
|
selected: nextSelection
|
|
@@ -1090,6 +2927,17 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
|
|
|
1090
2927
|
onChange?.(next);
|
|
1091
2928
|
return true;
|
|
1092
2929
|
};
|
|
2930
|
+
const fewestItems = Math.max(minItems ?? 1, 1);
|
|
2931
|
+
const refuseCount = (length) => {
|
|
2932
|
+
const description = maxItems !== void 0 && length > maxItems ? messages.validation.maxItems(maxItems) : minItems !== void 0 && length < fewestItems ? messages.validation.minItems(fewestItems) : null;
|
|
2933
|
+
if (description === null) return false;
|
|
2934
|
+
reportError({
|
|
2935
|
+
type: "items",
|
|
2936
|
+
title: messages.editErrors.itemUpdateFailed,
|
|
2937
|
+
description
|
|
2938
|
+
});
|
|
2939
|
+
return true;
|
|
2940
|
+
};
|
|
1093
2941
|
const elementIndicesOf = (snapshot, indices) => new Set(snapshot.items.filter((item) => indices.has(item.index)).map((item) => item.elementIndex));
|
|
1094
2942
|
const updateProperty = (elementIndex, propertyKey, next) => {
|
|
1095
2943
|
const snapshot = latest();
|
|
@@ -1106,6 +2954,7 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
|
|
|
1106
2954
|
const actions = {
|
|
1107
2955
|
add: () => {
|
|
1108
2956
|
const snapshot = latest();
|
|
2957
|
+
if (refuseCount(snapshot.items.length + 1)) return;
|
|
1109
2958
|
const next = appendArrayItem(snapshot.value, kind);
|
|
1110
2959
|
commit(snapshot, next, next ? [...snapshot.ids, nanoid(6)] : void 0, selectionAfter({ type: "append" }, snapshot.selected, snapshot.items.length));
|
|
1111
2960
|
},
|
|
@@ -1119,12 +2968,13 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
|
|
|
1119
2968
|
remove: (elementIndex) => {
|
|
1120
2969
|
const snapshot = latest();
|
|
1121
2970
|
const removed = locate(snapshot, elementIndex);
|
|
1122
|
-
if (removed === null) return;
|
|
2971
|
+
if (removed === null || refuseCount(snapshot.items.length - 1)) return;
|
|
1123
2972
|
commit(snapshot, removeArrayItems(snapshot.value, /* @__PURE__ */ new Set([removed?.elementIndex ?? elementIndex]), kind), removed ? removeIds(snapshot.ids, /* @__PURE__ */ new Set([removed.index])) : void 0, /* @__PURE__ */ new Set());
|
|
1124
2973
|
},
|
|
1125
2974
|
duplicateSelected: () => {
|
|
1126
2975
|
const snapshot = latest();
|
|
1127
2976
|
const selected = elementIndicesOf(snapshot, snapshot.selected);
|
|
2977
|
+
if (refuseCount(snapshot.items.length + selected.size)) return;
|
|
1128
2978
|
const next = duplicateArrayItems(snapshot.value, selected);
|
|
1129
2979
|
const copied = snapshot.items.filter((item) => selected.has(item.elementIndex)).sort((a, b) => a.elementIndex - b.elementIndex).map(() => nanoid(6));
|
|
1130
2980
|
commit(snapshot, next, next ? [...snapshot.ids, ...copied] : void 0, selectionAfter({
|
|
@@ -1145,99 +2995,33 @@ const useDndItems = (value, { render, onChange, onNodeChange } = {}) => {
|
|
|
1145
2995
|
},
|
|
1146
2996
|
removeSelected: () => {
|
|
1147
2997
|
const snapshot = latest();
|
|
1148
|
-
|
|
2998
|
+
const selected = elementIndicesOf(snapshot, snapshot.selected);
|
|
2999
|
+
if (refuseCount(snapshot.items.length - selected.size)) return;
|
|
3000
|
+
commit(snapshot, removeArrayItems(snapshot.value, selected, kind), removeIds(snapshot.ids, snapshot.selected), /* @__PURE__ */ new Set());
|
|
1149
3001
|
}
|
|
1150
3002
|
};
|
|
1151
|
-
const items =
|
|
3003
|
+
const items = rawItems.map((item) => toItemBindings(item, {
|
|
1152
3004
|
id: identity.ids[item.index] ?? item.id,
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
label: `item-${item.index}`,
|
|
1160
|
-
property: item.type,
|
|
1161
|
-
value: item.value ?? "",
|
|
1162
|
-
rawValue: item.type === "unknown" ? item.source : String(item.value ?? ""),
|
|
1163
|
-
canEditValue: canLosslesslyEvaluateSource(item.source),
|
|
1164
|
-
onChange: (next) => updateValue(item.elementIndex, next)
|
|
1165
|
-
}
|
|
1166
|
-
})) : objectItems.map((item) => {
|
|
1167
|
-
const id = identity.ids[item.index] ?? item.id;
|
|
1168
|
-
const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
|
|
1169
|
-
const binding = resolveRenderEntry(render, key);
|
|
1170
|
-
const propertySource = value.slice(prop.astNode.start, prop.astNode.end);
|
|
1171
|
-
return {
|
|
1172
|
-
...toBindingFields(binding),
|
|
1173
|
-
id: `item-${id}-${key}`,
|
|
1174
|
-
meta: {
|
|
1175
|
-
...binding.meta,
|
|
1176
|
-
valueType: prop.type
|
|
1177
|
-
},
|
|
1178
|
-
value: parseValue(String(prop.value)),
|
|
1179
|
-
rawValue: prop.type === "unknown" ? propertySource : String(prop.value),
|
|
1180
|
-
canEditValue: canLosslesslyEvaluateSource(propertySource),
|
|
1181
|
-
onChange: (next) => updateProperty(item.elementIndex, key, next)
|
|
1182
|
-
};
|
|
1183
|
-
});
|
|
1184
|
-
const nested = [...Object.entries(item.jsxBindings).map(([property, nodes]) => ({
|
|
1185
|
-
property,
|
|
1186
|
-
elements: nodes.flatMap((node) => {
|
|
1187
|
-
const source = resolvePanelBindings(node);
|
|
1188
|
-
if (!source) return [];
|
|
1189
|
-
return [{
|
|
1190
|
-
id: source.id,
|
|
1191
|
-
tagName: source.tagName,
|
|
1192
|
-
bindings: withPanelCommit(source.bindings, onNodeChange)
|
|
1193
|
-
}];
|
|
1194
|
-
})
|
|
1195
|
-
})), ...Object.entries(item.jsxFallbacks).map(([property, code]) => ({
|
|
1196
|
-
property,
|
|
1197
|
-
elements: [],
|
|
1198
|
-
fallback: {
|
|
1199
|
-
id: `item-${id}-${property}-jsx`,
|
|
1200
|
-
label: property,
|
|
1201
|
-
property,
|
|
1202
|
-
type: "jsx",
|
|
1203
|
-
value: code,
|
|
1204
|
-
rawValue: code,
|
|
1205
|
-
canEditValue: true,
|
|
1206
|
-
onChange: (next) => updateProperty(item.elementIndex, property, next)
|
|
1207
|
-
}
|
|
1208
|
-
}))];
|
|
1209
|
-
return {
|
|
1210
|
-
id,
|
|
1211
|
-
index: item.index,
|
|
1212
|
-
elementIndex: item.elementIndex,
|
|
1213
|
-
properties,
|
|
1214
|
-
nested
|
|
1215
|
-
};
|
|
1216
|
-
});
|
|
3005
|
+
value,
|
|
3006
|
+
render,
|
|
3007
|
+
onNodeChange,
|
|
3008
|
+
onValueChange: (next) => updateValue(item.elementIndex, next),
|
|
3009
|
+
onPropertyChange: (key, next) => updateProperty(item.elementIndex, key, next)
|
|
3010
|
+
}));
|
|
1217
3011
|
return {
|
|
1218
3012
|
kind,
|
|
1219
3013
|
items,
|
|
1220
3014
|
selection,
|
|
1221
|
-
expansion:
|
|
1222
|
-
expandedIds: items.flatMap((item) => collapsed.has(item.id) ? [] : [item.id]),
|
|
1223
|
-
isExpanded: (id) => !collapsed.has(id),
|
|
1224
|
-
toggle: (id) => setCollapsed((previous) => {
|
|
1225
|
-
const next = new Set(previous);
|
|
1226
|
-
if (!next.delete(id)) next.add(id);
|
|
1227
|
-
return next;
|
|
1228
|
-
}),
|
|
1229
|
-
setExpanded: (ids) => {
|
|
1230
|
-
const expanded = new Set(ids);
|
|
1231
|
-
setCollapsed(new Set(items.flatMap((item) => expanded.has(item.id) ? [] : [item.id])));
|
|
1232
|
-
}
|
|
1233
|
-
},
|
|
3015
|
+
expansion: useItemExpansion(items.map((item) => item.id)),
|
|
1234
3016
|
actions,
|
|
1235
3017
|
canEditStructure,
|
|
3018
|
+
canAddItems: (count = 1) => maxItems === void 0 || items.length + count <= maxItems,
|
|
3019
|
+
canRemoveItems: (count = 1) => items.length - count >= fewestItems,
|
|
1236
3020
|
parseError
|
|
1237
3021
|
};
|
|
1238
3022
|
};
|
|
1239
3023
|
//#endregion
|
|
1240
|
-
//#region src/components/dnd/panel/items.tsx
|
|
3024
|
+
//#region src/components/dnd/panel/items/items.tsx
|
|
1241
3025
|
const NestedGroup = ({ group, onNodeChange }) => {
|
|
1242
3026
|
if (group.fallback) return /* @__PURE__ */ jsxs("div", {
|
|
1243
3027
|
className: "space-y-2",
|
|
@@ -1259,48 +3043,30 @@ const NestedGroup = ({ group, onNodeChange }) => {
|
|
|
1259
3043
|
group.elements.length,
|
|
1260
3044
|
"):"
|
|
1261
3045
|
]
|
|
1262
|
-
}), group.elements.map((element) => /* @__PURE__ */
|
|
1263
|
-
|
|
3046
|
+
}), group.elements.map((element) => /* @__PURE__ */ jsx(ElementFields, {
|
|
3047
|
+
id: element.id,
|
|
1264
3048
|
className: "rounded border border-blue-100 bg-blue-50 p-2\n data-[picked]:ring-2 data-[picked]:ring-blue-400",
|
|
1265
|
-
|
|
3049
|
+
header: /* @__PURE__ */ jsxs("div", {
|
|
1266
3050
|
className: "mb-1 text-xs text-blue-600",
|
|
1267
3051
|
children: [
|
|
1268
3052
|
"<",
|
|
1269
3053
|
element.tagName,
|
|
1270
3054
|
">"
|
|
1271
3055
|
]
|
|
1272
|
-
}),
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
className: "space-y-1",
|
|
1276
|
-
children: element.bindings.map((binding) => /* @__PURE__ */ jsxs("div", {
|
|
1277
|
-
className: "space-y-1",
|
|
1278
|
-
children: [/* @__PURE__ */ jsxs("label", {
|
|
1279
|
-
className: "block text-xs font-semibold text-gray-700",
|
|
1280
|
-
children: [binding.label, /* @__PURE__ */ jsxs("span", {
|
|
1281
|
-
className: "ml-1 text-gray-400",
|
|
1282
|
-
children: [
|
|
1283
|
-
"(",
|
|
1284
|
-
binding.property,
|
|
1285
|
-
")"
|
|
1286
|
-
]
|
|
1287
|
-
})]
|
|
1288
|
-
}), /* @__PURE__ */ jsx(Field, {
|
|
1289
|
-
binding,
|
|
1290
|
-
onNodeChange
|
|
1291
|
-
})]
|
|
1292
|
-
}, `${binding.property}-${binding.label}`))
|
|
1293
|
-
})
|
|
1294
|
-
})]
|
|
3056
|
+
}),
|
|
3057
|
+
bindings: element.bindings,
|
|
3058
|
+
onNodeChange
|
|
1295
3059
|
}, `${group.property}-${element.id}`))]
|
|
1296
3060
|
});
|
|
1297
3061
|
};
|
|
1298
|
-
const Items = ({ value, render, onChange, onChildChange }) => {
|
|
3062
|
+
const Items = ({ value, render, minItems, maxItems, onChange, onChildChange }) => {
|
|
1299
3063
|
const messages = useLiveMessages();
|
|
1300
|
-
const { kind, items, selection, expansion, actions, canEditStructure, parseError } = useDndItems(value, {
|
|
3064
|
+
const { kind, items, selection, expansion, actions, canEditStructure, canAddItems, canRemoveItems, parseError } = useDndItems(value, {
|
|
1301
3065
|
render,
|
|
1302
3066
|
onChange,
|
|
1303
|
-
onNodeChange: onChildChange
|
|
3067
|
+
onNodeChange: onChildChange,
|
|
3068
|
+
minItems,
|
|
3069
|
+
maxItems
|
|
1304
3070
|
});
|
|
1305
3071
|
const header = /* @__PURE__ */ jsxs("div", {
|
|
1306
3072
|
className: "flex items-center justify-between",
|
|
@@ -1312,6 +3078,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
|
|
|
1312
3078
|
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
1313
3079
|
variant: "solid",
|
|
1314
3080
|
color: "green",
|
|
3081
|
+
disabled: !canAddItems(),
|
|
1315
3082
|
onClick: actions.add,
|
|
1316
3083
|
children: messages.items.add
|
|
1317
3084
|
})]
|
|
@@ -1333,59 +3100,27 @@ const Items = ({ value, render, onChange, onChildChange }) => {
|
|
|
1333
3100
|
const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
|
|
1334
3101
|
count: selection.selected.size,
|
|
1335
3102
|
disabled: !canEditStructure,
|
|
3103
|
+
duplicateDisabled: !canAddItems(selection.selected.size),
|
|
3104
|
+
deleteDisabled: !canRemoveItems(selection.selected.size),
|
|
1336
3105
|
onDuplicate: actions.duplicateSelected,
|
|
1337
3106
|
onMoveUp: () => actions.moveSelected("up"),
|
|
1338
3107
|
onMoveDown: () => actions.moveSelected("down"),
|
|
1339
3108
|
onDelete: actions.removeSelected,
|
|
1340
3109
|
onClear: selection.clear
|
|
1341
3110
|
});
|
|
1342
|
-
const itemControls = (item) => /* @__PURE__ */ jsxs("div", {
|
|
1343
|
-
className: "flex space-x-1",
|
|
1344
|
-
children: [
|
|
1345
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1346
|
-
size: "small",
|
|
1347
|
-
icon: /* @__PURE__ */ jsx(ArrowUp, {}),
|
|
1348
|
-
"aria-label": messages.items.moveUp,
|
|
1349
|
-
disabled: !canEditStructure || item.index === 0,
|
|
1350
|
-
onClick: () => actions.move(item.elementIndex, item.index - 1)
|
|
1351
|
-
}),
|
|
1352
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1353
|
-
size: "small",
|
|
1354
|
-
icon: /* @__PURE__ */ jsx(ArrowDown, {}),
|
|
1355
|
-
"aria-label": messages.items.moveDown,
|
|
1356
|
-
disabled: !canEditStructure || item.index === items.length - 1,
|
|
1357
|
-
onClick: () => actions.move(item.elementIndex, item.index + 1)
|
|
1358
|
-
}),
|
|
1359
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1360
|
-
danger: true,
|
|
1361
|
-
size: "small",
|
|
1362
|
-
icon: /* @__PURE__ */ jsx(X, {}),
|
|
1363
|
-
"aria-label": messages.items.delete,
|
|
1364
|
-
disabled: !canEditStructure || items.length <= 1,
|
|
1365
|
-
onClick: () => actions.remove(item.elementIndex)
|
|
1366
|
-
})
|
|
1367
|
-
]
|
|
1368
|
-
});
|
|
1369
|
-
const itemCheckbox = (item) => /* @__PURE__ */ jsx("div", {
|
|
1370
|
-
onClick: (e) => canEditStructure && selection.toggle(item.index, e.shiftKey),
|
|
1371
|
-
className: "inline-flex",
|
|
1372
|
-
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
1373
|
-
checked: selection.isSelected(item.index),
|
|
1374
|
-
disabled: !canEditStructure,
|
|
1375
|
-
onChange: () => {}
|
|
1376
|
-
})
|
|
1377
|
-
});
|
|
1378
3111
|
const objectBody = (item) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1379
3112
|
className: "space-y-2",
|
|
1380
|
-
children: item.properties.map((binding) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */
|
|
3113
|
+
children: item.properties.map((binding) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
|
|
1381
3114
|
className: "flex flex-col space-y-2",
|
|
1382
|
-
children:
|
|
1383
|
-
className: "w-20 shrink-0 text-xs font-medium",
|
|
1384
|
-
children: binding.label
|
|
1385
|
-
}), /* @__PURE__ */ jsx(Field, {
|
|
3115
|
+
children: /* @__PURE__ */ jsx(LabeledField, {
|
|
1386
3116
|
binding,
|
|
1387
|
-
|
|
1388
|
-
|
|
3117
|
+
labelClassName: "w-20 shrink-0 text-xs font-medium",
|
|
3118
|
+
label: binding.label,
|
|
3119
|
+
children: /* @__PURE__ */ jsx(Field, {
|
|
3120
|
+
binding,
|
|
3121
|
+
onNodeChange: onChildChange
|
|
3122
|
+
})
|
|
3123
|
+
})
|
|
1389
3124
|
}), /* @__PURE__ */ jsxs("span", {
|
|
1390
3125
|
className: "text-right text-xs text-gray-500",
|
|
1391
3126
|
children: [
|
|
@@ -1416,14 +3151,20 @@ const Items = ({ value, render, onChange, onChildChange }) => {
|
|
|
1416
3151
|
className: "space-y-4",
|
|
1417
3152
|
children: [
|
|
1418
3153
|
header,
|
|
1419
|
-
|
|
1420
|
-
className: "flex justify-
|
|
1421
|
-
children: /* @__PURE__ */ jsx(
|
|
3154
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3155
|
+
className: "flex items-center justify-between",
|
|
3156
|
+
children: [/* @__PURE__ */ jsx(SelectAllCheckbox, {
|
|
3157
|
+
total: items.length,
|
|
3158
|
+
selected: selection.selected.size,
|
|
3159
|
+
disabled: !canEditStructure,
|
|
3160
|
+
onSelectAll: () => selection.replace(new Set(items.map((item) => item.index))),
|
|
3161
|
+
onClear: selection.clear
|
|
3162
|
+
}), items.length > 1 && /* @__PURE__ */ jsx(Button, {
|
|
1422
3163
|
size: "small",
|
|
1423
3164
|
variant: "text",
|
|
1424
3165
|
onClick: () => expansion.setExpanded(allExpanded ? [] : items.map((item) => item.id)),
|
|
1425
3166
|
children: allExpanded ? messages.items.collapseAll : messages.items.expandAll
|
|
1426
|
-
})
|
|
3167
|
+
})]
|
|
1427
3168
|
}),
|
|
1428
3169
|
bulkBar,
|
|
1429
3170
|
!canEditStructure && /* @__PURE__ */ jsx("div", {
|
|
@@ -1440,8 +3181,20 @@ const Items = ({ value, render, onChange, onChildChange }) => {
|
|
|
1440
3181
|
disabled: !canEditStructure,
|
|
1441
3182
|
expanded: expansion.isExpanded(item.id),
|
|
1442
3183
|
onToggle: () => expansion.toggle(item.id),
|
|
1443
|
-
checkbox:
|
|
1444
|
-
|
|
3184
|
+
checkbox: /* @__PURE__ */ jsx(SelectionCheckbox, {
|
|
3185
|
+
checked: selection.isSelected(item.index),
|
|
3186
|
+
disabled: !canEditStructure,
|
|
3187
|
+
onToggle: (shiftKey) => selection.toggle(item.index, shiftKey)
|
|
3188
|
+
}),
|
|
3189
|
+
controls: /* @__PURE__ */ jsx(RowControls, {
|
|
3190
|
+
index: item.index,
|
|
3191
|
+
count: items.length,
|
|
3192
|
+
labels: messages.items,
|
|
3193
|
+
disabled: !canEditStructure,
|
|
3194
|
+
deleteDisabled: !canRemoveItems(),
|
|
3195
|
+
onMove: (toIndex) => actions.move(item.elementIndex, toIndex),
|
|
3196
|
+
onDelete: () => actions.remove(item.elementIndex)
|
|
3197
|
+
}),
|
|
1445
3198
|
className: kind === "primitive" ? "space-y-2 rounded border border-gray-100 bg-gray-50 p-2" : "space-y-3 rounded border bg-gray-50 p-3",
|
|
1446
3199
|
children: kind === "primitive" ? /* @__PURE__ */ jsx(Field, {
|
|
1447
3200
|
binding: item.value,
|
|
@@ -1454,22 +3207,18 @@ const Items = ({ value, render, onChange, onChildChange }) => {
|
|
|
1454
3207
|
};
|
|
1455
3208
|
//#endregion
|
|
1456
3209
|
//#region src/components/dnd/panel/field.tsx
|
|
1457
|
-
const normalizeToHex = (value) => {
|
|
1458
|
-
const trimmed = value.trim();
|
|
1459
|
-
if (/^#([0-9A-Fa-f]{3}){1,2}$/.test(trimmed)) return trimmed;
|
|
1460
|
-
return "#000000";
|
|
1461
|
-
};
|
|
1462
3210
|
const parseDateValue = (value) => {
|
|
1463
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})
|
|
3211
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?$/.exec(value.trim());
|
|
1464
3212
|
if (!match) return;
|
|
1465
|
-
const [, year, month, day] = match;
|
|
1466
|
-
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
|
3213
|
+
const [, year, month, day, hours = "0", minutes = "0"] = match;
|
|
3214
|
+
const date = new Date(Number(year), Number(month) - 1, Number(day), Number(hours), Number(minutes));
|
|
1467
3215
|
return Number.isNaN(date.getTime()) ? void 0 : date;
|
|
1468
3216
|
};
|
|
1469
|
-
const
|
|
1470
|
-
|
|
3217
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
3218
|
+
const formatDateValue = (date, withTime = false) => {
|
|
3219
|
+
const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
3220
|
+
return withTime ? `${day}T${pad(date.getHours())}:${pad(date.getMinutes())}` : day;
|
|
1471
3221
|
};
|
|
1472
|
-
const COLOR_COMMIT_DELAY = 75;
|
|
1473
3222
|
const RICHTEXT_TOOLBAR = [
|
|
1474
3223
|
"bold",
|
|
1475
3224
|
"italic",
|
|
@@ -1478,69 +3227,9 @@ const RICHTEXT_TOOLBAR = [
|
|
|
1478
3227
|
"orderedList",
|
|
1479
3228
|
"link"
|
|
1480
3229
|
];
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
const handleUpdate = useCallback((update) => {
|
|
1485
|
-
selectionRef.current = update.state.selection;
|
|
1486
|
-
}, []);
|
|
1487
|
-
const handleCreateEditor = useCallback((view) => {
|
|
1488
|
-
if (selectionRef.current) view.dispatch({ selection: selectionRef.current });
|
|
1489
|
-
if (focusedRef.current) view.focus();
|
|
1490
|
-
}, []);
|
|
1491
|
-
const handleFocus = useCallback(() => {
|
|
1492
|
-
focusedRef.current = true;
|
|
1493
|
-
}, []);
|
|
1494
|
-
const handleBlur = useCallback((event) => {
|
|
1495
|
-
if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) focusedRef.current = false;
|
|
1496
|
-
}, []);
|
|
1497
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1498
|
-
onFocusCapture: handleFocus,
|
|
1499
|
-
onBlurCapture: handleBlur,
|
|
1500
|
-
children: /* @__PURE__ */ jsx(Core, {
|
|
1501
|
-
value,
|
|
1502
|
-
height: "150px",
|
|
1503
|
-
fragment: !isHTML,
|
|
1504
|
-
raw: isHTML,
|
|
1505
|
-
onSave,
|
|
1506
|
-
onUpdate: handleUpdate,
|
|
1507
|
-
onCreateEditor: handleCreateEditor
|
|
1508
|
-
})
|
|
1509
|
-
});
|
|
1510
|
-
};
|
|
1511
|
-
const ColorPickerField = ({ value, onChange }) => {
|
|
1512
|
-
const [liveValue, setLiveValue] = useState(value);
|
|
1513
|
-
const [prevValue, setPrevValue] = useState(value);
|
|
1514
|
-
const lastCommittedRef = useRef(value);
|
|
1515
|
-
if (value !== prevValue) {
|
|
1516
|
-
setPrevValue(value);
|
|
1517
|
-
setLiveValue(value);
|
|
1518
|
-
}
|
|
1519
|
-
useEffect(() => {
|
|
1520
|
-
lastCommittedRef.current = value;
|
|
1521
|
-
}, [value]);
|
|
1522
|
-
const commit = (next) => {
|
|
1523
|
-
if (next === lastCommittedRef.current) return;
|
|
1524
|
-
lastCommittedRef.current = next;
|
|
1525
|
-
onChange(next);
|
|
1526
|
-
};
|
|
1527
|
-
const debouncedCommit = useDebounce(() => commit(liveValue), {
|
|
1528
|
-
delay: COLOR_COMMIT_DELAY,
|
|
1529
|
-
autoInvoke: false
|
|
1530
|
-
});
|
|
1531
|
-
return /* @__PURE__ */ jsx(ColorPicker, {
|
|
1532
|
-
showText: true,
|
|
1533
|
-
value: liveValue,
|
|
1534
|
-
onChange: (next) => {
|
|
1535
|
-
setLiveValue(next);
|
|
1536
|
-
debouncedCommit();
|
|
1537
|
-
},
|
|
1538
|
-
onOpenChange: (open) => {
|
|
1539
|
-
if (!open) commit(liveValue);
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
};
|
|
1543
|
-
const FieldError = ({ message }) => message ? /* @__PURE__ */ jsx("p", {
|
|
3230
|
+
const FieldError = ({ id, message }) => message ? /* @__PURE__ */ jsx("p", {
|
|
3231
|
+
id,
|
|
3232
|
+
role: "alert",
|
|
1544
3233
|
className: "mt-1 mb-0 text-xs text-red-500",
|
|
1545
3234
|
children: message
|
|
1546
3235
|
}) : null;
|
|
@@ -1549,6 +3238,13 @@ const BuiltinField = ({ binding, onNodeChange }) => {
|
|
|
1549
3238
|
const validationOptions = { messages: messages.validation };
|
|
1550
3239
|
const { id, value, rawValue, onChange } = binding;
|
|
1551
3240
|
const [validationError, setValidationError] = useState(null);
|
|
3241
|
+
const { controlId, errorId, isLabelled } = useFieldIds();
|
|
3242
|
+
const controlProps = {
|
|
3243
|
+
id: controlId,
|
|
3244
|
+
"aria-label": isLabelled ? void 0 : binding.label,
|
|
3245
|
+
"aria-invalid": validationError ? true : void 0,
|
|
3246
|
+
"aria-describedby": validationError ? errorId : void 0
|
|
3247
|
+
};
|
|
1552
3248
|
const [text, setText] = useState(rawValue);
|
|
1553
3249
|
const [prevRawValue, setPrevRawValue] = useState(rawValue);
|
|
1554
3250
|
if (rawValue !== prevRawValue) {
|
|
@@ -1570,9 +3266,20 @@ const BuiltinField = ({ binding, onNodeChange }) => {
|
|
|
1570
3266
|
const commitIfChanged = (next) => {
|
|
1571
3267
|
if (next !== (typeof next === "string" ? rawValue : value)) onChange(next);
|
|
1572
3268
|
};
|
|
3269
|
+
const validateAndCommit = (next) => {
|
|
3270
|
+
const result = validateBindingValue(binding, next, validationOptions);
|
|
3271
|
+
if (!result.valid) {
|
|
3272
|
+
setValidationError(result.message ?? messages.panel.invalidValue);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
setValidationError(null);
|
|
3276
|
+
commitIfChanged(next);
|
|
3277
|
+
};
|
|
1573
3278
|
if (kind === "items") return /* @__PURE__ */ jsx(Items, {
|
|
1574
3279
|
value: rawValue,
|
|
1575
3280
|
render: binding.render,
|
|
3281
|
+
minItems: binding.minItems,
|
|
3282
|
+
maxItems: binding.maxItems,
|
|
1576
3283
|
onChange,
|
|
1577
3284
|
onChildChange: onNodeChange
|
|
1578
3285
|
});
|
|
@@ -1597,26 +3304,31 @@ const BuiltinField = ({ binding, onNodeChange }) => {
|
|
|
1597
3304
|
className: "space-y-2 rounded border border-gray-200 bg-gray-50 p-2",
|
|
1598
3305
|
children: Object.entries(objectValue).map(([key, val]) => {
|
|
1599
3306
|
const nested = resolveRenderEntry(binding.render, key);
|
|
1600
|
-
return /* @__PURE__ */
|
|
3307
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1601
3308
|
className: "space-y-1",
|
|
1602
|
-
children:
|
|
1603
|
-
className: "block text-xs font-medium text-gray-600",
|
|
1604
|
-
children: nested.label
|
|
1605
|
-
}), /* @__PURE__ */ jsx(Field, {
|
|
3309
|
+
children: /* @__PURE__ */ jsx(LabeledField, {
|
|
1606
3310
|
binding: {
|
|
1607
|
-
...
|
|
1608
|
-
|
|
1609
|
-
value: val,
|
|
1610
|
-
rawValue: typeof val === "object" && val !== null ? JSON.stringify(val) : String(val),
|
|
1611
|
-
onChange: (next) => {
|
|
1612
|
-
onChange({
|
|
1613
|
-
...objectValue,
|
|
1614
|
-
[key]: next
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
3311
|
+
...nested,
|
|
3312
|
+
value: val
|
|
1617
3313
|
},
|
|
1618
|
-
|
|
1619
|
-
|
|
3314
|
+
labelClassName: "block text-xs font-medium text-gray-600",
|
|
3315
|
+
label: nested.label,
|
|
3316
|
+
children: /* @__PURE__ */ jsx(Field, {
|
|
3317
|
+
binding: {
|
|
3318
|
+
...toBindingFields(nested),
|
|
3319
|
+
id,
|
|
3320
|
+
value: val,
|
|
3321
|
+
rawValue: typeof val === "object" && val !== null ? JSON.stringify(val) : String(val),
|
|
3322
|
+
onChange: (next) => {
|
|
3323
|
+
onChange({
|
|
3324
|
+
...objectValue,
|
|
3325
|
+
[key]: next
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
},
|
|
3329
|
+
onNodeChange
|
|
3330
|
+
})
|
|
3331
|
+
})
|
|
1620
3332
|
}, key);
|
|
1621
3333
|
})
|
|
1622
3334
|
});
|
|
@@ -1629,76 +3341,76 @@ const BuiltinField = ({ binding, onNodeChange }) => {
|
|
|
1629
3341
|
});
|
|
1630
3342
|
const stringValue = rawValue;
|
|
1631
3343
|
if (kind === "select") return /* @__PURE__ */ jsx(Select, {
|
|
3344
|
+
"aria-label": binding.label,
|
|
1632
3345
|
value: stringValue,
|
|
1633
3346
|
options: binding.options ?? [],
|
|
1634
3347
|
onChange: commitIfChanged
|
|
1635
3348
|
});
|
|
1636
3349
|
if (kind === "color") return /* @__PURE__ */ jsx(ColorPickerField, {
|
|
1637
|
-
value:
|
|
3350
|
+
value: stringValue,
|
|
3351
|
+
label: binding.label,
|
|
1638
3352
|
onChange: commitIfChanged
|
|
1639
3353
|
});
|
|
1640
|
-
if (kind === "date"
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
3354
|
+
if (kind === "date" || kind === "datetime") {
|
|
3355
|
+
const withTime = kind === "datetime";
|
|
3356
|
+
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(DatePicker, {
|
|
3357
|
+
value: parseDateValue(stringValue),
|
|
3358
|
+
showTime: withTime,
|
|
3359
|
+
onChange: (date) => validateAndCommit(date ? formatDateValue(date, withTime) : "")
|
|
3360
|
+
}), /* @__PURE__ */ jsx(FieldError, {
|
|
3361
|
+
id: errorId,
|
|
3362
|
+
message: validationError
|
|
3363
|
+
})] });
|
|
3364
|
+
}
|
|
3365
|
+
if (kind === "time") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(TimePicker, {
|
|
3366
|
+
...controlProps,
|
|
3367
|
+
value: stringValue || null,
|
|
3368
|
+
onChange: (time) => validateAndCommit(time ?? "")
|
|
3369
|
+
}), /* @__PURE__ */ jsx(FieldError, {
|
|
3370
|
+
id: errorId,
|
|
3371
|
+
message: validationError
|
|
3372
|
+
})] });
|
|
3373
|
+
if (kind === "url" || kind === "email" || kind === "tel") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
|
|
3374
|
+
...controlProps,
|
|
3375
|
+
type: kind,
|
|
1655
3376
|
value: text,
|
|
1656
3377
|
onChange: (e) => setText(e.target.value),
|
|
1657
|
-
placeholder: messages.panel.urlPlaceholder,
|
|
1658
|
-
onBlur: (e) =>
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
return;
|
|
1664
|
-
}
|
|
1665
|
-
setValidationError(null);
|
|
1666
|
-
commitIfChanged(next);
|
|
1667
|
-
}
|
|
1668
|
-
}), /* @__PURE__ */ jsx(FieldError, { message: validationError })] });
|
|
3378
|
+
placeholder: kind === "url" ? messages.panel.urlPlaceholder : kind === "email" ? messages.panel.emailPlaceholder : messages.panel.telPlaceholder,
|
|
3379
|
+
onBlur: (e) => validateAndCommit(e.target.value.trim())
|
|
3380
|
+
}), /* @__PURE__ */ jsx(FieldError, {
|
|
3381
|
+
id: errorId,
|
|
3382
|
+
message: validationError
|
|
3383
|
+
})] });
|
|
1669
3384
|
if (kind === "number") return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
|
|
3385
|
+
...controlProps,
|
|
1670
3386
|
type: "number",
|
|
3387
|
+
step: binding.step,
|
|
1671
3388
|
value: text,
|
|
1672
3389
|
onChange: (e) => setText(e.target.value),
|
|
1673
3390
|
placeholder: messages.panel.numberPlaceholder,
|
|
1674
3391
|
onBlur: (e) => {
|
|
1675
3392
|
const raw = e.target.value.trim();
|
|
1676
3393
|
const next = raw !== "" && !Number.isNaN(Number(raw)) ? Number(raw) : raw;
|
|
1677
|
-
|
|
1678
|
-
if (!result.valid) {
|
|
1679
|
-
setValidationError(result.message ?? messages.panel.invalidValue);
|
|
1680
|
-
return;
|
|
1681
|
-
}
|
|
1682
|
-
setValidationError(null);
|
|
1683
|
-
commitIfChanged(next);
|
|
3394
|
+
validateAndCommit(next);
|
|
1684
3395
|
}
|
|
1685
|
-
}), /* @__PURE__ */ jsx(FieldError, {
|
|
3396
|
+
}), /* @__PURE__ */ jsx(FieldError, {
|
|
3397
|
+
id: errorId,
|
|
3398
|
+
message: validationError
|
|
3399
|
+
})] });
|
|
1686
3400
|
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input.TextArea, {
|
|
3401
|
+
...controlProps,
|
|
1687
3402
|
value: text,
|
|
1688
3403
|
onChange: (e) => setText(e.target.value),
|
|
1689
3404
|
placeholder: messages.panel.textPlaceholder,
|
|
1690
3405
|
onBlur: (e) => {
|
|
1691
3406
|
const raw = e.target.value.trim();
|
|
1692
3407
|
const next = binding.type ? raw : parseValue(raw);
|
|
1693
|
-
|
|
1694
|
-
if (!result.valid) {
|
|
1695
|
-
setValidationError(result.message ?? messages.panel.invalidValue);
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
setValidationError(null);
|
|
1699
|
-
commitIfChanged(next);
|
|
3408
|
+
validateAndCommit(next);
|
|
1700
3409
|
}
|
|
1701
|
-
}), /* @__PURE__ */ jsx(FieldError, {
|
|
3410
|
+
}), /* @__PURE__ */ jsx(FieldError, {
|
|
3411
|
+
id: errorId,
|
|
3412
|
+
message: validationError
|
|
3413
|
+
})] });
|
|
1702
3414
|
};
|
|
1703
3415
|
const Field = (props) => {
|
|
1704
3416
|
const { renderField } = useDndEditOptions();
|
|
@@ -1708,30 +3420,63 @@ const Field = (props) => {
|
|
|
1708
3420
|
return custom === void 0 ? builtin : /* @__PURE__ */ jsx(Fragment, { children: custom });
|
|
1709
3421
|
};
|
|
1710
3422
|
//#endregion
|
|
1711
|
-
//#region src/components/dnd/panel/
|
|
1712
|
-
const
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
3423
|
+
//#region src/components/dnd/panel/element-fields.tsx
|
|
3424
|
+
const KEEP_IN_VIEW_MS = 600;
|
|
3425
|
+
const ElementFields = ({ id, header, bindings, onNodeChange, children, ...props }) => {
|
|
3426
|
+
const { ids, scrollRootRef } = useContext(ElementSelectionContext);
|
|
3427
|
+
const ref = useRef(null);
|
|
3428
|
+
const isPicked = id !== void 0 && ids.has(id);
|
|
3429
|
+
useEffect(() => {
|
|
3430
|
+
const card = ref.current;
|
|
3431
|
+
if (!isPicked || !card) return;
|
|
3432
|
+
const until = performance.now() + KEEP_IN_VIEW_MS;
|
|
3433
|
+
let frame = 0;
|
|
3434
|
+
const keepInView = () => {
|
|
3435
|
+
const root = scrollRootRef?.current;
|
|
3436
|
+
const box = card.getBoundingClientRect();
|
|
3437
|
+
const view = root?.getBoundingClientRect();
|
|
3438
|
+
if (!view || box.top < view.top || box.bottom > view.bottom) card.scrollIntoView?.({ block: "nearest" });
|
|
3439
|
+
if (performance.now() < until) frame = requestAnimationFrame(keepInView);
|
|
3440
|
+
};
|
|
3441
|
+
keepInView();
|
|
3442
|
+
return () => cancelAnimationFrame(frame);
|
|
3443
|
+
}, [isPicked, scrollRootRef]);
|
|
3444
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3445
|
+
...props,
|
|
3446
|
+
ref,
|
|
3447
|
+
"data-node-id": id,
|
|
3448
|
+
"data-picked": isPicked ? "" : void 0,
|
|
3449
|
+
children: [
|
|
3450
|
+
header,
|
|
3451
|
+
bindings.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
3452
|
+
className: "space-y-2 rounded",
|
|
3453
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3454
|
+
className: "space-y-1",
|
|
3455
|
+
children: bindings.map((binding) => /* @__PURE__ */ jsx("div", {
|
|
3456
|
+
className: "space-y-1",
|
|
3457
|
+
children: /* @__PURE__ */ jsx(LabeledField, {
|
|
3458
|
+
binding,
|
|
3459
|
+
labelClassName: "block text-xs font-semibold text-gray-700",
|
|
3460
|
+
label: /* @__PURE__ */ jsxs(Fragment, { children: [binding.label, /* @__PURE__ */ jsxs("span", {
|
|
3461
|
+
className: "ml-1 text-gray-400",
|
|
3462
|
+
children: [
|
|
3463
|
+
"(",
|
|
3464
|
+
binding.property,
|
|
3465
|
+
")"
|
|
3466
|
+
]
|
|
3467
|
+
})] }),
|
|
3468
|
+
children: /* @__PURE__ */ jsx(Field, {
|
|
3469
|
+
binding,
|
|
3470
|
+
onNodeChange
|
|
3471
|
+
})
|
|
3472
|
+
})
|
|
3473
|
+
}, `${binding.property}-${binding.label}`))
|
|
3474
|
+
})
|
|
3475
|
+
}),
|
|
3476
|
+
children
|
|
3477
|
+
]
|
|
3478
|
+
});
|
|
3479
|
+
};
|
|
1735
3480
|
//#endregion
|
|
1736
3481
|
//#region src/components/dnd/panel/panel.tsx
|
|
1737
3482
|
const groupBindingsById = (bindings) => {
|
|
@@ -1767,26 +3512,10 @@ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canM
|
|
|
1767
3512
|
highlight(targetId);
|
|
1768
3513
|
return () => highlight(null);
|
|
1769
3514
|
}, [highlight, targetId]);
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
if (!target) return;
|
|
1775
|
-
target.setAttribute("data-picked", "");
|
|
1776
|
-
const until = performance.now() + 600;
|
|
1777
|
-
let frame = 0;
|
|
1778
|
-
const keepInView = () => {
|
|
1779
|
-
const box = target.getBoundingClientRect();
|
|
1780
|
-
const view = root.getBoundingClientRect();
|
|
1781
|
-
if (box.top < view.top || box.bottom > view.bottom) target.scrollIntoView?.({ block: "nearest" });
|
|
1782
|
-
if (performance.now() < until) frame = requestAnimationFrame(keepInView);
|
|
1783
|
-
};
|
|
1784
|
-
keepInView();
|
|
1785
|
-
return () => {
|
|
1786
|
-
cancelAnimationFrame(frame);
|
|
1787
|
-
target.removeAttribute("data-picked");
|
|
1788
|
-
};
|
|
1789
|
-
}, [pickedId, groups.length]);
|
|
3515
|
+
const selection = useMemo(() => ({
|
|
3516
|
+
ids: new Set(pickedId ? [pickedId] : []),
|
|
3517
|
+
scrollRootRef: rootRef
|
|
3518
|
+
}), [pickedId]);
|
|
1790
3519
|
if (!item) return /* @__PURE__ */ jsx(Typography.Paragraph, {
|
|
1791
3520
|
className: cn("p-4 text-sm text-gray-500"),
|
|
1792
3521
|
children: messages.panel.selectSection
|
|
@@ -1839,27 +3568,29 @@ const Panel$1 = ({ item, onDelete, onMoveUp, onMoveDown, canMoveUp = false, canM
|
|
|
1839
3568
|
className: "text-xs text-gray-400",
|
|
1840
3569
|
children: messages.panel.noEditableElements
|
|
1841
3570
|
}),
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
className: "
|
|
1851
|
-
|
|
1852
|
-
className: "
|
|
1853
|
-
children:
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
3571
|
+
/* @__PURE__ */ jsx(ElementSelectionContext.Provider, {
|
|
3572
|
+
value: selection,
|
|
3573
|
+
children: groups.map((group) => {
|
|
3574
|
+
const element = describeElement(group[0]);
|
|
3575
|
+
return /* @__PURE__ */ jsx(ElementFields, {
|
|
3576
|
+
id: group[0].id,
|
|
3577
|
+
role: "group",
|
|
3578
|
+
"aria-label": element.label,
|
|
3579
|
+
className: "rounded data-[picked]:ring-2\n data-[picked]:ring-blue-400 data-[picked]:ring-offset-2",
|
|
3580
|
+
header: groups.length > 1 && /* @__PURE__ */ jsxs("div", {
|
|
3581
|
+
className: "mb-2 flex min-w-0 items-baseline gap-1.5 border-b\n border-gray-100 pb-1 text-xs",
|
|
3582
|
+
children: [/* @__PURE__ */ jsx("code", {
|
|
3583
|
+
className: "shrink-0 text-gray-500",
|
|
3584
|
+
children: element.tagName
|
|
3585
|
+
}), element.text && /* @__PURE__ */ jsx("span", {
|
|
3586
|
+
className: "truncate text-gray-400",
|
|
3587
|
+
children: element.text
|
|
3588
|
+
})]
|
|
3589
|
+
}),
|
|
1859
3590
|
bindings: group,
|
|
1860
3591
|
onNodeChange
|
|
1861
|
-
})
|
|
1862
|
-
}
|
|
3592
|
+
}, group[0].id);
|
|
3593
|
+
})
|
|
1863
3594
|
})
|
|
1864
3595
|
]
|
|
1865
3596
|
});
|
|
@@ -1988,38 +3719,112 @@ const Layout = ({ palette, panel }) => {
|
|
|
1988
3719
|
] });
|
|
1989
3720
|
};
|
|
1990
3721
|
//#endregion
|
|
1991
|
-
//#region src/components/dnd/state/
|
|
1992
|
-
const
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
return (id, onDeleted) => {
|
|
1998
|
-
const section = sections.find((candidate) => candidate.id === id);
|
|
1999
|
-
if (!onBeforeDelete || !section) {
|
|
2000
|
-
remove(id);
|
|
2001
|
-
onDeleted?.();
|
|
2002
|
-
return;
|
|
2003
|
-
}
|
|
2004
|
-
const decide = (allowed) => {
|
|
2005
|
-
if (allowed) {
|
|
2006
|
-
removeRef.current(id);
|
|
2007
|
-
onDeleted?.();
|
|
2008
|
-
}
|
|
3722
|
+
//#region src/components/dnd/state/edit-errors.ts
|
|
3723
|
+
const describeUpdateFailure = (failure, label, { editErrors: m }) => {
|
|
3724
|
+
switch (failure?.reason) {
|
|
3725
|
+
case "attribute-not-found": return {
|
|
3726
|
+
title: m.updateFailed(label),
|
|
3727
|
+
description: m.attributeNotFound(failure.property)
|
|
2009
3728
|
};
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
3729
|
+
case "binding-not-declared": return {
|
|
3730
|
+
title: m.updateFailed(label),
|
|
3731
|
+
description: m.bindingNotDeclared({
|
|
3732
|
+
label,
|
|
3733
|
+
property: failure.property
|
|
3734
|
+
})
|
|
3735
|
+
};
|
|
3736
|
+
case "duplicate-binding": return {
|
|
3737
|
+
title: m.updateFailed(label),
|
|
3738
|
+
description: m.duplicateBinding({
|
|
3739
|
+
count: failure.count,
|
|
3740
|
+
label,
|
|
3741
|
+
property: failure.property
|
|
3742
|
+
})
|
|
3743
|
+
};
|
|
3744
|
+
case "reserved-property": return {
|
|
3745
|
+
title: m.cannotEdit(label),
|
|
3746
|
+
description: m.reservedProperty(failure.property)
|
|
3747
|
+
};
|
|
3748
|
+
case "required-property": return {
|
|
3749
|
+
title: m.cannotRemove(label),
|
|
3750
|
+
description: m.requiredProperty(failure.property)
|
|
3751
|
+
};
|
|
3752
|
+
case "no-binding": return {
|
|
3753
|
+
title: m.updateFailed(label),
|
|
3754
|
+
description: m.noBinding
|
|
3755
|
+
};
|
|
3756
|
+
case "element-not-found": return {
|
|
3757
|
+
title: m.updateFailed(label),
|
|
3758
|
+
description: m.elementNotFound
|
|
3759
|
+
};
|
|
3760
|
+
case "self-closing": return {
|
|
3761
|
+
title: m.cannotEdit(label),
|
|
3762
|
+
description: m.selfClosing(failure.property)
|
|
3763
|
+
};
|
|
3764
|
+
case "unsupported-syntax": return {
|
|
3765
|
+
title: m.cannotEdit(label),
|
|
3766
|
+
description: m.unsupportedSyntax(failure.property)
|
|
3767
|
+
};
|
|
3768
|
+
case "parse-error": return {
|
|
3769
|
+
title: m.updateFailed(label),
|
|
3770
|
+
description: failure.error instanceof Error ? failure.error.message : m.checkConsole
|
|
3771
|
+
};
|
|
3772
|
+
default: return { title: m.updateFailed(label) };
|
|
3773
|
+
}
|
|
3774
|
+
};
|
|
3775
|
+
const blockedEditError = (error, { editErrors: m }) => ({
|
|
3776
|
+
type: "parse",
|
|
3777
|
+
target: "document",
|
|
3778
|
+
reason: "parse-error",
|
|
3779
|
+
error,
|
|
3780
|
+
title: m.syntaxError,
|
|
3781
|
+
description: m.syntaxErrorDetail
|
|
3782
|
+
});
|
|
3783
|
+
const useEditErrors = (onEditError) => {
|
|
3784
|
+
const messages = useLiveMessages();
|
|
3785
|
+
const reportError = onEditError ?? toastEditError;
|
|
3786
|
+
const reportErrorRef = useLatest(reportError);
|
|
3787
|
+
const messagesRef = useLatest(messages);
|
|
3788
|
+
const reportLatest = useCallback((build) => reportErrorRef.current(build(messagesRef.current)), [reportErrorRef, messagesRef]);
|
|
3789
|
+
return {
|
|
3790
|
+
reportError,
|
|
3791
|
+
messages,
|
|
3792
|
+
reportLatest,
|
|
3793
|
+
onBlockedEdit: useCallback((problem) => {
|
|
3794
|
+
if (problem.reason === "parse-error") reportLatest((m) => blockedEditError(problem.error, m));
|
|
3795
|
+
}, [reportLatest])
|
|
3796
|
+
};
|
|
3797
|
+
};
|
|
3798
|
+
//#endregion
|
|
3799
|
+
//#region src/components/dnd/state/use-delete-flow.ts
|
|
3800
|
+
const useDeleteFlow = ({ sections, remove, onBeforeDelete }) => {
|
|
3801
|
+
const removeRef = useLatest(remove);
|
|
3802
|
+
return (id, onDeleted) => {
|
|
3803
|
+
const section = sections.find((candidate) => candidate.id === id);
|
|
3804
|
+
if (!onBeforeDelete || !section) {
|
|
3805
|
+
remove(id);
|
|
3806
|
+
onDeleted?.();
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
const decide = (allowed) => {
|
|
3810
|
+
if (allowed) {
|
|
3811
|
+
removeRef.current(id);
|
|
3812
|
+
onDeleted?.();
|
|
3813
|
+
}
|
|
3814
|
+
};
|
|
3815
|
+
try {
|
|
3816
|
+
const answer = onBeforeDelete(section);
|
|
3817
|
+
if (typeof answer === "boolean") {
|
|
3818
|
+
decide(answer);
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3821
|
+
answer.then(decide, (error) => {
|
|
3822
|
+
console.error("onBeforeDelete rejected; the section was kept", error);
|
|
3823
|
+
});
|
|
3824
|
+
} catch (error) {
|
|
3825
|
+
console.error("onBeforeDelete threw; the section was kept", error);
|
|
3826
|
+
}
|
|
3827
|
+
};
|
|
2023
3828
|
};
|
|
2024
3829
|
//#endregion
|
|
2025
3830
|
//#region src/components/dnd/state/use-dnd-keyboard.ts
|
|
@@ -2115,44 +3920,16 @@ const useInspectorState = ({ selectedId, selectOnly, sectionNodes, onNodePick })
|
|
|
2115
3920
|
const [picked, setPicked] = useState(null);
|
|
2116
3921
|
const [hoverRect, setHoverRect] = useState(null);
|
|
2117
3922
|
const [fieldTarget, setFieldTarget] = useState(null);
|
|
2118
|
-
const [fieldRect, setFieldRect] = useState(null);
|
|
2119
3923
|
if (picked && picked.sectionId !== selectedId) setPicked(null);
|
|
2120
3924
|
const stopInspecting = useCallback(() => {
|
|
2121
3925
|
setInspecting(false);
|
|
2122
3926
|
setHoverRect(null);
|
|
2123
3927
|
}, []);
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
const
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
window.addEventListener("keydown", onKeyDown);
|
|
2130
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2131
|
-
}, [inspecting, stopInspecting]);
|
|
2132
|
-
useEffect(() => {
|
|
2133
|
-
const section = selectedId === null ? null : sectionNodes.get(selectedId);
|
|
2134
|
-
if (!fieldTarget || !section) return;
|
|
2135
|
-
let frame = 0;
|
|
2136
|
-
const measure = () => {
|
|
2137
|
-
cancelAnimationFrame(frame);
|
|
2138
|
-
frame = requestAnimationFrame(() => {
|
|
2139
|
-
const element = findElementById(section, fieldTarget);
|
|
2140
|
-
setFieldRect(element ? viewportRect(element) : null);
|
|
2141
|
-
});
|
|
2142
|
-
};
|
|
2143
|
-
measure();
|
|
2144
|
-
window.addEventListener("scroll", measure, true);
|
|
2145
|
-
window.addEventListener("resize", measure);
|
|
2146
|
-
return () => {
|
|
2147
|
-
cancelAnimationFrame(frame);
|
|
2148
|
-
window.removeEventListener("scroll", measure, true);
|
|
2149
|
-
window.removeEventListener("resize", measure);
|
|
2150
|
-
};
|
|
2151
|
-
}, [
|
|
2152
|
-
fieldTarget,
|
|
2153
|
-
selectedId,
|
|
2154
|
-
sectionNodes
|
|
2155
|
-
]);
|
|
3928
|
+
useKeyPress("esc", stopInspecting, { enabled: inspecting });
|
|
3929
|
+
const fieldRect = useElementPosition(() => {
|
|
3930
|
+
const section = selectedId === null ? void 0 : sectionNodes.get(selectedId);
|
|
3931
|
+
return fieldTarget && section ? findElementById(section, fieldTarget) : null;
|
|
3932
|
+
}, { measure: viewportRect });
|
|
2156
3933
|
const inspector = {
|
|
2157
3934
|
active: inspecting,
|
|
2158
3935
|
activate: () => setInspecting(true),
|
|
@@ -2241,37 +4018,27 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
|
|
|
2241
4018
|
]);
|
|
2242
4019
|
const selectedIndex = sections.findIndex((s) => s.id === selectedId);
|
|
2243
4020
|
const selectedSection = selectedIndex >= 0 ? sections[selectedIndex] : void 0;
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
pendingRef.current = null;
|
|
2247
|
-
});
|
|
2248
|
-
const latestDocument = useCallback(() => {
|
|
2249
|
-
const pending = pendingRef.current;
|
|
2250
|
-
return pending?.from === document ? pending.code : document;
|
|
2251
|
-
}, [document]);
|
|
4021
|
+
const pending = usePendingCommit(document);
|
|
4022
|
+
const latestDocument = useCallback(() => pending.read() ?? document, [document, pending]);
|
|
2252
4023
|
const latestSections = useCallback(() => readSections(latestDocument()), [latestDocument, readSections]);
|
|
2253
4024
|
const commit = useCallback((nextSections) => {
|
|
2254
4025
|
const nextCode = fillSectionIds(replaceSections(latestDocument(), nextSections.map((s) => s.code), { containerId }), void 0, { containerId });
|
|
2255
|
-
|
|
2256
|
-
from: document,
|
|
2257
|
-
code: nextCode
|
|
2258
|
-
};
|
|
4026
|
+
pending.write(nextCode);
|
|
2259
4027
|
onChange?.(nextCode);
|
|
2260
4028
|
setCode(nextCode);
|
|
2261
4029
|
return readSections(nextCode);
|
|
2262
4030
|
}, [
|
|
2263
4031
|
containerId,
|
|
2264
|
-
document,
|
|
2265
4032
|
latestDocument,
|
|
2266
4033
|
onChange,
|
|
4034
|
+
pending,
|
|
2267
4035
|
readSections,
|
|
2268
4036
|
setCode
|
|
2269
4037
|
]);
|
|
2270
4038
|
const getCommittedSection = useCallback((id) => {
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
}, [document, readSections]);
|
|
4039
|
+
const code = pending.read();
|
|
4040
|
+
return code === void 0 ? void 0 : readSections(code).find((s) => s.id === id);
|
|
4041
|
+
}, [pending, readSections]);
|
|
2275
4042
|
const { problem, stale } = view;
|
|
2276
4043
|
const refuse = useCallback(() => {
|
|
2277
4044
|
if (!stale || !problem) return false;
|
|
@@ -2282,6 +4049,10 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
|
|
|
2282
4049
|
problem,
|
|
2283
4050
|
stale
|
|
2284
4051
|
]);
|
|
4052
|
+
const mutate = useCallback((fn) => {
|
|
4053
|
+
if (refuse()) return;
|
|
4054
|
+
fn(latestSections());
|
|
4055
|
+
}, [latestSections, refuse]);
|
|
2285
4056
|
return {
|
|
2286
4057
|
sections,
|
|
2287
4058
|
previews,
|
|
@@ -2293,36 +4064,25 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
|
|
|
2293
4064
|
}, []),
|
|
2294
4065
|
selectOnly: useCallback((id) => setSelectedId(id), []),
|
|
2295
4066
|
clearSelection: useCallback(() => setSelectedId(null), []),
|
|
2296
|
-
add: useCallback((section, atIndex) => {
|
|
2297
|
-
if (refuse()) return;
|
|
2298
|
-
const current = latestSections();
|
|
4067
|
+
add: useCallback((section, atIndex) => mutate((current) => {
|
|
2299
4068
|
const next = { code: section.code };
|
|
2300
4069
|
commit(atIndex === void 0 || atIndex < 0 ? [...current, next] : [
|
|
2301
4070
|
...current.slice(0, atIndex),
|
|
2302
4071
|
next,
|
|
2303
4072
|
...current.slice(atIndex)
|
|
2304
4073
|
]);
|
|
2305
|
-
}, [
|
|
2306
|
-
|
|
2307
|
-
refuse,
|
|
2308
|
-
latestSections
|
|
2309
|
-
]),
|
|
2310
|
-
remove: useCallback((id) => {
|
|
2311
|
-
if (refuse()) return;
|
|
2312
|
-
const current = latestSections();
|
|
4074
|
+
}), [commit, mutate]),
|
|
4075
|
+
remove: useCallback((id) => mutate((current) => {
|
|
2313
4076
|
const index = current.findIndex((s) => s.id === id);
|
|
2314
4077
|
if (index < 0) return;
|
|
2315
4078
|
if (id === selectedId) setSelectedId(null);
|
|
2316
4079
|
commit(current.filter((_, i) => i !== index));
|
|
2317
|
-
}, [
|
|
4080
|
+
}), [
|
|
2318
4081
|
commit,
|
|
2319
|
-
|
|
2320
|
-
latestSections,
|
|
4082
|
+
mutate,
|
|
2321
4083
|
selectedId
|
|
2322
4084
|
]),
|
|
2323
|
-
copy: useCallback((id) => {
|
|
2324
|
-
if (refuse()) return;
|
|
2325
|
-
const current = latestSections();
|
|
4085
|
+
copy: useCallback((id) => mutate((current) => {
|
|
2326
4086
|
const index = current.findIndex((s) => s.id === id);
|
|
2327
4087
|
const source = current[index];
|
|
2328
4088
|
if (!source) return;
|
|
@@ -2332,192 +4092,33 @@ const useSectionDocument = (value, onChange, { containerId, sectionNameFallback,
|
|
|
2332
4092
|
...current.slice(index + 1)
|
|
2333
4093
|
]);
|
|
2334
4094
|
setSelectedId(committed[index + 1]?.id ?? null);
|
|
2335
|
-
}, [
|
|
2336
|
-
|
|
2337
|
-
refuse,
|
|
2338
|
-
latestSections
|
|
2339
|
-
]),
|
|
2340
|
-
move: useCallback((id, direction) => {
|
|
2341
|
-
if (refuse()) return;
|
|
2342
|
-
const current = latestSections();
|
|
4095
|
+
}), [commit, mutate]),
|
|
4096
|
+
move: useCallback((id, direction) => mutate((current) => {
|
|
2343
4097
|
const index = current.findIndex((s) => s.id === id);
|
|
2344
4098
|
const targetIndex = direction === "up" ? index - 1 : index + 1;
|
|
2345
4099
|
if (index < 0 || targetIndex < 0 || targetIndex >= current.length) return;
|
|
2346
4100
|
commit(arrayMove(current, index, targetIndex));
|
|
2347
|
-
}, [
|
|
2348
|
-
|
|
2349
|
-
refuse,
|
|
2350
|
-
latestSections
|
|
2351
|
-
]),
|
|
2352
|
-
reorder: useCallback((activeId, overId) => {
|
|
2353
|
-
if (refuse()) return;
|
|
2354
|
-
const current = latestSections();
|
|
4101
|
+
}), [commit, mutate]),
|
|
4102
|
+
reorder: useCallback((activeId, overId) => mutate((current) => {
|
|
2355
4103
|
const prevIndex = current.findIndex((s) => s.id === activeId);
|
|
2356
4104
|
const nextIndex = current.findIndex((s) => s.id === overId);
|
|
2357
4105
|
if (prevIndex < 0 || nextIndex < 0 || prevIndex === nextIndex) return;
|
|
2358
4106
|
commit(arrayMove(current, prevIndex, nextIndex));
|
|
2359
|
-
}, [
|
|
2360
|
-
|
|
2361
|
-
refuse,
|
|
2362
|
-
latestSections
|
|
2363
|
-
]),
|
|
2364
|
-
patch: useCallback((next) => {
|
|
2365
|
-
if (refuse()) return;
|
|
2366
|
-
const current = latestSections();
|
|
4107
|
+
}), [commit, mutate]),
|
|
4108
|
+
patch: useCallback((next) => mutate((current) => {
|
|
2367
4109
|
commit(current.map((s) => s.id === next.id ? {
|
|
2368
4110
|
...s,
|
|
2369
4111
|
...next
|
|
2370
4112
|
} : s));
|
|
2371
|
-
}, [
|
|
2372
|
-
commit,
|
|
2373
|
-
refuse,
|
|
2374
|
-
latestSections
|
|
2375
|
-
]),
|
|
4113
|
+
}), [commit, mutate]),
|
|
2376
4114
|
getCommittedSection,
|
|
2377
4115
|
problem,
|
|
2378
4116
|
stale
|
|
2379
4117
|
};
|
|
2380
4118
|
};
|
|
2381
4119
|
//#endregion
|
|
2382
|
-
//#region src/components/dnd/
|
|
2383
|
-
const
|
|
2384
|
-
switch (failure?.reason) {
|
|
2385
|
-
case "attribute-not-found": return {
|
|
2386
|
-
title: m.updateFailed(label),
|
|
2387
|
-
description: m.attributeNotFound(failure.property)
|
|
2388
|
-
};
|
|
2389
|
-
case "binding-not-declared": return {
|
|
2390
|
-
title: m.updateFailed(label),
|
|
2391
|
-
description: m.bindingNotDeclared({
|
|
2392
|
-
label,
|
|
2393
|
-
property: failure.property
|
|
2394
|
-
})
|
|
2395
|
-
};
|
|
2396
|
-
case "duplicate-binding": return {
|
|
2397
|
-
title: m.updateFailed(label),
|
|
2398
|
-
description: m.duplicateBinding({
|
|
2399
|
-
count: failure.count,
|
|
2400
|
-
label,
|
|
2401
|
-
property: failure.property
|
|
2402
|
-
})
|
|
2403
|
-
};
|
|
2404
|
-
case "reserved-property": return {
|
|
2405
|
-
title: m.cannotEdit(label),
|
|
2406
|
-
description: m.reservedProperty(failure.property)
|
|
2407
|
-
};
|
|
2408
|
-
case "required-property": return {
|
|
2409
|
-
title: m.cannotRemove(label),
|
|
2410
|
-
description: m.requiredProperty(failure.property)
|
|
2411
|
-
};
|
|
2412
|
-
case "no-binding": return {
|
|
2413
|
-
title: m.updateFailed(label),
|
|
2414
|
-
description: m.noBinding
|
|
2415
|
-
};
|
|
2416
|
-
case "element-not-found": return {
|
|
2417
|
-
title: m.updateFailed(label),
|
|
2418
|
-
description: m.elementNotFound
|
|
2419
|
-
};
|
|
2420
|
-
case "self-closing": return {
|
|
2421
|
-
title: m.cannotEdit(label),
|
|
2422
|
-
description: m.selfClosing(failure.property)
|
|
2423
|
-
};
|
|
2424
|
-
case "unsupported-syntax": return {
|
|
2425
|
-
title: m.cannotEdit(label),
|
|
2426
|
-
description: m.unsupportedSyntax(failure.property)
|
|
2427
|
-
};
|
|
2428
|
-
case "parse-error": return {
|
|
2429
|
-
title: m.updateFailed(label),
|
|
2430
|
-
description: failure.error instanceof Error ? failure.error.message : m.checkConsole
|
|
2431
|
-
};
|
|
2432
|
-
default: return { title: m.updateFailed(label) };
|
|
2433
|
-
}
|
|
2434
|
-
};
|
|
2435
|
-
const blockedEditError = (error, { editErrors: m }) => ({
|
|
2436
|
-
type: "parse",
|
|
2437
|
-
target: "document",
|
|
2438
|
-
reason: "parse-error",
|
|
2439
|
-
error,
|
|
2440
|
-
title: m.syntaxError,
|
|
2441
|
-
description: m.syntaxErrorDetail
|
|
2442
|
-
});
|
|
2443
|
-
const conditionalModifiers = (args) => {
|
|
2444
|
-
const { active } = args;
|
|
2445
|
-
if (paletteSectionOf(active?.data)) return args.transform;
|
|
2446
|
-
return restrictToVerticalAxis(args);
|
|
2447
|
-
};
|
|
2448
|
-
const NO_MODULES = {};
|
|
2449
|
-
const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange: _onChange, className, items = [], frame, dynamicTailwind = false, provider, renderField, onEditError, renderSectionFallback, shouldForceSectionFallback, onBeforeDelete, sectionNameFallback, containerId, bindings: bindingRegistry, bindingKeys, onNodePick, children, ...restProps }) => {
|
|
2450
|
-
const modules = useStableModules(_modules);
|
|
2451
|
-
const [mobilePaletteOpen, setMobilePaletteOpen] = useState(false);
|
|
2452
|
-
const { breakpoint } = useResponsiveSize();
|
|
2453
|
-
const isMobile = breakpoint.current === "xs" || breakpoint.current === "sm";
|
|
2454
|
-
const { code } = usePreview();
|
|
2455
|
-
const isForced = (section) => {
|
|
2456
|
-
try {
|
|
2457
|
-
return shouldForceSectionFallback?.(section) === true;
|
|
2458
|
-
} catch (error) {
|
|
2459
|
-
console.error("shouldForceSectionFallback threw", error);
|
|
2460
|
-
return false;
|
|
2461
|
-
}
|
|
2462
|
-
};
|
|
2463
|
-
const value = _value === void 0 ? code : _value;
|
|
2464
|
-
const messages = useLiveMessages();
|
|
2465
|
-
const reportError = onEditError ?? toastEditError;
|
|
2466
|
-
const reportErrorRef = useRef(reportError);
|
|
2467
|
-
const messagesRef = useRef(messages);
|
|
2468
|
-
useEffect(() => {
|
|
2469
|
-
reportErrorRef.current = reportError;
|
|
2470
|
-
messagesRef.current = messages;
|
|
2471
|
-
});
|
|
2472
|
-
const onBlockedEdit = useCallback((problem) => {
|
|
2473
|
-
if (problem.reason === "parse-error") reportErrorRef.current(blockedEditError(problem.error, messagesRef.current));
|
|
2474
|
-
}, []);
|
|
2475
|
-
const { sections, previews, selectedId, selectedSection, selectedIndex, select, selectOnly, clearSelection, add: addSection, remove, copy: onCopy, move: moveSection, reorder, patch, getCommittedSection, problem, stale } = useSectionDocument(value, _onChange, {
|
|
2476
|
-
containerId,
|
|
2477
|
-
sectionNameFallback,
|
|
2478
|
-
onBlockedEdit
|
|
2479
|
-
});
|
|
2480
|
-
const missingContainer = problem?.reason === "container-not-found" ? problem.containerId : null;
|
|
2481
|
-
const onDragEnd = (event) => {
|
|
2482
|
-
const { active, over } = event;
|
|
2483
|
-
if (!over) return;
|
|
2484
|
-
const section = paletteSectionOf(active.data);
|
|
2485
|
-
if (section) {
|
|
2486
|
-
const atBottom = over.id === "sortable-area" || over.id === "sortable-area-bottom";
|
|
2487
|
-
addSection(section, atBottom ? void 0 : sections.findIndex((s) => s.id === over.id));
|
|
2488
|
-
return;
|
|
2489
|
-
}
|
|
2490
|
-
if (active.id !== over.id && sections.some((s) => s.id === active.id)) reorder(String(active.id), String(over.id));
|
|
2491
|
-
};
|
|
2492
|
-
const onSelect = (id) => select(id);
|
|
2493
|
-
const requestDelete = useDeleteFlow({
|
|
2494
|
-
sections,
|
|
2495
|
-
remove,
|
|
2496
|
-
onBeforeDelete
|
|
2497
|
-
});
|
|
2498
|
-
const onDelete = (id) => requestDelete(id);
|
|
2499
|
-
const { sensors, announcements, screenReaderInstructions, sectionNodes, onNavigate, onDeleteKey, onMoveButton } = useDndKeyboard({
|
|
2500
|
-
sections,
|
|
2501
|
-
selectedId,
|
|
2502
|
-
selectedIndex,
|
|
2503
|
-
selectOnly,
|
|
2504
|
-
move: moveSection,
|
|
2505
|
-
requestDelete
|
|
2506
|
-
});
|
|
2507
|
-
const { inspector, inspecting, onInspectMove, onInspectPick, onInspectLeave, highlightRect } = useInspectorState({
|
|
2508
|
-
selectedId,
|
|
2509
|
-
selectOnly,
|
|
2510
|
-
sectionNodes,
|
|
2511
|
-
onNodePick
|
|
2512
|
-
});
|
|
2513
|
-
const onChange = (next) => {
|
|
2514
|
-
if (!next.id) return;
|
|
2515
|
-
patch(next);
|
|
2516
|
-
};
|
|
2517
|
-
const bindingOptions = useMemo(() => ({
|
|
2518
|
-
bindings: bindingRegistry,
|
|
2519
|
-
bindingKeys
|
|
2520
|
-
}), [bindingRegistry, bindingKeys]);
|
|
4120
|
+
//#region src/components/dnd/state/use-section-editing.ts
|
|
4121
|
+
const useSectionEditing = ({ selectedSection, stale, problem, missingContainer, getCommittedSection, onChange, bindingOptions, reportError, messages, reportLatest }) => {
|
|
2521
4122
|
const selectedCode = selectedSection?.code;
|
|
2522
4123
|
const selectedSectionId = selectedSection?.id;
|
|
2523
4124
|
const { fields, updatedCode, parseError } = useMemo(() => {
|
|
@@ -2547,24 +4148,24 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
|
|
|
2547
4148
|
bindingOptions
|
|
2548
4149
|
]);
|
|
2549
4150
|
useEffect(() => {
|
|
2550
|
-
if (missingContainer !== null)
|
|
4151
|
+
if (missingContainer !== null) reportLatest((m) => ({
|
|
2551
4152
|
type: "parse",
|
|
2552
4153
|
target: "document",
|
|
2553
4154
|
reason: "container-not-found",
|
|
2554
4155
|
containerId: missingContainer,
|
|
2555
|
-
title:
|
|
2556
|
-
description:
|
|
2557
|
-
});
|
|
2558
|
-
}, [missingContainer]);
|
|
4156
|
+
title: m.editErrors.missingContainer(missingContainer),
|
|
4157
|
+
description: m.editErrors.missingContainerDetail(missingContainer)
|
|
4158
|
+
}));
|
|
4159
|
+
}, [missingContainer, reportLatest]);
|
|
2559
4160
|
useEffect(() => {
|
|
2560
|
-
if (parseError)
|
|
4161
|
+
if (parseError) reportLatest((m) => ({
|
|
2561
4162
|
type: "parse",
|
|
2562
4163
|
target: "section",
|
|
2563
4164
|
error: parseError.error,
|
|
2564
|
-
title:
|
|
2565
|
-
description:
|
|
2566
|
-
});
|
|
2567
|
-
}, [parseError]);
|
|
4165
|
+
title: m.editErrors.sectionParseFailed,
|
|
4166
|
+
description: m.editErrors.checkConsole
|
|
4167
|
+
}));
|
|
4168
|
+
}, [parseError, reportLatest]);
|
|
2568
4169
|
const commitChanges = (changes) => {
|
|
2569
4170
|
if (changes.length === 0) return;
|
|
2570
4171
|
if (stale && problem?.reason === "parse-error") {
|
|
@@ -2597,7 +4198,108 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
|
|
|
2597
4198
|
};
|
|
2598
4199
|
const onFieldChange = (change) => commitChanges([change]);
|
|
2599
4200
|
const bindingFields = useMemo(() => fields.flatMap((node) => resolvePanelBindings(node)?.bindings ?? []), [fields]);
|
|
2600
|
-
|
|
4201
|
+
return {
|
|
4202
|
+
bindings: withPanelCommit(bindingFields, onFieldChange),
|
|
4203
|
+
onFieldChange,
|
|
4204
|
+
commitChanges
|
|
4205
|
+
};
|
|
4206
|
+
};
|
|
4207
|
+
//#endregion
|
|
4208
|
+
//#region src/components/dnd/dnd.tsx
|
|
4209
|
+
const conditionalModifiers = (args) => {
|
|
4210
|
+
const { active } = args;
|
|
4211
|
+
if (paletteSectionOf(active?.data)) return args.transform;
|
|
4212
|
+
return restrictToVerticalAxis(args);
|
|
4213
|
+
};
|
|
4214
|
+
const NO_MODULES = {};
|
|
4215
|
+
const emptyCanvasNotice = (messages, problem, isMobile) => {
|
|
4216
|
+
if (problem?.reason === "parse-error") return {
|
|
4217
|
+
title: messages.canvas.syntaxError,
|
|
4218
|
+
detail: messages.canvas.syntaxErrorDetail
|
|
4219
|
+
};
|
|
4220
|
+
if (problem?.reason === "container-not-found") return {
|
|
4221
|
+
title: messages.canvas.missingContainer(problem.containerId),
|
|
4222
|
+
detail: messages.canvas.missingContainerDetail(problem.containerId)
|
|
4223
|
+
};
|
|
4224
|
+
return {
|
|
4225
|
+
title: messages.canvas.empty,
|
|
4226
|
+
detail: isMobile ? messages.canvas.emptyHintTouch : messages.canvas.emptyHintDrag
|
|
4227
|
+
};
|
|
4228
|
+
};
|
|
4229
|
+
const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange: _onChange, className, items = [], frame, dynamicTailwind = false, provider, renderField, onEditError, renderSectionFallback, shouldForceSectionFallback, onBeforeDelete, sectionNameFallback, containerId, bindings: bindingRegistry, bindingKeys, onNodePick, children, ...restProps }) => {
|
|
4230
|
+
const modules = useStableValue(_modules);
|
|
4231
|
+
const [mobilePaletteOpen, setMobilePaletteOpen] = useState(false);
|
|
4232
|
+
const { breakpoint } = useResponsiveSize();
|
|
4233
|
+
const isMobile = breakpoint.current === "xs" || breakpoint.current === "sm";
|
|
4234
|
+
const { code } = usePreview();
|
|
4235
|
+
const isForced = (section) => {
|
|
4236
|
+
try {
|
|
4237
|
+
return shouldForceSectionFallback?.(section) === true;
|
|
4238
|
+
} catch (error) {
|
|
4239
|
+
console.error("shouldForceSectionFallback threw", error);
|
|
4240
|
+
return false;
|
|
4241
|
+
}
|
|
4242
|
+
};
|
|
4243
|
+
const value = _value === void 0 ? code : _value;
|
|
4244
|
+
const { reportError, messages, onBlockedEdit, reportLatest } = useEditErrors(onEditError);
|
|
4245
|
+
const { sections, previews, selectedId, selectedSection, selectedIndex, select, selectOnly, clearSelection, add: addSection, remove, copy: onCopy, move: moveSection, reorder, patch, getCommittedSection, problem, stale } = useSectionDocument(value, _onChange, {
|
|
4246
|
+
containerId,
|
|
4247
|
+
sectionNameFallback,
|
|
4248
|
+
onBlockedEdit
|
|
4249
|
+
});
|
|
4250
|
+
const missingContainer = problem?.reason === "container-not-found" ? problem.containerId : null;
|
|
4251
|
+
const onDragEnd = (event) => {
|
|
4252
|
+
const { active, over } = event;
|
|
4253
|
+
if (!over) return;
|
|
4254
|
+
const section = paletteSectionOf(active.data);
|
|
4255
|
+
if (section) {
|
|
4256
|
+
const atBottom = over.id === "sortable-area" || over.id === "sortable-area-bottom";
|
|
4257
|
+
addSection(section, atBottom ? void 0 : sections.findIndex((s) => s.id === over.id));
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
if (active.id !== over.id && sections.some((s) => s.id === active.id)) reorder(String(active.id), String(over.id));
|
|
4261
|
+
};
|
|
4262
|
+
const onSelect = (id) => select(id);
|
|
4263
|
+
const requestDelete = useDeleteFlow({
|
|
4264
|
+
sections,
|
|
4265
|
+
remove,
|
|
4266
|
+
onBeforeDelete
|
|
4267
|
+
});
|
|
4268
|
+
const onDelete = (id) => requestDelete(id);
|
|
4269
|
+
const { sensors, announcements, screenReaderInstructions, sectionNodes, onNavigate, onDeleteKey, onMoveButton } = useDndKeyboard({
|
|
4270
|
+
sections,
|
|
4271
|
+
selectedId,
|
|
4272
|
+
selectedIndex,
|
|
4273
|
+
selectOnly,
|
|
4274
|
+
move: moveSection,
|
|
4275
|
+
requestDelete
|
|
4276
|
+
});
|
|
4277
|
+
const { inspector, inspecting, onInspectMove, onInspectPick, onInspectLeave, highlightRect } = useInspectorState({
|
|
4278
|
+
selectedId,
|
|
4279
|
+
selectOnly,
|
|
4280
|
+
sectionNodes,
|
|
4281
|
+
onNodePick
|
|
4282
|
+
});
|
|
4283
|
+
const onChange = (next) => {
|
|
4284
|
+
if (!next.id) return;
|
|
4285
|
+
patch(next);
|
|
4286
|
+
};
|
|
4287
|
+
const bindingOptions = useMemo(() => ({
|
|
4288
|
+
bindings: bindingRegistry,
|
|
4289
|
+
bindingKeys
|
|
4290
|
+
}), [bindingRegistry, bindingKeys]);
|
|
4291
|
+
const { bindings, onFieldChange, commitChanges } = useSectionEditing({
|
|
4292
|
+
selectedSection,
|
|
4293
|
+
stale,
|
|
4294
|
+
problem,
|
|
4295
|
+
missingContainer,
|
|
4296
|
+
getCommittedSection,
|
|
4297
|
+
onChange,
|
|
4298
|
+
bindingOptions,
|
|
4299
|
+
reportError,
|
|
4300
|
+
messages,
|
|
4301
|
+
reportLatest
|
|
4302
|
+
});
|
|
2601
4303
|
useEffect(() => {
|
|
2602
4304
|
if (frame?.scripts?.length) preloadScripts(frame.scripts);
|
|
2603
4305
|
}, [frame?.scripts]);
|
|
@@ -2622,67 +4324,60 @@ const Dnd$1 = ({ value: _value, props, modules: _modules = NO_MODULES, onChange:
|
|
|
2622
4324
|
readOnly: stale,
|
|
2623
4325
|
bindingOptions
|
|
2624
4326
|
};
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
children:
|
|
4327
|
+
const sectionHandlers = {
|
|
4328
|
+
inspecting,
|
|
4329
|
+
onInspectMove,
|
|
4330
|
+
onInspectPick,
|
|
4331
|
+
onInspectLeave,
|
|
4332
|
+
nodeRef: sectionNodes.register,
|
|
4333
|
+
onNavigate,
|
|
4334
|
+
onDeleteKey
|
|
4335
|
+
};
|
|
4336
|
+
const canvas = /* @__PURE__ */ jsxs(SectionHandlersContext.Provider, {
|
|
4337
|
+
value: sectionHandlers,
|
|
4338
|
+
children: [
|
|
4339
|
+
stale && /* @__PURE__ */ jsx("div", {
|
|
4340
|
+
role: "status",
|
|
4341
|
+
className: cn("sticky top-0 z-70", "border-b border-amber-200 bg-amber-50 px-3 py-2", "text-sm text-amber-900"),
|
|
4342
|
+
children: messages.canvas.stale
|
|
4343
|
+
}),
|
|
4344
|
+
/* @__PURE__ */ jsx(Droppable, {
|
|
4345
|
+
className: cn(!sections.length && "h-full"),
|
|
4346
|
+
children: !sections.length ? /* @__PURE__ */ jsx("div", {
|
|
4347
|
+
className: cn("flex items-center justify-center", "h-full", "text-gray-500"),
|
|
4348
|
+
children: /* @__PURE__ */ jsx(CanvasNotice, { ...emptyCanvasNotice(messages, problem, isMobile) })
|
|
4349
|
+
}) : /* @__PURE__ */ jsx(SortableContext, {
|
|
4350
|
+
items: sections.map((s) => s.id),
|
|
4351
|
+
strategy: verticalListSortingStrategy,
|
|
4352
|
+
children: sections.map((section, index) => /* @__PURE__ */ jsx(Sortable, {
|
|
4353
|
+
id: section.id,
|
|
4354
|
+
name: section.name,
|
|
4355
|
+
selected: selectedId === section.id,
|
|
4356
|
+
onClick: () => onSelect(section.id),
|
|
4357
|
+
onDelete,
|
|
4358
|
+
onCopy,
|
|
4359
|
+
onMoveUp: () => onMoveButton(section.id, "up"),
|
|
4360
|
+
onMoveDown: () => onMoveButton(section.id, "down"),
|
|
4361
|
+
canMoveUp: index > 0,
|
|
4362
|
+
canMoveDown: index < sections.length - 1,
|
|
4363
|
+
children: /* @__PURE__ */ jsx(renderer_default, {
|
|
4364
|
+
preview: previews[index],
|
|
4365
|
+
sectionId: section.id,
|
|
4366
|
+
sectionName: section.name,
|
|
4367
|
+
sectionCode: section.code,
|
|
4368
|
+
forceFallback: isForced(section),
|
|
4369
|
+
modules,
|
|
4370
|
+
frame,
|
|
4371
|
+
dynamicTailwind,
|
|
4372
|
+
provider,
|
|
4373
|
+
...props
|
|
4374
|
+
})
|
|
4375
|
+
}, section.id))
|
|
2647
4376
|
})
|
|
2648
|
-
})
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
id: section.id,
|
|
2653
|
-
name: section.name,
|
|
2654
|
-
selected: selectedId === section.id,
|
|
2655
|
-
onClick: () => onSelect(section.id),
|
|
2656
|
-
onDelete,
|
|
2657
|
-
onCopy,
|
|
2658
|
-
onMoveUp: () => onMoveButton(section.id, "up"),
|
|
2659
|
-
onMoveDown: () => onMoveButton(section.id, "down"),
|
|
2660
|
-
canMoveUp: index > 0,
|
|
2661
|
-
canMoveDown: index < sections.length - 1,
|
|
2662
|
-
nodeRef: sectionNodes.register(section.id),
|
|
2663
|
-
onNavigate: (to) => onNavigate(section.id, to),
|
|
2664
|
-
onDeleteKey: () => onDeleteKey(section.id),
|
|
2665
|
-
inspecting,
|
|
2666
|
-
onInspectMove,
|
|
2667
|
-
onInspectPick: (...args) => onInspectPick(section.id, ...args),
|
|
2668
|
-
onInspectLeave,
|
|
2669
|
-
children: /* @__PURE__ */ jsx(renderer_default, {
|
|
2670
|
-
preview: previews[index],
|
|
2671
|
-
sectionId: section.id,
|
|
2672
|
-
sectionName: section.name,
|
|
2673
|
-
sectionCode: section.code,
|
|
2674
|
-
forceFallback: isForced(section),
|
|
2675
|
-
modules,
|
|
2676
|
-
frame,
|
|
2677
|
-
dynamicTailwind,
|
|
2678
|
-
provider,
|
|
2679
|
-
...props
|
|
2680
|
-
})
|
|
2681
|
-
}, section.id))
|
|
2682
|
-
})
|
|
2683
|
-
}),
|
|
2684
|
-
/* @__PURE__ */ jsx(InspectorHighlight, { rect: highlightRect })
|
|
2685
|
-
] });
|
|
4377
|
+
}),
|
|
4378
|
+
/* @__PURE__ */ jsx(InspectorHighlight, { rect: highlightRect })
|
|
4379
|
+
]
|
|
4380
|
+
});
|
|
2686
4381
|
return /* @__PURE__ */ jsx(SectionFallbackContext.Provider, {
|
|
2687
4382
|
value: renderSectionFallback,
|
|
2688
4383
|
children: /* @__PURE__ */ jsxs(DndContext, {
|
|
@@ -2745,11 +4440,7 @@ Dnd.Palette = Palette;
|
|
|
2745
4440
|
Dnd.Canvas = Canvas;
|
|
2746
4441
|
Dnd.Panel = Panel;
|
|
2747
4442
|
Dnd.Layout = Layout;
|
|
2748
|
-
/** @deprecated Renamed to `useDndItems`. */
|
|
2749
|
-
const useItemsEditor = useDndItems;
|
|
2750
|
-
/** @deprecated Renamed to `useDndChildren`. */
|
|
2751
|
-
const useChildrenEditor = useDndChildren;
|
|
2752
4443
|
//#endregion
|
|
2753
|
-
export {
|
|
4444
|
+
export { DraggableItem as _, Panel as a, getFieldKind as c, resolvePanelBindings as d, withPanelCommit as f, useDndInspector as g, useDndPanel as h, Palette as i, isStructuralFieldKind as l, useDndPalette as m, Canvas as n, Field as o, useDndLayout as p, Layout as r, useDndItems as s, Dnd as t, useDndChildren as u };
|
|
2754
4445
|
|
|
2755
|
-
//# sourceMappingURL=dnd-
|
|
4446
|
+
//# sourceMappingURL=dnd-BlC1gqUy.js.map
|