@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.
Files changed (53) hide show
  1. package/README.md +82 -0
  2. package/dist/studio.js +5114 -3424
  3. package/dist/studio.js.map +65 -49
  4. package/package.json +6 -3
  5. package/src/browse/browse.js +27 -3
  6. package/src/canvas/canvas-diff.js +184 -0
  7. package/src/canvas/canvas-helpers.js +10 -14
  8. package/src/canvas/canvas-live-render.js +136 -17
  9. package/src/canvas/canvas-render.js +154 -21
  10. package/src/canvas/canvas-utils.js +21 -23
  11. package/src/editor/component-inline-edit.js +54 -41
  12. package/src/editor/content-inline-edit.js +46 -47
  13. package/src/editor/context-menu.js +63 -39
  14. package/src/editor/convert-to-component.js +11 -14
  15. package/src/editor/insertion-helper.js +8 -10
  16. package/src/editor/shortcuts.js +69 -39
  17. package/src/files/components.js +15 -4
  18. package/src/files/files.js +72 -24
  19. package/src/panels/activity-bar.js +29 -7
  20. package/src/panels/block-action-bar.js +104 -80
  21. package/src/panels/canvas-dnd.js +132 -50
  22. package/src/panels/dnd.js +32 -28
  23. package/src/panels/editors.js +7 -14
  24. package/src/panels/elements-panel.js +9 -3
  25. package/src/panels/events-panel.js +44 -39
  26. package/src/panels/git-panel.js +45 -3
  27. package/src/panels/layers-panel.js +16 -11
  28. package/src/panels/left-panel.js +108 -43
  29. package/src/panels/overlays.js +97 -35
  30. package/src/panels/panel-events.js +18 -11
  31. package/src/panels/properties-panel.js +467 -98
  32. package/src/panels/right-panel.js +85 -37
  33. package/src/panels/shared.js +0 -22
  34. package/src/panels/signals-panel.js +125 -54
  35. package/src/panels/statusbar.js +23 -8
  36. package/src/panels/style-inputs.js +4 -2
  37. package/src/panels/style-panel.js +128 -105
  38. package/src/panels/stylebook-panel.js +42 -17
  39. package/src/panels/tab-strip.js +124 -0
  40. package/src/panels/toolbar.js +39 -9
  41. package/src/reactivity.js +28 -0
  42. package/src/settings/content-types-editor.js +78 -8
  43. package/src/settings/defs-editor.js +56 -7
  44. package/src/settings/schema-field-ui.js +99 -11
  45. package/src/site-context.js +105 -0
  46. package/src/state.js +0 -456
  47. package/src/store.js +105 -124
  48. package/src/studio.js +112 -121
  49. package/src/tabs/tab.js +153 -0
  50. package/src/tabs/transact.js +406 -0
  51. package/src/ui/spectrum.js +2 -0
  52. package/src/view.js +3 -0
  53. package/src/workspace/workspace.js +89 -0
@@ -5,7 +5,14 @@
5
5
  */
6
6
 
7
7
  import { html, nothing } from "lit-html";
8
- import { addDef, removeDef, updateDef, renameDef, update } from "../store.js";
8
+ import { activeTab } from "../workspace/workspace.js";
9
+ import {
10
+ transactDoc,
11
+ mutateUpdateDef,
12
+ mutateAddDef,
13
+ mutateRemoveDef,
14
+ mutateRenameDef,
15
+ } from "../tabs/transact.js";
9
16
  import { renderFieldRow } from "../ui/field-row.js";
10
17
  import { fetchPluginSchema, pluginSchemaCache } from "../services/code-services.js";
11
18
 
@@ -266,7 +273,7 @@ export function renderSignalsTemplate(S, ctx) {
266
273
  class="signal-del"
267
274
  @click=${(/** @type {any} */ e) => {
268
275
  e.stopPropagation();
269
- update(removeDef(S, name));
276
+ transactDoc(activeTab.value, (t) => mutateRemoveDef(t, name));
270
277
  }}
271
278
  >
272
279
  <sp-icon-delete slot="icon"></sp-icon-delete>
@@ -304,7 +311,7 @@ export function renderSignalsTemplate(S, ctx) {
304
311
  while (S.document.state && S.document.state[n]) {
305
312
  n = nameBase + i++;
306
313
  }
307
- update(addDef(S, n, structuredClone(template)));
314
+ transactDoc(activeTab.value, (t) => mutateAddDef(t, n, structuredClone(template)));
308
315
  expandedSignal = n;
309
316
  ctx.renderLeftPanel();
310
317
  }}
@@ -396,7 +403,7 @@ function renderSignalEditorTemplate(
396
403
  const nameField = signalFieldRow("Name", name, (/** @type {any} */ v) => {
397
404
  if (v && v !== name && !(S.document.state && S.document.state[v])) {
398
405
  expandedSignal = v;
399
- update(renameDef(S, name, v));
406
+ transactDoc(activeTab.value, (t) => mutateRenameDef(t, name, v));
400
407
  }
401
408
  });
402
409
 
@@ -414,7 +421,9 @@ function renderSignalEditorTemplate(
414
421
  const cemFields = isCustomElementDoc(S)
415
422
  ? html`
416
423
  ${signalFieldRow("Attribute", def.attribute || "", (/** @type {any} */ v) =>
417
- update(updateDef(S, name, { attribute: v || undefined })),
424
+ transactDoc(activeTab.value, (t) =>
425
+ mutateUpdateDef(t, name, { attribute: v || undefined }),
426
+ ),
418
427
  )}
419
428
  ${renderFieldRow({
420
429
  prop: "reflects",
@@ -425,14 +434,19 @@ function renderSignalEditorTemplate(
425
434
  class="field-check"
426
435
  ?checked=${!!def.reflects}
427
436
  @change=${(/** @type {any} */ e) =>
428
- update(updateDef(S, name, { reflects: e.target.checked || undefined }))}
437
+ transactDoc(activeTab.value, (t) =>
438
+ mutateUpdateDef(t, name, { reflects: e.target.checked || undefined }),
439
+ )}
429
440
  ></sp-checkbox>
430
441
  `,
431
442
  })}
432
443
  ${signalFieldRow(
433
444
  "Deprecated",
434
445
  typeof def.deprecated === "string" ? def.deprecated : "",
435
- (/** @type {any} */ v) => update(updateDef(S, name, { deprecated: v || undefined })),
446
+ (/** @type {any} */ v) =>
447
+ transactDoc(activeTab.value, (t) =>
448
+ mutateUpdateDef(t, name, { deprecated: v || undefined }),
449
+ ),
436
450
  )}
437
451
  `
438
452
  : nothing;
@@ -442,8 +456,20 @@ function renderSignalEditorTemplate(
442
456
  "Type",
443
457
  ["string", "integer", "number", "boolean", "array", "object"],
444
458
  def.type || "string",
445
- (/** @type {any} */ v) => update(updateDef(S, name, { type: v })),
459
+ (/** @type {any} */ v) =>
460
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { type: v })),
446
461
  )}
462
+ ${def.type === "string" || !def.type
463
+ ? pickerRow(
464
+ "Format",
465
+ ["", "image", "date", "color"],
466
+ def.format || "",
467
+ (/** @type {any} */ v) =>
468
+ transactDoc(activeTab.value, (t) =>
469
+ mutateUpdateDef(t, name, { format: v || undefined }),
470
+ ),
471
+ )
472
+ : nothing}
447
473
  ${signalFieldRow("Default", defaultVal, (/** @type {any} */ v) => {
448
474
  let parsed = v;
449
475
  if (def.type === "integer") parsed = parseInt(v, 10) || 0;
@@ -456,10 +482,12 @@ function renderSignalEditorTemplate(
456
482
  parsed = v;
457
483
  }
458
484
  }
459
- update(updateDef(S, name, { default: parsed }));
485
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { default: parsed }));
460
486
  })}
461
487
  ${signalFieldRow("Description", def.description || "", (/** @type {any} */ v) =>
462
- update(updateDef(S, name, { description: v || undefined })),
488
+ transactDoc(activeTab.value, (t) =>
489
+ mutateUpdateDef(t, name, { description: v || undefined }),
490
+ ),
463
491
  )}
464
492
  ${cemFields}
465
493
  `;
@@ -482,7 +510,9 @@ function renderSignalEditorTemplate(
482
510
  const expr = e.target.value;
483
511
  const depMatches = expr.match(/\$[a-zA-Z_]\w*/g) || [];
484
512
  const deps = [...new Set(depMatches)].map((d) => `#/state/${d}`);
485
- update(updateDef(S, name, { $compute: expr, $deps: deps }));
513
+ transactDoc(activeTab.value, (t) =>
514
+ mutateUpdateDef(t, name, { $compute: expr, $deps: deps }),
515
+ );
486
516
  }, 500);
487
517
  }}
488
518
  ></textarea>
@@ -526,16 +556,17 @@ function renderDataSourceFields(
526
556
  if (proto === "Request") {
527
557
  return html`
528
558
  ${signalFieldRow("URL", def.url || "", (/** @type {any} */ v) =>
529
- update(updateDef(S, name, { url: v })),
559
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { url: v })),
530
560
  )}
531
561
  ${pickerRow(
532
562
  "Method",
533
563
  ["GET", "POST", "PUT", "DELETE", "PATCH"],
534
564
  def.method || "GET",
535
- (/** @type {any} */ v) => update(updateDef(S, name, { method: v })),
565
+ (/** @type {any} */ v) =>
566
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { method: v })),
536
567
  )}
537
568
  ${pickerRow("Timing", ["client", "server"], def.timing || "client", (/** @type {any} */ v) =>
538
- update(updateDef(S, name, { timing: v })),
569
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { timing: v })),
539
570
  )}
540
571
  `;
541
572
  }
@@ -548,13 +579,13 @@ function renderDataSourceFields(
548
579
  : "";
549
580
  return html`
550
581
  ${signalFieldRow("Key", def.key || "", (/** @type {any} */ v) =>
551
- update(updateDef(S, name, { key: v })),
582
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { key: v })),
552
583
  )}
553
584
  ${textareaRow("Default", defaultStr, (/** @type {any} */ v) => {
554
585
  try {
555
- update(updateDef(S, name, { default: JSON.parse(v) }));
586
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { default: JSON.parse(v) }));
556
587
  } catch {
557
- update(updateDef(S, name, { default: v }));
588
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { default: v }));
558
589
  }
559
590
  })}
560
591
  `;
@@ -562,23 +593,25 @@ function renderDataSourceFields(
562
593
  if (proto === "IndexedDB") {
563
594
  return html`
564
595
  ${signalFieldRow("Database", def.database || "", (/** @type {any} */ v) =>
565
- update(updateDef(S, name, { database: v })),
596
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { database: v })),
566
597
  )}
567
598
  ${signalFieldRow("Store", def.store || "", (/** @type {any} */ v) =>
568
- update(updateDef(S, name, { store: v })),
599
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { store: v })),
569
600
  )}
570
601
  ${signalFieldRow("Version", String(def.version || 1), (/** @type {any} */ v) =>
571
- update(updateDef(S, name, { version: parseInt(v, 10) || 1 })),
602
+ transactDoc(activeTab.value, (t) =>
603
+ mutateUpdateDef(t, name, { version: parseInt(v, 10) || 1 }),
604
+ ),
572
605
  )}
573
606
  `;
574
607
  }
575
608
  if (proto === "Cookie") {
576
609
  return html`
577
610
  ${signalFieldRow("Cookie", def.name || "", (/** @type {any} */ v) =>
578
- update(updateDef(S, name, { name: v })),
611
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { name: v })),
579
612
  )}
580
613
  ${signalFieldRow("Default", def.default || "", (/** @type {any} */ v) =>
581
- update(updateDef(S, name, { default: v })),
614
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { default: v })),
582
615
  )}
583
616
  `;
584
617
  }
@@ -593,7 +626,9 @@ function renderDataSourceFields(
593
626
  : "";
594
627
  return textareaRow(fieldLabel, defaultStr, (/** @type {any} */ v) => {
595
628
  try {
596
- update(updateDef(S, name, { [fieldName]: JSON.parse(v) }));
629
+ transactDoc(activeTab.value, (t) =>
630
+ mutateUpdateDef(t, name, { [fieldName]: JSON.parse(v) }),
631
+ );
597
632
  } catch {}
598
633
  });
599
634
  }
@@ -612,16 +647,19 @@ function renderFunctionFields(
612
647
  const srcFields = def.$src
613
648
  ? html`
614
649
  ${signalFieldRow("Source", def.$src || "", (/** @type {any} */ v) =>
615
- update(updateDef(S, name, { $src: v || undefined })),
650
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $src: v || undefined })),
616
651
  )}
617
652
  ${signalFieldRow("Export", def.$export || "", (/** @type {any} */ v) =>
618
- update(updateDef(S, name, { $export: v || undefined })),
653
+ transactDoc(activeTab.value, (t) =>
654
+ mutateUpdateDef(t, name, { $export: v || undefined }),
655
+ ),
619
656
  )}
620
657
  `
621
658
  : textareaRow(
622
659
  "Body",
623
660
  def.body || "",
624
- (/** @type {any} */ v) => update(updateDef(S, name, { body: v })),
661
+ (/** @type {any} */ v) =>
662
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { body: v })),
625
663
  { minHeight: "60px", mono: true },
626
664
  );
627
665
 
@@ -643,7 +681,9 @@ function renderFunctionFields(
643
681
  `
644
682
  : nothing}
645
683
  ${signalFieldRow("Description", def.description || "", (/** @type {any} */ v) =>
646
- update(updateDef(S, name, { description: v || undefined })),
684
+ transactDoc(activeTab.value, (t) =>
685
+ mutateUpdateDef(t, name, { description: v || undefined }),
686
+ ),
647
687
  )}
648
688
  `;
649
689
  }
@@ -677,8 +717,8 @@ function renderParameterEditorTemplate(
677
717
  <span
678
718
  style="cursor:pointer;opacity:0.5;margin-left:2px"
679
719
  @click=${() => {
680
- update(
681
- updateDef(S, name, {
720
+ transactDoc(activeTab.value, (t) =>
721
+ mutateUpdateDef(t, name, {
682
722
  parameters: params.filter(
683
723
  (/** @type {any} */ _, /** @type {any} */ j) => j !== i,
684
724
  ).length
@@ -698,8 +738,10 @@ function renderParameterEditorTemplate(
698
738
  placeholder="+"
699
739
  @keydown=${(/** @type {any} */ e) => {
700
740
  if (e.key === "Enter" && e.target.value.trim()) {
701
- update(
702
- updateDef(S, name, { parameters: [...params, { name: e.target.value.trim() }] }),
741
+ transactDoc(activeTab.value, (t) =>
742
+ mutateUpdateDef(t, name, {
743
+ parameters: [...params, { name: e.target.value.trim() }],
744
+ }),
703
745
  );
704
746
  }
705
747
  }}
@@ -735,7 +777,9 @@ function renderParameterEditorTemplate(
735
777
  @change=${(/** @type {any} */ e) => {
736
778
  const next = [...params];
737
779
  next[i] = { ...next[i], name: e.target.value };
738
- update(updateDef(S, name, { parameters: next }));
780
+ transactDoc(activeTab.value, (t) =>
781
+ mutateUpdateDef(t, name, { parameters: next }),
782
+ );
739
783
  }}
740
784
  />
741
785
  <input
@@ -749,7 +793,9 @@ function renderParameterEditorTemplate(
749
793
  ...next[i],
750
794
  type: e.target.value ? { text: e.target.value } : undefined,
751
795
  };
752
- update(updateDef(S, name, { parameters: next }));
796
+ transactDoc(activeTab.value, (t) =>
797
+ mutateUpdateDef(t, name, { parameters: next }),
798
+ );
753
799
  }}
754
800
  />
755
801
  <input
@@ -760,7 +806,9 @@ function renderParameterEditorTemplate(
760
806
  @change=${(/** @type {any} */ e) => {
761
807
  const next = [...params];
762
808
  next[i] = { ...next[i], description: e.target.value || undefined };
763
- update(updateDef(S, name, { parameters: next }));
809
+ transactDoc(activeTab.value, (t) =>
810
+ mutateUpdateDef(t, name, { parameters: next }),
811
+ );
764
812
  }}
765
813
  />
766
814
  <input
@@ -770,7 +818,9 @@ function renderParameterEditorTemplate(
770
818
  @change=${(/** @type {any} */ e) => {
771
819
  const next = [...params];
772
820
  next[i] = { ...next[i], optional: e.target.checked || undefined };
773
- update(updateDef(S, name, { parameters: next }));
821
+ transactDoc(activeTab.value, (t) =>
822
+ mutateUpdateDef(t, name, { parameters: next }),
823
+ );
774
824
  }}
775
825
  />
776
826
  <span
@@ -779,7 +829,9 @@ function renderParameterEditorTemplate(
779
829
  const next = params.filter(
780
830
  (/** @type {any} */ _, /** @type {any} */ j) => j !== i,
781
831
  );
782
- update(updateDef(S, name, { parameters: next.length ? next : undefined }));
832
+ transactDoc(activeTab.value, (t) =>
833
+ mutateUpdateDef(t, name, { parameters: next.length ? next : undefined }),
834
+ );
783
835
  }}
784
836
  >×</span
785
837
  >
@@ -788,7 +840,10 @@ function renderParameterEditorTemplate(
788
840
  )}
789
841
  <button
790
842
  class="kv-add"
791
- @click=${() => update(updateDef(S, name, { parameters: [...params, { name: "" }] }))}
843
+ @click=${() =>
844
+ transactDoc(activeTab.value, (t) =>
845
+ mutateUpdateDef(t, name, { parameters: [...params, { name: "" }] }),
846
+ )}
792
847
  >
793
848
  + Add parameter
794
849
  </button>
@@ -831,7 +886,7 @@ function renderEmitsEditorTemplate(
831
886
  @change=${(/** @type {any} */ e) => {
832
887
  const next = [...emits];
833
888
  next[i] = { ...next[i], name: e.target.value };
834
- update(updateDef(S, name, { emits: next }));
889
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
835
890
  }}
836
891
  />
837
892
  <input
@@ -842,7 +897,7 @@ function renderEmitsEditorTemplate(
842
897
  @change=${(/** @type {any} */ e) => {
843
898
  const next = [...emits];
844
899
  next[i] = { ...next[i], type: e.target.value ? { text: e.target.value } : undefined };
845
- update(updateDef(S, name, { emits: next }));
900
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
846
901
  }}
847
902
  />
848
903
  <input
@@ -853,14 +908,14 @@ function renderEmitsEditorTemplate(
853
908
  @change=${(/** @type {any} */ e) => {
854
909
  const next = [...emits];
855
910
  next[i] = { ...next[i], description: e.target.value || undefined };
856
- update(updateDef(S, name, { emits: next }));
911
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
857
912
  }}
858
913
  />
859
914
  <span
860
915
  style="cursor:pointer;opacity:0.5"
861
916
  @click=${() => {
862
- update(
863
- updateDef(S, name, {
917
+ transactDoc(activeTab.value, (t) =>
918
+ mutateUpdateDef(t, name, {
864
919
  emits: emits.filter((/** @type {any} */ _, /** @type {any} */ j) => j !== i)
865
920
  .length
866
921
  ? emits.filter((/** @type {any} */ _, /** @type {any} */ j) => j !== i)
@@ -875,7 +930,10 @@ function renderEmitsEditorTemplate(
875
930
  )}
876
931
  <button
877
932
  class="kv-add"
878
- @click=${() => update(updateDef(S, name, { emits: [...emits, { name: "" }] }))}
933
+ @click=${() =>
934
+ transactDoc(activeTab.value, (t) =>
935
+ mutateUpdateDef(t, name, { emits: [...emits, { name: "" }] }),
936
+ )}
879
937
  >
880
938
  + Add event
881
939
  </button>
@@ -892,7 +950,7 @@ export function renderSchemaFieldsTemplate(
892
950
  /** @type {any} */ schema,
893
951
  /** @type {any} */ def,
894
952
  /** @type {any} */ name,
895
- /** @type {any} */ S,
953
+ /** @type {any} */ _S,
896
954
  ) {
897
955
  if (!schema?.properties) return nothing;
898
956
 
@@ -915,8 +973,8 @@ export function renderSchemaFieldsTemplate(
915
973
  ? String(ps.default)
916
974
  : "__none__"}
917
975
  @change=${(/** @type {any} */ e) =>
918
- update(
919
- updateDef(S, name, {
976
+ transactDoc(activeTab.value, (t) =>
977
+ mutateUpdateDef(t, name, {
920
978
  [prop]: e.target.value === "__none__" ? undefined : e.target.value,
921
979
  }),
922
980
  )}
@@ -931,7 +989,9 @@ export function renderSchemaFieldsTemplate(
931
989
  control = html`<sp-checkbox
932
990
  ?checked=${currentValue ?? ps.default ?? false}
933
991
  @change=${(/** @type {any} */ e) =>
934
- update(updateDef(S, name, { [prop]: e.target.checked }))}
992
+ transactDoc(activeTab.value, (t) =>
993
+ mutateUpdateDef(t, name, { [prop]: e.target.checked }),
994
+ )}
935
995
  ></sp-checkbox>`;
936
996
  } else if (ps.type === "integer" || ps.type === "number") {
937
997
  /** @type {any} */
@@ -948,7 +1008,9 @@ export function renderSchemaFieldsTemplate(
948
1008
  debounce = setTimeout(() => {
949
1009
  const parsed =
950
1010
  ps.type === "integer" ? parseInt(e.target.value, 10) : parseFloat(e.target.value);
951
- update(updateDef(S, name, { [prop]: isNaN(parsed) ? undefined : parsed }));
1011
+ transactDoc(activeTab.value, (t) =>
1012
+ mutateUpdateDef(t, name, { [prop]: isNaN(parsed) ? undefined : parsed }),
1013
+ );
952
1014
  }, 400);
953
1015
  }}
954
1016
  ></sp-number-field>`;
@@ -984,7 +1046,9 @@ export function renderSchemaFieldsTemplate(
984
1046
  clearTimeout(debounce);
985
1047
  debounce = setTimeout(() => {
986
1048
  try {
987
- update(updateDef(S, name, { [prop]: JSON.parse(e.target.value) }));
1049
+ transactDoc(activeTab.value, (t) =>
1050
+ mutateUpdateDef(t, name, { [prop]: JSON.parse(e.target.value) }),
1051
+ );
988
1052
  } catch {}
989
1053
  }, 500);
990
1054
  }}
@@ -1004,7 +1068,9 @@ export function renderSchemaFieldsTemplate(
1004
1068
  clearTimeout(debounce);
1005
1069
  debounce = setTimeout(() => {
1006
1070
  try {
1007
- update(updateDef(S, name, { [prop]: JSON.parse(e.target.value) }));
1071
+ transactDoc(activeTab.value, (t) =>
1072
+ mutateUpdateDef(t, name, { [prop]: JSON.parse(e.target.value) }),
1073
+ );
1008
1074
  } catch {}
1009
1075
  }, 500);
1010
1076
  }}
@@ -1021,7 +1087,10 @@ export function renderSchemaFieldsTemplate(
1021
1087
  @input=${(/** @type {any} */ e) => {
1022
1088
  clearTimeout(debounce);
1023
1089
  debounce = setTimeout(
1024
- () => update(updateDef(S, name, { [prop]: e.target.value || undefined })),
1090
+ () =>
1091
+ transactDoc(activeTab.value, (t) =>
1092
+ mutateUpdateDef(t, name, { [prop]: e.target.value || undefined }),
1093
+ ),
1025
1094
  400,
1026
1095
  );
1027
1096
  }}
@@ -1077,16 +1146,18 @@ export function renderExternalPrototypeEditorTemplate(
1077
1146
 
1078
1147
  return html`
1079
1148
  ${signalFieldRow("Source", def.$src || "", (/** @type {any} */ v) => {
1080
- update(updateDef(S, name, { $src: v || undefined }));
1149
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $src: v || undefined }));
1081
1150
  pluginSchemaCache.delete(`${v}::${def.$prototype}`);
1082
1151
  })}
1083
1152
  ${signalFieldRow("Prototype", def.$prototype || "", (/** @type {any} */ v) => {
1084
- update(updateDef(S, name, { $prototype: v || undefined }));
1153
+ transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { $prototype: v || undefined }));
1085
1154
  pluginSchemaCache.delete(`${def.$src}::${v}`);
1086
1155
  })}
1087
1156
  ${def.$export
1088
1157
  ? signalFieldRow("Export", def.$export || "", (/** @type {any} */ v) =>
1089
- update(updateDef(S, name, { $export: v || undefined })),
1158
+ transactDoc(activeTab.value, (t) =>
1159
+ mutateUpdateDef(t, name, { $export: v || undefined }),
1160
+ ),
1090
1161
  )
1091
1162
  : nothing}
1092
1163
  ${schemaContent}
@@ -1,6 +1,8 @@
1
1
  /** Statusbar — status message display for Jx Studio */
2
2
 
3
- import { statusbarEl, getNodeAtPath, nodeLabel, getState, subscribe } from "../store.js";
3
+ import { statusbarEl, getNodeAtPath, nodeLabel } from "../store.js";
4
+ import { effect, effectScope } from "../reactivity.js";
5
+ import { activeTab } from "../workspace/workspace.js";
4
6
 
5
7
  // ─── Module state ────────────────────────────────────────────────────────────
6
8
 
@@ -9,8 +11,8 @@ let statusMsg = "";
9
11
  let statusTimeout;
10
12
  /** @type {(() => void) | null} */
11
13
  let _rerender = null;
12
- /** @type {(() => void) | null} */
13
- let _unsub = null;
14
+ /** @type {import("@vue/reactivity").EffectScope | null} */
15
+ let _scope = null;
14
16
 
15
17
  /**
16
18
  * Register the callback used to re-render the statusbar. Called once from studio.js during init.
@@ -21,16 +23,29 @@ export function setStatusbarRenderer(fn) {
21
23
  _rerender = fn;
22
24
  }
23
25
 
24
- /** Subscribe the statusbar to state changes. */
26
+ /** Subscribe the statusbar to state changes via reactive effect. */
25
27
  export function mountStatusbar() {
26
- _unsub = subscribe((change) => {
27
- if (change.selection || change.mode || change.doc) renderStatusbar(getState());
28
+ _scope = effectScope();
29
+ _scope.run(() => {
30
+ effect(() => {
31
+ const tab = activeTab.value;
32
+ if (!tab) return;
33
+ // Track relevant reactive properties
34
+ void tab.doc.document;
35
+ void tab.doc.mode;
36
+ void tab.session.selection;
37
+ renderStatusbar({
38
+ mode: tab.doc.mode,
39
+ document: tab.doc.document,
40
+ selection: tab.session.selection,
41
+ });
42
+ });
28
43
  });
29
44
  }
30
45
 
31
46
  export function unmountStatusbar() {
32
- _unsub?.();
33
- _unsub = null;
47
+ _scope?.stop();
48
+ _scope = null;
34
49
  }
35
50
 
36
51
  // ─── Statusbar ───────────────────────────────────────────────────────────────
@@ -2,7 +2,9 @@
2
2
 
3
3
  import { html } from "lit-html";
4
4
  import { live } from "lit-html/directives/live.js";
5
- import { getState, update, updateStyle, debouncedStyleCommit } from "../store.js";
5
+ import { getState, debouncedStyleCommit } from "../store.js";
6
+ import { activeTab } from "../workspace/workspace.js";
7
+ import { transactDoc, mutateUpdateStyle } from "../tabs/transact.js";
6
8
  import { widgetForType as _widgetForType } from "../ui/widgets.js";
7
9
  import { kebabToLabel, friendlyNameToVar, varDisplayName } from "../utils/studio-utils.js";
8
10
  import {
@@ -54,7 +56,7 @@ function handleFontPresetSelection(preset, onChange) {
54
56
  const S = getState();
55
57
  const varName = friendlyNameToVar(preset.title, "--font-");
56
58
  if (!S.document?.style?.[varName]) {
57
- update(updateStyle(S, [], varName, preset.value));
59
+ transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, preset.value));
58
60
  }
59
61
  onChange(`var(${varName})`);
60
62
  }