@jxsuite/studio 0.10.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/dist/studio.js +5114 -3424
- package/dist/studio.js.map +65 -49
- package/package.json +6 -3
- package/src/browse/browse.js +27 -3
- package/src/canvas/canvas-diff.js +184 -0
- package/src/canvas/canvas-helpers.js +10 -14
- package/src/canvas/canvas-live-render.js +136 -17
- package/src/canvas/canvas-render.js +154 -21
- package/src/canvas/canvas-utils.js +21 -23
- package/src/editor/component-inline-edit.js +54 -41
- package/src/editor/content-inline-edit.js +46 -47
- package/src/editor/context-menu.js +63 -39
- package/src/editor/convert-to-component.js +11 -14
- package/src/editor/insertion-helper.js +8 -10
- package/src/editor/shortcuts.js +69 -39
- package/src/files/components.js +15 -4
- package/src/files/files.js +72 -24
- package/src/panels/activity-bar.js +29 -7
- package/src/panels/block-action-bar.js +104 -80
- package/src/panels/canvas-dnd.js +132 -50
- package/src/panels/dnd.js +32 -28
- package/src/panels/editors.js +7 -14
- package/src/panels/elements-panel.js +9 -3
- package/src/panels/events-panel.js +44 -39
- package/src/panels/git-panel.js +45 -3
- package/src/panels/layers-panel.js +16 -11
- package/src/panels/left-panel.js +108 -43
- package/src/panels/overlays.js +97 -35
- package/src/panels/panel-events.js +18 -11
- package/src/panels/properties-panel.js +467 -98
- package/src/panels/right-panel.js +85 -37
- package/src/panels/shared.js +0 -22
- package/src/panels/signals-panel.js +125 -54
- package/src/panels/statusbar.js +23 -8
- package/src/panels/style-inputs.js +4 -2
- package/src/panels/style-panel.js +128 -105
- package/src/panels/stylebook-panel.js +42 -17
- package/src/panels/tab-strip.js +124 -0
- package/src/panels/toolbar.js +39 -9
- package/src/reactivity.js +28 -0
- package/src/settings/content-types-editor.js +78 -8
- package/src/settings/defs-editor.js +56 -7
- package/src/settings/schema-field-ui.js +99 -11
- package/src/site-context.js +105 -0
- package/src/state.js +0 -456
- package/src/store.js +105 -124
- package/src/studio.js +112 -121
- package/src/tabs/tab.js +153 -0
- package/src/tabs/transact.js +406 -0
- package/src/ui/spectrum.js +2 -0
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +89 -0
package/src/state.js
CHANGED
|
@@ -33,8 +33,6 @@
|
|
|
33
33
|
* }} StudioState
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
const HISTORY_LIMIT = 100;
|
|
37
|
-
|
|
38
36
|
// ─── Path utilities ───────────────────────────────────────────────────────────
|
|
39
37
|
|
|
40
38
|
/**
|
|
@@ -335,31 +333,6 @@ export function updateFrontmatter(state, field, value) {
|
|
|
335
333
|
};
|
|
336
334
|
}
|
|
337
335
|
|
|
338
|
-
// ─── Core mutation ────────────────────────────────────────────────────────────
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Apply a mutation to the document. Clones the document immutably, applies the mutation function to
|
|
342
|
-
* the clone, and pushes to history.
|
|
343
|
-
*
|
|
344
|
-
* @param {StudioState} state
|
|
345
|
-
* @param {(doc: any) => void} mutationFn
|
|
346
|
-
* @returns {StudioState}
|
|
347
|
-
*/
|
|
348
|
-
export function applyMutation(state, mutationFn) {
|
|
349
|
-
const newDoc = structuredClone(state.document);
|
|
350
|
-
mutationFn(newDoc);
|
|
351
|
-
const truncated = state.history.slice(0, state.historyIndex + 1);
|
|
352
|
-
truncated.push({ document: newDoc, selection: state.selection });
|
|
353
|
-
if (truncated.length > HISTORY_LIMIT) truncated.shift();
|
|
354
|
-
return {
|
|
355
|
-
...state,
|
|
356
|
-
document: newDoc,
|
|
357
|
-
history: truncated,
|
|
358
|
-
historyIndex: truncated.length - 1,
|
|
359
|
-
dirty: true,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
336
|
// ─── Selection / hover ────────────────────────────────────────────────────────
|
|
364
337
|
|
|
365
338
|
/**
|
|
@@ -380,366 +353,6 @@ export function hoverNode(state, path) {
|
|
|
380
353
|
return { ...state, hover: path };
|
|
381
354
|
}
|
|
382
355
|
|
|
383
|
-
// ─── Undo / redo ──────────────────────────────────────────────────────────────
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* @param {StudioState} state
|
|
387
|
-
* @returns {StudioState}
|
|
388
|
-
*/
|
|
389
|
-
export function undo(state) {
|
|
390
|
-
if (state.historyIndex <= 0) return state;
|
|
391
|
-
const idx = state.historyIndex - 1;
|
|
392
|
-
const snap = state.history[idx];
|
|
393
|
-
return {
|
|
394
|
-
...state,
|
|
395
|
-
document: snap.document,
|
|
396
|
-
selection: snap.selection,
|
|
397
|
-
historyIndex: idx,
|
|
398
|
-
dirty: true,
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* @param {StudioState} state
|
|
404
|
-
* @returns {StudioState}
|
|
405
|
-
*/
|
|
406
|
-
export function redo(state) {
|
|
407
|
-
if (state.historyIndex >= state.history.length - 1) return state;
|
|
408
|
-
const idx = state.historyIndex + 1;
|
|
409
|
-
const snap = state.history[idx];
|
|
410
|
-
return {
|
|
411
|
-
...state,
|
|
412
|
-
document: snap.document,
|
|
413
|
-
selection: snap.selection,
|
|
414
|
-
historyIndex: idx,
|
|
415
|
-
dirty: true,
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// ─── Document mutations ───────────────────────────────────────────────────────
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* @param {StudioState} state
|
|
423
|
-
* @param {JxPath} parentPath
|
|
424
|
-
* @param {number} index
|
|
425
|
-
* @param {any} nodeDef
|
|
426
|
-
* @returns {StudioState}
|
|
427
|
-
*/
|
|
428
|
-
export function insertNode(state, parentPath, index, nodeDef) {
|
|
429
|
-
return applyMutation(state, (doc) => {
|
|
430
|
-
const parent = getNodeAtPath(doc, parentPath);
|
|
431
|
-
if (!parent.children) parent.children = [];
|
|
432
|
-
parent.children.splice(index, 0, nodeDef);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* @param {StudioState} state
|
|
438
|
-
* @param {JxPath} path
|
|
439
|
-
* @returns {StudioState}
|
|
440
|
-
*/
|
|
441
|
-
export function removeNode(state, path) {
|
|
442
|
-
if (!path || path.length < 2) return state; // can't remove root
|
|
443
|
-
const elemPath = parentElementPath(path);
|
|
444
|
-
const idx = childIndex(path);
|
|
445
|
-
const newState = applyMutation(state, (doc) => {
|
|
446
|
-
getNodeAtPath(doc, /** @type {JxPath} */ (elemPath)).children.splice(idx, 1);
|
|
447
|
-
});
|
|
448
|
-
// Clear selection if we removed the selected node
|
|
449
|
-
if (state.selection && isAncestor(path, state.selection)) {
|
|
450
|
-
return { ...newState, selection: null };
|
|
451
|
-
}
|
|
452
|
-
return newState;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* @param {StudioState} state
|
|
457
|
-
* @param {JxPath} path
|
|
458
|
-
* @returns {StudioState}
|
|
459
|
-
*/
|
|
460
|
-
export function duplicateNode(state, path) {
|
|
461
|
-
if (!path || path.length < 2) return state;
|
|
462
|
-
const node = getNodeAtPath(state.document, path);
|
|
463
|
-
if (!node) return state;
|
|
464
|
-
const elemPath = /** @type {JxPath} */ (parentElementPath(path));
|
|
465
|
-
const idx = /** @type {number} */ (childIndex(path));
|
|
466
|
-
const newState = insertNode(state, elemPath, idx + 1, structuredClone(node));
|
|
467
|
-
return selectNode(newState, [...elemPath, "children", idx + 1]);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Wrap the node at `path` in a new wrapper element (e.g. a div).
|
|
472
|
-
*
|
|
473
|
-
* @param {StudioState} state
|
|
474
|
-
* @param {JxPath} path
|
|
475
|
-
* @param {string} wrapperTag
|
|
476
|
-
* @returns {StudioState}
|
|
477
|
-
*/
|
|
478
|
-
export function wrapNode(state, path, wrapperTag = "div") {
|
|
479
|
-
if (!path || path.length < 2) return state;
|
|
480
|
-
const node = getNodeAtPath(state.document, path);
|
|
481
|
-
if (!node) return state;
|
|
482
|
-
const elemPath = /** @type {JxPath} */ (parentElementPath(path));
|
|
483
|
-
const idx = /** @type {number} */ (childIndex(path));
|
|
484
|
-
const wrapper = { tagName: wrapperTag, children: [structuredClone(node)] };
|
|
485
|
-
const newState = applyMutation(state, (doc) => {
|
|
486
|
-
const parent = getNodeAtPath(doc, elemPath);
|
|
487
|
-
parent.children.splice(idx, 1, wrapper);
|
|
488
|
-
});
|
|
489
|
-
return selectNode(newState, [...elemPath, "children", idx]);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* @param {StudioState} state
|
|
494
|
-
* @param {JxPath} fromPath
|
|
495
|
-
* @param {JxPath} toParentPath
|
|
496
|
-
* @param {number} toIndex
|
|
497
|
-
* @returns {StudioState}
|
|
498
|
-
*/
|
|
499
|
-
export function moveNode(state, fromPath, toParentPath, toIndex) {
|
|
500
|
-
const newState = applyMutation(state, (doc) => {
|
|
501
|
-
const fromParentPath = /** @type {JxPath} */ (parentElementPath(fromPath));
|
|
502
|
-
const fromParent = getNodeAtPath(doc, fromParentPath);
|
|
503
|
-
const fromIdx = childIndex(fromPath);
|
|
504
|
-
const [node] = fromParent.children.splice(fromIdx, 1);
|
|
505
|
-
const toParent = getNodeAtPath(doc, toParentPath);
|
|
506
|
-
if (!toParent.children) toParent.children = [];
|
|
507
|
-
// Adjust target index if moving within the same parent and source was before target
|
|
508
|
-
let adjustedIndex = toIndex;
|
|
509
|
-
if (fromParent === toParent && /** @type {number} */ (fromIdx) < toIndex) {
|
|
510
|
-
adjustedIndex--;
|
|
511
|
-
}
|
|
512
|
-
toParent.children.splice(adjustedIndex, 0, node);
|
|
513
|
-
});
|
|
514
|
-
// Update selection to follow the moved node
|
|
515
|
-
if (pathsEqual(newState.selection, fromPath)) {
|
|
516
|
-
let adjustedIdx = toIndex;
|
|
517
|
-
// Adjust if same parent and source was before target
|
|
518
|
-
const fromParentPath = /** @type {JxPath} */ (parentElementPath(fromPath));
|
|
519
|
-
const fromIdx = childIndex(fromPath);
|
|
520
|
-
if (
|
|
521
|
-
fromParentPath.length === toParentPath.length &&
|
|
522
|
-
fromParentPath.every((v, i) => v === toParentPath[i]) &&
|
|
523
|
-
/** @type {number} */ (fromIdx) < toIndex
|
|
524
|
-
) {
|
|
525
|
-
adjustedIdx = toIndex - 1;
|
|
526
|
-
}
|
|
527
|
-
newState.selection = [...toParentPath, "children", adjustedIdx];
|
|
528
|
-
}
|
|
529
|
-
return newState;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* @param {StudioState} state
|
|
534
|
-
* @param {JxPath} path
|
|
535
|
-
* @param {string} key
|
|
536
|
-
* @param {any} value
|
|
537
|
-
* @returns {StudioState}
|
|
538
|
-
*/
|
|
539
|
-
export function updateProperty(state, path, key, value) {
|
|
540
|
-
return applyMutation(state, (doc) => {
|
|
541
|
-
const node = getNodeAtPath(doc, path);
|
|
542
|
-
if (value === undefined || value === null || value === "") delete node[key];
|
|
543
|
-
else node[key] = value;
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* @param {StudioState} state
|
|
549
|
-
* @param {JxPath} path
|
|
550
|
-
* @param {string} prop
|
|
551
|
-
* @param {any} value
|
|
552
|
-
* @returns {StudioState}
|
|
553
|
-
*/
|
|
554
|
-
export function updateStyle(state, path, prop, value) {
|
|
555
|
-
return applyMutation(state, (doc) => {
|
|
556
|
-
const node = getNodeAtPath(doc, path);
|
|
557
|
-
if (!node.style) node.style = {};
|
|
558
|
-
if (value === undefined || value === "") delete node.style[prop];
|
|
559
|
-
else node.style[prop] = value;
|
|
560
|
-
if (Object.keys(node.style).length === 0) delete node.style;
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* @param {StudioState} state
|
|
566
|
-
* @param {JxPath} path
|
|
567
|
-
* @param {string} attr
|
|
568
|
-
* @param {any} value
|
|
569
|
-
* @returns {StudioState}
|
|
570
|
-
*/
|
|
571
|
-
export function updateAttribute(state, path, attr, value) {
|
|
572
|
-
return applyMutation(state, (doc) => {
|
|
573
|
-
const node = getNodeAtPath(doc, path);
|
|
574
|
-
if (!node.attributes) node.attributes = {};
|
|
575
|
-
if (value === undefined || value === "") delete node.attributes[attr];
|
|
576
|
-
else node.attributes[attr] = value;
|
|
577
|
-
if (Object.keys(node.attributes).length === 0) delete node.attributes;
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* @param {StudioState} state
|
|
583
|
-
* @param {string} name
|
|
584
|
-
* @param {any} def
|
|
585
|
-
* @returns {StudioState}
|
|
586
|
-
*/
|
|
587
|
-
export function addDef(state, name, def) {
|
|
588
|
-
return applyMutation(state, (doc) => {
|
|
589
|
-
if (!doc.state) doc.state = {};
|
|
590
|
-
doc.state[name] = def;
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
/**
|
|
595
|
-
* @param {StudioState} state
|
|
596
|
-
* @param {string} name
|
|
597
|
-
* @returns {StudioState}
|
|
598
|
-
*/
|
|
599
|
-
export function removeDef(state, name) {
|
|
600
|
-
return applyMutation(state, (doc) => {
|
|
601
|
-
if (doc.state) {
|
|
602
|
-
delete doc.state[name];
|
|
603
|
-
if (Object.keys(doc.state).length === 0) delete doc.state;
|
|
604
|
-
}
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* @param {StudioState} state
|
|
610
|
-
* @param {string} name
|
|
611
|
-
* @param {Record<string, any>} updates
|
|
612
|
-
* @returns {StudioState}
|
|
613
|
-
*/
|
|
614
|
-
export function updateDef(state, name, updates) {
|
|
615
|
-
return applyMutation(state, (doc) => {
|
|
616
|
-
if (!doc.state) doc.state = {};
|
|
617
|
-
if (!doc.state[name]) doc.state[name] = {};
|
|
618
|
-
Object.assign(doc.state[name], updates);
|
|
619
|
-
for (const k of Object.keys(doc.state[name])) {
|
|
620
|
-
if (doc.state[name][k] === undefined || doc.state[name][k] === null) {
|
|
621
|
-
delete doc.state[name][k];
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* @param {StudioState} state
|
|
629
|
-
* @param {string} oldName
|
|
630
|
-
* @param {string} newName
|
|
631
|
-
* @returns {StudioState}
|
|
632
|
-
*/
|
|
633
|
-
export function renameDef(state, oldName, newName) {
|
|
634
|
-
return applyMutation(state, (doc) => {
|
|
635
|
-
if (!doc.state || !doc.state[oldName]) return;
|
|
636
|
-
doc.state[newName] = doc.state[oldName];
|
|
637
|
-
delete doc.state[oldName];
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// ─── Media mutations ─────────────────────────────────────────────────────────
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Update a style property inside a media override block (e.g., `@--md`).
|
|
645
|
-
*
|
|
646
|
-
* @param {StudioState} state
|
|
647
|
-
* @param {JxPath} path
|
|
648
|
-
* @param {string} mediaName
|
|
649
|
-
* @param {string} prop
|
|
650
|
-
* @param {any} value
|
|
651
|
-
* @returns {StudioState}
|
|
652
|
-
*/
|
|
653
|
-
export function updateMediaStyle(state, path, mediaName, prop, value) {
|
|
654
|
-
return applyMutation(state, (doc) => {
|
|
655
|
-
const node = getNodeAtPath(doc, path);
|
|
656
|
-
if (!node.style) node.style = {};
|
|
657
|
-
const key = `@${mediaName}`;
|
|
658
|
-
if (!node.style[key]) node.style[key] = {};
|
|
659
|
-
if (value === undefined || value === "") {
|
|
660
|
-
delete node.style[key][prop];
|
|
661
|
-
if (Object.keys(node.style[key]).length === 0) delete node.style[key];
|
|
662
|
-
} else {
|
|
663
|
-
node.style[key][prop] = value;
|
|
664
|
-
}
|
|
665
|
-
if (Object.keys(node.style).length === 0) delete node.style;
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Update a style property inside a nested selector block (e.g., :hover).
|
|
671
|
-
*
|
|
672
|
-
* @param {StudioState} state
|
|
673
|
-
* @param {JxPath} path
|
|
674
|
-
* @param {string} selector
|
|
675
|
-
* @param {string} prop
|
|
676
|
-
* @param {any} value
|
|
677
|
-
* @returns {StudioState}
|
|
678
|
-
*/
|
|
679
|
-
export function updateNestedStyle(state, path, selector, prop, value) {
|
|
680
|
-
return applyMutation(state, (doc) => {
|
|
681
|
-
const node = getNodeAtPath(doc, path);
|
|
682
|
-
if (!node.style) node.style = {};
|
|
683
|
-
if (!node.style[selector]) node.style[selector] = {};
|
|
684
|
-
if (value === undefined || value === "") {
|
|
685
|
-
delete node.style[selector][prop];
|
|
686
|
-
if (Object.keys(node.style[selector]).length === 0) delete node.style[selector];
|
|
687
|
-
} else {
|
|
688
|
-
node.style[selector][prop] = value;
|
|
689
|
-
}
|
|
690
|
-
if (Object.keys(node.style).length === 0) delete node.style;
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Update a style property inside a nested selector within a media block (e.g., `@--md` > `:hover`).
|
|
696
|
-
*
|
|
697
|
-
* @param {StudioState} state
|
|
698
|
-
* @param {JxPath} path
|
|
699
|
-
* @param {string} mediaName
|
|
700
|
-
* @param {string} selector
|
|
701
|
-
* @param {string} prop
|
|
702
|
-
* @param {any} value
|
|
703
|
-
* @returns {StudioState}
|
|
704
|
-
*/
|
|
705
|
-
export function updateMediaNestedStyle(state, path, mediaName, selector, prop, value) {
|
|
706
|
-
return applyMutation(state, (doc) => {
|
|
707
|
-
const node = getNodeAtPath(doc, path);
|
|
708
|
-
if (!node.style) node.style = {};
|
|
709
|
-
const key = `@${mediaName}`;
|
|
710
|
-
if (!node.style[key]) node.style[key] = {};
|
|
711
|
-
if (!node.style[key][selector]) node.style[key][selector] = {};
|
|
712
|
-
if (value === undefined || value === "") {
|
|
713
|
-
delete node.style[key][selector][prop];
|
|
714
|
-
if (Object.keys(node.style[key][selector]).length === 0) delete node.style[key][selector];
|
|
715
|
-
if (Object.keys(node.style[key]).length === 0) delete node.style[key];
|
|
716
|
-
} else {
|
|
717
|
-
node.style[key][selector][prop] = value;
|
|
718
|
-
}
|
|
719
|
-
if (Object.keys(node.style).length === 0) delete node.style;
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Add or update a named media entry at the document root.
|
|
725
|
-
*
|
|
726
|
-
* @param {StudioState} state
|
|
727
|
-
* @param {string} name
|
|
728
|
-
* @param {any} query
|
|
729
|
-
* @returns {StudioState}
|
|
730
|
-
*/
|
|
731
|
-
export function updateMedia(state, name, query) {
|
|
732
|
-
return applyMutation(state, (doc) => {
|
|
733
|
-
if (!doc.$media) doc.$media = {};
|
|
734
|
-
if (query === undefined || query === "") {
|
|
735
|
-
delete doc.$media[name];
|
|
736
|
-
if (Object.keys(doc.$media).length === 0) delete doc.$media;
|
|
737
|
-
} else {
|
|
738
|
-
doc.$media[name] = query;
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
|
|
743
356
|
// ─── Document stack (component navigation) ──────────────────────────────────
|
|
744
357
|
|
|
745
358
|
/**
|
|
@@ -785,72 +398,3 @@ export function popDocument(state) {
|
|
|
785
398
|
ui: { ...state.ui, leftTab: "layers" },
|
|
786
399
|
};
|
|
787
400
|
}
|
|
788
|
-
|
|
789
|
-
// ─── $props mutations ────────────────────────────────────────────────────────
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* Update a $prop on a component instance.
|
|
793
|
-
*
|
|
794
|
-
* @param {StudioState} state
|
|
795
|
-
* @param {JxPath} path
|
|
796
|
-
* @param {string} propName
|
|
797
|
-
* @param {any} value
|
|
798
|
-
* @returns {StudioState}
|
|
799
|
-
*/
|
|
800
|
-
export function updateProp(state, path, propName, value) {
|
|
801
|
-
return applyMutation(state, (doc) => {
|
|
802
|
-
const node = getNodeAtPath(doc, path);
|
|
803
|
-
if (!node.$props) node.$props = {};
|
|
804
|
-
if (value === undefined || value === null || value === "") delete node.$props[propName];
|
|
805
|
-
else node.$props[propName] = value;
|
|
806
|
-
if (Object.keys(node.$props).length === 0) delete node.$props;
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
// ─── $switch case mutations ──────────────────────────────────────────────────
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* @param {StudioState} state
|
|
814
|
-
* @param {JxPath} path
|
|
815
|
-
* @param {string} caseName
|
|
816
|
-
* @param {any} [caseDef]
|
|
817
|
-
* @returns {StudioState}
|
|
818
|
-
*/
|
|
819
|
-
export function addSwitchCase(state, path, caseName, caseDef) {
|
|
820
|
-
return applyMutation(state, (doc) => {
|
|
821
|
-
const node = getNodeAtPath(doc, path);
|
|
822
|
-
if (!node.cases) node.cases = {};
|
|
823
|
-
node.cases[caseName] = caseDef || { tagName: "div", textContent: caseName };
|
|
824
|
-
});
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
/**
|
|
828
|
-
* @param {StudioState} state
|
|
829
|
-
* @param {JxPath} path
|
|
830
|
-
* @param {string} caseName
|
|
831
|
-
* @returns {StudioState}
|
|
832
|
-
*/
|
|
833
|
-
export function removeSwitchCase(state, path, caseName) {
|
|
834
|
-
return applyMutation(state, (doc) => {
|
|
835
|
-
const node = getNodeAtPath(doc, path);
|
|
836
|
-
if (node.cases) {
|
|
837
|
-
delete node.cases[caseName];
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
/**
|
|
843
|
-
* @param {StudioState} state
|
|
844
|
-
* @param {JxPath} path
|
|
845
|
-
* @param {string} oldName
|
|
846
|
-
* @param {string} newName
|
|
847
|
-
* @returns {StudioState}
|
|
848
|
-
*/
|
|
849
|
-
export function renameSwitchCase(state, path, oldName, newName) {
|
|
850
|
-
return applyMutation(state, (doc) => {
|
|
851
|
-
const node = getNodeAtPath(doc, path);
|
|
852
|
-
if (!node.cases || !node.cases[oldName]) return;
|
|
853
|
-
node.cases[newName] = node.cases[oldName];
|
|
854
|
-
delete node.cases[oldName];
|
|
855
|
-
});
|
|
856
|
-
}
|