@jxsuite/studio 0.19.0 → 0.21.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 (99) hide show
  1. package/dist/studio.css +98 -98
  2. package/dist/studio.js +13476 -12330
  3. package/dist/studio.js.map +152 -148
  4. package/dist/workers/editor.worker.js +79 -79
  5. package/dist/workers/json.worker.js +109 -109
  6. package/dist/workers/ts.worker.js +82 -82
  7. package/package.json +19 -7
  8. package/src/browse/browse-modal.js +16 -13
  9. package/src/browse/browse.js +44 -35
  10. package/src/canvas/canvas-diff.js +24 -16
  11. package/src/canvas/canvas-helpers.js +24 -19
  12. package/src/canvas/canvas-live-render.js +113 -39
  13. package/src/canvas/canvas-render.js +119 -73
  14. package/src/canvas/canvas-utils.js +51 -21
  15. package/src/canvas/nested-site-style.js +50 -0
  16. package/src/editor/component-inline-edit.js +14 -13
  17. package/src/editor/content-inline-edit.js +22 -10
  18. package/src/editor/context-menu.js +28 -14
  19. package/src/editor/convert-targets.js +60 -0
  20. package/src/editor/convert-to-component.js +20 -19
  21. package/src/editor/convert-to-repeater.js +226 -0
  22. package/src/editor/inline-edit.js +69 -28
  23. package/src/editor/insertion-helper.js +16 -12
  24. package/src/editor/shortcuts.js +14 -4
  25. package/src/editor/slash-menu.js +40 -19
  26. package/src/files/components.js +1 -1
  27. package/src/files/file-ops.js +37 -16
  28. package/src/files/files.js +322 -68
  29. package/src/github/github-auth.js +122 -0
  30. package/src/github/github-publish.js +126 -0
  31. package/src/markdown/md-convert.js +172 -107
  32. package/src/new-project/new-project-modal.js +204 -0
  33. package/src/panels/activity-bar.js +27 -25
  34. package/src/panels/ai-panel.js +406 -0
  35. package/src/panels/block-action-bar.js +77 -28
  36. package/src/panels/canvas-dnd.js +8 -2
  37. package/src/panels/data-explorer.js +19 -13
  38. package/src/panels/dnd.js +144 -92
  39. package/src/panels/editors.js +55 -35
  40. package/src/panels/elements-panel.js +33 -19
  41. package/src/panels/events-panel.js +15 -10
  42. package/src/panels/git-panel.js +475 -99
  43. package/src/panels/head-panel.js +121 -81
  44. package/src/panels/imports-panel.js +94 -65
  45. package/src/panels/layers-panel.js +64 -43
  46. package/src/panels/left-panel.js +105 -48
  47. package/src/panels/overlays.js +1 -0
  48. package/src/panels/panel-events.js +45 -30
  49. package/src/panels/preview-render.js +23 -18
  50. package/src/panels/properties-panel.js +378 -257
  51. package/src/panels/pseudo-preview.js +2 -2
  52. package/src/panels/quick-search.js +11 -9
  53. package/src/panels/right-panel.js +42 -16
  54. package/src/panels/shared.js +4 -4
  55. package/src/panels/signals-panel.js +415 -166
  56. package/src/panels/statusbar.js +10 -6
  57. package/src/panels/style-inputs.js +67 -35
  58. package/src/panels/style-panel.js +239 -134
  59. package/src/panels/style-utils.js +34 -16
  60. package/src/panels/stylebook-layers-panel.js +12 -10
  61. package/src/panels/stylebook-panel.js +134 -66
  62. package/src/panels/tab-strip.js +5 -2
  63. package/src/panels/toolbar.js +333 -109
  64. package/src/panels/welcome-screen.js +121 -0
  65. package/src/platform.js +2 -4
  66. package/src/platforms/devserver.js +113 -7
  67. package/src/resize-edges.js +98 -0
  68. package/src/services/cem-export.js +12 -7
  69. package/src/services/code-services.js +30 -12
  70. package/src/settings/content-types-editor.js +8 -8
  71. package/src/settings/css-vars-editor.js +30 -24
  72. package/src/settings/defs-editor.js +12 -7
  73. package/src/settings/general-settings.js +85 -3
  74. package/src/settings/head-editor.js +30 -24
  75. package/src/settings/schema-field-ui.js +58 -39
  76. package/src/settings/settings-modal.js +12 -9
  77. package/src/site-context.js +41 -31
  78. package/src/state.js +70 -29
  79. package/src/store.js +36 -35
  80. package/src/studio.js +120 -90
  81. package/src/tabs/tab.js +67 -43
  82. package/src/tabs/transact.js +23 -13
  83. package/src/ui/button-group.js +24 -24
  84. package/src/ui/color-selector.js +28 -23
  85. package/src/ui/field-row.js +3 -3
  86. package/src/ui/icons.js +2 -2
  87. package/src/ui/layers.js +31 -1
  88. package/src/ui/media-picker.js +7 -7
  89. package/src/ui/spectrum.js +5 -1
  90. package/src/ui/unit-selector.js +18 -16
  91. package/src/ui/value-selector.js +21 -15
  92. package/src/ui/widgets.js +22 -19
  93. package/src/utils/canvas-media.js +5 -4
  94. package/src/utils/edit-display.js +32 -26
  95. package/src/utils/google-fonts.js +11 -11
  96. package/src/utils/inherited-style.js +9 -8
  97. package/src/utils/studio-utils.js +18 -18
  98. package/src/view.js +58 -1
  99. package/src/workspace/workspace.js +29 -7
package/src/state.js CHANGED
@@ -9,28 +9,37 @@
9
9
  */
10
10
 
11
11
  /**
12
- * @typedef {Record<string, any>} JxNode
13
- *
14
12
  * @typedef {(string | number)[]} JxPath
15
13
  *
16
- * @typedef {{ document: JxNode; selection: JxPath | null }} HistorySnapshot
14
+ * @typedef {{ document: JxMutableNode; selection: JxPath | null }} HistorySnapshot
17
15
  *
18
16
  * @typedef {{
19
- * document: JxNode;
17
+ * document: JxMutableNode;
20
18
  * selection: JxPath | null;
21
19
  * hover: JxPath | null;
22
20
  * history: HistorySnapshot[];
23
21
  * historyIndex: number;
24
22
  * dirty: boolean;
25
- * fileHandle: any;
23
+ * fileHandle: FileSystemFileHandle | null;
26
24
  * documentPath: string | null;
27
- * documentStack: any[];
25
+ * documentStack: StudioStackFrame[];
28
26
  * handlersSource: string | null;
29
27
  * mode: string;
30
- * content: { frontmatter: Record<string, any> };
31
- * ui: Record<string, any>;
32
- * canvas: { status: string; scope: any; error: string | null };
28
+ * content: { frontmatter: Record<string, unknown> };
29
+ * ui: Record<string, unknown>;
30
+ * canvas: { status: string; scope: Record<string, unknown> | null; error: string | null };
33
31
  * }} StudioState
32
+ *
33
+ * @typedef {{
34
+ * document: JxMutableNode;
35
+ * selection: JxPath | null;
36
+ * fileHandle: FileSystemFileHandle | null;
37
+ * documentPath: string | null;
38
+ * dirty: boolean;
39
+ * history: HistorySnapshot[];
40
+ * historyIndex: number;
41
+ * mode: string;
42
+ * }} StudioStackFrame
34
43
  */
35
44
 
36
45
  // ─── Path utilities ───────────────────────────────────────────────────────────
@@ -38,14 +47,14 @@
38
47
  /**
39
48
  * Walk the document tree and return the node at the given path.
40
49
  *
41
- * @param {any} doc
50
+ * @param {JxMutableNode} doc
42
51
  * @param {JxPath} path
43
- * @returns {any}
52
+ * @returns {JxMutableNode}
44
53
  */
45
54
  export function getNodeAtPath(doc, path) {
46
55
  let node = doc;
47
56
  for (const key of path) {
48
- if (node == null) return undefined;
57
+ if (node == null) return /** @type {JxMutableNode} */ (/** @type {unknown} */ (undefined));
49
58
  node = node[key];
50
59
  }
51
60
  return node;
@@ -114,10 +123,15 @@ export function isAncestor(path, descendant) {
114
123
  *
115
124
  * NodeType: 'element' (default) | 'map' | 'case' | 'case-ref'
116
125
  *
117
- * @param {any} doc
126
+ * @param {JxMutableNode | string | number | boolean} doc
118
127
  * @param {JxPath} [path]
119
128
  * @param {number} [depth]
120
- * @returns {{ node: any; path: JxPath; depth: number; nodeType: string }[]}
129
+ * @returns {{
130
+ * node: JxMutableNode | string | number | boolean;
131
+ * path: JxPath;
132
+ * depth: number;
133
+ * nodeType: string;
134
+ * }[]}
121
135
  */
122
136
  export function flattenTree(doc, path = [], depth = 0) {
123
137
  // Text node children: bare primitives get a "text" row
@@ -125,7 +139,14 @@ export function flattenTree(doc, path = [], depth = 0) {
125
139
  return [{ node: doc, path, depth, nodeType: "text" }];
126
140
  }
127
141
 
128
- /** @type {{ node: any; path: JxPath; depth: number; nodeType: string }[]} */
142
+ /**
143
+ * @type {{
144
+ * node: JxMutableNode | string | number | boolean;
145
+ * path: JxPath;
146
+ * depth: number;
147
+ * nodeType: string;
148
+ * }[]}
149
+ */
129
150
  const rows = [{ node: doc, path, depth, nodeType: "element" }];
130
151
 
131
152
  // Custom component instances without user-authored children are atomic in the layer tree
@@ -140,12 +161,22 @@ export function flattenTree(doc, path = [], depth = 0) {
140
161
  const childPath = [...path, "children", i];
141
162
  rows.push(...flattenTree(children[i], childPath, depth + 1));
142
163
  }
143
- } else if (children && typeof children === "object" && children.$prototype === "Array") {
164
+ } else if (
165
+ children &&
166
+ typeof children === "object" &&
167
+ /** @type {JxMutableNode} */ (children).$prototype === "Array"
168
+ ) {
144
169
  // $map — emit the map container, then recurse into the template
145
170
  rows.push({ node: children, path: [...path, "children"], depth: depth + 1, nodeType: "map" });
146
- const mapDef = children.map;
171
+ const mapDef = /** @type {JxMutableNode} */ (children).map;
147
172
  if (mapDef && typeof mapDef === "object") {
148
- rows.push(...flattenTree(mapDef, [...path, "children", "map"], depth + 2));
173
+ rows.push(
174
+ ...flattenTree(
175
+ /** @type {JxMutableNode} */ (mapDef),
176
+ [...path, "children", "map"],
177
+ depth + 2,
178
+ ),
179
+ );
149
180
  }
150
181
  }
151
182
 
@@ -153,7 +184,7 @@ export function flattenTree(doc, path = [], depth = 0) {
153
184
  if (doc.$switch && doc.cases && typeof doc.cases === "object") {
154
185
  for (const [caseName, caseDef] of Object.entries(doc.cases)) {
155
186
  const casePath = [...path, "cases", caseName];
156
- if (caseDef && typeof caseDef === "object" && /** @type {any} */ (caseDef).$ref) {
187
+ if (caseDef && typeof caseDef === "object" && /** @type {JxMutableNode} */ (caseDef).$ref) {
157
188
  rows.push({ node: caseDef, path: casePath, depth: depth + 1, nodeType: "case-ref" });
158
189
  } else if (caseDef && typeof caseDef === "object") {
159
190
  rows.push({ node: caseDef, path: casePath, depth: depth + 1, nodeType: "case" });
@@ -170,7 +201,7 @@ export function flattenTree(doc, path = [], depth = 0) {
170
201
  /**
171
202
  * Get a display label for a node (for layers + overlays).
172
203
  *
173
- * @param {any} node
204
+ * @param {JxMutableNode | null} node
174
205
  * @returns {string}
175
206
  */
176
207
  export function nodeLabel(node) {
@@ -193,7 +224,7 @@ export function nodeLabel(node) {
193
224
  // ─── State factory ────────────────────────────────────────────────────────────
194
225
 
195
226
  /**
196
- * @param {any} doc
227
+ * @param {JxMutableNode} doc
197
228
  * @returns {StudioState}
198
229
  */
199
230
  export function createState(doc) {
@@ -248,19 +279,19 @@ export function createState(doc) {
248
279
  /**
249
280
  * Compose a flat StudioState from separate doc and session slices.
250
281
  *
251
- * @param {any} doc
252
- * @param {any} session
282
+ * @param {Partial<StudioState>} doc
283
+ * @param {Partial<StudioState>} session
253
284
  * @returns {StudioState}
254
285
  */
255
286
  export function toFlat(doc, session) {
256
- return { ...doc, ...session };
287
+ return /** @type {StudioState} */ ({ ...doc, ...session });
257
288
  }
258
289
 
259
290
  /**
260
291
  * Decompose a flat StudioState into doc and session slices.
261
292
  *
262
293
  * @param {StudioState} S
263
- * @returns {{ doc: any; session: any }}
294
+ * @returns {{ doc: Partial<StudioState>; session: Partial<StudioState> }}
264
295
  */
265
296
  export function fromFlat(S) {
266
297
  const {
@@ -303,14 +334,24 @@ export function fromFlat(S) {
303
334
  // selectedPath: string|null, searchQuery: string }
304
335
  // DirEntry: { name, path, type: "file"|"directory", size, modified }
305
336
 
306
- /** @type {any} */
337
+ /** @type {ProjectState | null} */
307
338
  export let projectState = null;
308
339
 
309
- /** @param {any} ps */
340
+ /** @param {ProjectState | null} ps */
310
341
  export function setProjectState(ps) {
311
342
  projectState = ps;
312
343
  }
313
344
 
345
+ /**
346
+ * Return the current project state, asserting it is non-null. Only call when a project is known to
347
+ * be loaded.
348
+ *
349
+ * @returns {ProjectState}
350
+ */
351
+ export function requireProjectState() {
352
+ return /** @type {ProjectState} */ (projectState);
353
+ }
354
+
314
355
  // ─── Frontmatter mutation ───────────────────────────────────────────────────
315
356
 
316
357
  /**
@@ -319,7 +360,7 @@ export function setProjectState(ps) {
319
360
  *
320
361
  * @param {StudioState} state
321
362
  * @param {string} field
322
- * @param {any} value
363
+ * @param {unknown} value
323
364
  * @returns {StudioState}
324
365
  */
325
366
  export function updateFrontmatter(state, field, value) {
@@ -359,7 +400,7 @@ export function hoverNode(state, path) {
359
400
  * Push current document onto the stack and switch to editing a new document.
360
401
  *
361
402
  * @param {StudioState} state
362
- * @param {any} doc
403
+ * @param {JxMutableNode} doc
363
404
  * @param {string | null} documentPath
364
405
  * @returns {StudioState}
365
406
  */
package/src/store.js CHANGED
@@ -22,42 +22,34 @@ export {
22
22
  isAncestor,
23
23
  projectState,
24
24
  setProjectState,
25
+ requireProjectState,
25
26
  updateFrontmatter,
26
27
  } from "./state.js";
27
28
 
28
- // ─── DOM shortcuts & element refs ────────────────────────────────────────────
29
+ // ─── Shell element refs (populated by initShellRefs) ─────────────────────────
29
30
 
30
- export const $ = (/** @type {string} */ sel) => document.querySelector(sel);
31
- export const _$$ = (/** @type {string} */ sel) => document.querySelectorAll(sel);
31
+ export let canvasWrap = /** @type {HTMLElement} */ (/** @type {unknown} */ (null));
32
+ export let activityBar = /** @type {HTMLElement} */ (/** @type {unknown} */ (null));
33
+ export let leftPanel = /** @type {HTMLElement} */ (/** @type {unknown} */ (null));
34
+ export let rightPanel = /** @type {HTMLElement} */ (/** @type {unknown} */ (null));
35
+ export let toolbarEl = /** @type {HTMLElement} */ (/** @type {unknown} */ (null));
36
+ export let statusbarEl = /** @type {HTMLElement} */ (/** @type {unknown} */ (null));
32
37
 
33
- export const canvasWrap = /** @type {HTMLElement} */ (document.querySelector("#canvas-wrap"));
34
- export const activityBar = /** @type {HTMLElement} */ (document.querySelector("#activity-bar"));
35
- export const leftPanel = /** @type {HTMLElement} */ (document.querySelector("#left-panel"));
36
- export const rightPanel = /** @type {HTMLElement} */ (document.querySelector("#right-panel"));
37
- export const toolbarEl = /** @type {HTMLElement} */ (document.querySelector("#toolbar"));
38
- export const statusbarEl = /** @type {HTMLElement} */ (document.querySelector("#statusbar"));
38
+ export function initShellRefs() {
39
+ canvasWrap = /** @type {HTMLElement} */ (document.querySelector("#canvas-wrap"));
40
+ activityBar = /** @type {HTMLElement} */ (document.querySelector("#activity-bar"));
41
+ leftPanel = /** @type {HTMLElement} */ (document.querySelector("#left-panel"));
42
+ rightPanel = /** @type {HTMLElement} */ (document.querySelector("#right-panel"));
43
+ toolbarEl = /** @type {HTMLElement} */ (document.querySelector("#toolbar"));
44
+ statusbarEl = /** @type {HTMLElement} */ (document.querySelector("#statusbar"));
45
+ }
39
46
 
40
47
  // ─── Shared containers (mutated in place by owner modules) ───────────────────
41
48
 
42
- /** WeakMap<HTMLElement, Array> — maps rendered DOM elements to their JSON paths */
49
+ /** @type {WeakMap<Element, JxPath>} */
43
50
  export const elToPath = new WeakMap();
44
51
 
45
- /**
46
- * Canvas panels: Array<{ mediaName, canvas, overlay, overlayClk, viewport, dropLine, element,
47
- * _width }>
48
- *
49
- * @type {{
50
- * mediaName: string;
51
- * canvas: HTMLElement;
52
- * overlay: HTMLElement;
53
- * overlayClk: HTMLElement;
54
- * viewport: HTMLElement;
55
- * scrollContainer?: HTMLElement | null;
56
- * dropLine: HTMLElement;
57
- * element?: HTMLElement | null;
58
- * _width?: number | null;
59
- * }[]}
60
- */
52
+ /** @type {import("./panels/canvas-dnd.js").CanvasPanel[]} */
61
53
  export const canvasPanels = [];
62
54
 
63
55
  // ─── Shared constants ────────────────────────────────────────────────────────
@@ -130,8 +122,8 @@ export function cancelStyleDebounce(/** @type {string} */ prop) {
130
122
  /**
131
123
  * Strip all on* event handler properties from a Jx document tree (deep clone).
132
124
  *
133
- * @param {import("./state.js").JxNode | unknown} node
134
- * @returns {import("./state.js").JxNode | unknown}
125
+ * @param {JxMutableNode} node
126
+ * @returns {JxMutableNode}
135
127
  */
136
128
  export function stripEventHandlers(node) {
137
129
  if (!node || typeof node !== "object") return node;
@@ -218,18 +210,27 @@ export function renderOnly(...names) {
218
210
  export function updateSession(patch) {
219
211
  const tab = activeTab.value;
220
212
  if (tab) {
221
- const p = /** @type {any} */ (patch);
222
- if (p.selection !== undefined) tab.session.selection = p.selection;
223
- if (p.hover !== undefined) tab.session.hover = p.hover;
213
+ const p = /**
214
+ * @type {{
215
+ * selection?: unknown;
216
+ * hover?: unknown;
217
+ * clipboard?: unknown;
218
+ * ui?: Record<string, unknown>;
219
+ * canvas?: Record<string, unknown>;
220
+ * }}
221
+ */ (patch);
222
+ if (p.selection !== undefined)
223
+ tab.session.selection = /** @type {JxPath | null} */ (p.selection);
224
+ if (p.hover !== undefined) tab.session.hover = /** @type {JxPath | null} */ (p.hover);
224
225
  if (p.clipboard !== undefined) tab.session.clipboard = p.clipboard;
225
226
  if (p.ui) {
226
227
  for (const [k, v] of Object.entries(p.ui)) {
227
- /** @type {any} */ (tab.session.ui)[k] = v;
228
+ /** @type {Record<string, unknown>} */ (tab.session.ui)[k] = v;
228
229
  }
229
230
  }
230
231
  if (p.canvas) {
231
232
  for (const [k, v] of Object.entries(p.canvas)) {
232
- /** @type {any} */ (tab.session.canvas)[k] = v;
233
+ /** @type {Record<string, unknown>} */ (tab.session.canvas)[k] = v;
233
234
  }
234
235
  }
235
236
  }
@@ -244,7 +245,7 @@ export function updateSession(patch) {
244
245
  export function updateUi(field, value) {
245
246
  const tab = activeTab.value;
246
247
  if (tab) {
247
- /** @type {any} */ (tab.session.ui)[field] = value;
248
+ /** @type {Record<string, unknown>} */ (tab.session.ui)[field] = value;
248
249
  }
249
250
  }
250
251
 
@@ -257,7 +258,7 @@ export function updateCanvas(patch) {
257
258
  const tab = activeTab.value;
258
259
  if (tab) {
259
260
  for (const [k, v] of Object.entries(patch)) {
260
- /** @type {any} */ (tab.session.canvas)[k] = v;
261
+ /** @type {Record<string, unknown>} */ (tab.session.canvas)[k] = v;
261
262
  }
262
263
  }
263
264
  }