@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
@@ -34,7 +34,7 @@ export function updateForcedPseudoPreview() {
34
34
  const node = getNodeAtPath(tab.doc.document, tab.session.selection);
35
35
  if (!node?.style) return;
36
36
  const activeMedia = tab.session.ui.activeMedia;
37
- /** @type {any} */
37
+ /** @type {JxStyle} */
38
38
  const ctx = activeMedia ? node.style[`@${activeMedia}`] || {} : node.style;
39
39
  const rules = ctx[sel];
40
40
  if (!rules || typeof rules !== "object") return;
@@ -43,7 +43,7 @@ export function updateForcedPseudoPreview() {
43
43
  .filter(([k]) => typeof rules[k] === "string" || typeof rules[k] === "number")
44
44
  .map(
45
45
  ([k, v]) =>
46
- `${k.replace(/[A-Z]/g, (/** @type {any} */ c) => `-${c.toLowerCase()}`)}: ${v} !important`,
46
+ `${k.replace(/[A-Z]/g, (/** @type {string} */ c) => `-${c.toLowerCase()}`)}: ${v} !important`,
47
47
  )
48
48
  .join("; ");
49
49
  if (!cssProps) return;
@@ -1,6 +1,7 @@
1
1
  import { html, render as litRender, nothing } from "lit-html";
2
2
  import { classMap } from "lit-html/directives/class-map.js";
3
3
  import { live } from "lit-html/directives/live.js";
4
+ import { ref } from "lit-html/directives/ref.js";
4
5
  import { getPlatform } from "../platform.js";
5
6
  import { openFileInTab } from "../files/files.js";
6
7
  import { getRecentFiles, trackRecentFile } from "../recent-projects.js";
@@ -8,7 +9,7 @@ import { getLayerSlot } from "../ui/layers.js";
8
9
 
9
10
  let _open = false;
10
11
  let _query = "";
11
- /** @type {any[]} */
12
+ /** @type {{ path: string; name?: string }[]} */
12
13
  let _results = [];
13
14
  let _selectedIndex = 0;
14
15
  let _debounceTimer = 0;
@@ -28,10 +29,6 @@ export function openQuickSearch() {
28
29
  _results = [];
29
30
  _selectedIndex = 0;
30
31
  renderOverlay();
31
- requestAnimationFrame(() => {
32
- const input = getContainer().querySelector(".quick-search-input");
33
- if (input) /** @type {HTMLInputElement} */ (input).focus();
34
- });
35
32
  }
36
33
 
37
34
  export function closeQuickSearch() {
@@ -60,7 +57,9 @@ async function doSearch(/** @type {string} */ query) {
60
57
  function onInput(/** @type {Event} */ e) {
61
58
  _query = /** @type {HTMLInputElement} */ (e.target).value;
62
59
  clearTimeout(_debounceTimer);
63
- _debounceTimer = /** @type {any} */ (setTimeout(() => doSearch(_query), 150));
60
+ _debounceTimer = /** @type {number} */ (
61
+ /** @type {unknown} */ (setTimeout(() => doSearch(_query), 150))
62
+ );
64
63
  renderOverlay();
65
64
  }
66
65
 
@@ -88,10 +87,10 @@ function onKeydown(/** @type {KeyboardEvent} */ e) {
88
87
  }
89
88
  }
90
89
 
91
- function selectItem(/** @type {any} */ item) {
90
+ function selectItem(/** @type {{ path: string; name?: string }} */ item) {
92
91
  closeQuickSearch();
93
92
  const path = item.path;
94
- trackRecentFile({ path, name: path.split("/").pop() });
93
+ trackRecentFile({ path, name: path.split("/").pop() || "" });
95
94
  openFileInTab(path);
96
95
  }
97
96
 
@@ -134,6 +133,9 @@ function renderOverlay() {
134
133
  .value=${live(_query)}
135
134
  @input=${onInput}
136
135
  @keydown=${onKeydown}
136
+ ${ref((el) => {
137
+ if (el) requestAnimationFrame(() => /** @type {HTMLInputElement} */ (el).focus());
138
+ })}
137
139
  />
138
140
  <div class="quick-search-results">
139
141
  ${items.length === 0 && _query.trim()
@@ -156,7 +158,7 @@ function renderOverlay() {
156
158
  }}
157
159
  >
158
160
  <span class="quick-search-icon"
159
- >${fileIcon(item.name || item.path.split("/").pop())}</span
161
+ >${fileIcon(item.name || item.path.split("/").pop() || "")}</span
160
162
  >
161
163
  <span class="quick-search-name">${item.name || item.path.split("/").pop()}</span>
162
164
  <span class="quick-search-path">${dirPart(item.path)}</span>
@@ -15,6 +15,12 @@ import { isCustomElementDoc } from "./signals-panel.js";
15
15
  import { isColorPopoverOpen } from "../ui/color-selector.js";
16
16
  import { renderStylePanelTemplate } from "./style-panel.js";
17
17
  import { renderPropertiesPanelTemplate } from "./properties-panel.js";
18
+ import {
19
+ renderAiPanelTemplate,
20
+ mountAiPanel,
21
+ mountQuikChat,
22
+ registerRightPanelRender,
23
+ } from "./ai-panel.js";
18
24
 
19
25
  /**
20
26
  * @typedef {{
@@ -33,6 +39,16 @@ let _scope = null;
33
39
 
34
40
  let _rendering = false;
35
41
  let _scheduled = false;
42
+ let _hasFocus = false;
43
+
44
+ function _onFocusIn() {
45
+ _hasFocus = true;
46
+ }
47
+
48
+ function _onFocusOut() {
49
+ _hasFocus = false;
50
+ render();
51
+ }
36
52
 
37
53
  /**
38
54
  * Mount the right panel.
@@ -41,6 +57,10 @@ let _scheduled = false;
41
57
  */
42
58
  export function mount(ctx) {
43
59
  _ctx = ctx;
60
+ mountAiPanel();
61
+ registerRightPanelRender(render);
62
+ rightPanel.addEventListener("focusin", _onFocusIn);
63
+ rightPanel.addEventListener("focusout", _onFocusOut);
44
64
  _scope = effectScope();
45
65
  _scope.run(() => {
46
66
  effect(() => {
@@ -58,20 +78,7 @@ export function mount(ctx) {
58
78
  void tab.session.ui.styleFilterActive;
59
79
  void tab.session.ui.inspectorSections;
60
80
 
61
- const colorPopoverOpen = isColorPopoverOpen();
62
- const activeTag = document.activeElement?.tagName;
63
- const rightHasFocus =
64
- !colorPopoverOpen &&
65
- rightPanel.contains(document.activeElement) &&
66
- (activeTag === "INPUT" ||
67
- activeTag === "TEXTAREA" ||
68
- activeTag === "SP-TEXTFIELD" ||
69
- activeTag === "SP-NUMBER-FIELD" ||
70
- activeTag === "SP-PICKER" ||
71
- activeTag === "SP-COMBOBOX" ||
72
- activeTag === "SP-SEARCH");
73
-
74
- if (!rightHasFocus) {
81
+ if (!_hasFocus && !isColorPopoverOpen()) {
75
82
  render();
76
83
  }
77
84
  });
@@ -82,6 +89,9 @@ export function unmount() {
82
89
  _scope?.stop();
83
90
  _scope = null;
84
91
  _ctx = null;
92
+ rightPanel.removeEventListener("focusin", _onFocusIn);
93
+ rightPanel.removeEventListener("focusout", _onFocusOut);
94
+ _hasFocus = false;
85
95
  }
86
96
 
87
97
  export function render() {
@@ -113,6 +123,7 @@ function _flush() {
113
123
  } finally {
114
124
  _rendering = false;
115
125
  }
126
+ requestAnimationFrame(() => mountQuikChat());
116
127
  _ctx.updateForcedPseudoPreview();
117
128
  }
118
129
 
@@ -120,7 +131,14 @@ function rightPanelTemplate() {
120
131
  const ctx = /** @type {RightPanelCtx} */ (_ctx);
121
132
  const aTab = activeTab.value;
122
133
  if (!aTab) return nothing;
123
- const S = /** @type {any} */ ({
134
+ const S = /**
135
+ * @type {{
136
+ * ui: Record<string, unknown>;
137
+ * document: JxMutableNode;
138
+ * mode: string;
139
+ * selection: (string | number)[] | null;
140
+ * }}
141
+ */ ({
124
142
  ui: aTab.session.ui,
125
143
  document: aTab.doc.document,
126
144
  mode: aTab.doc.mode,
@@ -132,6 +150,7 @@ function rightPanelTemplate() {
132
150
  { value: "properties", icon: "sp-icon-properties", label: "Properties" },
133
151
  { value: "events", icon: "sp-icon-event", label: "Events" },
134
152
  { value: "style", icon: "sp-icon-brush", label: "Style" },
153
+ { value: "assistant", icon: "sp-icon-chat", label: "Assistant" },
135
154
  ];
136
155
 
137
156
  const tabsT = html`
@@ -171,10 +190,17 @@ function rightPanelTemplate() {
171
190
  } catch (/** @type {unknown} */ e) {
172
191
  console.error("[renderStylePanelTemplate]", e);
173
192
  }
193
+ } else if (tab === "assistant") {
194
+ bodyT = renderAiPanelTemplate();
174
195
  }
175
196
 
176
197
  return html`
177
198
  ${tabsT}
178
- <div class="panel-body">${bodyT}</div>
199
+ <div
200
+ class="panel-body"
201
+ style=${tab === "assistant" ? "display:flex;flex-direction:column;overflow:hidden" : ""}
202
+ >
203
+ ${bodyT}
204
+ </div>
179
205
  `;
180
206
  }
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Convert a $media key like "--tablet" to a friendly display name "Tablet". "--" returns "Base".
8
8
  *
9
- * @param {any} name
9
+ * @param {string} name
10
10
  */
11
11
  export function mediaDisplayName(name) {
12
12
  if (name === "--") return "Base";
@@ -14,7 +14,7 @@ export function mediaDisplayName(name) {
14
14
  name
15
15
  .replace(/^--/, "")
16
16
  .replace(/-/g, " ")
17
- .replace(/\b\w/g, (/** @type {any} */ c) => c.toUpperCase()) || name
17
+ .replace(/\b\w/g, (/** @type {string} */ c) => c.toUpperCase()) || name
18
18
  );
19
19
  }
20
20
 
@@ -23,10 +23,10 @@ export const unsafeTags = new Set(["script", "style", "link", "iframe", "object"
23
23
  /**
24
24
  * Generate a sensible default Jx node for a given tag name.
25
25
  *
26
- * @param {any} tag
26
+ * @param {string} tag
27
27
  */
28
28
  export function defaultDef(tag) {
29
- /** @type {any} */
29
+ /** @type {JxMutableNode} */
30
30
  const def = { tagName: tag };
31
31
  if (/^h[1-6]$/.test(tag)) def.textContent = "Heading";
32
32
  else if (tag === "p") def.textContent = "Paragraph text";