@jbpark/live-editor 2.1.1 → 2.2.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.
@@ -166,37 +166,141 @@ const Overlay = ({ sections, renderProps }) => {
166
166
  return null;
167
167
  };
168
168
  //#endregion
169
- //#region src/components/dnd/panel/node.tsx
169
+ //#region src/components/dnd/panel/use-items-editor.ts
170
170
  var import_lib = /* @__PURE__ */ __toESM(require_lib(), 1);
171
- const Node = ({ data, onChange }) => {
172
- const idAttr = data.dataAttributes.find((a) => a.name === "data-id");
173
- const bindingAttr = data.dataAttributes.find((a) => a.name === "data-binding");
174
- if (!idAttr?.value || !bindingAttr?.value) return null;
175
- const dataId = idAttr.value;
176
- const bindings = data.bindings || parseBinding(bindingAttr.value);
177
- if (!bindings.length) return null;
178
- return /* @__PURE__ */ jsx("div", {
179
- className: "space-y-2 rounded",
180
- children: /* @__PURE__ */ jsx("div", {
181
- className: "space-y-1",
182
- children: bindings.map((binding) => {
183
- const currentValue = getCurrentValue(data, binding.property);
184
- const structuredValue = getStructuredValue(data, binding.property, binding.type);
185
- return /* @__PURE__ */ jsxs("div", {
186
- className: "space-y-1",
187
- children: [/* @__PURE__ */ jsxs("label", {
188
- className: "block text-xs font-semibold text-gray-700",
189
- children: [binding.label, /* @__PURE__ */ jsxs("span", {
190
- className: "ml-1 text-gray-400",
191
- children: [
192
- "(",
193
- binding.property,
194
- ")"
195
- ]
196
- })]
197
- }), /* @__PURE__ */ jsx(Field, {
198
- binding: {
199
- id: dataId,
171
+ const resolveLeaf = (render, key) => {
172
+ const leaf = render?.[key];
173
+ return leaf && "type" in leaf ? leaf : null;
174
+ };
175
+ const resolveMap = (render, key) => {
176
+ const leaf = render?.[key];
177
+ return leaf && !("type" in leaf) ? leaf : void 0;
178
+ };
179
+ const bindingsInJSX = (node) => {
180
+ const nodes = extract(generateCode(node));
181
+ const container = nodes.find((n) => n.bindings?.some((b) => b.property === "children"));
182
+ if (container) return [container];
183
+ const bindings = [];
184
+ nodes.forEach((n) => {
185
+ if (n.bindings && n.bindings.length > 0 && n.dataAttributes.some((a) => a.name === "data-id")) bindings.push(n);
186
+ bindings.push(...findEditableChildren(n));
187
+ });
188
+ return bindings;
189
+ };
190
+ const parseSource = (value) => {
191
+ const ast = parseArrayExpression(value);
192
+ if (!ast) return {
193
+ objectItems: [],
194
+ primitiveItems: [],
195
+ parseError: true
196
+ };
197
+ const objectItems = [];
198
+ const primitiveItems = [];
199
+ ast.elements.filter((element) => Boolean(element)).forEach((element, elementIndex) => {
200
+ if (!import_lib.isObjectExpression(element)) {
201
+ const extracted = extractNodeValue(element);
202
+ primitiveItems.push({
203
+ id: nanoid(6),
204
+ index: primitiveItems.length,
205
+ elementIndex,
206
+ value: extracted.value,
207
+ type: extracted.type
208
+ });
209
+ return;
210
+ }
211
+ const jsxBindings = {};
212
+ const jsxFallbacks = {};
213
+ element.properties.forEach((prop) => {
214
+ if (!import_lib.isObjectProperty(prop) || !import_lib.isIdentifier(prop.key) || !(import_lib.isJSXElement(prop.value) || import_lib.isJSXFragment(prop.value))) return;
215
+ const propertyName = prop.key.name;
216
+ try {
217
+ const found = bindingsInJSX(prop.value);
218
+ if (found.length > 0) jsxBindings[propertyName] = found;
219
+ else jsxFallbacks[propertyName] = generateCode(prop.value);
220
+ } catch (error) {
221
+ console.error(`Failed to parse JSX in property '${propertyName}':`, error);
222
+ }
223
+ });
224
+ objectItems.push({
225
+ id: nanoid(6),
226
+ index: objectItems.length,
227
+ elementIndex,
228
+ editableProperties: extractObjectProperties(element),
229
+ jsxBindings,
230
+ jsxFallbacks
231
+ });
232
+ });
233
+ return {
234
+ objectItems,
235
+ primitiveItems,
236
+ parseError: false
237
+ };
238
+ };
239
+ const useItemsEditor = (value, { render, onChange, onNodeChange } = {}) => {
240
+ const { objectItems, primitiveItems, parseError } = useMemo(() => parseSource(value), [value]);
241
+ useEffect(() => {
242
+ if (parseError) Toast.error("Failed to parse items", { description: "Check the console for details." });
243
+ }, [parseError]);
244
+ const kind = primitiveItems.length > 0 && objectItems.length === 0 ? "primitive" : "object";
245
+ const isPrimitive = kind === "primitive";
246
+ const selection = useMultiSelect(isPrimitive ? primitiveItems.length : objectItems.length);
247
+ const commit = (next) => {
248
+ if (next === null) {
249
+ Toast.error("Failed to update this item", { description: "Check the console for details." });
250
+ return;
251
+ }
252
+ onChange?.(next);
253
+ };
254
+ const elementIndicesOf = (indices) => {
255
+ return new Set((isPrimitive ? primitiveItems : objectItems).filter((item) => indices.has(item.index)).map((item) => item.elementIndex));
256
+ };
257
+ const updateProperty = (elementIndex, propertyKey, next) => {
258
+ commit(updateArrayItemProperty(value, elementIndex, propertyKey, next, render));
259
+ };
260
+ return {
261
+ kind,
262
+ items: isPrimitive ? primitiveItems.map((item) => ({
263
+ id: item.id,
264
+ index: item.index,
265
+ elementIndex: item.elementIndex,
266
+ properties: [],
267
+ nested: [],
268
+ value: {
269
+ id: `primitive-${item.id}`,
270
+ label: `item-${item.index}`,
271
+ property: item.type,
272
+ value: item.value ?? "",
273
+ rawValue: String(item.value ?? ""),
274
+ onChange: (next) => commit(updateArrayItemValue(value, item.elementIndex, next))
275
+ }
276
+ })) : objectItems.map((item) => {
277
+ const properties = Object.entries(item.editableProperties).map(([key, prop]) => {
278
+ const leaf = resolveLeaf(render, key);
279
+ return {
280
+ id: `item-${item.id}-${key}`,
281
+ label: key,
282
+ property: leaf ? leaf.property ?? leaf.type : key,
283
+ type: leaf?.type,
284
+ render: leaf ? leaf.render : resolveMap(render, key),
285
+ value: parseValue(String(prop.value)),
286
+ rawValue: String(prop.value),
287
+ meta: { valueType: prop.type },
288
+ onChange: (next) => updateProperty(item.elementIndex, key, next)
289
+ };
290
+ });
291
+ const nested = [...Object.entries(item.jsxBindings).map(([property, nodes]) => ({
292
+ property,
293
+ elements: nodes.flatMap((node) => {
294
+ const id = node.dataAttributes.find((a) => a.name === "data-id")?.value;
295
+ const attr = node.dataAttributes.find((a) => a.name === "data-binding")?.value;
296
+ if (!id || !attr) return [];
297
+ const parsed = node.bindings ?? parseBinding(attr);
298
+ if (!parsed.length) return [];
299
+ return [{
300
+ id,
301
+ tagName: node.tagName || "element",
302
+ bindings: parsed.map((binding) => ({
303
+ id,
200
304
  label: binding.label,
201
305
  property: binding.property,
202
306
  type: binding.type,
@@ -208,21 +312,68 @@ const Node = ({ data, onChange }) => {
208
312
  pattern: binding.pattern,
209
313
  required: binding.required,
210
314
  meta: binding.meta,
211
- value: structuredValue,
212
- rawValue: currentValue,
213
- onChange: (next) => onChange?.({
214
- id: dataId,
315
+ value: getStructuredValue(node, binding.property, binding.type),
316
+ rawValue: getCurrentValue(node, binding.property),
317
+ onChange: (next) => onNodeChange?.({
318
+ id,
215
319
  label: binding.label,
216
320
  property: binding.property,
217
321
  value: next
218
322
  })
219
- },
220
- onNodeChange: onChange
221
- })]
222
- }, binding.label);
223
- })
224
- })
225
- });
323
+ }))
324
+ }];
325
+ })
326
+ })), ...Object.entries(item.jsxFallbacks).map(([property, code]) => ({
327
+ property,
328
+ elements: [],
329
+ fallback: {
330
+ id: `item-${item.id}-${property}-jsx`,
331
+ label: property,
332
+ property,
333
+ type: "jsx",
334
+ value: code,
335
+ rawValue: code,
336
+ onChange: (next) => updateProperty(item.elementIndex, property, next)
337
+ }
338
+ }))];
339
+ return {
340
+ id: item.id,
341
+ index: item.index,
342
+ elementIndex: item.elementIndex,
343
+ properties,
344
+ nested
345
+ };
346
+ }),
347
+ selection,
348
+ actions: {
349
+ add: () => commit(appendArrayItem(value, kind)),
350
+ move: (elementIndex, toIndex) => {
351
+ const target = (isPrimitive ? primitiveItems : objectItems).find((item) => item.index === toIndex);
352
+ if (!target) return;
353
+ commit(moveArrayItem(value, elementIndex, target.elementIndex));
354
+ selection.clear();
355
+ },
356
+ remove: (elementIndex) => {
357
+ commit(removeArrayItems(value, /* @__PURE__ */ new Set([elementIndex]), kind));
358
+ selection.clear();
359
+ },
360
+ duplicateSelected: () => commit(duplicateArrayItems(value, elementIndicesOf(selection.selected))),
361
+ moveSelected: (direction) => {
362
+ const result = moveArrayItems(value, elementIndicesOf(selection.selected), direction);
363
+ if (!result) {
364
+ commit(null);
365
+ return;
366
+ }
367
+ selection.replace(result.indices);
368
+ commit(result.code);
369
+ },
370
+ removeSelected: () => {
371
+ commit(removeArrayItems(value, elementIndicesOf(selection.selected), kind));
372
+ selection.clear();
373
+ }
374
+ },
375
+ parseError
376
+ };
226
377
  };
227
378
  //#endregion
228
379
  //#region src/components/dnd/panel/items.tsx
@@ -270,143 +421,101 @@ const BulkActionsBar = ({ count, onDuplicate, onMoveUp, onMoveDown, onDelete, on
270
421
  })]
271
422
  });
272
423
  };
424
+ const NestedGroup = ({ group, onNodeChange }) => {
425
+ if (group.fallback) return /* @__PURE__ */ jsxs("div", {
426
+ className: "space-y-2",
427
+ children: [/* @__PURE__ */ jsx("div", {
428
+ className: "text-xs font-medium text-blue-700",
429
+ children: group.property
430
+ }), /* @__PURE__ */ jsx(Field, {
431
+ binding: group.fallback,
432
+ onNodeChange
433
+ })]
434
+ });
435
+ return /* @__PURE__ */ jsxs("div", {
436
+ className: "space-y-2",
437
+ children: [/* @__PURE__ */ jsxs("div", {
438
+ className: "text-xs font-medium text-blue-700",
439
+ children: [
440
+ group.property,
441
+ " Bindings (",
442
+ group.elements.length,
443
+ "):"
444
+ ]
445
+ }), group.elements.map((element) => /* @__PURE__ */ jsxs("div", {
446
+ className: "rounded border border-blue-100 bg-blue-50 p-2",
447
+ children: [/* @__PURE__ */ jsxs("div", {
448
+ className: "mb-1 text-xs text-blue-600",
449
+ children: [
450
+ "<",
451
+ element.tagName,
452
+ ">"
453
+ ]
454
+ }), /* @__PURE__ */ jsx("div", {
455
+ className: "space-y-2 rounded",
456
+ children: /* @__PURE__ */ jsx("div", {
457
+ className: "space-y-1",
458
+ children: element.bindings.map((binding) => /* @__PURE__ */ jsxs("div", {
459
+ className: "space-y-1",
460
+ children: [/* @__PURE__ */ jsxs("label", {
461
+ className: "block text-xs font-semibold text-gray-700",
462
+ children: [binding.label, /* @__PURE__ */ jsxs("span", {
463
+ className: "ml-1 text-gray-400",
464
+ children: [
465
+ "(",
466
+ binding.property,
467
+ ")"
468
+ ]
469
+ })]
470
+ }), /* @__PURE__ */ jsx(Field, {
471
+ binding,
472
+ onNodeChange
473
+ })]
474
+ }, binding.label))
475
+ })
476
+ })]
477
+ }, `${group.property}-${element.id}`))]
478
+ });
479
+ };
273
480
  const Items = ({ value, render, onChange, onChildChange }) => {
274
- const { objectItems, primitiveItems, parseError } = useMemo(() => {
275
- const ast = parseArrayExpression(value);
276
- if (!ast) return {
277
- objectItems: [],
278
- primitiveItems: [],
279
- parseError: true
280
- };
281
- const objectItems = [];
282
- const primitiveItems = [];
283
- ast.elements.filter((element) => Boolean(element)).forEach((element, elementIndex) => {
284
- if (!import_lib.isObjectExpression(element)) {
285
- const extracted = extractNodeValue(element);
286
- primitiveItems.push({
287
- id: nanoid(6),
288
- index: primitiveItems.length,
289
- elementIndex,
290
- value: extracted.value,
291
- type: extracted.type
292
- });
293
- return;
294
- }
295
- const jsxBindings = {};
296
- const jsxFallbacks = {};
297
- element.properties.forEach((prop) => {
298
- if (!import_lib.isObjectProperty(prop) || !import_lib.isIdentifier(prop.key) || !(import_lib.isJSXElement(prop.value) || import_lib.isJSXFragment(prop.value))) return;
299
- const propertyName = prop.key.name;
300
- try {
301
- const jsxCode = generateCode(prop.value);
302
- const nodes = extract(jsxCode);
303
- const bindings = [];
304
- const bindingContainer = nodes.find((node) => node.bindings?.some((b) => b.property === "children"));
305
- if (bindingContainer) bindings.push(bindingContainer);
306
- else nodes.forEach((node) => {
307
- if (node.bindings && node.bindings.length > 0 && node.dataAttributes.some((a) => a.name === "data-id")) bindings.push(node);
308
- const editableChildren = findEditableChildren(node);
309
- bindings.push(...editableChildren);
310
- });
311
- if (bindings.length > 0) jsxBindings[propertyName] = bindings;
312
- else jsxFallbacks[propertyName] = jsxCode;
313
- } catch (error) {
314
- console.error(`Failed to parse JSX in property '${propertyName}':`, error);
315
- }
316
- });
317
- objectItems.push({
318
- id: nanoid(6),
319
- index: objectItems.length,
320
- elementIndex,
321
- editableProperties: extractObjectProperties(element),
322
- jsxBindings,
323
- jsxFallbacks
324
- });
325
- });
326
- return {
327
- objectItems,
328
- primitiveItems,
329
- parseError: false
330
- };
331
- }, [value]);
332
- useEffect(() => {
333
- if (parseError) Toast.error("Failed to parse items", { description: "Check the console for details." });
334
- }, [parseError]);
335
- const isPrimitive = primitiveItems.length > 0 && objectItems.length === 0;
336
- const selection = useMultiSelect(isPrimitive ? primitiveItems.length : objectItems.length);
337
- const commit = (next) => {
338
- if (next === null) {
339
- Toast.error("Failed to update this item", { description: "Check the console for details." });
340
- return;
341
- }
342
- onChange?.(next);
343
- };
344
- const elementIndicesOf = (items, indices) => {
345
- return new Set(items.filter((item) => indices.has(item.index)).map((item) => item.elementIndex));
346
- };
347
- const updatePrimitive = (elementIndex, next) => {
348
- commit(updateArrayItemValue(value, elementIndex, next));
349
- };
350
- const movePrimitive = (from, to) => {
351
- const target = primitiveItems.find((item) => item.index === to);
352
- if (!target) return;
353
- commit(moveArrayItem(value, from, target.elementIndex));
354
- selection.clear();
355
- };
356
- const deleteSelectedPrimitives = (indices) => {
357
- commit(removeArrayItems(value, elementIndicesOf(primitiveItems, indices), "primitive"));
358
- };
359
- const deletePrimitive = (elementIndex) => {
360
- commit(removeArrayItems(value, /* @__PURE__ */ new Set([elementIndex]), "primitive"));
361
- selection.clear();
362
- };
363
- const addPrimitive = () => {
364
- commit(appendArrayItem(value, "primitive"));
365
- };
366
- const duplicateSelectedPrimitives = (indices) => {
367
- commit(duplicateArrayItems(value, elementIndicesOf(primitiveItems, indices)));
368
- };
369
- const moveSelectedPrimitives = (indices, direction) => {
370
- const result = moveArrayItems(value, elementIndicesOf(primitiveItems, indices), direction);
371
- if (!result) {
372
- commit(null);
373
- return;
374
- }
375
- selection.replace(result.indices);
376
- commit(result.code);
377
- };
378
- const moveItem = (from, to) => {
379
- const target = objectItems.find((item) => item.index === to);
380
- if (!target) return;
381
- commit(moveArrayItem(value, from, target.elementIndex));
382
- selection.clear();
383
- };
384
- const updateProperty = (elementIndex, propertyKey, next) => {
385
- commit(updateArrayItemProperty(value, elementIndex, propertyKey, next, render));
386
- };
387
- const deleteSelectedItems = (indices) => {
388
- commit(removeArrayItems(value, elementIndicesOf(objectItems, indices), "object"));
389
- };
390
- const deleteItem = (elementIndex) => {
391
- commit(removeArrayItems(value, /* @__PURE__ */ new Set([elementIndex]), "object"));
392
- selection.clear();
393
- };
394
- const addItem = () => {
395
- commit(appendArrayItem(value, "object"));
396
- };
397
- const duplicateSelectedItems = (indices) => {
398
- commit(duplicateArrayItems(value, elementIndicesOf(objectItems, indices)));
399
- };
400
- const moveSelectedItems = (indices, direction) => {
401
- const result = moveArrayItems(value, elementIndicesOf(objectItems, indices), direction);
402
- if (!result) {
403
- commit(null);
404
- return;
405
- }
406
- selection.replace(result.indices);
407
- commit(result.code);
408
- };
409
- if (isPrimitive) return /* @__PURE__ */ jsxs("div", {
481
+ const { kind, items, selection, actions } = useItemsEditor(value, {
482
+ render,
483
+ onChange,
484
+ onNodeChange: onChildChange
485
+ });
486
+ const bulkBar = /* @__PURE__ */ jsx(BulkActionsBar, {
487
+ count: selection.selected.size,
488
+ onDuplicate: actions.duplicateSelected,
489
+ onMoveUp: () => actions.moveSelected("up"),
490
+ onMoveDown: () => actions.moveSelected("down"),
491
+ onDelete: actions.removeSelected,
492
+ onClear: selection.clear
493
+ });
494
+ const itemControls = (item) => /* @__PURE__ */ jsxs("div", {
495
+ className: "flex space-x-1",
496
+ children: [
497
+ /* @__PURE__ */ jsx(Button, {
498
+ size: "small",
499
+ icon: /* @__PURE__ */ jsx(ArrowUp, {}),
500
+ disabled: item.index === 0,
501
+ onClick: () => actions.move(item.elementIndex, item.index - 1)
502
+ }),
503
+ /* @__PURE__ */ jsx(Button, {
504
+ size: "small",
505
+ icon: /* @__PURE__ */ jsx(ArrowDown, {}),
506
+ disabled: item.index === items.length - 1,
507
+ onClick: () => actions.move(item.elementIndex, item.index + 1)
508
+ }),
509
+ /* @__PURE__ */ jsx(Button, {
510
+ danger: true,
511
+ size: "small",
512
+ icon: /* @__PURE__ */ jsx(X, {}),
513
+ disabled: items.length <= 1,
514
+ onClick: () => actions.remove(item.elementIndex)
515
+ })
516
+ ]
517
+ });
518
+ if (kind === "primitive") return /* @__PURE__ */ jsxs("div", {
410
519
  className: "space-y-4",
411
520
  children: [
412
521
  /* @__PURE__ */ jsxs("div", {
@@ -415,7 +524,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
415
524
  className: "text-sm font-semibold",
416
525
  children: [
417
526
  "Items (",
418
- primitiveItems.length,
527
+ items.length,
419
528
  ")"
420
529
  ]
421
530
  }), /* @__PURE__ */ jsx(Button, {
@@ -423,22 +532,12 @@ const Items = ({ value, render, onChange, onChildChange }) => {
423
532
  icon: /* @__PURE__ */ jsx(Plus, {}),
424
533
  variant: "solid",
425
534
  color: "green",
426
- onClick: addPrimitive,
535
+ onClick: actions.add,
427
536
  children: "Add Item"
428
537
  })]
429
538
  }),
430
- /* @__PURE__ */ jsx(BulkActionsBar, {
431
- count: selection.selected.size,
432
- onDuplicate: () => duplicateSelectedPrimitives(selection.selected),
433
- onMoveUp: () => moveSelectedPrimitives(selection.selected, "up"),
434
- onMoveDown: () => moveSelectedPrimitives(selection.selected, "down"),
435
- onDelete: () => {
436
- deleteSelectedPrimitives(selection.selected);
437
- selection.clear();
438
- },
439
- onClear: selection.clear
440
- }),
441
- primitiveItems.map((item, i) => /* @__PURE__ */ jsxs("div", {
539
+ bulkBar,
540
+ items.map((item) => /* @__PURE__ */ jsxs("div", {
442
541
  className: "space-y-2 rounded border border-gray-100 bg-gray-50 p-2",
443
542
  children: [/* @__PURE__ */ jsxs("div", {
444
543
  className: "flex items-center justify-between space-x-1",
@@ -449,39 +548,9 @@ const Items = ({ value, render, onChange, onChildChange }) => {
449
548
  checked: selection.isSelected(item.index),
450
549
  onChange: () => {}
451
550
  })
452
- }), /* @__PURE__ */ jsxs("div", {
453
- className: "flex space-x-1",
454
- children: [
455
- /* @__PURE__ */ jsx(Button, {
456
- size: "small",
457
- icon: /* @__PURE__ */ jsx(ArrowUp, {}),
458
- disabled: i === 0,
459
- onClick: () => movePrimitive(item.elementIndex, item.index - 1)
460
- }),
461
- /* @__PURE__ */ jsx(Button, {
462
- size: "small",
463
- icon: /* @__PURE__ */ jsx(ArrowDown, {}),
464
- disabled: i === primitiveItems.length - 1,
465
- onClick: () => movePrimitive(item.elementIndex, item.index + 1)
466
- }),
467
- /* @__PURE__ */ jsx(Button, {
468
- danger: true,
469
- size: "small",
470
- icon: /* @__PURE__ */ jsx(X, {}),
471
- disabled: primitiveItems.length <= 1,
472
- onClick: () => deletePrimitive(item.elementIndex)
473
- })
474
- ]
475
- })]
551
+ }), itemControls(item)]
476
552
  }), /* @__PURE__ */ jsx(Field, {
477
- binding: {
478
- id: `primitive-${item.id}`,
479
- label: `item-${i}`,
480
- property: item.type,
481
- value: item.value ?? "",
482
- rawValue: String(item.value ?? ""),
483
- onChange: (next) => updatePrimitive(item.elementIndex, next)
484
- },
553
+ binding: item.value,
485
554
  onNodeChange: onChildChange
486
555
  })]
487
556
  }, item.id))
@@ -496,7 +565,7 @@ const Items = ({ value, render, onChange, onChildChange }) => {
496
565
  className: "text-sm font-semibold",
497
566
  children: [
498
567
  "Items (",
499
- objectItems.length,
568
+ items.length,
500
569
  ")"
501
570
  ]
502
571
  }), /* @__PURE__ */ jsx(Button, {
@@ -504,23 +573,13 @@ const Items = ({ value, render, onChange, onChildChange }) => {
504
573
  icon: /* @__PURE__ */ jsx(Plus, {}),
505
574
  variant: "solid",
506
575
  color: "green",
507
- disabled: objectItems.length === 0,
508
- onClick: addItem,
576
+ disabled: items.length === 0,
577
+ onClick: actions.add,
509
578
  children: "Add Item"
510
579
  })]
511
580
  }),
512
- /* @__PURE__ */ jsx(BulkActionsBar, {
513
- count: selection.selected.size,
514
- onDuplicate: () => duplicateSelectedItems(selection.selected),
515
- onMoveUp: () => moveSelectedItems(selection.selected, "up"),
516
- onMoveDown: () => moveSelectedItems(selection.selected, "down"),
517
- onDelete: () => {
518
- deleteSelectedItems(selection.selected);
519
- selection.clear();
520
- },
521
- onClear: selection.clear
522
- }),
523
- objectItems.map((item) => /* @__PURE__ */ jsxs("div", {
581
+ bulkBar,
582
+ items.map((item) => /* @__PURE__ */ jsxs("div", {
524
583
  className: "space-y-3 rounded border bg-gray-50 p-3",
525
584
  children: [
526
585
  /* @__PURE__ */ jsxs("div", {
@@ -538,107 +597,34 @@ const Items = ({ value, render, onChange, onChildChange }) => {
538
597
  className: "text-xs font-medium",
539
598
  children: ["Item ", item.index + 1]
540
599
  })]
541
- }), /* @__PURE__ */ jsxs("div", {
542
- className: "flex space-x-1",
543
- children: [
544
- /* @__PURE__ */ jsx(Button, {
545
- size: "small",
546
- icon: /* @__PURE__ */ jsx(ArrowUp, {}),
547
- disabled: item.index === 0,
548
- onClick: () => moveItem(item.elementIndex, item.index - 1)
549
- }),
550
- /* @__PURE__ */ jsx(Button, {
551
- size: "small",
552
- icon: /* @__PURE__ */ jsx(ArrowDown, {}),
553
- disabled: item.index === objectItems.length - 1,
554
- onClick: () => moveItem(item.elementIndex, item.index + 1)
555
- }),
556
- /* @__PURE__ */ jsx(Button, {
557
- danger: true,
558
- size: "small",
559
- icon: /* @__PURE__ */ jsx(X, {}),
560
- disabled: objectItems.length <= 1,
561
- onClick: () => deleteItem(item.elementIndex)
562
- })
563
- ]
564
- })]
600
+ }), itemControls(item)]
565
601
  }),
566
602
  /* @__PURE__ */ jsx("div", {
567
603
  className: "space-y-2",
568
- children: Object.entries(item.editableProperties).map(([key, prop]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
604
+ children: item.properties.map((binding) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
569
605
  className: "flex flex-col space-y-2",
570
606
  children: [/* @__PURE__ */ jsx("label", {
571
607
  className: "w-20 shrink-0 text-xs font-medium",
572
- children: key
608
+ children: binding.label
573
609
  }), /* @__PURE__ */ jsx(Field, {
574
- binding: {
575
- id: `item-${item.id}-${key}`,
576
- label: key,
577
- property: render?.[key] && "type" in render[key] ? render[key].property ?? render[key].type : key,
578
- type: render?.[key] && "type" in render[key] ? render[key].type : void 0,
579
- render: render?.[key] && "type" in render[key] ? render[key].render : render?.[key] && !("type" in render[key]) ? render[key] : void 0,
580
- value: parseValue(String(prop.value)),
581
- rawValue: String(prop.value),
582
- onChange: (next) => updateProperty(item.elementIndex, key, next)
583
- },
610
+ binding,
584
611
  onNodeChange: onChildChange
585
612
  })]
586
613
  }), /* @__PURE__ */ jsxs("span", {
587
614
  className: "text-right text-xs text-gray-500",
588
615
  children: [
589
616
  "(",
590
- prop.type,
617
+ String(binding.meta?.valueType),
591
618
  ")"
592
619
  ]
593
- })] }, `${item.id}-${key}`))
620
+ })] }, `${item.id}-${binding.label}`))
594
621
  }),
595
622
  /* @__PURE__ */ jsx("div", {
596
623
  className: "space-y-3 border-t pt-2",
597
- children: Object.entries(item.jsxBindings).length > 0 || Object.entries(item.jsxFallbacks).length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [Object.entries(item.jsxBindings).map(([propertyName, bindings]) => /* @__PURE__ */ jsxs("div", {
598
- className: "space-y-2",
599
- children: [/* @__PURE__ */ jsxs("div", {
600
- className: "text-xs font-medium text-blue-700",
601
- children: [
602
- propertyName,
603
- " Bindings (",
604
- bindings.length,
605
- "):"
606
- ]
607
- }), bindings.map((bindingNode, idx) => {
608
- const nodeId = bindingNode.dataAttributes.find((a) => a.name === "data-id")?.value;
609
- return /* @__PURE__ */ jsxs("div", {
610
- className: "rounded border border-blue-100 bg-blue-50\n p-2",
611
- children: [/* @__PURE__ */ jsxs("div", {
612
- className: "mb-1 text-xs text-blue-600",
613
- children: [
614
- "<",
615
- bindingNode.tagName || "element",
616
- ">"
617
- ]
618
- }), /* @__PURE__ */ jsx(Node, {
619
- data: bindingNode,
620
- onChange: onChildChange
621
- })]
622
- }, `binding-${item.id}-${propertyName}-${nodeId || idx}`);
623
- })]
624
- }, propertyName)), Object.entries(item.jsxFallbacks).map(([propertyName, code]) => /* @__PURE__ */ jsxs("div", {
625
- className: "space-y-2",
626
- children: [/* @__PURE__ */ jsx("div", {
627
- className: "text-xs font-medium text-blue-700",
628
- children: propertyName
629
- }), /* @__PURE__ */ jsx(Field, {
630
- binding: {
631
- id: `item-${item.id}-${propertyName}-jsx`,
632
- label: propertyName,
633
- property: propertyName,
634
- type: "jsx",
635
- value: code,
636
- rawValue: code,
637
- onChange: (next) => updateProperty(item.elementIndex, propertyName, next)
638
- },
639
- onNodeChange: onChildChange
640
- })]
641
- }, propertyName))] }) : /* @__PURE__ */ jsx("div", {
624
+ children: item.nested.length ? item.nested.map((group) => /* @__PURE__ */ jsx(NestedGroup, {
625
+ group,
626
+ onNodeChange: onChildChange
627
+ }, group.property)) : /* @__PURE__ */ jsx("div", {
642
628
  className: "text-xs text-gray-500",
643
629
  children: "✓ No JSX bindings found"
644
630
  })
@@ -649,6 +635,64 @@ const Items = ({ value, render, onChange, onChildChange }) => {
649
635
  });
650
636
  };
651
637
  //#endregion
638
+ //#region src/components/dnd/panel/node.tsx
639
+ const Node = ({ data, onChange }) => {
640
+ const idAttr = data.dataAttributes.find((a) => a.name === "data-id");
641
+ const bindingAttr = data.dataAttributes.find((a) => a.name === "data-binding");
642
+ if (!idAttr?.value || !bindingAttr?.value) return null;
643
+ const dataId = idAttr.value;
644
+ const bindings = data.bindings || parseBinding(bindingAttr.value);
645
+ if (!bindings.length) return null;
646
+ return /* @__PURE__ */ jsx("div", {
647
+ className: "space-y-2 rounded",
648
+ children: /* @__PURE__ */ jsx("div", {
649
+ className: "space-y-1",
650
+ children: bindings.map((binding) => {
651
+ const currentValue = getCurrentValue(data, binding.property);
652
+ const structuredValue = getStructuredValue(data, binding.property, binding.type);
653
+ return /* @__PURE__ */ jsxs("div", {
654
+ className: "space-y-1",
655
+ children: [/* @__PURE__ */ jsxs("label", {
656
+ className: "block text-xs font-semibold text-gray-700",
657
+ children: [binding.label, /* @__PURE__ */ jsxs("span", {
658
+ className: "ml-1 text-gray-400",
659
+ children: [
660
+ "(",
661
+ binding.property,
662
+ ")"
663
+ ]
664
+ })]
665
+ }), /* @__PURE__ */ jsx(Field, {
666
+ binding: {
667
+ id: dataId,
668
+ label: binding.label,
669
+ property: binding.property,
670
+ type: binding.type,
671
+ widget: binding.widget,
672
+ options: binding.options,
673
+ render: binding.render,
674
+ min: binding.min,
675
+ max: binding.max,
676
+ pattern: binding.pattern,
677
+ required: binding.required,
678
+ meta: binding.meta,
679
+ value: structuredValue,
680
+ rawValue: currentValue,
681
+ onChange: (next) => onChange?.({
682
+ id: dataId,
683
+ label: binding.label,
684
+ property: binding.property,
685
+ value: next
686
+ })
687
+ },
688
+ onNodeChange: onChange
689
+ })]
690
+ }, binding.label);
691
+ })
692
+ })
693
+ });
694
+ };
695
+ //#endregion
652
696
  //#region src/components/dnd/panel/children.tsx
653
697
  const Children = ({ value, onChange, onNodeChange }) => {
654
698
  const items = useMemo(() => Array.isArray(value) ? value : [], [value]);
@@ -1499,7 +1543,8 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1499
1543
  onMoveDown: () => moveSection(selectedId, "down"),
1500
1544
  canMoveUp,
1501
1545
  canMoveDown,
1502
- bindings
1546
+ bindings,
1547
+ onNodeChange: onFieldChange
1503
1548
  });
1504
1549
  return /* @__PURE__ */ jsx(Panel, {
1505
1550
  item: selectedItem,
@@ -1634,7 +1679,8 @@ const Dnd$1 = ({ value: _value, props, modules = {}, onChange: _onChange, classN
1634
1679
  const Dnd = Dnd$1;
1635
1680
  Dnd.DraggableItem = DraggableItem;
1636
1681
  Dnd.DefaultPanel = Panel;
1682
+ Dnd.Field = Field;
1637
1683
  //#endregion
1638
- export { DraggableItem as a, ICON_OPTIONS as i, Panel as n, ICON_MAP as r, Dnd as t };
1684
+ export { ICON_OPTIONS as a, ICON_MAP as i, Panel as n, useItemsEditor as o, Field as r, DraggableItem as s, Dnd as t };
1639
1685
 
1640
- //# sourceMappingURL=dnd-C1YGcp4k.js.map
1686
+ //# sourceMappingURL=dnd-DRivgZdt.js.map