@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
@@ -7,7 +7,7 @@ import { activeTab } from "../workspace/workspace.js";
7
7
  // ─── Module state ────────────────────────────────────────────────────────────
8
8
 
9
9
  let statusMsg = "";
10
- /** @type {any} */
10
+ /** @type {ReturnType<typeof setTimeout> | undefined} */
11
11
  let statusTimeout;
12
12
  /** @type {(() => void) | null} */
13
13
  let _rerender = null;
@@ -38,16 +38,19 @@ export function mountStatusbar() {
38
38
  renderStatusbar();
39
39
  });
40
40
  });
41
+
42
+ statusbarEl?.addEventListener("click", _onStatusbarClick);
41
43
  }
42
44
 
43
45
  export function unmountStatusbar() {
44
46
  _scope?.stop();
45
47
  _scope = null;
48
+ statusbarEl?.removeEventListener("click", _onStatusbarClick);
46
49
  }
47
50
 
48
51
  // ─── Statusbar ───────────────────────────────────────────────────────────────
49
52
 
50
- /** @param {any} text */
53
+ /** @param {string} text */
51
54
  function esc(text) {
52
55
  return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53
56
  }
@@ -58,7 +61,7 @@ export function renderStatusbar() {
58
61
  const parts = [];
59
62
  if (tab?.doc.mode === "content") parts.push("Content Mode");
60
63
  if (tab?.session.selection?.length) {
61
- const sel = /** @type {any[]} */ (tab.session.selection);
64
+ const sel = /** @type {JxPath} */ (tab.session.selection);
62
65
  const node = getNodeAtPath(tab.doc.document, sel);
63
66
  parts.push(`Selected: ${esc(nodeLabel(node))}`);
64
67
 
@@ -81,7 +84,8 @@ export function renderStatusbar() {
81
84
  statusbarEl.innerHTML = parts.join(" | ") || "Jx Studio";
82
85
  }
83
86
 
84
- statusbarEl?.addEventListener("click", (e) => {
87
+ /** @param {Event} e */
88
+ function _onStatusbarClick(e) {
85
89
  const target = /** @type {HTMLElement} */ (e.target);
86
90
  if (!target.classList.contains("sb-path-seg")) return;
87
91
  const pathStr = target.dataset.path;
@@ -93,12 +97,12 @@ statusbarEl?.addEventListener("click", (e) => {
93
97
  } catch {
94
98
  // ignore
95
99
  }
96
- });
100
+ }
97
101
 
98
102
  /**
99
103
  * Show a temporary status message.
100
104
  *
101
- * @param {any} msg
105
+ * @param {string} msg
102
106
  * @param {number} [duration]
103
107
  */
104
108
  export function statusMessage(msg, duration = 3000) {
@@ -18,7 +18,8 @@ import {
18
18
  /**
19
19
  * Dual-mode keyword input — shared by select (enum) and combobox (examples) widgets.
20
20
  *
21
- * @param {any} options @param {any} prop @param {any} value @param {any} onChange
21
+ * @param {string[]} options @param {string} prop @param {string} value @param {(value: string) =>
22
+ * void} onChange
22
23
  */
23
24
  export function renderKeywordInput(options, prop, value, onChange) {
24
25
  const cssInitialMap = getCssInitialMap();
@@ -26,10 +27,10 @@ export function renderKeywordInput(options, prop, value, onChange) {
26
27
  const font = isTypoPreview ? currentFontFamily() : "";
27
28
  const cssProp = isTypoPreview ? camelToKebab(prop) : "";
28
29
 
29
- const comboOptions = options.map((/** @type {any} */ v) => {
30
+ const comboOptions = options.map((/** @type {string} */ v) => {
30
31
  const label = v.includes("-")
31
32
  ? kebabToLabel(v)
32
- : v.replace(/^./, (/** @type {any} */ c) => c.toUpperCase());
33
+ : v.replace(/^./, (/** @type {string} */ c) => c.toUpperCase());
33
34
  const style = isTypoPreview ? `${cssProp}: ${v};${font ? ` font-family: ${font}` : ""}` : "";
34
35
  return { value: v, label, style };
35
36
  });
@@ -39,19 +40,23 @@ export function renderKeywordInput(options, prop, value, onChange) {
39
40
  .value=${value || ""}
40
41
  placeholder=${cssInitialMap.get(prop) || ""}
41
42
  .options=${comboOptions}
42
- @change=${(/** @type {any} */ e) => onChange(e.target.value)}
43
- @input=${debouncedStyleCommit(`kw:${prop}`, 400, (/** @type {any} */ e) =>
44
- onChange(e.target.value),
43
+ @change=${(/** @type {Event} */ e) =>
44
+ onChange(/** @type {HTMLInputElement} */ (e.target).value)}
45
+ @input=${debouncedStyleCommit(`kw:${prop}`, 400, (/** @type {Event} */ e) =>
46
+ onChange(/** @type {HTMLInputElement} */ (e.target).value),
45
47
  )}
46
48
  ></jx-value-selector>`;
47
49
  }
48
50
 
49
- /** @param {any} entry @param {any} prop @param {any} value @param {any} onChange */
51
+ /**
52
+ * @param {{ enum?: string[] }} entry @param {string} prop @param {string} value @param {(value:
53
+ * string) => void} onChange
54
+ */
50
55
  export function renderSelectInput(entry, prop, value, onChange) {
51
56
  return renderKeywordInput(entry.enum || [], prop, value, onChange);
52
57
  }
53
58
 
54
- /** @param {any} preset @param {any} onChange */
59
+ /** @param {{ title: string; value: string }} preset @param {(value: string) => void} onChange */
55
60
  function handleFontPresetSelection(preset, onChange) {
56
61
  const varName = friendlyNameToVar(preset.title, "--font-");
57
62
  if (!activeTab.value?.doc.document?.style?.[varName]) {
@@ -60,12 +65,17 @@ function handleFontPresetSelection(preset, onChange) {
60
65
  onChange(`var(${varName})`);
61
66
  }
62
67
 
63
- /** @param {any} val @param {any} presets @param {any} onChange */
68
+ /**
69
+ * @param {string} val @param {{ title: string; value: string }[]} presets @param {(value: string)
70
+ * => void} onChange
71
+ */
64
72
  function handleFontSelection(val, presets, onChange) {
65
73
  if (!val) return;
66
74
  if (val.startsWith("__preset__:")) {
67
75
  const title = val.slice("__preset__:".length);
68
- const preset = presets.find((/** @type {any} */ p) => p.title === title);
76
+ const preset = presets.find(
77
+ (/** @type {{ title: string; value: string }} */ p) => p.title === title,
78
+ );
69
79
  if (preset) handleFontPresetSelection(preset, onChange);
70
80
  return;
71
81
  }
@@ -73,14 +83,17 @@ function handleFontSelection(val, presets, onChange) {
73
83
  onChange(`var(${val})`);
74
84
  return;
75
85
  }
76
- const preset = presets.find((/** @type {any} */ p) => p.title === val);
86
+ const preset = presets.find(
87
+ (/** @type {{ title: string; value: string }} */ p) => p.title === val,
88
+ );
77
89
  if (preset) {
78
90
  handleFontPresetSelection(preset, onChange);
79
91
  return;
80
92
  }
81
93
  const fontVars = getFontVars();
82
94
  const matchedVar = fontVars.find(
83
- (/** @type {any} */ fv) => varDisplayName(fv.name, "--font-") === val,
95
+ (/** @type {{ name: string; value: string }} */ fv) =>
96
+ varDisplayName(fv.name, "--font-") === val,
84
97
  );
85
98
  if (matchedVar) {
86
99
  onChange(`var(${matchedVar.name})`);
@@ -92,19 +105,23 @@ function handleFontSelection(val, presets, onChange) {
92
105
  /**
93
106
  * Build font options array for jx-value-selector.
94
107
  *
95
- * @param {any[]} fontVars @param {any[]} presets
96
- * @returns {{ value: string; label: string; style: string }[] | { divider: true }[]}
108
+ * @param {{ name: string; value: string }[]} fontVars @param {{ title: string; value: string }[]}
109
+ * presets
110
+ * @returns {({ value: string; label: string; style: string } | { divider: true })[]}
97
111
  */
98
112
  export function buildFontOptions(fontVars, presets) {
99
- /** @type {any[]} */
100
- const opts = fontVars.map((/** @type {any} */ fv) => ({
113
+ /** @type {({ value: string; label: string; style: string } | { divider: true })[]} */
114
+ const opts = fontVars.map((/** @type {{ name: string; value: string }} */ fv) => ({
101
115
  value: fv.name,
102
116
  label: varDisplayName(fv.name, "--font-"),
103
117
  style: `font-family: ${fv.value}`,
104
118
  }));
105
119
  const unadded = presets.filter(
106
- (/** @type {any} */ p) =>
107
- !fontVars.some((/** @type {any} */ fv) => fv.name === friendlyNameToVar(p.title, "--font-")),
120
+ (/** @type {{ title: string; value: string }} */ p) =>
121
+ !fontVars.some(
122
+ (/** @type {{ name: string; value: string }} */ fv) =>
123
+ fv.name === friendlyNameToVar(p.title, "--font-"),
124
+ ),
108
125
  );
109
126
  if (unadded.length > 0 && opts.length > 0) opts.push({ divider: true });
110
127
  for (const p of unadded) {
@@ -117,7 +134,14 @@ export function buildFontOptions(fontVars, presets) {
117
134
  return opts;
118
135
  }
119
136
 
120
- /** @param {any} entry @param {any} prop @param {any} value @param {any} onChange */
137
+ /**
138
+ * @param {{
139
+ * enum?: string[];
140
+ * examples?: string[];
141
+ * presets?: { title: string; value: string }[];
142
+ * }} entry
143
+ * @param {string} prop @param {string} value @param {(value: string) => void} onChange
144
+ */
121
145
  export function renderComboboxInput(entry, prop, value, onChange) {
122
146
  const cssInitialMap = getCssInitialMap();
123
147
  const fontVars = prop === "fontFamily" ? getFontVars() : [];
@@ -133,9 +157,10 @@ export function renderComboboxInput(entry, prop, value, onChange) {
133
157
  .value=${comboValue}
134
158
  placeholder=${cssInitialMap.get("fontFamily") || ""}
135
159
  .options=${fontOptions}
136
- @change=${(/** @type {any} */ e) => handleFontSelection(e.target.value, presets, onChange)}
137
- @input=${debouncedStyleCommit("combo:fontFamily", 400, (/** @type {any} */ e) =>
138
- onChange(e.target.value),
160
+ @change=${(/** @type {Event} */ e) =>
161
+ handleFontSelection(/** @type {HTMLInputElement} */ (e.target).value, presets, onChange)}
162
+ @input=${debouncedStyleCommit("combo:fontFamily", 400, (/** @type {Event} */ e) =>
163
+ onChange(/** @type {HTMLInputElement} */ (e.target).value),
139
164
  )}
140
165
  ></jx-value-selector>`;
141
166
  }
@@ -149,8 +174,8 @@ export function renderComboboxInput(entry, prop, value, onChange) {
149
174
  size="s"
150
175
  placeholder=${cssInitialMap.get(prop) || ""}
151
176
  .value=${live(value || "")}
152
- @input=${debouncedStyleCommit(`combo:${prop}`, 400, (/** @type {any} */ e) =>
153
- onChange(e.target.value),
177
+ @input=${debouncedStyleCommit(`combo:${prop}`, 400, (/** @type {Event} */ e) =>
178
+ onChange(/** @type {HTMLInputElement} */ (e.target).value),
154
179
  )}
155
180
  ></sp-textfield>
156
181
  `;
@@ -161,17 +186,24 @@ export function renderComboboxInput(entry, prop, value, onChange) {
161
186
  * inputs and CSS initial-value placeholders.
162
187
  */
163
188
  export function widgetForType(
164
- /** @type {any} */ type,
165
- /** @type {any} */ entry,
166
- /** @type {any} */ prop,
167
- /** @type {any} */ value,
168
- /** @type {any} */ onCommit,
169
- /** @type {any} */ opts = {},
189
+ /** @type {string} */ type,
190
+ /** @type {Record<string, unknown>} */ entry,
191
+ /** @type {string} */ prop,
192
+ /** @type {string} */ value,
193
+ /** @type {(value: string) => void} */ onCommit,
194
+ /** @type {{ placeholder?: string }} */ opts = {},
170
195
  ) {
171
196
  const cssInitialMap = getCssInitialMap();
172
- return _widgetForType(type, entry, prop, value, onCommit, {
173
- placeholder: opts.placeholder || cssInitialMap.get(prop) || "",
174
- renderSelect: renderSelectInput,
175
- renderCombobox: renderComboboxInput,
176
- });
197
+ return _widgetForType(
198
+ type,
199
+ entry,
200
+ prop,
201
+ value,
202
+ /** @type {(val: string | number) => void} */ (onCommit),
203
+ {
204
+ placeholder: opts.placeholder || cssInitialMap.get(prop) || "",
205
+ renderSelect: renderSelectInput,
206
+ renderCombobox: renderComboboxInput,
207
+ },
208
+ );
177
209
  }