@jxsuite/studio 0.21.4 → 0.22.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.
@@ -4,7 +4,7 @@
4
4
  * to avoid moving ~2000 lines in one step.
5
5
  */
6
6
 
7
- import { html, render as litRender, nothing } from "lit-html";
7
+ import { html, render as litRender } from "lit-html";
8
8
  import { updateUi, rightPanel } from "../store.js";
9
9
  import { effect, effectScope } from "../reactivity.js";
10
10
  import { activeTab } from "../workspace/workspace.js";
@@ -41,8 +41,17 @@ let _rendering = false;
41
41
  let _scheduled = false;
42
42
  let _hasFocus = false;
43
43
 
44
- function _onFocusIn() {
45
- _hasFocus = true;
44
+ function _isTextInput(/** @type {Element | null} */ el) {
45
+ if (!el) return false;
46
+ const tag = el.tagName.toLowerCase();
47
+ if (tag === "input" || tag === "textarea") return true;
48
+ if (tag === "sp-textfield" || tag === "sp-number-field" || tag === "sp-search") return true;
49
+ if (el.shadowRoot?.activeElement) return _isTextInput(el.shadowRoot.activeElement);
50
+ return false;
51
+ }
52
+
53
+ function _onFocusIn(/** @type {FocusEvent} */ e) {
54
+ _hasFocus = _isTextInput(/** @type {Element} */ (e.target));
46
55
  }
47
56
 
48
57
  function _onFocusOut() {
@@ -92,6 +101,11 @@ export function unmount() {
92
101
  rightPanel.removeEventListener("focusin", _onFocusIn);
93
102
  rightPanel.removeEventListener("focusout", _onFocusOut);
94
103
  _hasFocus = false;
104
+ _propsContainer = null;
105
+ _eventsContainer = null;
106
+ _styleContainer = null;
107
+ _assistantContainer = null;
108
+ _lastTab = null;
95
109
  }
96
110
 
97
111
  export function render() {
@@ -103,104 +117,142 @@ export function render() {
103
117
  }
104
118
  }
105
119
 
120
+ /** @type {HTMLElement | null} */
121
+ let _propsContainer = null;
122
+ /** @type {HTMLElement | null} */
123
+ let _eventsContainer = null;
124
+ /** @type {HTMLElement | null} */
125
+ let _styleContainer = null;
126
+ /** @type {HTMLElement | null} */
127
+ let _assistantContainer = null;
128
+ /** @type {string | null} */
129
+ let _lastTab = null;
130
+
131
+ function _ensureContainers() {
132
+ if (_propsContainer) return;
133
+ _propsContainer = document.createElement("div");
134
+ _propsContainer.className = "panel-body";
135
+ _eventsContainer = document.createElement("div");
136
+ _eventsContainer.className = "panel-body";
137
+ _styleContainer = document.createElement("div");
138
+ _styleContainer.className = "panel-body";
139
+ _assistantContainer = document.createElement("div");
140
+ _assistantContainer.className = "panel-body";
141
+ _assistantContainer.style.cssText = "display:flex;flex-direction:column;overflow:hidden";
142
+ }
143
+
106
144
  function _flush() {
107
145
  _scheduled = false;
108
146
  if (!_ctx) return;
109
147
  if (_rendering) return;
110
148
  _rendering = true;
111
149
  try {
112
- litRender(rightPanelTemplate(), rightPanel);
113
- } catch (e) {
114
- console.error("right-panel render error:", e);
115
- try {
150
+ const ctx = /** @type {RightPanelCtx} */ (_ctx);
151
+ const aTab = activeTab.value;
152
+ if (!aTab) {
116
153
  rightPanel.textContent = "";
117
- // @ts-ignore
118
- delete rightPanel["_$litPart$"];
119
- litRender(rightPanelTemplate(), rightPanel);
120
- } catch (e2) {
121
- console.error("right-panel retry failed:", e2);
154
+ return;
155
+ }
156
+ const S = /**
157
+ * @type {{
158
+ * ui: Record<string, unknown>;
159
+ * document: JxMutableNode;
160
+ * mode: string;
161
+ * selection: (string | number)[] | null;
162
+ * }}
163
+ */ ({
164
+ ui: aTab.session.ui,
165
+ document: aTab.doc.document,
166
+ mode: aTab.doc.mode,
167
+ selection: aTab.session.selection,
168
+ });
169
+ const tab = /** @type {string} */ (S.ui.rightTab);
170
+
171
+ // Render tabs header
172
+ const panelTabs = [
173
+ { value: "properties", icon: "sp-icon-properties", label: "Properties" },
174
+ { value: "events", icon: "sp-icon-event", label: "Events" },
175
+ { value: "style", icon: "sp-icon-brush", label: "Style" },
176
+ { value: "assistant", icon: "sp-icon-chat", label: "Assistant" },
177
+ ];
178
+ const tabsT = html`
179
+ <div class="panel-tabs">
180
+ <sp-tabs
181
+ selected=${tab}
182
+ quiet
183
+ @change=${(/** @type {Event & { target: { selected: string } }} */ e) => {
184
+ const sel = e.target.selected;
185
+ if (sel && sel !== tab) {
186
+ updateUi("rightTab", sel);
187
+ render();
188
+ }
189
+ }}
190
+ >
191
+ ${panelTabs.map(
192
+ (t) => html`
193
+ <sp-tab value=${t.value} title=${t.label} aria-label=${t.label}>
194
+ ${tabIcon(t.icon, "xs")}
195
+ </sp-tab>
196
+ `,
197
+ )}
198
+ </sp-tabs>
199
+ </div>
200
+ `;
201
+
202
+ _ensureContainers();
203
+ const containers = /** @type {HTMLElement[]} */ ([
204
+ _propsContainer,
205
+ _eventsContainer,
206
+ _styleContainer,
207
+ _assistantContainer,
208
+ ]);
209
+ const tabKeys = ["properties", "events", "style", "assistant"];
210
+
211
+ // Show/hide containers
212
+ for (let i = 0; i < containers.length; i++) {
213
+ if (tabKeys[i] === tab) {
214
+ containers[i].style.display = tabKeys[i] === "assistant" ? "flex" : "";
215
+ } else {
216
+ containers[i].style.display = "none";
217
+ }
218
+ }
219
+
220
+ // Render tabs into the right panel, append containers
221
+ litRender(tabsT, rightPanel);
222
+ for (const c of containers) {
223
+ if (!c.parentNode) rightPanel.appendChild(c);
224
+ }
225
+
226
+ // Only render the active panel's content
227
+ if (tab === "properties") {
228
+ litRender(
229
+ renderPropertiesPanelTemplate({ navigateToComponent: ctx.navigateToComponent }),
230
+ /** @type {HTMLElement} */ (_propsContainer),
231
+ );
232
+ } else if (tab === "events") {
233
+ litRender(
234
+ eventsSidebarTemplate({ isCustomElementDoc: () => isCustomElementDoc(S) }),
235
+ /** @type {HTMLElement} */ (_eventsContainer),
236
+ );
237
+ } else if (tab === "style") {
238
+ try {
239
+ litRender(
240
+ renderStylePanelTemplate({ getCanvasMode: ctx.getCanvasMode }),
241
+ /** @type {HTMLElement} */ (_styleContainer),
242
+ );
243
+ } catch (/** @type {unknown} */ e) {
244
+ console.error("[renderStylePanelTemplate]", e);
245
+ }
246
+ } else if (tab === "assistant") {
247
+ litRender(renderAiPanelTemplate(), /** @type {HTMLElement} */ (_assistantContainer));
122
248
  }
249
+
250
+ _lastTab = tab;
251
+ } catch (e) {
252
+ console.error("right-panel render error:", e);
123
253
  } finally {
124
254
  _rendering = false;
125
255
  }
126
256
  requestAnimationFrame(() => mountQuikChat());
127
257
  _ctx.updateForcedPseudoPreview();
128
258
  }
129
-
130
- function rightPanelTemplate() {
131
- const ctx = /** @type {RightPanelCtx} */ (_ctx);
132
- const aTab = activeTab.value;
133
- if (!aTab) return nothing;
134
- const S = /**
135
- * @type {{
136
- * ui: Record<string, unknown>;
137
- * document: JxMutableNode;
138
- * mode: string;
139
- * selection: (string | number)[] | null;
140
- * }}
141
- */ ({
142
- ui: aTab.session.ui,
143
- document: aTab.doc.document,
144
- mode: aTab.doc.mode,
145
- selection: aTab.session.selection,
146
- });
147
- const tab = S.ui.rightTab;
148
-
149
- const panelTabs = [
150
- { value: "properties", icon: "sp-icon-properties", label: "Properties" },
151
- { value: "events", icon: "sp-icon-event", label: "Events" },
152
- { value: "style", icon: "sp-icon-brush", label: "Style" },
153
- { value: "assistant", icon: "sp-icon-chat", label: "Assistant" },
154
- ];
155
-
156
- const tabsT = html`
157
- <div class="panel-tabs">
158
- <sp-tabs
159
- selected=${tab}
160
- quiet
161
- @change=${(/** @type {Event & { target: { selected: string } }} */ e) => {
162
- const sel = e.target.selected;
163
- if (sel && sel !== tab) {
164
- updateUi("rightTab", sel);
165
- }
166
- }}
167
- >
168
- ${panelTabs.map(
169
- (t) => html`
170
- <sp-tab value=${t.value} title=${t.label} aria-label=${t.label}>
171
- ${tabIcon(t.icon, "xs")}
172
- </sp-tab>
173
- `,
174
- )}
175
- </sp-tabs>
176
- </div>
177
- `;
178
-
179
- /** @type {import("lit-html").TemplateResult | typeof nothing} */
180
- let bodyT = nothing;
181
- if (tab === "properties") {
182
- bodyT = renderPropertiesPanelTemplate({ navigateToComponent: ctx.navigateToComponent });
183
- } else if (tab === "events") {
184
- bodyT = eventsSidebarTemplate({
185
- isCustomElementDoc: () => isCustomElementDoc(S),
186
- });
187
- } else if (tab === "style") {
188
- try {
189
- bodyT = renderStylePanelTemplate({ getCanvasMode: ctx.getCanvasMode });
190
- } catch (/** @type {unknown} */ e) {
191
- console.error("[renderStylePanelTemplate]", e);
192
- }
193
- } else if (tab === "assistant") {
194
- bodyT = renderAiPanelTemplate();
195
- }
196
-
197
- return html`
198
- ${tabsT}
199
- <div
200
- class="panel-body"
201
- style=${tab === "assistant" ? "display:flex;flex-direction:column;overflow:hidden" : ""}
202
- >
203
- ${bodyT}
204
- </div>
205
- `;
206
- }