@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.
- package/dist/studio.js +532 -252
- package/dist/studio.js.map +15 -15
- package/package.json +4 -4
- package/src/canvas/canvas-live-render.js +6 -5
- package/src/canvas/canvas-render.js +4 -3
- package/src/editor/context-menu.js +146 -26
- package/src/editor/insertion-helper.js +4 -3
- package/src/markdown/md-convert.js +7 -0
- package/src/panels/head-panel.js +105 -2
- package/src/panels/right-panel.js +142 -90
- package/src/panels/signals-panel.js +246 -129
- package/src/panels/style-panel.js +44 -1
- package/src/services/code-services.js +12 -5
- package/src/settings/head-editor.js +4 -4
- package/src/tabs/transact.js +1 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* to avoid moving ~2000 lines in one step.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { html, render as litRender
|
|
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
|
|
45
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
try {
|
|
150
|
+
const ctx = /** @type {RightPanelCtx} */ (_ctx);
|
|
151
|
+
const aTab = activeTab.value;
|
|
152
|
+
if (!aTab) {
|
|
116
153
|
rightPanel.textContent = "";
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
}
|