@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.
- package/dist/studio.js +86144 -83971
- package/dist/studio.js.map +229 -219
- package/package.json +5 -5
- package/src/browse/browse-modal.ts +9 -3
- package/src/browse/browse.ts +139 -82
- package/src/canvas/canvas-diff.ts +35 -18
- package/src/canvas/canvas-helpers.ts +55 -21
- package/src/canvas/canvas-live-render.ts +140 -89
- package/src/canvas/canvas-render.ts +127 -82
- package/src/canvas/canvas-utils.ts +68 -42
- package/src/canvas/nested-site-style.ts +15 -7
- package/src/editor/component-inline-edit.ts +90 -51
- package/src/editor/content-inline-edit.ts +75 -72
- package/src/editor/context-menu.ts +125 -71
- package/src/editor/convert-targets.ts +17 -13
- package/src/editor/convert-to-component.ts +51 -28
- package/src/editor/convert-to-repeater.ts +44 -19
- package/src/editor/inline-edit.ts +107 -52
- package/src/editor/inline-format.ts +128 -60
- package/src/editor/insertion-helper.ts +26 -14
- package/src/editor/shortcuts.ts +89 -43
- package/src/editor/slash-menu.ts +41 -26
- package/src/files/components.ts +9 -7
- package/src/files/file-ops.ts +53 -33
- package/src/files/files.ts +246 -143
- package/src/format/constraints.ts +25 -15
- package/src/format/format-host.ts +33 -10
- package/src/github/github-auth.ts +24 -14
- package/src/github/github-publish.ts +20 -14
- package/src/new-project/new-project-modal.ts +27 -18
- package/src/panels/activity-bar.ts +27 -26
- package/src/panels/ai-panel.ts +100 -44
- package/src/panels/block-action-bar.ts +84 -52
- package/src/panels/canvas-dnd.ts +47 -28
- package/src/panels/data-explorer.ts +24 -11
- package/src/panels/dnd.ts +148 -100
- package/src/panels/editors.ts +54 -37
- package/src/panels/elements-panel.ts +23 -13
- package/src/panels/events-panel.ts +48 -38
- package/src/panels/git-panel.ts +95 -60
- package/src/panels/head-panel.ts +138 -84
- package/src/panels/imports-panel.ts +66 -29
- package/src/panels/layers-panel.ts +80 -36
- package/src/panels/left-panel.ts +86 -53
- package/src/panels/overlays.ts +36 -19
- package/src/panels/panel-events.ts +48 -27
- package/src/panels/panel-scheduler.ts +41 -23
- package/src/panels/preview-render.ts +26 -26
- package/src/panels/properties-panel.ts +155 -118
- package/src/panels/pseudo-preview.ts +31 -15
- package/src/panels/quick-search.ts +28 -13
- package/src/panels/right-panel.ts +35 -24
- package/src/panels/shared.ts +46 -29
- package/src/panels/signals-panel.ts +303 -186
- package/src/panels/statusbar.ts +27 -12
- package/src/panels/style-inputs.ts +30 -24
- package/src/panels/style-panel.ts +130 -90
- package/src/panels/style-utils.ts +63 -23
- package/src/panels/stylebook-layers-panel.ts +27 -30
- package/src/panels/stylebook-panel.ts +140 -72
- package/src/panels/tab-strip.ts +21 -10
- package/src/panels/toolbar.ts +52 -40
- package/src/panels/welcome-screen.ts +1 -1
- package/src/platform.ts +2 -1
- package/src/platforms/devserver.ts +221 -105
- package/src/recent-projects.ts +15 -7
- package/src/resize-edges.ts +7 -5
- package/src/services/cem-export.ts +73 -57
- package/src/services/code-services.ts +52 -25
- package/src/services/monaco-setup.ts +5 -5
- package/src/settings/content-types-editor.ts +133 -62
- package/src/settings/css-vars-editor.ts +31 -14
- package/src/settings/defs-editor.ts +125 -63
- package/src/settings/general-settings.ts +11 -6
- package/src/settings/head-editor.ts +30 -15
- package/src/settings/schema-field-ui.ts +75 -35
- package/src/settings/settings-modal.ts +35 -17
- package/src/site-context.ts +80 -47
- package/src/state.ts +98 -65
- package/src/store.ts +42 -23
- package/src/studio.ts +165 -144
- package/src/tabs/tab.ts +51 -45
- package/src/tabs/transact.ts +230 -109
- package/src/types.ts +60 -51
- package/src/ui/button-group.ts +3 -2
- package/src/ui/color-selector.ts +33 -15
- package/src/ui/expression-editor.ts +139 -86
- package/src/ui/field-input.ts +35 -18
- package/src/ui/field-row.ts +1 -1
- package/src/ui/icons.ts +5 -6
- package/src/ui/layers.ts +33 -26
- package/src/ui/media-picker.ts +13 -7
- package/src/ui/panel-resize.ts +39 -20
- package/src/ui/spectrum.ts +4 -2
- package/src/ui/unit-selector.ts +20 -11
- package/src/ui/value-selector.ts +13 -7
- package/src/ui/widgets.ts +34 -18
- package/src/utils/canvas-media.ts +66 -33
- package/src/utils/edit-display.ts +43 -44
- package/src/utils/google-fonts.ts +10 -6
- package/src/utils/inherited-style.ts +22 -8
- package/src/utils/studio-utils.ts +88 -45
- package/src/view.ts +9 -5
- package/src/workspace/workspace.ts +49 -29
package/src/state.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { ProjectState } from "./types";
|
|
13
|
+
import { isRef } from "@jxsuite/schema/guards";
|
|
13
14
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
14
15
|
|
|
15
16
|
export type JxPath = (string | number)[];
|
|
@@ -63,12 +64,26 @@ interface StudioStackFrame {
|
|
|
63
64
|
export function getNodeAtPath(doc: JxMutableNode, path: JxPath) {
|
|
64
65
|
let node = doc;
|
|
65
66
|
for (const key of path) {
|
|
66
|
-
if (node == null)
|
|
67
|
-
|
|
67
|
+
if (node == null) {
|
|
68
|
+
return undefined as unknown as JxMutableNode;
|
|
69
|
+
}
|
|
70
|
+
// Paths address nodes by construction; non-node leaves surface as undefined above.
|
|
71
|
+
node = node[key] as JxMutableNode;
|
|
68
72
|
}
|
|
69
73
|
return node;
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The node's children when they are a static array (the edit-mode invariant); an empty array for
|
|
78
|
+
* mapped-array or absent children.
|
|
79
|
+
*
|
|
80
|
+
* @param {JxMutableNode | null | undefined} node
|
|
81
|
+
* @returns {(JxMutableNode | string)[]}
|
|
82
|
+
*/
|
|
83
|
+
export function childList(node: JxMutableNode | null | undefined): (JxMutableNode | string)[] {
|
|
84
|
+
return Array.isArray(node?.children) ? node.children : [];
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
/**
|
|
73
88
|
* Return the path to the parent element (strips trailing 'children' + index).
|
|
74
89
|
*
|
|
@@ -86,7 +101,7 @@ export function parentElementPath(path: JxPath) {
|
|
|
86
101
|
* @returns {string | number}
|
|
87
102
|
*/
|
|
88
103
|
export function childIndex(path: JxPath) {
|
|
89
|
-
return path
|
|
104
|
+
return path.at(-1);
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
/**
|
|
@@ -107,8 +122,12 @@ export function pathKey(path: JxPath) {
|
|
|
107
122
|
* @returns {boolean}
|
|
108
123
|
*/
|
|
109
124
|
export function pathsEqual(a: JxPath | null, b: JxPath | null) {
|
|
110
|
-
if (a === b)
|
|
111
|
-
|
|
125
|
+
if (a === b) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (!a || !b || a.length !== b.length) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
112
131
|
return a.every((v, i) => v === b[i]);
|
|
113
132
|
}
|
|
114
133
|
|
|
@@ -120,7 +139,9 @@ export function pathsEqual(a: JxPath | null, b: JxPath | null) {
|
|
|
120
139
|
* @returns {boolean}
|
|
121
140
|
*/
|
|
122
141
|
export function isAncestor(path: JxPath, descendant: JxPath) {
|
|
123
|
-
if (path.length > descendant.length)
|
|
142
|
+
if (path.length > descendant.length) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
124
145
|
return path.every((v, i) => v === descendant[i]);
|
|
125
146
|
}
|
|
126
147
|
|
|
@@ -142,31 +163,31 @@ export function isAncestor(path: JxPath, descendant: JxPath) {
|
|
|
142
163
|
* nodeType: string;
|
|
143
164
|
* }[]}
|
|
144
165
|
*/
|
|
145
|
-
export
|
|
166
|
+
export interface FlatRow {
|
|
146
167
|
node: JxMutableNode | string | number | boolean;
|
|
147
168
|
path: JxPath;
|
|
148
169
|
depth: number;
|
|
149
170
|
nodeType: string;
|
|
150
|
-
}
|
|
171
|
+
}
|
|
151
172
|
|
|
152
173
|
export function flattenTree(
|
|
153
174
|
doc: JxMutableNode | string | number | boolean,
|
|
154
175
|
path: JxPath = [],
|
|
155
|
-
depth
|
|
176
|
+
depth = 0,
|
|
156
177
|
): FlatRow[] {
|
|
157
178
|
// Text node children: bare primitives get a "text" row
|
|
158
179
|
if (typeof doc === "string" || typeof doc === "number" || typeof doc === "boolean") {
|
|
159
|
-
return [{ node: doc,
|
|
180
|
+
return [{ depth, node: doc, nodeType: "text", path }];
|
|
160
181
|
}
|
|
161
182
|
|
|
162
|
-
const rows: FlatRow[] = [{ node: doc,
|
|
183
|
+
const rows: FlatRow[] = [{ depth, node: doc, nodeType: "element", path }];
|
|
163
184
|
|
|
164
185
|
// Custom component instances without user-authored children are atomic in the layer tree
|
|
165
186
|
if (doc.$props && (doc.tagName || "").includes("-") && !Array.isArray(doc.children)) {
|
|
166
187
|
return rows;
|
|
167
188
|
}
|
|
168
189
|
|
|
169
|
-
const children = doc
|
|
190
|
+
const { children } = doc;
|
|
170
191
|
|
|
171
192
|
if (Array.isArray(children)) {
|
|
172
193
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -180,10 +201,10 @@ export function flattenTree(
|
|
|
180
201
|
) {
|
|
181
202
|
// $map — emit the map container, then recurse into the template
|
|
182
203
|
rows.push({
|
|
183
|
-
node: children as JxMutableNode,
|
|
184
|
-
path: [...path, "children"],
|
|
185
204
|
depth: depth + 1,
|
|
205
|
+
node: children as JxMutableNode,
|
|
186
206
|
nodeType: "map",
|
|
207
|
+
path: [...path, "children"],
|
|
187
208
|
});
|
|
188
209
|
const mapDef = (children as JxMutableNode).map;
|
|
189
210
|
if (mapDef && typeof mapDef === "object") {
|
|
@@ -197,17 +218,17 @@ export function flattenTree(
|
|
|
197
218
|
const casePath = [...path, "cases", caseName];
|
|
198
219
|
if (caseDef && typeof caseDef === "object" && (caseDef as JxMutableNode).$ref) {
|
|
199
220
|
rows.push({
|
|
200
|
-
node: caseDef as JxMutableNode,
|
|
201
|
-
path: casePath,
|
|
202
221
|
depth: depth + 1,
|
|
222
|
+
node: caseDef as JxMutableNode,
|
|
203
223
|
nodeType: "case-ref",
|
|
224
|
+
path: casePath,
|
|
204
225
|
});
|
|
205
226
|
} else if (caseDef && typeof caseDef === "object") {
|
|
206
227
|
rows.push({
|
|
207
|
-
node: caseDef as JxMutableNode,
|
|
208
|
-
path: casePath,
|
|
209
228
|
depth: depth + 1,
|
|
229
|
+
node: caseDef as JxMutableNode,
|
|
210
230
|
nodeType: "case",
|
|
231
|
+
path: casePath,
|
|
211
232
|
});
|
|
212
233
|
// Recurse into case children (skip the case node itself — already emitted)
|
|
213
234
|
const caseChildren = flattenTree(caseDef as JxMutableNode, casePath, depth + 2);
|
|
@@ -226,14 +247,21 @@ export function flattenTree(
|
|
|
226
247
|
* @returns {string}
|
|
227
248
|
*/
|
|
228
249
|
export function nodeLabel(node: JxMutableNode | null) {
|
|
229
|
-
if (!node)
|
|
250
|
+
if (!node) {
|
|
251
|
+
return "?";
|
|
252
|
+
}
|
|
230
253
|
// $map container (Repeater)
|
|
231
254
|
if (node.$prototype === "Array") {
|
|
232
|
-
const
|
|
255
|
+
const { items } = node;
|
|
256
|
+
const ref = (isRef(items) ? items.$ref : undefined) || "items";
|
|
233
257
|
return `Repeater → ${ref}`;
|
|
234
258
|
}
|
|
235
|
-
if (node.$title)
|
|
236
|
-
|
|
259
|
+
if (node.$title) {
|
|
260
|
+
return node.$title;
|
|
261
|
+
}
|
|
262
|
+
if (node.$id) {
|
|
263
|
+
return node.$id;
|
|
264
|
+
}
|
|
237
265
|
const tag = node.tagName ?? "div";
|
|
238
266
|
const suffix = node.$switch ? " ⇆" : "";
|
|
239
267
|
if (typeof node.textContent === "string" && node.textContent.length > 0) {
|
|
@@ -251,47 +279,47 @@ export function nodeLabel(node: JxMutableNode | null) {
|
|
|
251
279
|
export function createState(doc: JxMutableNode): StudioState {
|
|
252
280
|
const initial = { document: doc, selection: null };
|
|
253
281
|
return {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
282
|
+
canvas: {
|
|
283
|
+
error: null, // Error message on failure
|
|
284
|
+
scope: null, // $defs scope from runtime buildScope
|
|
285
|
+
status: "idle", // "idle" | "loading" | "ready" | "error"
|
|
286
|
+
},
|
|
287
|
+
content: { frontmatter: {} }, // Frontmatter metadata for .md files
|
|
259
288
|
dirty: false,
|
|
289
|
+
document: doc,
|
|
290
|
+
documentPath: null, // Root-relative path, e.g. "examples/markdown/blog.json"
|
|
291
|
+
documentStack: [], // Frames for component navigation
|
|
260
292
|
fileHandle: null,
|
|
261
|
-
documentPath: null, // root-relative path, e.g. "examples/markdown/blog.json"
|
|
262
|
-
documentStack: [], // frames for component navigation
|
|
263
293
|
handlersSource: null,
|
|
294
|
+
history: [initial],
|
|
295
|
+
historyIndex: 0,
|
|
296
|
+
hover: null,
|
|
264
297
|
mode: "component", // 'component' | 'content'
|
|
265
|
-
|
|
298
|
+
selection: null,
|
|
266
299
|
ui: {
|
|
267
|
-
rightTab: "properties", // 'properties' | 'events' | 'style'
|
|
268
|
-
zoom: 1,
|
|
269
300
|
activeMedia: null, // '--md' | null (base) — focused canvas/breakpoint
|
|
270
301
|
activeSelector: null, // ':hover' | '.child' | null (base) — nested selector context
|
|
302
|
+
editingFunction: null, // Null | { type: 'def', defName } | { type: 'event', path, eventKey }
|
|
271
303
|
featureToggles: {}, // { '--dark': true } — non-size media toggles
|
|
272
|
-
|
|
304
|
+
gitBranches: null, // { current, branches: [] }
|
|
305
|
+
gitCommitMessage: "", // Commit message input
|
|
306
|
+
gitDiffState: null,
|
|
307
|
+
gitError: null, // Error message string
|
|
308
|
+
gitLoading: false, // Loading indicator during async ops
|
|
309
|
+
gitStatus: null, // { branch, ahead, behind, files: [] }
|
|
273
310
|
inspectorSections: {}, // { identity: true, ... } — properties panel section open/closed state
|
|
311
|
+
pendingInlineEdit: null, // Null | { path, mediaName } — deferred inline edit awaiting canvas readiness
|
|
312
|
+
rightTab: "properties", // 'properties' | 'events' | 'style'
|
|
313
|
+
settingsTab: "stylebook", // "stylebook" | "definitions" | "contentTypes"
|
|
314
|
+
styleFilter: "", // Free-text filter for CSS property names
|
|
315
|
+
styleFilterActive: false, // True = show only props with values set
|
|
316
|
+
styleSections: {}, // { layout: true, ... } — section open/closed state
|
|
274
317
|
styleShorthands: {}, // { padding: true, ... } — shorthand expand/collapse state
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
stylebookSelection: null, // tag name string, e.g. "h1"
|
|
318
|
+
stylebookCustomizedOnly: false, // Show only customized elements
|
|
319
|
+
stylebookFilter: "", // Search filter text
|
|
320
|
+
stylebookSelection: null, // Tag name string, e.g. "h1"
|
|
279
321
|
stylebookTab: "elements", // "elements" | "variables"
|
|
280
|
-
|
|
281
|
-
stylebookCustomizedOnly: false, // show only customized elements
|
|
282
|
-
settingsTab: "stylebook", // "stylebook" | "definitions" | "contentTypes"
|
|
283
|
-
gitStatus: null, // { branch, ahead, behind, files: [] }
|
|
284
|
-
gitBranches: null, // { current, branches: [] }
|
|
285
|
-
gitCommitMessage: "", // commit message input
|
|
286
|
-
gitLoading: false, // loading indicator during async ops
|
|
287
|
-
gitError: null, // error message string
|
|
288
|
-
gitDiffState: null,
|
|
289
|
-
pendingInlineEdit: null, // null | { path, mediaName } — deferred inline edit awaiting canvas readiness
|
|
290
|
-
},
|
|
291
|
-
canvas: {
|
|
292
|
-
status: "idle", // "idle" | "loading" | "ready" | "error"
|
|
293
|
-
scope: null, // $defs scope from runtime buildScope
|
|
294
|
-
error: null, // error message on failure
|
|
322
|
+
zoom: 1,
|
|
295
323
|
},
|
|
296
324
|
};
|
|
297
325
|
}
|
|
@@ -334,26 +362,26 @@ export function fromFlat(S: StudioState) {
|
|
|
334
362
|
} = S;
|
|
335
363
|
return {
|
|
336
364
|
doc: {
|
|
337
|
-
|
|
365
|
+
content,
|
|
338
366
|
dirty,
|
|
339
|
-
|
|
367
|
+
document,
|
|
340
368
|
documentPath,
|
|
341
369
|
documentStack,
|
|
370
|
+
fileHandle,
|
|
342
371
|
handlersSource,
|
|
343
|
-
mode,
|
|
344
|
-
content,
|
|
345
372
|
history,
|
|
346
373
|
historyIndex,
|
|
374
|
+
mode,
|
|
347
375
|
},
|
|
348
|
-
session: {
|
|
376
|
+
session: { canvas, hover, selection, ui },
|
|
349
377
|
};
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
// ─── Project state (persists across document switches) ────────────────────────
|
|
353
381
|
//
|
|
354
382
|
// Shape: { root, name, projectRoot, isSiteProject, projectConfig,
|
|
355
|
-
//
|
|
356
|
-
//
|
|
383
|
+
// Dirs: Map<string, DirEntry[]>, expanded: Set<string>,
|
|
384
|
+
// SelectedPath: string|null, searchQuery: string }
|
|
357
385
|
// DirEntry: { name, path, type: "file"|"directory", size, modified }
|
|
358
386
|
|
|
359
387
|
export let projectState: ProjectState | null = null;
|
|
@@ -386,8 +414,11 @@ export function requireProjectState() {
|
|
|
386
414
|
*/
|
|
387
415
|
export function updateFrontmatter(state: StudioState, field: string, value: unknown) {
|
|
388
416
|
const fm = { ...state.content?.frontmatter };
|
|
389
|
-
if (value === undefined || value === null || value === "")
|
|
390
|
-
|
|
417
|
+
if (value === undefined || value === null || value === "") {
|
|
418
|
+
delete fm[field];
|
|
419
|
+
} else {
|
|
420
|
+
fm[field] = value;
|
|
421
|
+
}
|
|
391
422
|
return {
|
|
392
423
|
...state,
|
|
393
424
|
content: { ...state.content, frontmatter: fm },
|
|
@@ -427,14 +458,14 @@ export function hoverNode(state: StudioState, path: JxPath | null) {
|
|
|
427
458
|
*/
|
|
428
459
|
export function pushDocument(state: StudioState, doc: JxMutableNode, documentPath: string | null) {
|
|
429
460
|
const frame = {
|
|
461
|
+
dirty: state.dirty,
|
|
430
462
|
document: state.document,
|
|
431
|
-
selection: state.selection,
|
|
432
|
-
fileHandle: state.fileHandle,
|
|
433
463
|
documentPath: state.documentPath,
|
|
434
|
-
|
|
464
|
+
fileHandle: state.fileHandle,
|
|
435
465
|
history: state.history,
|
|
436
466
|
historyIndex: state.historyIndex,
|
|
437
467
|
mode: state.mode,
|
|
468
|
+
selection: state.selection,
|
|
438
469
|
};
|
|
439
470
|
const newState = createState(doc);
|
|
440
471
|
newState.documentStack = [...(state.documentStack || []), frame];
|
|
@@ -450,7 +481,9 @@ export function pushDocument(state: StudioState, doc: JxMutableNode, documentPat
|
|
|
450
481
|
* @returns {StudioState}
|
|
451
482
|
*/
|
|
452
483
|
export function popDocument(state: StudioState) {
|
|
453
|
-
if (!state.documentStack || state.documentStack.length === 0)
|
|
484
|
+
if (!state.documentStack || state.documentStack.length === 0) {
|
|
485
|
+
return state;
|
|
486
|
+
}
|
|
454
487
|
const stack = [...state.documentStack];
|
|
455
488
|
const frame = stack.pop();
|
|
456
489
|
return {
|
package/src/store.ts
CHANGED
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
// ─── Re-exports from state.js ────────────────────────────────────────────────
|
|
11
11
|
|
|
12
12
|
import { activeTab } from "./workspace/workspace";
|
|
13
|
+
import { isEventBinding } from "@jxsuite/schema/guards";
|
|
13
14
|
import type { JxPath } from "./state";
|
|
14
15
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
16
|
+
import type { CanvasPanel } from "./panels/canvas-dnd.js";
|
|
15
17
|
|
|
16
18
|
export {
|
|
17
19
|
createState,
|
|
18
20
|
getNodeAtPath,
|
|
21
|
+
childList,
|
|
19
22
|
flattenTree,
|
|
20
23
|
nodeLabel,
|
|
21
24
|
pathKey,
|
|
@@ -49,9 +52,9 @@ export function initShellRefs() {
|
|
|
49
52
|
|
|
50
53
|
// ─── Shared containers (mutated in place by owner modules) ───────────────────
|
|
51
54
|
|
|
52
|
-
export const elToPath
|
|
55
|
+
export const elToPath = new WeakMap<Element, JxPath>();
|
|
53
56
|
|
|
54
|
-
export const canvasPanels:
|
|
57
|
+
export const canvasPanels: CanvasPanel[] = [];
|
|
55
58
|
|
|
56
59
|
// ─── Shared constants ────────────────────────────────────────────────────────
|
|
57
60
|
|
|
@@ -101,8 +104,12 @@ const _styleDebounceTimers = new Map();
|
|
|
101
104
|
* @param {number} ms
|
|
102
105
|
* @param {(...args: unknown[]) => void} fn
|
|
103
106
|
*/
|
|
104
|
-
export function debouncedStyleCommit
|
|
105
|
-
|
|
107
|
+
export function debouncedStyleCommit<A extends unknown[]>(
|
|
108
|
+
prop: string,
|
|
109
|
+
ms: number,
|
|
110
|
+
fn: (...args: A) => void,
|
|
111
|
+
) {
|
|
112
|
+
return (...args: A) => {
|
|
106
113
|
clearTimeout(_styleDebounceTimers.get(prop));
|
|
107
114
|
_styleDebounceTimers.set(
|
|
108
115
|
prop,
|
|
@@ -127,30 +134,34 @@ export function cancelStyleDebounce(prop: string) {
|
|
|
127
134
|
* @returns {JxMutableNode}
|
|
128
135
|
*/
|
|
129
136
|
export function stripEventHandlers(node: JxMutableNode): JxMutableNode {
|
|
130
|
-
if (!node || typeof node !== "object")
|
|
131
|
-
|
|
137
|
+
if (!node || typeof node !== "object") {
|
|
138
|
+
return node;
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(node)) {
|
|
141
|
+
// Arrays of nodes round-trip element-wise; the array itself is not a node.
|
|
142
|
+
return node.map((n) => stripEventHandlers(n)) as unknown as JxMutableNode;
|
|
143
|
+
}
|
|
132
144
|
const out: Record<string, unknown> = {};
|
|
133
145
|
for (const [k, v] of Object.entries(node)) {
|
|
134
|
-
if (
|
|
135
|
-
k.startsWith("on") &&
|
|
136
|
-
typeof v === "object" &&
|
|
137
|
-
(v?.$ref || v?.$prototype === "Function" || v?.$expression)
|
|
138
|
-
)
|
|
146
|
+
if (k.startsWith("on") && isEventBinding(v)) {
|
|
139
147
|
continue;
|
|
148
|
+
}
|
|
140
149
|
if (k === "children") {
|
|
141
150
|
out.children = Array.isArray(v)
|
|
142
|
-
? v.map(stripEventHandlers)
|
|
151
|
+
? v.map((c) => stripEventHandlers(c))
|
|
143
152
|
: stripEventHandlers(v as JxMutableNode);
|
|
144
153
|
} else if (k === "cases" && typeof v === "object") {
|
|
145
154
|
const cases: Record<string, unknown> = {};
|
|
146
|
-
for (const [ck, cv] of Object.entries(v as Record<string, unknown>))
|
|
155
|
+
for (const [ck, cv] of Object.entries(v as Record<string, unknown>)) {
|
|
147
156
|
cases[ck] = stripEventHandlers(cv as JxMutableNode);
|
|
157
|
+
}
|
|
148
158
|
out.cases = cases;
|
|
149
159
|
} else if (k === "state" && typeof v === "object" && v !== null) {
|
|
150
160
|
const state: Record<string, unknown> = {};
|
|
151
161
|
for (const [sk, sv] of Object.entries(v as Record<string, unknown>)) {
|
|
152
|
-
if (sv && typeof sv === "object" && (sv as Record<string, unknown>).timing === "server")
|
|
162
|
+
if (sv && typeof sv === "object" && (sv as Record<string, unknown>).timing === "server") {
|
|
153
163
|
continue;
|
|
164
|
+
}
|
|
154
165
|
state[sk] = sv;
|
|
155
166
|
}
|
|
156
167
|
out.state = state;
|
|
@@ -165,7 +176,7 @@ export function stripEventHandlers(node: JxMutableNode): JxMutableNode {
|
|
|
165
176
|
|
|
166
177
|
// ─── Render orchestration ────────────────────────────────────────────────────
|
|
167
178
|
|
|
168
|
-
const _renderers
|
|
179
|
+
const _renderers = new Map<string, () => void>();
|
|
169
180
|
|
|
170
181
|
/**
|
|
171
182
|
* Register a named renderer. Called at module import time by each module.
|
|
@@ -182,8 +193,8 @@ export function render() {
|
|
|
182
193
|
for (const [name, fn] of _renderers.entries()) {
|
|
183
194
|
try {
|
|
184
195
|
fn();
|
|
185
|
-
} catch (
|
|
186
|
-
console.error(`Renderer "${name}" failed:`,
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(`Renderer "${name}" failed:`, error);
|
|
187
198
|
}
|
|
188
199
|
}
|
|
189
200
|
}
|
|
@@ -196,11 +207,13 @@ export function render() {
|
|
|
196
207
|
export function renderOnly(...names: string[]) {
|
|
197
208
|
for (const name of names) {
|
|
198
209
|
const fn = _renderers.get(name);
|
|
199
|
-
if (!fn)
|
|
210
|
+
if (!fn) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
200
213
|
try {
|
|
201
214
|
fn();
|
|
202
|
-
} catch (
|
|
203
|
-
console.error(`Renderer "${name}" failed:`,
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error(`Renderer "${name}" failed:`, error);
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
}
|
|
@@ -221,9 +234,15 @@ export function updateSession(patch: {
|
|
|
221
234
|
}) {
|
|
222
235
|
const tab = activeTab.value;
|
|
223
236
|
if (tab) {
|
|
224
|
-
if (patch.selection !== undefined)
|
|
225
|
-
|
|
226
|
-
|
|
237
|
+
if (patch.selection !== undefined) {
|
|
238
|
+
tab.session.selection = patch.selection as JxPath | null;
|
|
239
|
+
}
|
|
240
|
+
if (patch.hover !== undefined) {
|
|
241
|
+
tab.session.hover = patch.hover as JxPath | null;
|
|
242
|
+
}
|
|
243
|
+
if (patch.clipboard !== undefined) {
|
|
244
|
+
tab.session.clipboard = patch.clipboard!;
|
|
245
|
+
}
|
|
227
246
|
if (patch.ui) {
|
|
228
247
|
for (const [k, v] of Object.entries(patch.ui)) {
|
|
229
248
|
(tab.session.ui as unknown as Record<string, unknown>)[k] = v;
|