@jxsuite/studio 0.11.0 → 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 (47) hide show
  1. package/dist/studio.js +4248 -3412
  2. package/dist/studio.js.map +49 -43
  3. package/package.json +5 -3
  4. package/src/canvas/canvas-diff.js +184 -0
  5. package/src/canvas/canvas-helpers.js +10 -14
  6. package/src/canvas/canvas-live-render.js +3 -2
  7. package/src/canvas/canvas-render.js +152 -20
  8. package/src/canvas/canvas-utils.js +21 -23
  9. package/src/editor/component-inline-edit.js +54 -41
  10. package/src/editor/content-inline-edit.js +46 -47
  11. package/src/editor/context-menu.js +63 -39
  12. package/src/editor/convert-to-component.js +11 -14
  13. package/src/editor/insertion-helper.js +8 -10
  14. package/src/editor/shortcuts.js +69 -39
  15. package/src/files/components.js +15 -4
  16. package/src/files/files.js +72 -24
  17. package/src/panels/activity-bar.js +29 -7
  18. package/src/panels/block-action-bar.js +104 -80
  19. package/src/panels/dnd.js +32 -28
  20. package/src/panels/editors.js +7 -14
  21. package/src/panels/elements-panel.js +9 -3
  22. package/src/panels/events-panel.js +44 -39
  23. package/src/panels/git-panel.js +45 -3
  24. package/src/panels/layers-panel.js +16 -11
  25. package/src/panels/left-panel.js +108 -43
  26. package/src/panels/overlays.js +91 -41
  27. package/src/panels/panel-events.js +9 -12
  28. package/src/panels/properties-panel.js +179 -104
  29. package/src/panels/right-panel.js +85 -37
  30. package/src/panels/shared.js +0 -22
  31. package/src/panels/signals-panel.js +125 -54
  32. package/src/panels/statusbar.js +23 -8
  33. package/src/panels/style-inputs.js +4 -2
  34. package/src/panels/style-panel.js +128 -105
  35. package/src/panels/stylebook-panel.js +11 -15
  36. package/src/panels/tab-strip.js +124 -0
  37. package/src/panels/toolbar.js +39 -9
  38. package/src/reactivity.js +28 -0
  39. package/src/settings/content-types-editor.js +56 -7
  40. package/src/settings/defs-editor.js +56 -7
  41. package/src/settings/schema-field-ui.js +60 -25
  42. package/src/state.js +0 -456
  43. package/src/store.js +97 -124
  44. package/src/studio.js +112 -121
  45. package/src/tabs/tab.js +153 -0
  46. package/src/tabs/transact.js +406 -0
  47. package/src/workspace/workspace.js +89 -0
@@ -4,42 +4,83 @@
4
4
  */
5
5
 
6
6
  import { html, render as litRender, nothing } from "lit-html";
7
- import { getState, canvasPanels, pathsEqual, subscribe } from "../store.js";
7
+ import { canvasPanels, pathsEqual } from "../store.js";
8
+ import { effect, effectScope } from "../reactivity.js";
9
+ import { activeTab } from "../workspace/workspace.js";
8
10
  import { view } from "../view.js";
9
- import {
10
- findCanvasElement,
11
- getActivePanel,
12
- overlayBoxDescriptor,
13
- } from "../canvas/canvas-helpers.js";
11
+ import { findCanvasElement, getActivePanel, effectiveZoom } from "../canvas/canvas-helpers.js";
14
12
  import { layoutElements } from "../canvas/canvas-live-render.js";
15
13
 
16
- /** @type {any} */
14
+ /**
15
+ * @typedef {{
16
+ * cls: string;
17
+ * top: string;
18
+ * left: string;
19
+ * width: string;
20
+ * height: string;
21
+ * border?: string;
22
+ * isLayout?: boolean;
23
+ * }} OverlayBox
24
+ */
25
+
26
+ /**
27
+ * @typedef {{
28
+ * getCanvasMode: () => string;
29
+ * isEditing: () => boolean;
30
+ * renderBlockActionBar: () => void;
31
+ * }} OverlaysCtx
32
+ */
33
+
34
+ /** @type {OverlaysCtx | null} */
17
35
  let _ctx = null;
18
36
 
19
- /** @type {(() => void) | null} */
20
- let _unsub = null;
37
+ /** @type {import("@vue/reactivity").EffectScope | null} */
38
+ let _scope = null;
39
+
40
+ let _scheduled = false;
21
41
 
22
42
  /**
23
43
  * Mount the overlays panel.
24
44
  *
25
- * @param {any} ctx — { getCanvasMode, isEditing, renderBlockActionBar }
45
+ * @param {OverlaysCtx} ctx
26
46
  */
27
47
  export function mount(ctx) {
28
48
  _ctx = ctx;
29
- _unsub = subscribe((change) => {
30
- if (change.selection || change.hover || change.mode || change.ui || change.doc) render();
49
+ _scope = effectScope();
50
+ _scope.run(() => {
51
+ effect(() => {
52
+ const tab = activeTab.value;
53
+ if (!tab) return;
54
+ // Track selection, hover, and mode
55
+ void tab.session.selection;
56
+ void tab.session.hover;
57
+ void tab.doc.mode;
58
+ render();
59
+ });
31
60
  });
32
61
  }
33
62
 
34
63
  export function unmount() {
35
- _unsub?.();
36
- _unsub = null;
64
+ _scope?.stop();
65
+ _scope = null;
37
66
  _ctx = null;
38
67
  }
39
68
 
40
69
  export function render() {
41
70
  if (!_ctx) return;
42
- const S = getState();
71
+ if (!_scheduled) {
72
+ _scheduled = true;
73
+ queueMicrotask(_flush);
74
+ }
75
+ }
76
+
77
+ function _flush() {
78
+ _scheduled = false;
79
+ if (!_ctx) return;
80
+ const tab = activeTab.value;
81
+ if (!tab) return;
82
+ const { selection, hover } = tab.session;
83
+ const { stylebookTab } = tab.session.ui;
43
84
  const canvasMode = _ctx.getCanvasMode();
44
85
 
45
86
  if (canvasMode !== "design" && canvasMode !== "edit" && canvasMode !== "settings") {
@@ -55,7 +96,7 @@ export function render() {
55
96
  }
56
97
 
57
98
  if (canvasMode === "settings") {
58
- const enable = S.ui.stylebookTab === "elements";
99
+ const enable = stylebookTab === "elements";
59
100
  for (const p of canvasPanels) {
60
101
  p.overlayClk.style.pointerEvents = enable ? "" : "none";
61
102
  }
@@ -72,33 +113,46 @@ export function render() {
72
113
  }
73
114
 
74
115
  for (const p of canvasPanels) {
75
- /**
76
- * @type {{
77
- * cls: string;
78
- * top: string;
79
- * left: string;
80
- * width: string;
81
- * height: string;
82
- * border?: string;
83
- * }[]}
84
- */
116
+ /** @type {OverlayBox[]} */
85
117
  const boxes = [];
86
118
 
87
- if (S.hover && !pathsEqual(S.hover, S.selection)) {
88
- const el = findCanvasElement(S.hover, p.canvas);
119
+ // Batch layout reads: read viewport geometry once per panel
120
+ const vpRect = p.viewport.getBoundingClientRect();
121
+ const scrollTop = p.viewport.scrollTop;
122
+ const scrollLeft = p.viewport.scrollLeft;
123
+ const scale = effectiveZoom();
124
+
125
+ if (hover && !pathsEqual(hover, selection)) {
126
+ const el = findCanvasElement(hover, p.canvas);
89
127
  if (el) {
90
- const desc = overlayBoxDescriptor(el, "hover", p);
91
- if (layoutElements.has(el)) /** @type {any} */ (desc).isLayout = true;
128
+ const elRect = el.getBoundingClientRect();
129
+ /** @type {OverlayBox} */
130
+ const desc = {
131
+ cls: "overlay-box overlay-hover",
132
+ top: `${(elRect.top - vpRect.top + scrollTop) / scale}px`,
133
+ left: `${(elRect.left - vpRect.left + scrollLeft) / scale}px`,
134
+ width: `${elRect.width / scale}px`,
135
+ height: `${elRect.height / scale}px`,
136
+ };
137
+ if (layoutElements.has(el)) desc.isLayout = true;
92
138
  boxes.push(desc);
93
139
  }
94
140
  }
95
141
 
96
- if (S.selection && p === getActivePanel()) {
97
- const el = findCanvasElement(S.selection, p.canvas);
142
+ if (selection && p === getActivePanel()) {
143
+ const el = findCanvasElement(selection, p.canvas);
98
144
  if (el) {
99
- const desc = overlayBoxDescriptor(el, "selection", p);
100
- if (view.componentInlineEdit || _ctx.isEditing()) /** @type {any} */ (desc).border = "none";
101
- if (layoutElements.has(el)) /** @type {any} */ (desc).isLayout = true;
145
+ const elRect = el.getBoundingClientRect();
146
+ /** @type {OverlayBox} */
147
+ const desc = {
148
+ cls: "overlay-box overlay-selection",
149
+ top: `${(elRect.top - vpRect.top + scrollTop) / scale}px`,
150
+ left: `${(elRect.left - vpRect.left + scrollLeft) / scale}px`,
151
+ width: `${elRect.width / scale}px`,
152
+ height: `${elRect.height / scale}px`,
153
+ };
154
+ if (view.componentInlineEdit || _ctx.isEditing()) desc.border = "none";
155
+ if (layoutElements.has(el)) desc.isLayout = true;
102
156
  boxes.push(desc);
103
157
  }
104
158
  }
@@ -109,16 +163,12 @@ export function render() {
109
163
  ${boxes.map(
110
164
  (b) => html`
111
165
  <div
112
- class="${b.cls}${/** @type {any} */ (b).isLayout ? " overlay-layout" : ""}"
166
+ class="${b.cls}${b.isLayout ? " overlay-layout" : ""}"
113
167
  style="top:${b.top};left:${b.left};width:${b.width};height:${b.height}${b.border
114
168
  ? `;border:${b.border}`
115
169
  : ""}"
116
170
  >
117
- ${
118
- /** @type {any} */ (b).isLayout
119
- ? html`<span class="overlay-layout-badge">Layout</span>`
120
- : nothing
121
- }
171
+ ${b.isLayout ? html`<span class="overlay-layout-badge">Layout</span>` : nothing}
122
172
  </div>
123
173
  `,
124
174
  )}
@@ -5,18 +5,16 @@
5
5
  */
6
6
 
7
7
  import {
8
- update,
9
8
  updateUi,
10
- selectNode,
11
9
  hoverNode,
12
10
  elToPath,
13
11
  pathsEqual,
14
- insertNode,
15
12
  parentElementPath,
16
13
  childIndex,
17
14
  getNodeAtPath,
18
15
  renderOnly,
19
16
  } from "../store.js";
17
+ import { activeTab } from "../workspace/workspace.js";
20
18
  import { view } from "../view.js";
21
19
  import { stopEditing, isEditing, isEditableBlock } from "../editor/inline-edit.js";
22
20
  import { showContextMenu } from "../editor/context-menu.js";
@@ -91,7 +89,7 @@ export function registerPanelEvents(panel) {
91
89
  // Layout element clicked — show layout info instead of selecting in page doc
92
90
  if (layoutElements.has(el)) {
93
91
  view.layoutSelection = { el, layoutPath: activeLayoutPath };
94
- update(selectNode(S, null));
92
+ activeTab.value.session.selection = null;
95
93
  renderOnly("rightPanel");
96
94
  return;
97
95
  }
@@ -117,16 +115,18 @@ export function registerPanelEvents(panel) {
117
115
 
118
116
  if (canvasMode === "design" && S.mode !== "content") {
119
117
  updateUi("pendingInlineEdit", { path, mediaName });
120
- update(selectNode(withMedia, path));
118
+ activeTab.value.session.ui.activeMedia = newMedia;
119
+ activeTab.value.session.selection = path;
121
120
  return;
122
121
  }
123
122
 
124
- update(selectNode(withMedia, path));
123
+ activeTab.value.session.ui.activeMedia = newMedia;
124
+ activeTab.value.session.selection = path;
125
125
  return;
126
126
  }
127
127
  }
128
128
  }
129
- update(selectNode(S, null));
129
+ activeTab.value.session.selection = null;
130
130
  },
131
131
  opts,
132
132
  );
@@ -161,8 +161,8 @@ export function registerPanelEvents(panel) {
161
161
  const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
162
162
  if (isEditableBlock(resolvedEl)) {
163
163
  const newMedia = mediaName === "base" ? null : (mediaName ?? null);
164
- const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
165
- update(selectNode(withMedia, path));
164
+ activeTab.value.session.ui.activeMedia = newMedia;
165
+ activeTab.value.session.selection = path;
166
166
  _ctx.enterInlineEdit(resolvedEl, path);
167
167
  return;
168
168
  }
@@ -253,13 +253,10 @@ export function registerPanelEvents(panel) {
253
253
 
254
254
  insertionHelper.mount({
255
255
  getState: _ctx.getState,
256
- update,
257
256
  getCanvasMode: _ctx.getCanvasMode,
258
257
  withPanelPointerEvents,
259
258
  effectiveZoom: effectiveZoom,
260
259
  defaultDef,
261
- insertNode,
262
- selectNode,
263
260
  parentElementPath,
264
261
  childIndex,
265
262
  getNodeAtPath,