@jxsuite/studio 0.33.0 → 0.35.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 (106) hide show
  1. package/dist/iframe-entry.js +6238 -0
  2. package/dist/iframe-entry.js.map +35 -0
  3. package/dist/studio.js +30972 -17222
  4. package/dist/studio.js.map +328 -83
  5. package/package.json +9 -9
  6. package/src/browse/browse.ts +11 -4
  7. package/src/canvas/canvas-helpers.ts +2 -56
  8. package/src/canvas/canvas-live-render.ts +102 -435
  9. package/src/canvas/canvas-origin.ts +66 -0
  10. package/src/canvas/canvas-patcher.ts +63 -403
  11. package/src/canvas/canvas-render.ts +70 -212
  12. package/src/canvas/canvas-utils.ts +37 -65
  13. package/src/canvas/iframe-channel.ts +154 -0
  14. package/src/canvas/iframe-drop.ts +484 -0
  15. package/src/canvas/iframe-entry.ts +600 -0
  16. package/src/canvas/iframe-host.ts +1373 -0
  17. package/src/canvas/iframe-inline-edit.ts +367 -0
  18. package/src/canvas/iframe-insert.ts +164 -0
  19. package/src/canvas/iframe-interaction.ts +176 -0
  20. package/src/canvas/iframe-keys.ts +85 -0
  21. package/src/canvas/iframe-overlay.ts +218 -0
  22. package/src/canvas/iframe-patch.ts +363 -0
  23. package/src/canvas/iframe-protocol.ts +361 -0
  24. package/src/canvas/iframe-render.ts +458 -0
  25. package/src/canvas/iframe-slash.ts +114 -0
  26. package/src/canvas/iframe-subtree.ts +113 -0
  27. package/src/canvas/path-mapping.ts +86 -0
  28. package/src/canvas/serialize-scope.ts +65 -0
  29. package/src/editor/canvas-context-menu.ts +40 -0
  30. package/src/editor/canvas-slash-bridge.ts +21 -0
  31. package/src/editor/context-menu.ts +2 -1
  32. package/src/editor/inline-edit-apply.ts +183 -0
  33. package/src/editor/inline-edit.ts +99 -21
  34. package/src/editor/inline-link.ts +89 -0
  35. package/src/editor/insert-zone-action.ts +35 -0
  36. package/src/editor/merge-tags.ts +26 -2
  37. package/src/editor/repeater-scope.ts +144 -0
  38. package/src/editor/shortcuts.ts +14 -28
  39. package/src/editor/slash-menu.ts +73 -42
  40. package/src/files/files.ts +5 -1
  41. package/src/new-project/design-fields.ts +260 -0
  42. package/src/new-project/import-tab.ts +322 -0
  43. package/src/new-project/new-project-modal.ts +472 -89
  44. package/src/new-project/templates.ts +61 -0
  45. package/src/packages/ensure-deps.ts +6 -0
  46. package/src/packages/pull-package-sync.ts +339 -0
  47. package/src/page-params.ts +383 -0
  48. package/src/panels/ai-chat/attached-context.ts +49 -0
  49. package/src/panels/ai-chat/chat-markdown.ts +38 -0
  50. package/src/panels/ai-chat/chat-view.ts +250 -0
  51. package/src/panels/ai-chat/composer.ts +315 -0
  52. package/src/panels/ai-chat/sessions-view.ts +98 -0
  53. package/src/panels/ai-panel.ts +189 -457
  54. package/src/panels/block-action-bar.ts +296 -138
  55. package/src/panels/canvas-dnd-bridge.ts +397 -0
  56. package/src/panels/component-preview.ts +56 -0
  57. package/src/panels/dnd.ts +41 -17
  58. package/src/panels/drag-ghost.ts +62 -0
  59. package/src/panels/editors.ts +1 -1
  60. package/src/panels/git-panel.ts +16 -1
  61. package/src/panels/overlays.ts +10 -125
  62. package/src/panels/properties-panel.ts +210 -0
  63. package/src/panels/right-panel.ts +21 -7
  64. package/src/panels/signals-panel.ts +136 -22
  65. package/src/panels/stylebook-doc.ts +373 -0
  66. package/src/panels/stylebook-panel.ts +46 -689
  67. package/src/panels/tab-bar.ts +159 -13
  68. package/src/panels/toolbar.ts +3 -2
  69. package/src/platforms/devserver.ts +45 -1
  70. package/src/services/agent-seed.ts +91 -0
  71. package/src/services/ai-models.ts +64 -0
  72. package/src/services/ai-session-store.ts +250 -0
  73. package/src/services/ai-settings.ts +5 -0
  74. package/src/services/automation.ts +140 -0
  75. package/src/services/document-assistant.ts +98 -38
  76. package/src/services/import-client.ts +134 -0
  77. package/src/services/monaco-setup.ts +12 -0
  78. package/src/services/render-critic.ts +9 -9
  79. package/src/services/settings-store.ts +93 -0
  80. package/src/settings/css-vars-editor.ts +2 -2
  81. package/src/store.ts +4 -62
  82. package/src/studio.ts +115 -40
  83. package/src/tabs/doc-op-apply.ts +89 -0
  84. package/src/tabs/patch-ops.ts +6 -2
  85. package/src/tabs/tab.ts +23 -4
  86. package/src/tabs/transact.ts +2 -74
  87. package/src/types.ts +98 -18
  88. package/src/ui/ai-credentials-form.ts +205 -0
  89. package/src/ui/jx-theme.ts +63 -0
  90. package/src/ui/media-picker.ts +6 -4
  91. package/src/ui/spectrum.ts +13 -0
  92. package/src/utils/canvas-media.ts +0 -137
  93. package/src/utils/edit-display.ts +23 -3
  94. package/src/utils/geometry.ts +43 -0
  95. package/src/utils/link-target.ts +93 -0
  96. package/src/utils/strip-events.ts +54 -0
  97. package/src/view.ts +0 -23
  98. package/src/workspace/workspace.ts +14 -1
  99. package/src/canvas/canvas-subtree-render.ts +0 -113
  100. package/src/editor/component-inline-edit.ts +0 -349
  101. package/src/editor/content-inline-edit.ts +0 -207
  102. package/src/editor/insertion-helper.ts +0 -308
  103. package/src/panels/canvas-dnd.ts +0 -329
  104. package/src/panels/panel-events.ts +0 -306
  105. package/src/panels/preview-render.ts +0 -132
  106. package/src/panels/pseudo-preview.ts +0 -75
@@ -1,306 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * Panel events — extracted from studio.js (Phase 4m). Unified event handler system for canvas
4
- * panels: click-to-select, double-click inline edit, context menu, hover tracking, insertion
5
- * helper.
6
- */
7
-
8
- import {
9
- childIndex,
10
- elToPath,
11
- getNodeAtPath,
12
- parentElementPath,
13
- pathsEqual,
14
- renderOnly,
15
- updateUi,
16
- } from "../store";
17
- import { activeTab } from "../workspace/workspace";
18
- import { view } from "../view";
19
- import { isEditableBlock, isEditing, stopEditing } from "../editor/inline-edit";
20
- import { showContextMenu } from "../editor/context-menu";
21
- import * as insertionHelper from "../editor/insertion-helper";
22
- import { defaultDef } from "../panels/shared";
23
- import {
24
- bubbleInlinePath,
25
- effectiveZoom,
26
- findCanvasElement,
27
- panelMediaToActiveMedia,
28
- } from "../canvas/canvas-helpers";
29
- import { activeLayoutPath, layoutElements } from "../canvas/canvas-live-render";
30
- import type { CanvasPanel } from "./canvas-dnd";
31
- import type { JxPath } from "../state";
32
-
33
- interface PanelEventsCtx {
34
- getCanvasMode: () => string;
35
- enterInlineEdit: (el: HTMLElement, path: JxPath) => void;
36
- navigateToComponent: (path: string) => void;
37
- }
38
-
39
- let _ctx: PanelEventsCtx | null = null;
40
-
41
- /**
42
- * Initialize the panel events module.
43
- *
44
- * @param {PanelEventsCtx} ctx
45
- */
46
- export function initPanelEvents(ctx: PanelEventsCtx) {
47
- _ctx = ctx;
48
- }
49
-
50
- /** @param {import("../canvas/canvas-render.js").CanvasPanel} panel */
51
- export function registerPanelEvents(panel: CanvasPanel) {
52
- const ctx = _ctx as PanelEventsCtx;
53
- const canvas = panel.canvas as HTMLElement;
54
- const overlayClk = panel.overlayClk as HTMLElement;
55
- const { mediaName } = panel;
56
- const ac = new AbortController();
57
- const opts = { signal: ac.signal };
58
- view.canvasEventCleanups.push(() => ac.abort());
59
-
60
- /** @param {() => unknown} fn */
61
- function withPanelPointerEvents(fn: () => unknown) {
62
- const els = canvas.querySelectorAll("*") as NodeListOf<HTMLElement>;
63
- for (const el of els) {
64
- el.style.pointerEvents = "auto";
65
- }
66
- overlayClk.style.display = "none";
67
- const result = fn();
68
- overlayClk.style.display = "";
69
- for (const el of els) {
70
- el.style.pointerEvents = "none";
71
- }
72
- return result;
73
- }
74
-
75
- overlayClk.addEventListener(
76
- "click",
77
- (e: MouseEvent) => {
78
- const barInner = view.blockActionBarEl?.firstElementChild;
79
- if (barInner) {
80
- const r = barInner.getBoundingClientRect();
81
- if (
82
- e.clientX >= r.left &&
83
- e.clientX <= r.right &&
84
- e.clientY >= r.top &&
85
- e.clientY <= r.bottom
86
- ) {
87
- return;
88
- }
89
- }
90
- if (isEditing()) {
91
- stopEditing();
92
- }
93
-
94
- const tab = activeTab.value;
95
- const canvasMode = ctx.getCanvasMode();
96
-
97
- const elements = withPanelPointerEvents(() =>
98
- document.elementsFromPoint(e.clientX, e.clientY),
99
- ) as Element[];
100
-
101
- if (!tab) {
102
- return;
103
- }
104
-
105
- for (const el of elements) {
106
- if (canvas.contains(el) && el !== canvas) {
107
- // Layout element clicked — show layout info instead of selecting in page doc
108
- if (layoutElements.has(el)) {
109
- view.layoutSelection = { el, layoutPath: activeLayoutPath };
110
- tab.session.selection = null;
111
- renderOnly("rightPanel");
112
- return;
113
- }
114
- view.layoutSelection = null;
115
-
116
- const originalPath = elToPath.get(el);
117
- if (originalPath) {
118
- const path = bubbleInlinePath(tab.doc.document, originalPath);
119
- const newMedia = panelMediaToActiveMedia(mediaName);
120
-
121
- const resolvedEl = (
122
- path === originalPath
123
- ? el
124
- : (findCanvasElement(path, canvas) as HTMLElement | null) || el
125
- ) as HTMLElement;
126
-
127
- if (
128
- pathsEqual(path, tab.session.selection) &&
129
- isEditableBlock(resolvedEl) &&
130
- (canvasMode === "edit" || tab.doc.mode === "content")
131
- ) {
132
- tab.session.ui.activeMedia = newMedia;
133
- ctx.enterInlineEdit(resolvedEl, path);
134
- return;
135
- }
136
-
137
- if (canvasMode === "design" && tab.doc.mode !== "content") {
138
- updateUi("pendingInlineEdit", { mediaName, path });
139
- tab.session.ui.activeMedia = newMedia;
140
- tab.session.selection = path;
141
- return;
142
- }
143
-
144
- tab.session.ui.activeMedia = newMedia;
145
- tab.session.selection = path;
146
- return;
147
- }
148
- }
149
- }
150
- tab.session.selection = null;
151
- },
152
- opts,
153
- );
154
-
155
- overlayClk.addEventListener(
156
- "dblclick",
157
- (e: MouseEvent) => {
158
- const barInner = view.blockActionBarEl?.firstElementChild;
159
- if (barInner) {
160
- const r = barInner.getBoundingClientRect();
161
- if (
162
- e.clientX >= r.left &&
163
- e.clientX <= r.right &&
164
- e.clientY >= r.top &&
165
- e.clientY <= r.bottom
166
- ) {
167
- return;
168
- }
169
- }
170
- const canvasMode = ctx.getCanvasMode();
171
- if (canvasMode !== "edit" && canvasMode !== "design") {
172
- return;
173
- }
174
-
175
- const tab = activeTab.value;
176
- const elements = withPanelPointerEvents(() =>
177
- document.elementsFromPoint(e.clientX, e.clientY),
178
- ) as Element[];
179
-
180
- if (!tab) {
181
- return;
182
- }
183
-
184
- for (const el of elements) {
185
- if (canvas.contains(el) && el !== canvas) {
186
- const originalPath = elToPath.get(el);
187
- if (originalPath) {
188
- const path = bubbleInlinePath(tab.doc.document, originalPath);
189
- const resolvedEl = (
190
- path === originalPath
191
- ? el
192
- : (findCanvasElement(path, canvas) as HTMLElement | null) || el
193
- ) as HTMLElement;
194
- if (isEditableBlock(resolvedEl)) {
195
- const newMedia = panelMediaToActiveMedia(mediaName);
196
- tab.session.ui.activeMedia = newMedia;
197
- tab.session.selection = path;
198
- ctx.enterInlineEdit(resolvedEl, path);
199
- return;
200
- }
201
- }
202
- }
203
- }
204
- },
205
- opts,
206
- );
207
-
208
- overlayClk.addEventListener(
209
- "contextmenu",
210
- (e: MouseEvent) => {
211
- const barInner = view.blockActionBarEl?.firstElementChild;
212
- if (barInner) {
213
- const r = barInner.getBoundingClientRect();
214
- if (
215
- e.clientX >= r.left &&
216
- e.clientX <= r.right &&
217
- e.clientY >= r.top &&
218
- e.clientY <= r.bottom
219
- ) {
220
- return;
221
- }
222
- }
223
- const tab = activeTab.value;
224
- const elements = withPanelPointerEvents(() =>
225
- document.elementsFromPoint(e.clientX, e.clientY),
226
- ) as Element[];
227
- for (const el of elements) {
228
- if (canvas.contains(el) && el !== canvas) {
229
- let path = elToPath.get(el);
230
- if (path) {
231
- path = bubbleInlinePath(tab?.doc.document, path);
232
- showContextMenu(e, path, {
233
- onEditComponent: ctx.navigateToComponent,
234
- });
235
- return;
236
- }
237
- }
238
- }
239
- e.preventDefault();
240
- },
241
- opts,
242
- );
243
-
244
- overlayClk.addEventListener(
245
- "mousemove",
246
- (e: MouseEvent) => {
247
- const barInner = view.blockActionBarEl?.firstElementChild;
248
- if (barInner) {
249
- const r = barInner.getBoundingClientRect();
250
- if (
251
- e.clientX >= r.left &&
252
- e.clientX <= r.right &&
253
- e.clientY >= r.top &&
254
- e.clientY <= r.bottom
255
- ) {
256
- return;
257
- }
258
- }
259
- const tab = activeTab.value;
260
- if (!tab) {
261
- return;
262
- }
263
- const el = withPanelPointerEvents(() =>
264
- document.elementFromPoint(e.clientX, e.clientY),
265
- ) as Element | null;
266
- if (el && canvas.contains(el) && el !== canvas) {
267
- let path = elToPath.get(el);
268
- if (path) {
269
- path = bubbleInlinePath(tab.doc.document, path);
270
- if (!pathsEqual(path, tab.session.hover)) {
271
- tab.session.hover = path;
272
- renderOnly("overlays");
273
- }
274
- }
275
- } else if (tab.session.hover) {
276
- tab.session.hover = null;
277
- renderOnly("overlays");
278
- }
279
- },
280
- opts,
281
- );
282
-
283
- overlayClk.addEventListener(
284
- "mouseleave",
285
- () => {
286
- if (activeTab.value?.session.hover) {
287
- activeTab.value.session.hover = null;
288
- renderOnly("overlays");
289
- }
290
- },
291
- opts,
292
- );
293
-
294
- insertionHelper.mount({
295
- childIndex: (path: JxPath) => childIndex(path) ?? 0,
296
- defaultDef,
297
- effectiveZoom,
298
- elToPath,
299
- getCanvasMode: ctx.getCanvasMode,
300
- getNodeAtPath,
301
- panel: panel as unknown as Parameters<typeof insertionHelper.mount>[0]["panel"],
302
- parentElementPath,
303
- withPanelPointerEvents,
304
- });
305
- view.canvasEventCleanups.push(() => insertionHelper.unmount());
306
- }
@@ -1,132 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * Preview render — extracted from studio.js (Phase 4m). Structural preview renderer that creates
4
- * DOM from Jx node trees as a fallback when runtime rendering fails.
5
- */
6
-
7
- import { elToPath } from "../store";
8
- import { isMappedArray, isRef } from "@jxsuite/schema/guards";
9
- import { activeTab } from "../workspace/workspace";
10
- import { applyCanvasStyle } from "../utils/canvas-media";
11
- import { resolveDefaultForCanvas } from "../panels/signals-panel";
12
- import type { JxPath } from "../state";
13
- import type { JxMutableNode } from "@jxsuite/schema/types";
14
-
15
- /**
16
- * Recursively render a Jx node to the canvas DOM. Media-aware: applies base styles + active
17
- * breakpoint/feature overrides.
18
- *
19
- * @param {JxMutableNode | string | number | boolean | null | undefined} node
20
- * @param {JxPath} path
21
- * @param {HTMLElement} parent
22
- * @param {Set<string>} activeBreakpoints
23
- * @param {Record<string, boolean>} featureToggles
24
- */
25
- export function renderCanvasNode(
26
- node: JxMutableNode | string | number | boolean | null | undefined,
27
- path: JxPath,
28
- parent: HTMLElement,
29
- activeBreakpoints: Set<string>,
30
- featureToggles: Record<string, boolean>,
31
- ) {
32
- if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
33
- parent.append(document.createTextNode(String(node)));
34
- return;
35
- }
36
- if (!node || typeof node !== "object") {
37
- return;
38
- }
39
-
40
- // Array pseudo-element: render as a repeater perimeter at the array's own path, with a single
41
- // Template instance inside (matches the edit-mode visual).
42
- if (isMappedArray(node)) {
43
- const wrapper = document.createElement("div");
44
- wrapper.className = "repeater-perimeter";
45
- elToPath.set(wrapper, path);
46
- const template = node.map;
47
- if (template && typeof template === "object") {
48
- renderCanvasNode(template, [...path, "map"], wrapper, activeBreakpoints, featureToggles);
49
- }
50
- wrapper.style.pointerEvents = "none";
51
- parent.append(wrapper);
52
- return wrapper;
53
- }
54
-
55
- const tag = node.tagName || "div";
56
- const el = document.createElement(tag);
57
-
58
- elToPath.set(el, path);
59
-
60
- if (typeof node.textContent === "string") {
61
- el.textContent = node.textContent;
62
- } else if (isRef(node.textContent)) {
63
- const tc = node.textContent;
64
- const resolved = resolveDefaultForCanvas(tc, activeTab.value?.doc.document?.state);
65
- el.textContent = typeof resolved === "string" ? resolved : null;
66
- el.style.opacity = "0.7";
67
- el.style.fontStyle = "italic";
68
- el.title = `Bound: ${tc.$ref}`;
69
- }
70
-
71
- if (node.id) {
72
- el.id = node.id;
73
- }
74
- if (node.className) {
75
- el.className = node.className;
76
- }
77
-
78
- applyCanvasStyle(el, node.style, activeBreakpoints, featureToggles);
79
-
80
- if (node.attributes && typeof node.attributes === "object") {
81
- for (const [attr, val] of Object.entries(node.attributes)) {
82
- try {
83
- if (isRef(val)) {
84
- const resolved = resolveDefaultForCanvas(val, activeTab.value?.doc.document?.state);
85
- el.setAttribute(attr, String(resolved ?? ""));
86
- } else {
87
- el.setAttribute(attr, String(val));
88
- }
89
- } catch {}
90
- }
91
- }
92
-
93
- if (Array.isArray(node.children)) {
94
- for (let i = 0; i < node.children.length; i++) {
95
- renderCanvasNode(
96
- node.children[i],
97
- [...path, "children", i],
98
- el,
99
- activeBreakpoints,
100
- featureToggles,
101
- );
102
- }
103
- } else if (isMappedArray(node.children)) {
104
- const template = node.children.map;
105
- if (template && typeof template === "object") {
106
- const wrapper = document.createElement("div");
107
- wrapper.className = "repeater-perimeter";
108
- elToPath.set(wrapper, [...path, "children"]);
109
- renderCanvasNode(
110
- template,
111
- [...path, "children", "map"],
112
- wrapper,
113
- activeBreakpoints,
114
- featureToggles,
115
- );
116
- el.append(wrapper);
117
- }
118
- }
119
-
120
- if (node.$switch && node.cases && typeof node.cases === "object") {
121
- const keys = Object.keys(node.cases);
122
- const placeholder = document.createElement("div");
123
- placeholder.textContent = `[$switch: ${keys.join(" | ")}]`;
124
- placeholder.style.cssText =
125
- "font-family:var(--font-mono);font-size:var(--spectrum-font-size-50,11px);padding:6px 10px;background:color-mix(in srgb, var(--danger) 8%, transparent);border:1px dashed color-mix(in srgb, var(--danger) 40%, transparent);border-radius:var(--radius);color:var(--danger);font-style:italic";
126
- el.append(placeholder);
127
- }
128
-
129
- el.style.pointerEvents = "none";
130
- parent.append(el);
131
- return el;
132
- }
@@ -1,75 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * Pseudo-state preview — extracted from studio.js (Phase 4m). When a pseudo-selector (:hover,
4
- * :focus, etc.) is active in the style sidebar, force those styles onto the selected element.
5
- */
6
-
7
- import { getNodeAtPath } from "../store";
8
- import { getNestedStyle } from "@jxsuite/schema/guards";
9
- import { activeTab } from "../workspace/workspace";
10
- import { view } from "../view";
11
- import { findCanvasElement, getActivePanel } from "../canvas/canvas-helpers";
12
-
13
- import type { JxStyle } from "@jxsuite/schema/types";
14
-
15
- const pseudoStyleHost = document.createElement("div");
16
- pseudoStyleHost.style.display = "contents";
17
- (document.querySelector("sp-theme") || document.body).append(pseudoStyleHost);
18
-
19
- export function updateForcedPseudoPreview() {
20
- if (view.forcedStyleTag) {
21
- view.forcedStyleTag.remove();
22
- view.forcedStyleTag = null;
23
- }
24
- if (view.forcedAttrEl) {
25
- delete view.forcedAttrEl.dataset.studioForced;
26
- view.forcedAttrEl = null;
27
- }
28
-
29
- const tab = activeTab.value;
30
- const sel = tab?.session.ui?.activeSelector;
31
- if (!sel || !sel.startsWith(":") || !tab?.session.selection) {
32
- return;
33
- }
34
-
35
- const panel = getActivePanel();
36
- if (!panel) {
37
- return;
38
- }
39
- const el = findCanvasElement(tab.session.selection, panel.canvas);
40
- if (!el) {
41
- return;
42
- }
43
-
44
- const node = getNodeAtPath(tab.doc.document, tab.session.selection);
45
- if (!node?.style) {
46
- return;
47
- }
48
- const { activeMedia } = tab.session.ui;
49
- const ctx: JxStyle = activeMedia
50
- ? (getNestedStyle(node.style, `@${activeMedia}`) ?? {})
51
- : node.style;
52
- const rules = getNestedStyle(ctx, sel);
53
- if (!rules) {
54
- return;
55
- }
56
-
57
- const cssProps = Object.entries(rules)
58
- .filter(([k]) => typeof rules[k] === "string" || typeof rules[k] === "number")
59
- .map(
60
- ([k, v]) =>
61
- `${k.replaceAll(/[A-Z]/g, (c: string) => `-${c.toLowerCase()}`)}: ${v} !important`,
62
- )
63
- .join("; ");
64
- if (!cssProps) {
65
- return;
66
- }
67
-
68
- el.dataset.studioForced = "1";
69
- view.forcedAttrEl = el;
70
-
71
- const tag = document.createElement("style");
72
- tag.textContent = `[data-studio-forced] { ${cssProps} }`;
73
- pseudoStyleHost.append(tag);
74
- view.forcedStyleTag = tag;
75
- }