@jxsuite/studio 0.28.2 → 0.28.4

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 (104) hide show
  1. package/dist/studio.js +86144 -83971
  2. package/dist/studio.js.map +229 -219
  3. package/package.json +5 -5
  4. package/src/browse/browse-modal.ts +9 -3
  5. package/src/browse/browse.ts +139 -82
  6. package/src/canvas/canvas-diff.ts +35 -18
  7. package/src/canvas/canvas-helpers.ts +55 -21
  8. package/src/canvas/canvas-live-render.ts +140 -89
  9. package/src/canvas/canvas-render.ts +127 -82
  10. package/src/canvas/canvas-utils.ts +68 -42
  11. package/src/canvas/nested-site-style.ts +15 -7
  12. package/src/editor/component-inline-edit.ts +90 -51
  13. package/src/editor/content-inline-edit.ts +75 -72
  14. package/src/editor/context-menu.ts +125 -71
  15. package/src/editor/convert-targets.ts +17 -13
  16. package/src/editor/convert-to-component.ts +51 -28
  17. package/src/editor/convert-to-repeater.ts +44 -19
  18. package/src/editor/inline-edit.ts +107 -52
  19. package/src/editor/inline-format.ts +128 -60
  20. package/src/editor/insertion-helper.ts +26 -14
  21. package/src/editor/shortcuts.ts +89 -43
  22. package/src/editor/slash-menu.ts +41 -26
  23. package/src/files/components.ts +9 -7
  24. package/src/files/file-ops.ts +53 -33
  25. package/src/files/files.ts +246 -143
  26. package/src/format/constraints.ts +25 -15
  27. package/src/format/format-host.ts +33 -10
  28. package/src/github/github-auth.ts +24 -14
  29. package/src/github/github-publish.ts +20 -14
  30. package/src/new-project/new-project-modal.ts +27 -18
  31. package/src/panels/activity-bar.ts +27 -26
  32. package/src/panels/ai-panel.ts +100 -44
  33. package/src/panels/block-action-bar.ts +84 -52
  34. package/src/panels/canvas-dnd.ts +47 -28
  35. package/src/panels/data-explorer.ts +24 -11
  36. package/src/panels/dnd.ts +148 -100
  37. package/src/panels/editors.ts +54 -37
  38. package/src/panels/elements-panel.ts +23 -13
  39. package/src/panels/events-panel.ts +48 -38
  40. package/src/panels/git-panel.ts +95 -60
  41. package/src/panels/head-panel.ts +138 -84
  42. package/src/panels/imports-panel.ts +66 -29
  43. package/src/panels/layers-panel.ts +80 -36
  44. package/src/panels/left-panel.ts +86 -53
  45. package/src/panels/overlays.ts +36 -19
  46. package/src/panels/panel-events.ts +48 -27
  47. package/src/panels/panel-scheduler.ts +41 -23
  48. package/src/panels/preview-render.ts +26 -26
  49. package/src/panels/properties-panel.ts +155 -118
  50. package/src/panels/pseudo-preview.ts +31 -15
  51. package/src/panels/quick-search.ts +28 -13
  52. package/src/panels/right-panel.ts +35 -24
  53. package/src/panels/shared.ts +46 -29
  54. package/src/panels/signals-panel.ts +303 -186
  55. package/src/panels/statusbar.ts +27 -12
  56. package/src/panels/style-inputs.ts +30 -24
  57. package/src/panels/style-panel.ts +130 -90
  58. package/src/panels/style-utils.ts +63 -23
  59. package/src/panels/stylebook-layers-panel.ts +27 -30
  60. package/src/panels/stylebook-panel.ts +140 -72
  61. package/src/panels/tab-strip.ts +21 -10
  62. package/src/panels/toolbar.ts +52 -40
  63. package/src/panels/welcome-screen.ts +1 -1
  64. package/src/platform.ts +2 -1
  65. package/src/platforms/devserver.ts +221 -105
  66. package/src/recent-projects.ts +15 -7
  67. package/src/resize-edges.ts +7 -5
  68. package/src/services/cem-export.ts +73 -57
  69. package/src/services/code-services.ts +52 -25
  70. package/src/services/monaco-setup.ts +5 -5
  71. package/src/settings/content-types-editor.ts +133 -62
  72. package/src/settings/css-vars-editor.ts +31 -14
  73. package/src/settings/defs-editor.ts +125 -63
  74. package/src/settings/general-settings.ts +11 -6
  75. package/src/settings/head-editor.ts +30 -15
  76. package/src/settings/schema-field-ui.ts +75 -35
  77. package/src/settings/settings-modal.ts +35 -17
  78. package/src/site-context.ts +80 -47
  79. package/src/state.ts +98 -65
  80. package/src/store.ts +42 -23
  81. package/src/studio.ts +165 -144
  82. package/src/tabs/tab.ts +51 -45
  83. package/src/tabs/transact.ts +230 -109
  84. package/src/types.ts +60 -51
  85. package/src/ui/button-group.ts +3 -2
  86. package/src/ui/color-selector.ts +33 -15
  87. package/src/ui/expression-editor.ts +139 -86
  88. package/src/ui/field-input.ts +35 -18
  89. package/src/ui/field-row.ts +1 -1
  90. package/src/ui/icons.ts +5 -6
  91. package/src/ui/layers.ts +33 -26
  92. package/src/ui/media-picker.ts +13 -7
  93. package/src/ui/panel-resize.ts +39 -20
  94. package/src/ui/spectrum.ts +4 -2
  95. package/src/ui/unit-selector.ts +20 -11
  96. package/src/ui/value-selector.ts +13 -7
  97. package/src/ui/widgets.ts +34 -18
  98. package/src/utils/canvas-media.ts +66 -33
  99. package/src/utils/edit-display.ts +43 -44
  100. package/src/utils/google-fonts.ts +10 -6
  101. package/src/utils/inherited-style.ts +22 -8
  102. package/src/utils/studio-utils.ts +88 -45
  103. package/src/view.ts +9 -5
  104. package/src/workspace/workspace.ts +49 -29
@@ -5,16 +5,18 @@
5
5
  */
6
6
 
7
7
  import { html, render as litRender, nothing } from "lit-html";
8
+ import type { CanvasPanel } from "../types";
8
9
  import { ref } from "lit-html/directives/ref.js";
9
10
  import { classMap } from "lit-html/directives/class-map.js";
10
11
  import { styleMap } from "lit-html/directives/style-map.js";
11
12
  import { ifDefined } from "lit-html/directives/if-defined.js";
12
13
 
13
- import { renderOnly, updateUi, canvasWrap, canvasPanels } from "../store";
14
+ import { canvasPanels, canvasWrap, renderOnly, updateUi } from "../store";
14
15
  import { activeTab } from "../workspace/workspace";
15
16
  import { view } from "../view";
16
17
  import { getLayerSlot } from "../ui/layers";
17
- import { getActivePanel, findCanvasElement } from "./canvas-helpers";
18
+ import { findCanvasElement, getActivePanel, panelMediaToActiveMedia } from "./canvas-helpers";
19
+ import type { TemplateResult } from "lit-html";
18
20
 
19
21
  let _ctx: {
20
22
  getCanvasMode: () => string;
@@ -57,34 +59,28 @@ export function canvasPanelTemplate(
57
59
  label: string | null,
58
60
  fullWidth: boolean,
59
61
  width: number | null = null,
60
- ) {
61
- const panel: {
62
- mediaName: string;
63
- element: HTMLElement | null;
64
- canvas: HTMLElement | null;
65
- overlay: HTMLElement | null;
66
- overlayClk: HTMLElement | null;
67
- viewport: HTMLElement | null;
68
- scrollContainer: HTMLElement | null;
69
- dropLine: HTMLElement | null;
70
- _width: number | null;
71
- } = {
72
- mediaName: mediaName || "",
73
- element: null,
62
+ ): { tpl: TemplateResult; panel: CanvasPanel } {
63
+ // The DOM fields start null and are wired by the template's ref() directives,
64
+ // Which lit runs synchronously during render — before any consumer reads them.
65
+ const panel = {
66
+ _width: width || null,
74
67
  canvas: null,
68
+ dropLine: null,
69
+ element: null,
70
+ mediaName: mediaName || "",
75
71
  overlay: null,
76
72
  overlayClk: null,
77
- viewport: null,
78
73
  scrollContainer: null,
79
- dropLine: null,
80
- _width: width || null,
81
- };
74
+ viewport: null,
75
+ } as unknown as CanvasPanel;
82
76
  const tpl = html`
83
77
  <div
84
78
  class=${classMap({ "canvas-panel": true, "full-width": fullWidth })}
85
79
  data-media=${ifDefined(mediaName !== null ? mediaName : undefined)}
86
80
  ${ref((el) => {
87
- if (el) panel.element = el as HTMLElement;
81
+ if (el) {
82
+ panel.element = el as HTMLElement;
83
+ }
88
84
  })}
89
85
  >
90
86
  ${label
@@ -92,7 +88,7 @@ export function canvasPanelTemplate(
92
88
  <div
93
89
  class="canvas-panel-header"
94
90
  @click=${() => {
95
- updateUi("activeMedia", mediaName === "base" ? null : mediaName);
91
+ updateUi("activeMedia", panelMediaToActiveMedia(mediaName));
96
92
  }}
97
93
  >
98
94
  ${label}
@@ -103,45 +99,57 @@ export function canvasPanelTemplate(
103
99
  class="canvas-panel-viewport"
104
100
  style=${styleMap({ width: width && !fullWidth ? `${width}px` : "" })}
105
101
  ${ref((el) => {
106
- if (el) panel.viewport = el as HTMLElement;
102
+ if (el) {
103
+ panel.viewport = el as HTMLElement;
104
+ }
107
105
  })}
108
106
  >
109
107
  <div
110
108
  class="canvas-panel-canvas"
111
109
  style=${styleMap({ width: width ? `${width}px` : "" })}
112
110
  ${ref((el) => {
113
- if (el) panel.canvas = el as HTMLElement;
111
+ if (el) {
112
+ panel.canvas = el as HTMLElement;
113
+ }
114
114
  })}
115
115
  ></div>
116
116
  <div
117
117
  class="canvas-panel-overlay"
118
118
  ${ref((el) => {
119
- if (el) panel.overlay = el as HTMLElement;
119
+ if (el) {
120
+ panel.overlay = el as HTMLElement;
121
+ }
120
122
  })}
121
123
  >
122
124
  <div
123
125
  class="canvas-drop-indicator"
124
126
  style="display:none"
125
127
  ${ref((el) => {
126
- if (el) panel.dropLine = el as HTMLElement;
128
+ if (el) {
129
+ panel.dropLine = el as HTMLElement;
130
+ }
127
131
  })}
128
132
  ></div>
129
133
  </div>
130
134
  <div
131
135
  class="canvas-panel-click"
132
136
  ${ref((el) => {
133
- if (el) panel.overlayClk = el as HTMLElement;
137
+ if (el) {
138
+ panel.overlayClk = el as HTMLElement;
139
+ }
134
140
  })}
135
141
  ></div>
136
142
  </div>
137
143
  </div>
138
144
  `;
139
- return { tpl, panel };
145
+ return { panel, tpl };
140
146
  }
141
147
 
142
148
  /** Center canvas horizontally in viewport, top-aligned vertically. */
143
149
  export function centerCanvas() {
144
- if (!view.panzoomWrap) return;
150
+ if (!view.panzoomWrap) {
151
+ return;
152
+ }
145
153
  const wrapWidth = canvasWrap.clientWidth;
146
154
  const contentWidth = view.panzoomWrap.scrollWidth;
147
155
  const zoom = _ctx.getZoom();
@@ -159,7 +167,9 @@ export function observeCenterUntilStable() {
159
167
  view.centerObserver.disconnect();
160
168
  view.centerObserver = null;
161
169
  }
162
- if (!view.panzoomWrap) return;
170
+ if (!view.panzoomWrap) {
171
+ return;
172
+ }
163
173
  view.needsCenter = true;
164
174
  view.centerObserver = new ResizeObserver(() => {
165
175
  if (!view.needsCenter) {
@@ -176,17 +186,23 @@ export function observeCenterUntilStable() {
176
186
 
177
187
  /** Apply the current zoom + pan transform to the panzoom wrapper. */
178
188
  export function applyTransform() {
179
- if (!view.panzoomWrap) return;
189
+ if (!view.panzoomWrap) {
190
+ return;
191
+ }
180
192
  const zoom = _ctx.getZoom();
181
193
  view.panzoomWrap.style.transform = `translate(${view.panX}px, ${view.panY}px) scale(${zoom})`;
182
194
  renderZoomIndicator();
183
195
  renderOnly("overlays");
184
- if (_ctx.getCanvasMode() === "stylebook") _ctx.renderStylebookOverlays();
196
+ if (_ctx.getCanvasMode() === "stylebook") {
197
+ _ctx.renderStylebookOverlays();
198
+ }
185
199
  }
186
200
 
187
201
  /** Calculate zoom + pan to fit all panels within the viewport. */
188
202
  export function fitToScreen() {
189
- if (!view.panzoomWrap) return;
203
+ if (!view.panzoomWrap) {
204
+ return;
205
+ }
190
206
  const wrapWidth = canvasWrap.clientWidth;
191
207
  const wrapHeight = canvasWrap.clientHeight;
192
208
  const gap = 24;
@@ -204,7 +220,7 @@ export function fitToScreen() {
204
220
 
205
221
  const fitZoomW = wrapWidth / totalPanelWidth;
206
222
  const fitZoomH = wrapHeight / maxPanelHeight;
207
- const fitZoom = Math.min(5.0, Math.max(0.05, Math.min(fitZoomW, fitZoomH)));
223
+ const fitZoom = Math.min(5, Math.max(0.05, Math.min(fitZoomW, fitZoomH)));
208
224
 
209
225
  _ctx.setZoomDirect(fitZoom);
210
226
 
@@ -217,7 +233,9 @@ export function fitToScreen() {
217
233
 
218
234
  /** Reset zoom to 100% and re-center horizontally. */
219
235
  export function resetZoom() {
220
- if (!view.panzoomWrap) return;
236
+ if (!view.panzoomWrap) {
237
+ return;
238
+ }
221
239
  _ctx.setZoomDirect(1);
222
240
  centerCanvas();
223
241
  applyTransform();
@@ -244,21 +262,23 @@ function _panToEl(el: HTMLElement, panel?: { scrollContainer?: HTMLElement | nul
244
262
 
245
263
  if (panel?.scrollContainer) {
246
264
  panel.scrollContainer.scrollTo({
247
- top: panel.scrollContainer.scrollTop - offsetY,
248
265
  behavior: "smooth",
266
+ top: panel.scrollContainer.scrollTop - offsetY,
249
267
  });
250
268
  } else {
251
269
  const startY = view.panY;
252
270
  const targetY = startY + offsetY;
253
271
  const start = performance.now();
254
272
  const duration = 250;
255
- function step(now: number) {
273
+ const step = (now: number) => {
256
274
  const t = Math.min((now - start) / duration, 1);
257
275
  const ease = t * (2 - t);
258
276
  view.panY = startY + (targetY - startY) * ease;
259
277
  applyTransform();
260
- if (t < 1) requestAnimationFrame(step);
261
- }
278
+ if (t < 1) {
279
+ requestAnimationFrame(step);
280
+ }
281
+ };
262
282
  requestAnimationFrame(step);
263
283
  }
264
284
  }
@@ -270,9 +290,13 @@ function _panToEl(el: HTMLElement, panel?: { scrollContainer?: HTMLElement | nul
270
290
  */
271
291
  export function panToElement(path: (string | number)[]) {
272
292
  const panel = getActivePanel();
273
- if (!panel?.canvas) return;
293
+ if (!panel?.canvas) {
294
+ return;
295
+ }
274
296
  const el = findCanvasElement(path, panel.canvas);
275
- if (!el) return;
297
+ if (!el) {
298
+ return;
299
+ }
276
300
  _panToEl(el, panel);
277
301
  }
278
302
 
@@ -337,7 +361,9 @@ export function renderZoomIndicator() {
337
361
 
338
362
  /** Position the zoom indicator relative to canvas-wrap bounds. */
339
363
  export function positionZoomIndicator() {
340
- if (!_zoomIndicatorEl) return;
364
+ if (!_zoomIndicatorEl) {
365
+ return;
366
+ }
341
367
  const rect = canvasWrap.getBoundingClientRect();
342
368
  _zoomIndicatorEl.style.left = `${rect.left + rect.width / 2}px`;
343
369
  _zoomIndicatorEl.style.top = `${rect.bottom - 32}px`;
@@ -2,21 +2,27 @@
2
2
  * Generate scoped CSS from nested style objects (e.g. `table: { width: "100%", thead: { ... } }`).
3
3
  * Returns a CSS string with rules scoped under the given selector.
4
4
  *
5
- * @param {Record<string, any>} styleObj - The full style object (flat + nested)
5
+ * @param {import("@jxsuite/schema/types").JxStyle} styleObj - The full style object (flat + nested)
6
6
  * @param {string} scope - The scoping selector (e.g. `[data-jx-site]`)
7
7
  * @returns {string} Generated CSS text
8
8
  */
9
- export function buildNestedSiteCSS(styleObj: Record<string, any>, scope: string) {
9
+ import type { JxStyle } from "@jxsuite/schema/types";
10
+
11
+ export function buildNestedSiteCSS(styleObj: JxStyle, scope: string) {
10
12
  let css = "";
11
13
 
12
- function emit(parentSel: string, rules: Record<string, any>) {
14
+ function emit(parentSel: string, rules: JxStyle) {
13
15
  const props = Object.entries(rules)
14
16
  .filter(([, val]) => val === null || typeof val !== "object" || Array.isArray(val))
15
17
  .map(([p, val]) => `${camelToKebab(p)}: ${val}`)
16
18
  .join("; ");
17
- if (props) css += `${parentSel} { ${props} }\n`;
19
+ if (props) {
20
+ css += `${parentSel} { ${props} }\n`;
21
+ }
18
22
  for (const [sel, sub] of Object.entries(rules)) {
19
- if (sub === null || typeof sub !== "object" || Array.isArray(sub)) continue;
23
+ if (sub === null || typeof sub !== "object" || Array.isArray(sub)) {
24
+ continue;
25
+ }
20
26
  const resolved = sel.startsWith("&")
21
27
  ? sel.replace("&", parentSel)
22
28
  : sel.startsWith("[") || sel.startsWith(":") || sel.startsWith(".")
@@ -27,7 +33,9 @@ export function buildNestedSiteCSS(styleObj: Record<string, any>, scope: string)
27
33
  }
28
34
 
29
35
  for (const [k, v] of Object.entries(styleObj)) {
30
- if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
36
+ if (v === null || typeof v !== "object" || Array.isArray(v)) {
37
+ continue;
38
+ }
31
39
  const resolved = k.startsWith("&")
32
40
  ? k.replace("&", scope)
33
41
  : k.startsWith("[") || k.startsWith(":") || k.startsWith(".")
@@ -46,5 +54,5 @@ export function buildNestedSiteCSS(styleObj: Record<string, any>, scope: string)
46
54
  * @returns {string}
47
55
  */
48
56
  function camelToKebab(s: string) {
49
- return s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
57
+ return s.replaceAll(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
50
58
  }
@@ -5,24 +5,25 @@
5
5
  */
6
6
 
7
7
  import {
8
- updateUi,
9
- renderOnly,
10
- getNodeAtPath,
11
- parentElementPath,
12
- childIndex,
13
8
  canvasPanels,
9
+ childIndex,
14
10
  elToPath,
11
+ getNodeAtPath,
12
+ parentElementPath,
13
+ renderOnly,
14
+ updateUi,
15
15
  } from "../store";
16
16
  import { activeTab } from "../workspace/workspace";
17
17
  import type { JxPath } from "../state";
18
18
  import {
19
- transactDoc,
20
- mutateRemoveNode,
21
19
  mutateInsertNode,
20
+ mutateRemoveNode,
22
21
  mutateUpdateProperty,
22
+ transactDoc,
23
23
  } from "../tabs/transact";
24
24
  import { view } from "../view";
25
- import { isSlashMenuOpen, showSlashMenu, dismissSlashMenu } from "./slash-menu";
25
+ import { panelMediaToActiveMedia } from "../canvas/canvas-helpers";
26
+ import { dismissSlashMenu, isSlashMenuOpen, showSlashMenu } from "./slash-menu";
26
27
  import { renderBlockActionBar } from "../panels/block-action-bar";
27
28
  import { defaultDef } from "../panels/shared";
28
29
 
@@ -64,15 +65,27 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
64
65
  }
65
66
 
66
67
  const node = getNodeAtPath(activeTab.value!.doc.document, path);
67
- if (!node) return;
68
+ if (!node) {
69
+ return;
70
+ }
68
71
 
69
72
  const tc = node.textContent;
70
- if (node.$props && (node.tagName || "").includes("-")) return;
71
- if (Array.isArray(node.children) && node.children.length > 0) return;
72
- if (node.children && typeof node.children === "object") return;
73
- if (tc && typeof tc === "object") return;
73
+ if (node.$props && (node.tagName || "").includes("-")) {
74
+ return;
75
+ }
76
+ if (Array.isArray(node.children) && node.children.length > 0) {
77
+ return;
78
+ }
79
+ if (node.children && typeof node.children === "object") {
80
+ return;
81
+ }
82
+ if (tc && typeof tc === "object") {
83
+ return;
84
+ }
74
85
  const voids = new Set(["img", "input", "br", "hr", "video", "audio", "source", "embed", "slot"]);
75
- if (voids.has(node.tagName || "")) return;
86
+ if (voids.has(node.tagName || "")) {
87
+ return;
88
+ }
76
89
 
77
90
  for (const p of canvasPanels) {
78
91
  const boxes = p.overlay.querySelectorAll(".overlay-box") as NodeListOf<HTMLElement>;
@@ -94,9 +107,9 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
94
107
 
95
108
  view.componentInlineEdit = {
96
109
  el,
97
- path,
98
- originalText: rawText,
99
110
  mediaName: canvasPanels.find((p) => p.canvas.contains(el))?.mediaName || null,
111
+ originalText: rawText,
112
+ path,
100
113
  };
101
114
 
102
115
  el.focus();
@@ -115,31 +128,43 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
115
128
  document.removeEventListener("mousedown", outsideHandler, true);
116
129
  return;
117
130
  }
118
- if (view.componentInlineEdit.el.contains(evt.target as Node)) return;
119
- if (isSlashMenuOpen()) return;
120
- if (view.blockActionBarEl && view.blockActionBarEl.contains(evt.target as Node)) return;
131
+ if (view.componentInlineEdit.el.contains(evt.target as Node)) {
132
+ return;
133
+ }
134
+ if (isSlashMenuOpen()) {
135
+ return;
136
+ }
137
+ if (view.blockActionBarEl && view.blockActionBarEl.contains(evt.target as Node)) {
138
+ return;
139
+ }
121
140
  document.removeEventListener("mousedown", outsideHandler, true);
122
141
 
123
- let hitPath: (string | number)[] | null = null,
124
- hitMedia = null;
142
+ let hitMedia = null;
143
+ let hitPath: (string | number)[] | null = null;
125
144
  for (const p of canvasPanels) {
126
145
  const els = p.canvas.querySelectorAll("*") as NodeListOf<HTMLElement>;
127
- for (const el of els) el.style.pointerEvents = "auto";
146
+ for (const child of els) {
147
+ child.style.pointerEvents = "auto";
148
+ }
128
149
  p.overlayClk.style.display = "none";
129
150
  const found = document.elementsFromPoint(evt.clientX, evt.clientY);
130
151
  p.overlayClk.style.display = "";
131
- for (const el of els) el.style.pointerEvents = "none";
152
+ for (const child of els) {
153
+ child.style.pointerEvents = "none";
154
+ }
132
155
  for (const hit of found) {
133
156
  if (p.canvas.contains(hit) && hit !== p.canvas) {
134
- const path = elToPath.get(hit);
135
- if (path) {
136
- hitPath = path;
157
+ const hitElPath = elToPath.get(hit);
158
+ if (hitElPath) {
159
+ hitPath = hitElPath;
137
160
  hitMedia = p.mediaName;
138
161
  break;
139
162
  }
140
163
  }
141
164
  }
142
- if (hitPath) break;
165
+ if (hitPath) {
166
+ break;
167
+ }
143
168
  }
144
169
 
145
170
  const { el: editEl, path: editPath, originalText } = view.componentInlineEdit;
@@ -151,8 +176,8 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
151
176
 
152
177
  if (hitPath) {
153
178
  let hp = hitPath;
154
- const media = hitMedia === "base" ? null : (hitMedia ?? null);
155
- updateUi("pendingInlineEdit", { path: hp, mediaName: hitMedia });
179
+ const media = panelMediaToActiveMedia(hitMedia);
180
+ updateUi("pendingInlineEdit", { mediaName: hitMedia, path: hp });
156
181
  activeTab.value!.session.ui.activeMedia = media;
157
182
  if (isEmpty && pPath) {
158
183
  transactDoc(activeTab.value, (t) => {
@@ -167,7 +192,7 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
167
192
  hitIdx > removedIdx
168
193
  ) {
169
194
  hp = [...pPath, "children", hitIdx - 1];
170
- updateUi("pendingInlineEdit", { path: hp, mediaName: hitMedia });
195
+ updateUi("pendingInlineEdit", { mediaName: hitMedia, path: hp });
171
196
  }
172
197
  t.session.selection = hp;
173
198
  });
@@ -179,17 +204,15 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
179
204
  } else {
180
205
  activeTab.value!.session.selection = hp;
181
206
  }
207
+ } else if (isEmpty && pPath) {
208
+ transactDoc(activeTab.value, (t) => mutateRemoveNode(t, editPath));
209
+ } else if (newText !== originalText) {
210
+ transactDoc(activeTab.value, (t) =>
211
+ mutateUpdateProperty(t, editPath, "textContent", newText || undefined),
212
+ );
182
213
  } else {
183
- if (isEmpty && pPath) {
184
- transactDoc(activeTab.value, (t) => mutateRemoveNode(t, editPath));
185
- } else if (newText !== originalText) {
186
- transactDoc(activeTab.value, (t) =>
187
- mutateUpdateProperty(t, editPath, "textContent", newText || undefined),
188
- );
189
- } else {
190
- renderOnly("canvas");
191
- renderOnly("overlays");
192
- }
214
+ renderOnly("canvas");
215
+ renderOnly("overlays");
193
216
  }
194
217
  };
195
218
  document.addEventListener("mousedown", outsideHandler, true);
@@ -200,8 +223,8 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
200
223
 
201
224
  /** @param {KeyboardEvent} e */
202
225
  function componentInlineKeydown(e: KeyboardEvent) {
203
- if (isSlashMenuOpen()) {
204
- if (["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(e.key)) return;
226
+ if (isSlashMenuOpen() && ["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(e.key)) {
227
+ return;
205
228
  }
206
229
 
207
230
  if (e.key === "Enter" && !e.shiftKey) {
@@ -215,7 +238,9 @@ function componentInlineKeydown(e: KeyboardEvent) {
215
238
  }
216
239
 
217
240
  function splitParagraph() {
218
- if (!view.componentInlineEdit) return;
241
+ if (!view.componentInlineEdit) {
242
+ return;
243
+ }
219
244
  const { el, path, mediaName } = view.componentInlineEdit;
220
245
 
221
246
  const sel = el.ownerDocument.defaultView?.getSelection() as Selection | null;
@@ -235,7 +260,9 @@ function splitParagraph() {
235
260
  const tag = "p";
236
261
  const pPath = parentElementPath(path) as JxPath;
237
262
  const idx = childIndex(path) as number;
238
- if (!pPath) return;
263
+ if (!pPath) {
264
+ return;
265
+ }
239
266
 
240
267
  const newDef = { tagName: tag, textContent: textAfter };
241
268
  const newPath = [...pPath, "children", idx + 1];
@@ -248,11 +275,13 @@ function splitParagraph() {
248
275
  t.session.selection = newPath;
249
276
  });
250
277
 
251
- updateUi("pendingInlineEdit", { path: newPath, mediaName });
278
+ updateUi("pendingInlineEdit", { mediaName, path: newPath });
252
279
  }
253
280
 
254
281
  function _commitComponentInlineEdit() {
255
- if (!view.componentInlineEdit) return;
282
+ if (!view.componentInlineEdit) {
283
+ return;
284
+ }
256
285
  const { el, path, originalText } = view.componentInlineEdit;
257
286
  const newText = (el.textContent ?? "").trim();
258
287
 
@@ -272,7 +301,9 @@ function _commitComponentInlineEdit() {
272
301
  }
273
302
 
274
303
  function cancelComponentInlineEdit() {
275
- if (!view.componentInlineEdit) return;
304
+ if (!view.componentInlineEdit) {
305
+ return;
306
+ }
276
307
  const { el } = view.componentInlineEdit;
277
308
  cleanupComponentInlineEdit(el);
278
309
  renderOnly("canvas");
@@ -305,7 +336,9 @@ function cleanupComponentInlineEdit(el: HTMLElement) {
305
336
  // ─── Component-mode slash commands ──────────────────────────────────────────
306
337
 
307
338
  function componentInlineInput() {
308
- if (!view.componentInlineEdit) return;
339
+ if (!view.componentInlineEdit) {
340
+ return;
341
+ }
309
342
  const { el, originalText } = view.componentInlineEdit;
310
343
  const text = el.textContent || "";
311
344
 
@@ -319,11 +352,15 @@ function componentInlineInput() {
319
352
 
320
353
  /** @param {{ tag: string; label: string; description: string }} cmd */
321
354
  function handleComponentSlashSelect(cmd: { tag: string; label: string; description: string }) {
322
- if (!view.componentInlineEdit) return;
355
+ if (!view.componentInlineEdit) {
356
+ return;
357
+ }
323
358
  const { el, path, mediaName } = view.componentInlineEdit;
324
359
  const pPath = parentElementPath(path);
325
360
  const idx = childIndex(path) as number;
326
- if (!pPath) return;
361
+ if (!pPath) {
362
+ return;
363
+ }
327
364
 
328
365
  cleanupComponentInlineEdit(el);
329
366
 
@@ -337,5 +374,7 @@ function handleComponentSlashSelect(cmd: { tag: string; label: string; descripti
337
374
  });
338
375
 
339
376
  const hasText = newDef.textContent != null;
340
- if (hasText) updateUi("pendingInlineEdit", { path: newPath, mediaName });
377
+ if (hasText) {
378
+ updateUi("pendingInlineEdit", { mediaName, path: newPath });
379
+ }
341
380
  }