@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.
Files changed (104) hide show
  1. package/dist/studio.js +86144 -83971
  2. package/dist/studio.js.map +229 -219
  3. package/package.json +5 -5
  4. package/src/browse/browse-modal.ts +9 -3
  5. package/src/browse/browse.ts +139 -82
  6. package/src/canvas/canvas-diff.ts +35 -18
  7. package/src/canvas/canvas-helpers.ts +55 -21
  8. package/src/canvas/canvas-live-render.ts +140 -89
  9. package/src/canvas/canvas-render.ts +127 -82
  10. package/src/canvas/canvas-utils.ts +68 -42
  11. package/src/canvas/nested-site-style.ts +15 -7
  12. package/src/editor/component-inline-edit.ts +90 -51
  13. package/src/editor/content-inline-edit.ts +75 -72
  14. package/src/editor/context-menu.ts +125 -71
  15. package/src/editor/convert-targets.ts +17 -13
  16. package/src/editor/convert-to-component.ts +51 -28
  17. package/src/editor/convert-to-repeater.ts +44 -19
  18. package/src/editor/inline-edit.ts +107 -52
  19. package/src/editor/inline-format.ts +128 -60
  20. package/src/editor/insertion-helper.ts +26 -14
  21. package/src/editor/shortcuts.ts +89 -43
  22. package/src/editor/slash-menu.ts +41 -26
  23. package/src/files/components.ts +9 -7
  24. package/src/files/file-ops.ts +53 -33
  25. package/src/files/files.ts +246 -143
  26. package/src/format/constraints.ts +25 -15
  27. package/src/format/format-host.ts +33 -10
  28. package/src/github/github-auth.ts +24 -14
  29. package/src/github/github-publish.ts +20 -14
  30. package/src/new-project/new-project-modal.ts +27 -18
  31. package/src/panels/activity-bar.ts +27 -26
  32. package/src/panels/ai-panel.ts +100 -44
  33. package/src/panels/block-action-bar.ts +84 -52
  34. package/src/panels/canvas-dnd.ts +47 -28
  35. package/src/panels/data-explorer.ts +24 -11
  36. package/src/panels/dnd.ts +148 -100
  37. package/src/panels/editors.ts +54 -37
  38. package/src/panels/elements-panel.ts +23 -13
  39. package/src/panels/events-panel.ts +48 -38
  40. package/src/panels/git-panel.ts +95 -60
  41. package/src/panels/head-panel.ts +138 -84
  42. package/src/panels/imports-panel.ts +66 -29
  43. package/src/panels/layers-panel.ts +80 -36
  44. package/src/panels/left-panel.ts +86 -53
  45. package/src/panels/overlays.ts +36 -19
  46. package/src/panels/panel-events.ts +48 -27
  47. package/src/panels/panel-scheduler.ts +41 -23
  48. package/src/panels/preview-render.ts +26 -26
  49. package/src/panels/properties-panel.ts +155 -118
  50. package/src/panels/pseudo-preview.ts +31 -15
  51. package/src/panels/quick-search.ts +28 -13
  52. package/src/panels/right-panel.ts +35 -24
  53. package/src/panels/shared.ts +46 -29
  54. package/src/panels/signals-panel.ts +303 -186
  55. package/src/panels/statusbar.ts +27 -12
  56. package/src/panels/style-inputs.ts +30 -24
  57. package/src/panels/style-panel.ts +130 -90
  58. package/src/panels/style-utils.ts +63 -23
  59. package/src/panels/stylebook-layers-panel.ts +27 -30
  60. package/src/panels/stylebook-panel.ts +140 -72
  61. package/src/panels/tab-strip.ts +21 -10
  62. package/src/panels/toolbar.ts +52 -40
  63. package/src/panels/welcome-screen.ts +1 -1
  64. package/src/platform.ts +2 -1
  65. package/src/platforms/devserver.ts +221 -105
  66. package/src/recent-projects.ts +15 -7
  67. package/src/resize-edges.ts +7 -5
  68. package/src/services/cem-export.ts +73 -57
  69. package/src/services/code-services.ts +52 -25
  70. package/src/services/monaco-setup.ts +5 -5
  71. package/src/settings/content-types-editor.ts +133 -62
  72. package/src/settings/css-vars-editor.ts +31 -14
  73. package/src/settings/defs-editor.ts +125 -63
  74. package/src/settings/general-settings.ts +11 -6
  75. package/src/settings/head-editor.ts +30 -15
  76. package/src/settings/schema-field-ui.ts +75 -35
  77. package/src/settings/settings-modal.ts +35 -17
  78. package/src/site-context.ts +80 -47
  79. package/src/state.ts +98 -65
  80. package/src/store.ts +42 -23
  81. package/src/studio.ts +165 -144
  82. package/src/tabs/tab.ts +51 -45
  83. package/src/tabs/transact.ts +230 -109
  84. package/src/types.ts +60 -51
  85. package/src/ui/button-group.ts +3 -2
  86. package/src/ui/color-selector.ts +33 -15
  87. package/src/ui/expression-editor.ts +139 -86
  88. package/src/ui/field-input.ts +35 -18
  89. package/src/ui/field-row.ts +1 -1
  90. package/src/ui/icons.ts +5 -6
  91. package/src/ui/layers.ts +33 -26
  92. package/src/ui/media-picker.ts +13 -7
  93. package/src/ui/panel-resize.ts +39 -20
  94. package/src/ui/spectrum.ts +4 -2
  95. package/src/ui/unit-selector.ts +20 -11
  96. package/src/ui/value-selector.ts +13 -7
  97. package/src/ui/widgets.ts +34 -18
  98. package/src/utils/canvas-media.ts +66 -33
  99. package/src/utils/edit-display.ts +43 -44
  100. package/src/utils/google-fonts.ts +10 -6
  101. package/src/utils/inherited-style.ts +22 -8
  102. package/src/utils/studio-utils.ts +88 -45
  103. package/src/view.ts +9 -5
  104. package/src/workspace/workspace.ts +49 -29
@@ -1,10 +1,11 @@
1
1
  /// <reference lib="dom" />
2
2
  /** Statusbar — status message display for Jx Studio */
3
3
 
4
- import { statusbarEl, getNodeAtPath, nodeLabel, updateSession, renderOnly } from "../store";
4
+ import { getNodeAtPath, nodeLabel, renderOnly, statusbarEl, updateSession } from "../store";
5
5
  import { effect, effectScope } from "../reactivity";
6
6
  import { activeTab } from "../workspace/workspace";
7
7
  import type { JxPath } from "../state";
8
+ import type { EffectScope } from "@vue/reactivity";
8
9
 
9
10
  // ─── Module state ────────────────────────────────────────────────────────────
10
11
 
@@ -12,7 +13,7 @@ let statusMsg = "";
12
13
  /** @type {ReturnType<typeof setTimeout> | undefined} */
13
14
  let statusTimeout: ReturnType<typeof setTimeout> | undefined;
14
15
  let _rerender: (() => void) | null = null;
15
- let _scope: import("@vue/reactivity").EffectScope | null = null;
16
+ let _scope: EffectScope | null = null;
16
17
 
17
18
  /**
18
19
  * Register the callback used to re-render the statusbar. Called once from studio.js during init.
@@ -29,7 +30,9 @@ export function mountStatusbar() {
29
30
  _scope.run(() => {
30
31
  effect(() => {
31
32
  const tab = activeTab.value;
32
- if (!tab) return;
33
+ if (!tab) {
34
+ return;
35
+ }
33
36
  // Track relevant reactive properties
34
37
  void tab.doc.document;
35
38
  void tab.doc.mode;
@@ -52,14 +55,16 @@ export function unmountStatusbar() {
52
55
 
53
56
  /** @param {string} text */
54
57
  function esc(text: string) {
55
- return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
58
+ return String(text).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
56
59
  }
57
60
 
58
61
  /** Render the statusbar content. */
59
62
  export function renderStatusbar() {
60
63
  const tab = activeTab.value;
61
64
  const parts = [];
62
- if (tab?.doc.mode === "content") parts.push("Content Mode");
65
+ if (tab?.doc.mode === "content") {
66
+ parts.push("Content Mode");
67
+ }
63
68
  if (tab?.session.selection?.length) {
64
69
  const sel = tab.session.selection as JxPath;
65
70
  const node = getNodeAtPath(tab.doc.document, sel);
@@ -69,7 +74,11 @@ export function renderStatusbar() {
69
74
  for (let i = 0; i < sel.length; i += 2) {
70
75
  const subPath = sel.slice(0, i + 2);
71
76
  const childNode = getNodeAtPath(tab.doc.document, subPath);
72
- const label = childNode?.tagName || childNode?.tag || `[${sel[i + 1]}]`;
77
+ const fallbackTag = childNode?.tag;
78
+ const label =
79
+ childNode?.tagName ||
80
+ (typeof fallbackTag === "string" ? fallbackTag : "") ||
81
+ `[${sel[i + 1]}]`;
73
82
  const dataPath = JSON.stringify(subPath);
74
83
  pathSegments.push(
75
84
  `<span class="sb-path-seg" data-path='${esc(dataPath)}'>${esc(label)}</span>`,
@@ -78,24 +87,30 @@ export function renderStatusbar() {
78
87
  parts.push(`Path: ${pathSegments.join(' <span class="sb-path-sep">&gt;</span> ')}`);
79
88
  } else if (tab?.session.ui.stylebookSelection) {
80
89
  const sel = tab.session.ui.stylebookSelection;
81
- parts.push(`Style: ${esc(sel.replace(/ /g, " > "))}`);
90
+ parts.push(`Style: ${esc(sel.replaceAll(" ", " > "))}`);
91
+ }
92
+ if (statusMsg) {
93
+ parts.push(esc(statusMsg));
82
94
  }
83
- if (statusMsg) parts.push(esc(statusMsg));
84
95
  statusbarEl.innerHTML = parts.join(" | ") || "Jx Studio";
85
96
  }
86
97
 
87
98
  /** @param {Event} e */
88
99
  function _onStatusbarClick(e: Event) {
89
100
  const target = e.target as HTMLElement;
90
- if (!target.classList.contains("sb-path-seg")) return;
101
+ if (!target.classList.contains("sb-path-seg")) {
102
+ return;
103
+ }
91
104
  const pathStr = target.dataset.path;
92
- if (!pathStr) return;
105
+ if (!pathStr) {
106
+ return;
107
+ }
93
108
  try {
94
109
  const path = JSON.parse(pathStr);
95
110
  updateSession({ selection: path });
96
111
  renderOnly("leftPanel", "rightPanel", "canvas");
97
112
  } catch {
98
- // ignore
113
+ // Ignore
99
114
  }
100
115
  }
101
116
 
@@ -105,7 +120,7 @@ function _onStatusbarClick(e: Event) {
105
120
  * @param {string} msg
106
121
  * @param {number} [duration]
107
122
  */
108
- export function statusMessage(msg: string, duration: number = 3000) {
123
+ export function statusMessage(msg: string, duration = 3000) {
109
124
  statusMsg = msg;
110
125
  _rerender?.();
111
126
  clearTimeout(statusTimeout);
@@ -5,15 +5,15 @@ import { html } from "lit-html";
5
5
  import { live } from "lit-html/directives/live.js";
6
6
  import { debouncedStyleCommit } from "../store";
7
7
  import { activeTab } from "../workspace/workspace";
8
- import { transactDoc, mutateUpdateStyle } from "../tabs/transact";
8
+ import { mutateUpdateStyle, transactDoc } from "../tabs/transact";
9
9
  import { widgetForType as _widgetForType } from "../ui/widgets";
10
- import { kebabToLabel, friendlyNameToVar, varDisplayName } from "../utils/studio-utils";
10
+ import { friendlyNameToVar, kebabToLabel, varDisplayName } from "../utils/studio-utils";
11
11
  import {
12
12
  TYPO_PREVIEW_PROPS,
13
+ camelToKebab,
13
14
  currentFontFamily,
14
- getFontVars,
15
15
  getCssInitialMap,
16
- camelToKebab,
16
+ getFontVars,
17
17
  } from "./style-utils";
18
18
 
19
19
  /**
@@ -38,7 +38,7 @@ export function renderKeywordInput(
38
38
  ? kebabToLabel(v)
39
39
  : v.replace(/^./, (c: string) => c.toUpperCase());
40
40
  const style = isTypoPreview ? `${cssProp}: ${v};${font ? ` font-family: ${font}` : ""}` : "";
41
- return { value: v, label, style };
41
+ return { label, style, value: v };
42
42
  });
43
43
 
44
44
  return html`<jx-value-selector
@@ -54,16 +54,17 @@ export function renderKeywordInput(
54
54
  }
55
55
 
56
56
  /**
57
- * @param {{ enum?: string[] }} entry @param {string} prop @param {string} value @param {(value:
58
- * string) => void} onChange
57
+ * @param {Record<string, unknown>} entry @param {string} prop @param {string | number | undefined}
58
+ * value @param {(value: string) => void} onChange
59
59
  */
60
60
  export function renderSelectInput(
61
- entry: { enum?: string[] },
61
+ entry: Record<string, unknown>,
62
62
  prop: string,
63
- value: string,
63
+ value: string | number | undefined,
64
64
  onChange: (value: string) => void,
65
65
  ) {
66
- return renderKeywordInput(entry.enum || [], prop, value, onChange);
66
+ const options = Array.isArray(entry.enum) ? (entry.enum as string[]) : [];
67
+ return renderKeywordInput(options, prop, String(value ?? ""), onChange);
67
68
  }
68
69
 
69
70
  /** @param {{ title: string; value: string }} preset @param {(value: string) => void} onChange */
@@ -87,13 +88,17 @@ function handleFontSelection(
87
88
  presets: { title: string; value: string }[],
88
89
  onChange: (value: string) => void,
89
90
  ) {
90
- if (!val) return;
91
+ if (!val) {
92
+ return;
93
+ }
91
94
  if (val.startsWith("__preset__:")) {
92
95
  const title = val.slice("__preset__:".length);
93
96
  const preset = presets.find(
94
97
  (/** @type {{ title: string; value: string }} */ p) => p.title === title,
95
98
  );
96
- if (preset) handleFontPresetSelection(preset, onChange);
99
+ if (preset) {
100
+ handleFontPresetSelection(preset, onChange);
101
+ }
97
102
  return;
98
103
  }
99
104
  if (val.startsWith("--")) {
@@ -132,9 +137,9 @@ export function buildFontOptions(
132
137
  ) {
133
138
  const opts: ({ value: string; label: string; style: string } | { divider: true })[] =
134
139
  fontVars.map((fv) => ({
135
- value: fv.name,
136
140
  label: varDisplayName(fv.name, "--font-"),
137
141
  style: `font-family: ${fv.value}`,
142
+ value: fv.name,
138
143
  }));
139
144
  const unadded = presets.filter(
140
145
  (/** @type {{ title: string; value: string }} */ p) =>
@@ -143,12 +148,14 @@ export function buildFontOptions(
143
148
  fv.name === friendlyNameToVar(p.title, "--font-"),
144
149
  ),
145
150
  );
146
- if (unadded.length > 0 && opts.length > 0) opts.push({ divider: true });
151
+ if (unadded.length > 0 && opts.length > 0) {
152
+ opts.push({ divider: true });
153
+ }
147
154
  for (const p of unadded) {
148
155
  opts.push({
149
- value: "__preset__:" + p.title,
150
156
  label: p.title,
151
157
  style: `font-family: ${p.value}`,
158
+ value: `__preset__:${p.title}`,
152
159
  });
153
160
  }
154
161
  return opts;
@@ -163,19 +170,18 @@ export function buildFontOptions(
163
170
  * @param {string} prop @param {string} value @param {(value: string) => void} onChange
164
171
  */
165
172
  export function renderComboboxInput(
166
- entry: {
167
- enum?: string[];
168
- examples?: string[];
169
- presets?: { title: string; value: string }[];
170
- },
173
+ entry: Record<string, unknown>,
171
174
  prop: string,
172
- value: string,
175
+ rawValue: string | number | undefined,
173
176
  onChange: (value: string) => void,
174
177
  ) {
178
+ const value = String(rawValue ?? "");
175
179
  const cssInitialMap = getCssInitialMap();
176
180
  const fontVars = prop === "fontFamily" ? getFontVars() : [];
177
- const presets = entry.presets || [];
178
- const examples = entry.examples || [];
181
+ const presets = Array.isArray(entry.presets)
182
+ ? (entry.presets as { title: string; value: string }[])
183
+ : [];
184
+ const examples = Array.isArray(entry.examples) ? (entry.examples as string[]) : [];
179
185
 
180
186
  if (prop === "fontFamily") {
181
187
  const varMatch = typeof value === "string" && value.match(/^var\((--[^)]+)\)$/);
@@ -225,7 +231,7 @@ export function widgetForType(
225
231
  const cssInitialMap = getCssInitialMap();
226
232
  return _widgetForType(type, entry, prop, value, onCommit as (val: string | number) => void, {
227
233
  placeholder: opts.placeholder || cssInitialMap.get(prop) || "",
228
- renderSelect: renderSelectInput,
229
234
  renderCombobox: renderComboboxInput,
235
+ renderSelect: renderSelectInput,
230
236
  });
231
237
  }