@jxsuite/studio 0.0.1
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.css +3676 -0
- package/dist/studio.js +188743 -0
- package/dist/studio.js.map +1448 -0
- package/package.json +67 -0
- package/src/editor/context-menu.js +144 -0
- package/src/editor/inline-edit.js +597 -0
- package/src/editor/inline-format.js +572 -0
- package/src/editor/shortcuts.js +275 -0
- package/src/editor/slash-menu.js +167 -0
- package/src/files/components.js +40 -0
- package/src/files/file-ops.js +195 -0
- package/src/files/files.js +569 -0
- package/src/markdown/md-allowlist.js +101 -0
- package/src/markdown/md-convert.js +491 -0
- package/src/panels/activity-bar.js +69 -0
- package/src/panels/data-explorer.js +181 -0
- package/src/panels/events-panel.js +235 -0
- package/src/panels/imports-panel.js +427 -0
- package/src/panels/signals-panel.js +1093 -0
- package/src/panels/statusbar.js +56 -0
- package/src/platform.js +31 -0
- package/src/platforms/devserver.js +293 -0
- package/src/services/cem-export.js +130 -0
- package/src/services/code-services.js +98 -0
- package/src/site-context.js +122 -0
- package/src/state.js +744 -0
- package/src/store.js +332 -0
- package/src/studio.js +7692 -0
- package/src/ui/icons.js +83 -0
- package/src/ui/jx-styled-combobox.js +142 -0
- package/src/ui/spectrum.js +238 -0
- package/src/utils/studio-utils.js +185 -0
package/src/store.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store.js — Shared state hub for Jx Studio
|
|
3
|
+
*
|
|
4
|
+
* Every other studio module imports from this file for shared state, DOM refs, render
|
|
5
|
+
* orchestration, and state.js re-exports. This prevents circular dependencies by keeping store.js
|
|
6
|
+
* free of domain-specific imports.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ─── Re-exports from state.js ────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
createState,
|
|
13
|
+
selectNode,
|
|
14
|
+
hoverNode,
|
|
15
|
+
undo,
|
|
16
|
+
redo,
|
|
17
|
+
insertNode,
|
|
18
|
+
removeNode,
|
|
19
|
+
duplicateNode,
|
|
20
|
+
moveNode,
|
|
21
|
+
updateProperty,
|
|
22
|
+
updateStyle,
|
|
23
|
+
updateAttribute,
|
|
24
|
+
addDef,
|
|
25
|
+
removeDef,
|
|
26
|
+
updateDef,
|
|
27
|
+
renameDef,
|
|
28
|
+
updateMediaStyle,
|
|
29
|
+
updateMedia,
|
|
30
|
+
updateNestedStyle,
|
|
31
|
+
updateMediaNestedStyle,
|
|
32
|
+
pushDocument,
|
|
33
|
+
popDocument,
|
|
34
|
+
updateProp,
|
|
35
|
+
addSwitchCase,
|
|
36
|
+
removeSwitchCase,
|
|
37
|
+
renameSwitchCase,
|
|
38
|
+
applyMutation,
|
|
39
|
+
getNodeAtPath,
|
|
40
|
+
flattenTree,
|
|
41
|
+
nodeLabel,
|
|
42
|
+
pathKey,
|
|
43
|
+
pathsEqual,
|
|
44
|
+
parentElementPath,
|
|
45
|
+
childIndex,
|
|
46
|
+
isAncestor,
|
|
47
|
+
projectState,
|
|
48
|
+
setProjectState,
|
|
49
|
+
} from "./state.js";
|
|
50
|
+
|
|
51
|
+
// ─── DOM shortcuts & element refs ────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export const $ = (/** @type {string} */ sel) => document.querySelector(sel);
|
|
54
|
+
export const _$$ = (/** @type {string} */ sel) => document.querySelectorAll(sel);
|
|
55
|
+
|
|
56
|
+
export const canvasWrap = /** @type {any} */ (document.querySelector("#canvas-wrap"));
|
|
57
|
+
export const activityBar = /** @type {any} */ (document.querySelector("#activity-bar"));
|
|
58
|
+
export const leftPanel = /** @type {any} */ (document.querySelector("#left-panel"));
|
|
59
|
+
export const rightPanel = /** @type {any} */ (document.querySelector("#right-panel"));
|
|
60
|
+
export const toolbarEl = /** @type {any} */ (document.querySelector("#toolbar"));
|
|
61
|
+
export const statusbarEl = /** @type {any} */ (document.querySelector("#statusbar"));
|
|
62
|
+
|
|
63
|
+
// ─── Shared mutable state container ─────────────────────────────────────────
|
|
64
|
+
// A plain object so all importers share the same reference and see mutations.
|
|
65
|
+
// Used by extracted modules; studio.js keeps local aliases during migration.
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @type {{
|
|
69
|
+
* S: any;
|
|
70
|
+
* canvasMode: string;
|
|
71
|
+
* panX: number;
|
|
72
|
+
* panY: number;
|
|
73
|
+
* panzoomWrap: any;
|
|
74
|
+
* componentInlineEdit: any;
|
|
75
|
+
* pendingInlineEdit: any;
|
|
76
|
+
* monacoEditor: any;
|
|
77
|
+
* functionEditor: any;
|
|
78
|
+
* liveScope: any;
|
|
79
|
+
* blockActionBarEl: any;
|
|
80
|
+
* inlineEditCleanup: any;
|
|
81
|
+
* selDragCleanup: any;
|
|
82
|
+
* componentSlashMenu: any;
|
|
83
|
+
* }}
|
|
84
|
+
*/
|
|
85
|
+
export const ctx = {
|
|
86
|
+
S: undefined,
|
|
87
|
+
canvasMode: "design",
|
|
88
|
+
panX: 0,
|
|
89
|
+
panY: 0,
|
|
90
|
+
panzoomWrap: null,
|
|
91
|
+
componentInlineEdit: null,
|
|
92
|
+
pendingInlineEdit: null,
|
|
93
|
+
monacoEditor: null,
|
|
94
|
+
functionEditor: null,
|
|
95
|
+
liveScope: null,
|
|
96
|
+
blockActionBarEl: null,
|
|
97
|
+
inlineEditCleanup: null,
|
|
98
|
+
selDragCleanup: null,
|
|
99
|
+
componentSlashMenu: null,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ─── Shared containers (mutated in place by owner modules) ───────────────────
|
|
103
|
+
|
|
104
|
+
/** WeakMap<HTMLElement, Array> — maps rendered DOM elements to their JSON paths */
|
|
105
|
+
export const elToPath = new WeakMap();
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Canvas panels: Array<{ mediaName, canvas, overlay, overlayClk, viewport, dropLine }>
|
|
109
|
+
*
|
|
110
|
+
* @type {any[]}
|
|
111
|
+
*/
|
|
112
|
+
export const canvasPanels = [];
|
|
113
|
+
|
|
114
|
+
// ─── Shared constants ────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/** Void elements that cannot accept children */
|
|
117
|
+
export const VOID_ELEMENTS = new Set([
|
|
118
|
+
"area",
|
|
119
|
+
"base",
|
|
120
|
+
"br",
|
|
121
|
+
"col",
|
|
122
|
+
"embed",
|
|
123
|
+
"hr",
|
|
124
|
+
"img",
|
|
125
|
+
"input",
|
|
126
|
+
"link",
|
|
127
|
+
"meta",
|
|
128
|
+
"param",
|
|
129
|
+
"source",
|
|
130
|
+
"track",
|
|
131
|
+
"wbr",
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
export const COMMON_SELECTORS = [
|
|
135
|
+
":hover",
|
|
136
|
+
":focus",
|
|
137
|
+
":active",
|
|
138
|
+
":focus-within",
|
|
139
|
+
":focus-visible",
|
|
140
|
+
":disabled",
|
|
141
|
+
":first-child",
|
|
142
|
+
":last-child",
|
|
143
|
+
"::before",
|
|
144
|
+
"::after",
|
|
145
|
+
"::placeholder",
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
/** @param {any} k */
|
|
149
|
+
export function isNestedSelector(k) {
|
|
150
|
+
return k.startsWith(":") || k.startsWith(".") || k.startsWith("&") || k.startsWith("[");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Shared utilities ────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
const _styleDebounceTimers = new Map();
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {any} prop
|
|
159
|
+
* @param {any} ms
|
|
160
|
+
* @param {any} fn
|
|
161
|
+
*/
|
|
162
|
+
export function debouncedStyleCommit(prop, ms, fn) {
|
|
163
|
+
return (/** @type {any[]} */ ...args) => {
|
|
164
|
+
clearTimeout(_styleDebounceTimers.get(prop));
|
|
165
|
+
_styleDebounceTimers.set(
|
|
166
|
+
prop,
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
_styleDebounceTimers.delete(prop);
|
|
169
|
+
fn(...args);
|
|
170
|
+
}, ms),
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Cancel a pending debounced commit for the given prop key. */
|
|
176
|
+
export function cancelStyleDebounce(/** @type {string} */ prop) {
|
|
177
|
+
clearTimeout(_styleDebounceTimers.get(prop));
|
|
178
|
+
_styleDebounceTimers.delete(prop);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Strip all on* event handler properties from a Jx document tree (deep clone).
|
|
183
|
+
*
|
|
184
|
+
* @param {any} node
|
|
185
|
+
* @returns {any}
|
|
186
|
+
*/
|
|
187
|
+
export function stripEventHandlers(node) {
|
|
188
|
+
if (!node || typeof node !== "object") return node;
|
|
189
|
+
if (Array.isArray(node)) return node.map(stripEventHandlers);
|
|
190
|
+
/** @type {Record<string, any>} */
|
|
191
|
+
const out = {};
|
|
192
|
+
for (const [k, v] of Object.entries(node)) {
|
|
193
|
+
if (k.startsWith("on") && typeof v === "object" && (v?.$ref || v?.$prototype === "Function"))
|
|
194
|
+
continue;
|
|
195
|
+
if (k === "children") {
|
|
196
|
+
out.children = Array.isArray(v) ? v.map(stripEventHandlers) : stripEventHandlers(v);
|
|
197
|
+
} else if (k === "cases" && typeof v === "object") {
|
|
198
|
+
/** @type {Record<string, any>} */
|
|
199
|
+
const cases = {};
|
|
200
|
+
for (const [ck, cv] of Object.entries(v)) cases[ck] = stripEventHandlers(cv);
|
|
201
|
+
out.cases = cases;
|
|
202
|
+
} else if (k === "state" || k === "style" || k === "attributes" || k === "$media") {
|
|
203
|
+
out[k] = v;
|
|
204
|
+
} else {
|
|
205
|
+
out[k] = v;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── Render orchestration ────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
/** @type {Map<string, Function>} */
|
|
214
|
+
const _renderers = new Map();
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Register a named renderer. Called at module import time by each module.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} name
|
|
220
|
+
* @param {Function} fn
|
|
221
|
+
*/
|
|
222
|
+
export function registerRenderer(name, fn) {
|
|
223
|
+
_renderers.set(name, fn);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Call all registered renderers (full repaint). */
|
|
227
|
+
export function render() {
|
|
228
|
+
for (const fn of _renderers.values()) fn();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Call specific renderers by name.
|
|
233
|
+
*
|
|
234
|
+
* @param {...string} names
|
|
235
|
+
*/
|
|
236
|
+
export function renderOnly(...names) {
|
|
237
|
+
for (const name of names) {
|
|
238
|
+
const fn = _renderers.get(name);
|
|
239
|
+
if (fn) fn();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ─── Update dispatch (late-bound) ────────────────────────────────────────────
|
|
244
|
+
// studio.js registers the real update implementation via setUpdateFn() during bootstrap.
|
|
245
|
+
// This allows extracted modules to import `update` from store.js without circular deps.
|
|
246
|
+
|
|
247
|
+
/** @type {Function} */
|
|
248
|
+
let _updateFn = () => {
|
|
249
|
+
throw new Error("update() called before setUpdateFn() — bootstrap not complete");
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/** @type {Function} */
|
|
253
|
+
let _getStateFn = () => null;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Register the update implementation. Called by studio.js at module load time.
|
|
257
|
+
*
|
|
258
|
+
* @param {Function} fn
|
|
259
|
+
*/
|
|
260
|
+
export function setUpdateFn(fn) {
|
|
261
|
+
_updateFn = fn;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Register the state getter. Called by studio.js at module load time.
|
|
266
|
+
*
|
|
267
|
+
* @param {Function} fn — returns current S
|
|
268
|
+
*/
|
|
269
|
+
export function setGetStateFn(fn) {
|
|
270
|
+
_getStateFn = fn;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get the current state (live, not stale).
|
|
275
|
+
*
|
|
276
|
+
* @returns {any}
|
|
277
|
+
*/
|
|
278
|
+
export function getState() {
|
|
279
|
+
return _getStateFn();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Dispatch a state update + selective re-render.
|
|
284
|
+
*
|
|
285
|
+
* @param {any} newState
|
|
286
|
+
*/
|
|
287
|
+
export function update(newState) {
|
|
288
|
+
_updateFn(newState);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** @type {Function[]} */
|
|
292
|
+
const _updateMiddleware = [];
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Register middleware that runs after every update().
|
|
296
|
+
*
|
|
297
|
+
* @param {Function} fn — receives (state) after core update
|
|
298
|
+
*/
|
|
299
|
+
export function addUpdateMiddleware(fn) {
|
|
300
|
+
_updateMiddleware.push(fn);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Run all registered update middleware.
|
|
305
|
+
*
|
|
306
|
+
* @param {any} state
|
|
307
|
+
*/
|
|
308
|
+
export function runUpdateMiddleware(state) {
|
|
309
|
+
for (const mw of _updateMiddleware) mw(state);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** @type {Function[]} */
|
|
313
|
+
const _postRenderHooks = [];
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Register a hook that runs after renders in update().
|
|
317
|
+
*
|
|
318
|
+
* @param {Function} fn — receives (prevDoc, prevSel)
|
|
319
|
+
*/
|
|
320
|
+
export function addPostRenderHook(fn) {
|
|
321
|
+
_postRenderHooks.push(fn);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Run all registered post-render hooks.
|
|
326
|
+
*
|
|
327
|
+
* @param {any} prevDoc
|
|
328
|
+
* @param {any} prevSel
|
|
329
|
+
*/
|
|
330
|
+
export function runPostRenderHooks(prevDoc, prevSel) {
|
|
331
|
+
for (const hook of _postRenderHooks) hook(prevDoc, prevSel);
|
|
332
|
+
}
|