@jxsuite/studio 0.37.1 → 1.1.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/iframe-entry.js +622 -49
- package/dist/iframe-entry.js.map +15 -12
- package/dist/studio.css +1333 -0
- package/dist/studio.js +103709 -81001
- package/dist/studio.js.map +201 -157
- package/package.json +47 -45
- package/src/account-status.ts +39 -0
- package/src/browse/browse.ts +10 -14
- package/src/canvas/canvas-live-render.ts +21 -2
- package/src/canvas/canvas-render.ts +35 -16
- package/src/canvas/canvas-utils.ts +118 -72
- package/src/canvas/iframe-entry.ts +20 -0
- package/src/canvas/iframe-eval.ts +155 -0
- package/src/canvas/iframe-host.ts +132 -6
- package/src/canvas/iframe-inline-edit.ts +107 -1
- package/src/canvas/iframe-protocol.ts +43 -3
- package/src/canvas/iframe-render.ts +18 -0
- package/src/collab/collab-session.ts +23 -8
- package/src/collab/collab-state.ts +6 -3
- package/src/component-props.ts +104 -0
- package/src/editor/context-menu.ts +1 -1
- package/src/editor/inline-edit-apply.ts +36 -1
- package/src/editor/inline-edit.ts +58 -1
- package/src/editor/repeater-scope.ts +8 -13
- package/src/editor/shortcuts.ts +41 -12
- package/src/files/file-ops.ts +14 -0
- package/src/files/files.ts +56 -1
- package/src/format/format-host.ts +63 -1
- package/src/grid/cell-editors.ts +288 -0
- package/src/grid/cell-popovers.ts +108 -0
- package/src/grid/csv-codec.ts +122 -0
- package/src/grid/edit-buffer.ts +490 -0
- package/src/grid/grid-controller.ts +417 -0
- package/src/grid/grid-layout.ts +83 -0
- package/src/grid/grid-open.ts +167 -0
- package/src/grid/grid-panel.ts +302 -0
- package/src/grid/grid-source.ts +210 -0
- package/src/grid/grid-view.ts +367 -0
- package/src/grid/schema-columns.ts +261 -0
- package/src/grid/sources/connector-source.ts +217 -0
- package/src/grid/sources/content-source.ts +542 -0
- package/src/grid/sources/csv-file-source.ts +247 -0
- package/src/grid/tabulator-tables.d.ts +120 -0
- package/src/new-project/add-repo-modal.ts +183 -0
- package/src/new-project/new-project-modal.ts +22 -3
- package/src/page-params.ts +34 -8
- package/src/panels/ai-chat/chat-markdown.ts +2 -2
- package/src/panels/ai-panel.ts +61 -4
- package/src/panels/block-action-bar.ts +8 -3
- package/src/panels/chat-panel.ts +98 -0
- package/src/panels/data-grid.ts +241 -0
- package/src/panels/editors.ts +43 -17
- package/src/panels/events-panel.ts +137 -44
- package/src/panels/formula-workspace.ts +379 -0
- package/src/panels/frontmatter-fields.ts +231 -0
- package/src/panels/frontmatter-panel.ts +132 -0
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/head-panel.ts +11 -175
- package/src/panels/properties-panel.ts +154 -144
- package/src/panels/right-panel.ts +9 -47
- package/src/panels/signals-panel.ts +217 -460
- package/src/panels/statement-editor.ts +710 -0
- package/src/panels/statusbar.ts +1 -1
- package/src/panels/style-panel.ts +25 -1
- package/src/panels/stylebook-panel.ts +0 -2
- package/src/panels/tab-bar.ts +175 -6
- package/src/panels/tab-strip.ts +62 -6
- package/src/panels/toolbar.ts +37 -3
- package/src/panels/welcome-screen.ts +50 -0
- package/src/platform-errors.ts +30 -0
- package/src/platforms/cloud.ts +172 -89
- package/src/platforms/devserver.ts +172 -0
- package/src/services/ai-project-tools.ts +541 -0
- package/src/services/ai-session-store.ts +28 -0
- package/src/services/ai-system-prompt.ts +194 -24
- package/src/services/ai-tools.ts +88 -21
- package/src/services/automation.ts +16 -0
- package/src/services/context-resolver.ts +73 -0
- package/src/services/data-service.ts +155 -0
- package/src/services/document-assistant.ts +81 -11
- package/src/services/gated-registry.ts +72 -0
- package/src/services/live-preview.ts +0 -0
- package/src/services/monaco-setup.ts +75 -26
- package/src/services/preview-eval.ts +68 -0
- package/src/services/project-adoption.ts +31 -0
- package/src/settings/contributed-section.ts +406 -0
- package/src/settings/extension-sections.ts +145 -0
- package/src/settings/schema-field-ui.ts +4 -2
- package/src/settings/settings-modal.ts +101 -42
- package/src/site-context.ts +12 -1
- package/src/store.ts +4 -0
- package/src/studio.ts +107 -52
- package/src/tabs/patch-ops.ts +25 -0
- package/src/tabs/tab.ts +39 -1
- package/src/tabs/transact.ts +61 -14
- package/src/types.ts +132 -1
- package/src/ui/dynamic-slot.ts +272 -0
- package/src/ui/expression-editor.ts +423 -125
- package/src/ui/field-row.ts +5 -0
- package/src/ui/form-controls.ts +322 -0
- package/src/ui/formula-catalog.ts +557 -0
- package/src/ui/formula-chips.ts +216 -0
- package/src/ui/formula-palette.ts +211 -0
- package/src/ui/layers.ts +40 -0
- package/src/ui/media-picker.ts +1 -1
- package/src/ui/panel-resize.ts +15 -1
- package/src/ui/progress-modal.ts +2 -2
- package/src/ui/schema-form.ts +524 -0
- package/src/utils/preview-format.ts +26 -0
- package/src/utils/studio-utils.ts +4 -3
- package/src/view.ts +4 -4
- package/src/workspace/workspace.ts +14 -0
- package/src/settings/content-types-editor.ts +0 -599
|
@@ -21,6 +21,7 @@ import { view } from "../view";
|
|
|
21
21
|
import { componentRegistry } from "../files/components";
|
|
22
22
|
import { widgetForType } from "./style-inputs";
|
|
23
23
|
import { renderFieldRow } from "../ui/field-row";
|
|
24
|
+
import { renderDynamicSlot, slotMode } from "../ui/dynamic-slot";
|
|
24
25
|
import { spTextArea, spTextField } from "../ui/field-input";
|
|
25
26
|
import {
|
|
26
27
|
attrLabel,
|
|
@@ -41,11 +42,13 @@ import { getPlatform } from "../platform";
|
|
|
41
42
|
import htmlMeta from "../../data/html-meta.json";
|
|
42
43
|
|
|
43
44
|
import type {
|
|
45
|
+
JxAttributeValue,
|
|
44
46
|
JxMutableNode,
|
|
45
47
|
JxPrototypeDef,
|
|
46
48
|
JxStateDefinition,
|
|
47
49
|
JxStateObject,
|
|
48
50
|
} from "@jxsuite/schema/types";
|
|
51
|
+
import type { SlotMode } from "../ui/dynamic-slot";
|
|
49
52
|
import type { JxPath } from "../state";
|
|
50
53
|
import type { SignalDef } from "./signals-panel.js";
|
|
51
54
|
|
|
@@ -113,11 +116,13 @@ function bindableFieldRow(
|
|
|
113
116
|
onChange: (v?: JsonValue) => void,
|
|
114
117
|
filterFn: ((d: SignalDef) => boolean) | null = null,
|
|
115
118
|
extraSignals: SignalOption[] | null = null,
|
|
119
|
+
fieldKey = `prop:${label}`,
|
|
120
|
+
caps: SlotMode[] = ["literal", "ref"],
|
|
116
121
|
) {
|
|
117
122
|
const tab = activeTab.value;
|
|
118
123
|
const defs = tab!.doc.document.state || {};
|
|
119
124
|
const boundRef = isRef(rawValue) ? rawValue.$ref : null;
|
|
120
|
-
const
|
|
125
|
+
const isDynamic = slotMode(rawValue) !== "literal";
|
|
121
126
|
|
|
122
127
|
const signalDefs = Object.entries(defs).filter(([, d]) =>
|
|
123
128
|
filterFn
|
|
@@ -126,70 +131,38 @@ function bindableFieldRow(
|
|
|
126
131
|
(d as JxPrototypeDef)?.$prototype !== "Function",
|
|
127
132
|
);
|
|
128
133
|
|
|
129
|
-
const staticVal =
|
|
130
|
-
const
|
|
134
|
+
const staticVal = isDynamic ? "" : (rawValue ?? "");
|
|
135
|
+
const draftKey = `prop:${label}`;
|
|
131
136
|
const staticTpl =
|
|
132
137
|
type === "textarea"
|
|
133
|
-
? spTextArea(
|
|
138
|
+
? spTextArea(draftKey, String(staticVal), (v: string) => onChange(v))
|
|
134
139
|
: type === "checkbox"
|
|
135
140
|
? html`<sp-checkbox
|
|
136
141
|
?checked=${Boolean(staticVal)}
|
|
137
142
|
@change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
|
|
138
143
|
></sp-checkbox>`
|
|
139
|
-
: spTextField(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
)}
|
|
158
|
-
${extraSignals
|
|
159
|
-
? html`
|
|
160
|
-
<sp-menu-divider></sp-menu-divider>
|
|
161
|
-
${extraSignals.map(
|
|
162
|
-
(sig: SignalOption) =>
|
|
163
|
-
html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
|
|
164
|
-
)}
|
|
165
|
-
`
|
|
166
|
-
: nothing}
|
|
167
|
-
</sp-picker>
|
|
168
|
-
`;
|
|
169
|
-
|
|
170
|
-
const onToggle = () => {
|
|
171
|
-
if (boundRef !== null) {
|
|
172
|
-
const defName = boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef;
|
|
173
|
-
const staticDefault = defaultAsString(defs[defName]);
|
|
174
|
-
onChange(staticDefault || undefined);
|
|
175
|
-
} else if (signalDefs.length > 0) {
|
|
176
|
-
onChange({ $ref: `#/state/${signalDefs[0]![0]}` });
|
|
177
|
-
} else if (extraSignals && extraSignals.length > 0) {
|
|
178
|
-
onChange({ $ref: extraSignals[0]!.value });
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
144
|
+
: spTextField(draftKey, String(staticVal), (v: string) => onChange(v));
|
|
145
|
+
|
|
146
|
+
// De-escalating to literal restores the bound signal's declared default (old unbind behavior).
|
|
147
|
+
const literalDefault = boundRef
|
|
148
|
+
? defaultAsString(defs[boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef]) ||
|
|
149
|
+
undefined
|
|
150
|
+
: undefined;
|
|
151
|
+
|
|
152
|
+
const slot = renderDynamicSlot({
|
|
153
|
+
caps,
|
|
154
|
+
extraSignals,
|
|
155
|
+
fieldKey,
|
|
156
|
+
literalDefault,
|
|
157
|
+
onChange,
|
|
158
|
+
staticWidget: staticTpl,
|
|
159
|
+
stateDefs: signalDefs.map(([defName]) => defName),
|
|
160
|
+
value: rawValue,
|
|
161
|
+
});
|
|
182
162
|
return html`
|
|
183
163
|
<div class="field-row">
|
|
184
164
|
<sp-field-label size="s">${label}</sp-field-label>
|
|
185
|
-
${
|
|
186
|
-
<sp-action-button
|
|
187
|
-
size="xs"
|
|
188
|
-
quiet
|
|
189
|
-
title=${isBound ? "Unbind (switch to static)" : "Bind to signal"}
|
|
190
|
-
@click=${onToggle}
|
|
191
|
-
>${isBound ? "\u26A1" : "\u2194"}</sp-action-button
|
|
192
|
-
>
|
|
165
|
+
${slot.modeButton} ${slot.widget}
|
|
193
166
|
</div>
|
|
194
167
|
`;
|
|
195
168
|
}
|
|
@@ -261,21 +234,42 @@ function renderRepeaterFieldsTemplate(
|
|
|
261
234
|
_mapSignals: SignalOption[] | null,
|
|
262
235
|
) {
|
|
263
236
|
return html`
|
|
264
|
-
${bindableFieldRow(
|
|
265
|
-
|
|
237
|
+
${bindableFieldRow(
|
|
238
|
+
"Items",
|
|
239
|
+
"text",
|
|
240
|
+
node.items,
|
|
241
|
+
(v: JsonValue) =>
|
|
242
|
+
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "items", v)),
|
|
243
|
+
null,
|
|
244
|
+
null,
|
|
245
|
+
`prop|${path.join("/")}|items`,
|
|
266
246
|
)}
|
|
267
247
|
${node.filter
|
|
268
|
-
? bindableFieldRow(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
248
|
+
? bindableFieldRow(
|
|
249
|
+
"Filter",
|
|
250
|
+
"text",
|
|
251
|
+
node.filter,
|
|
252
|
+
(v: JsonValue) =>
|
|
253
|
+
transactDoc(activeTab.value, (t) =>
|
|
254
|
+
mutateUpdateProperty(t, path, "filter", v || undefined),
|
|
255
|
+
),
|
|
256
|
+
null,
|
|
257
|
+
null,
|
|
258
|
+
`prop|${path.join("/")}|filter`,
|
|
272
259
|
)
|
|
273
260
|
: nothing}
|
|
274
261
|
${node.sort
|
|
275
|
-
? bindableFieldRow(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
262
|
+
? bindableFieldRow(
|
|
263
|
+
"Sort",
|
|
264
|
+
"text",
|
|
265
|
+
node.sort,
|
|
266
|
+
(v: JsonValue) =>
|
|
267
|
+
transactDoc(activeTab.value, (t) =>
|
|
268
|
+
mutateUpdateProperty(t, path, "sort", v || undefined),
|
|
269
|
+
),
|
|
270
|
+
null,
|
|
271
|
+
null,
|
|
272
|
+
`prop|${path.join("/")}|sort`,
|
|
279
273
|
)
|
|
280
274
|
: nothing}
|
|
281
275
|
<div style="display:flex;gap:8px;margin-top:4px">
|
|
@@ -331,6 +325,10 @@ function renderSwitchFieldsTemplate(
|
|
|
331
325
|
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "$switch", v)),
|
|
332
326
|
null,
|
|
333
327
|
mapSignals,
|
|
328
|
+
`prop|${path.join("/")}|$switch`,
|
|
329
|
+
// No literal rung: a $switch is inherently dynamic.
|
|
330
|
+
// De-escalating to literal would delete the key and demote the node mid-cycle.
|
|
331
|
+
["ref", "template"],
|
|
334
332
|
)}
|
|
335
333
|
<div
|
|
336
334
|
style="font-size:var(--spectrum-font-size-50, 11px);font-weight:600;color:var(--fg-dim);margin:8px 0 4px;text-transform:uppercase;letter-spacing:0.05em"
|
|
@@ -410,7 +408,12 @@ function renderComponentPropsFieldsTemplate(
|
|
|
410
408
|
const updateFn = isNpm
|
|
411
409
|
? (name: string, v?: JsonValue) =>
|
|
412
410
|
transactDoc(activeTab.value, (t) =>
|
|
413
|
-
mutateUpdateAttribute(
|
|
411
|
+
mutateUpdateAttribute(
|
|
412
|
+
t,
|
|
413
|
+
path,
|
|
414
|
+
name,
|
|
415
|
+
v === "" ? undefined : (v as JxAttributeValue | undefined),
|
|
416
|
+
),
|
|
414
417
|
)
|
|
415
418
|
: (name: string, v?: JsonValue) =>
|
|
416
419
|
transactDoc(activeTab.value, (t) => mutateUpdateProp(t, path, name, v));
|
|
@@ -429,58 +432,21 @@ function renderComponentPropsFieldsTemplate(
|
|
|
429
432
|
) => {
|
|
430
433
|
const rawValue = currentVals[prop.name];
|
|
431
434
|
const boundRef = isRef(rawValue) ? rawValue.$ref : null;
|
|
432
|
-
const isBound = boundRef !== null;
|
|
433
435
|
const hasVal = rawValue !== undefined && rawValue !== null;
|
|
434
436
|
const parsed = parseCemType(prop.type);
|
|
435
437
|
const onChange = (v?: JsonValue) => updateFn(prop.name, v);
|
|
436
|
-
const staticVal =
|
|
438
|
+
const staticVal = slotMode(rawValue) === "literal" ? String(rawValue ?? "") : "";
|
|
437
439
|
|
|
438
440
|
const clearProp = (e: Event) => {
|
|
439
441
|
e.stopPropagation();
|
|
440
442
|
updateFn(prop.name);
|
|
441
443
|
};
|
|
442
444
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
} else if (signalDefs.length > 0) {
|
|
449
|
-
onChange({ $ref: `#/state/${signalDefs[0]![0]}` });
|
|
450
|
-
} else if (extraSignals && extraSignals.length > 0) {
|
|
451
|
-
onChange({ $ref: extraSignals[0]!.value });
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
const boundTpl = html`
|
|
456
|
-
<sp-picker
|
|
457
|
-
size="s"
|
|
458
|
-
quiet
|
|
459
|
-
placeholder="— select signal —"
|
|
460
|
-
value=${boundRef ?? nothing}
|
|
461
|
-
@change=${(e: Event) => {
|
|
462
|
-
if ((e.target as HTMLInputElement).value) {
|
|
463
|
-
onChange({ $ref: (e.target as HTMLInputElement).value });
|
|
464
|
-
} else {
|
|
465
|
-
onChange();
|
|
466
|
-
}
|
|
467
|
-
}}
|
|
468
|
-
>
|
|
469
|
-
${signalDefs.map(
|
|
470
|
-
([defName]) =>
|
|
471
|
-
html`<sp-menu-item value=${`#/state/${defName}`}>${defName}</sp-menu-item>`,
|
|
472
|
-
)}
|
|
473
|
-
${extraSignals
|
|
474
|
-
? html`
|
|
475
|
-
<sp-menu-divider></sp-menu-divider>
|
|
476
|
-
${extraSignals.map(
|
|
477
|
-
(sig: SignalOption) =>
|
|
478
|
-
html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
|
|
479
|
-
)}
|
|
480
|
-
`
|
|
481
|
-
: nothing}
|
|
482
|
-
</sp-picker>
|
|
483
|
-
`;
|
|
445
|
+
// De-escalating to literal restores the bound signal's declared default (old unbind behavior).
|
|
446
|
+
const literalDefault = boundRef
|
|
447
|
+
? defaultAsString(defs[boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef]) ||
|
|
448
|
+
undefined
|
|
449
|
+
: undefined;
|
|
484
450
|
|
|
485
451
|
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
486
452
|
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -532,6 +498,16 @@ function renderComponentPropsFieldsTemplate(
|
|
|
532
498
|
);
|
|
533
499
|
}
|
|
534
500
|
|
|
501
|
+
const slot = renderDynamicSlot({
|
|
502
|
+
caps: ["literal", "ref", "template"],
|
|
503
|
+
extraSignals,
|
|
504
|
+
fieldKey: `cprop|${path.join("/")}|${prop.name}`,
|
|
505
|
+
literalDefault,
|
|
506
|
+
onChange,
|
|
507
|
+
staticWidget: widgetTpl,
|
|
508
|
+
stateDefs: signalDefs.map(([defName]) => defName),
|
|
509
|
+
value: rawValue,
|
|
510
|
+
});
|
|
535
511
|
return html`
|
|
536
512
|
<div class="style-row" data-prop=${prop.name}>
|
|
537
513
|
<div class="style-row-label">
|
|
@@ -541,15 +517,9 @@ function renderComponentPropsFieldsTemplate(
|
|
|
541
517
|
<sp-field-label size="s" title=${prop.description || prop.name}
|
|
542
518
|
>${camelToLabel(prop.name)}</sp-field-label
|
|
543
519
|
>
|
|
544
|
-
|
|
545
|
-
size="xs"
|
|
546
|
-
quiet
|
|
547
|
-
title=${isBound ? "Unbind (switch to static)" : "Bind to signal"}
|
|
548
|
-
@click=${onToggleBind}
|
|
549
|
-
>${isBound ? "\u26A1" : "\u2194"}</sp-action-button
|
|
550
|
-
>
|
|
520
|
+
${slot.modeButton}
|
|
551
521
|
</div>
|
|
552
|
-
${
|
|
522
|
+
${slot.widget}
|
|
553
523
|
</div>
|
|
554
524
|
`;
|
|
555
525
|
},
|
|
@@ -1162,9 +1132,22 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1162
1132
|
]
|
|
1163
1133
|
: null;
|
|
1164
1134
|
|
|
1135
|
+
// Signals offered to attribute/textContent bindings (handlers and Functions excluded).
|
|
1136
|
+
const bindableSignals = Object.entries(tab.doc.document.state || {})
|
|
1137
|
+
.filter(
|
|
1138
|
+
([, d]) =>
|
|
1139
|
+
!(d as Record<string, unknown>)?.$handler &&
|
|
1140
|
+
(d as JxPrototypeDef)?.$prototype !== "Function",
|
|
1141
|
+
)
|
|
1142
|
+
.map(([defName]) => defName);
|
|
1143
|
+
|
|
1165
1144
|
function renderAttrRow(attr: string, entry: HtmlMetaEntry, value: unknown) {
|
|
1166
1145
|
const type = inferInputType(entry);
|
|
1167
1146
|
const hasVal = value !== undefined && value !== "";
|
|
1147
|
+
const commitAttr = (v?: JsonValue) =>
|
|
1148
|
+
transactDoc(activeTab.value!, (t) =>
|
|
1149
|
+
mutateUpdateAttribute(t, path, attr, v as JxAttributeValue | undefined),
|
|
1150
|
+
);
|
|
1168
1151
|
|
|
1169
1152
|
// Enhanced Link handling: only for anchors (a/area) with a plain (non-binding) value. Bindings
|
|
1170
1153
|
// ($ref objects or ${…} template strings) fall through to the raw widget to stay editable.
|
|
@@ -1178,39 +1161,54 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1178
1161
|
}
|
|
1179
1162
|
}
|
|
1180
1163
|
|
|
1164
|
+
// Attribute strings are schema-legal at three rungs: literal, $ref binding, ${} template.
|
|
1165
|
+
const attrSlot = (staticWidget: unknown) =>
|
|
1166
|
+
renderDynamicSlot({
|
|
1167
|
+
caps: ["literal", "ref", "template"],
|
|
1168
|
+
extraSignals: mapSignals,
|
|
1169
|
+
fieldKey: `attr|${path.join("/")}|${attr}`,
|
|
1170
|
+
onChange: commitAttr,
|
|
1171
|
+
staticWidget,
|
|
1172
|
+
stateDefs: bindableSignals,
|
|
1173
|
+
value,
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1181
1176
|
if (entry.type === "boolean") {
|
|
1177
|
+
const checkboxWidget = html`
|
|
1178
|
+
<sp-checkbox
|
|
1179
|
+
size="s"
|
|
1180
|
+
.checked=${live(Boolean(value))}
|
|
1181
|
+
@change=${(e: Event) =>
|
|
1182
|
+
commitAttr((e.target as HTMLInputElement).checked ? "" : undefined)}
|
|
1183
|
+
>
|
|
1184
|
+
</sp-checkbox>
|
|
1185
|
+
`;
|
|
1186
|
+
const slot = attrSlot(checkboxWidget);
|
|
1182
1187
|
return renderFieldRow({
|
|
1183
1188
|
hasValue: hasVal,
|
|
1184
1189
|
label: attrLabel(entry, attr),
|
|
1190
|
+
labelExtra: slot.modeButton,
|
|
1185
1191
|
onClear: () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr)),
|
|
1186
1192
|
prop: attr,
|
|
1187
|
-
widget:
|
|
1188
|
-
<sp-checkbox
|
|
1189
|
-
size="s"
|
|
1190
|
-
.checked=${live(Boolean(value))}
|
|
1191
|
-
@change=${(e: Event) =>
|
|
1192
|
-
transactDoc(activeTab.value, (t) =>
|
|
1193
|
-
mutateUpdateAttribute(
|
|
1194
|
-
t,
|
|
1195
|
-
path,
|
|
1196
|
-
attr,
|
|
1197
|
-
(e.target as HTMLInputElement).checked ? "" : undefined,
|
|
1198
|
-
),
|
|
1199
|
-
)}
|
|
1200
|
-
>
|
|
1201
|
-
</sp-checkbox>
|
|
1202
|
-
`,
|
|
1193
|
+
widget: slot.widget,
|
|
1203
1194
|
});
|
|
1204
1195
|
}
|
|
1205
1196
|
|
|
1197
|
+
const literalWidget = widgetForType(
|
|
1198
|
+
type,
|
|
1199
|
+
entry,
|
|
1200
|
+
attr,
|
|
1201
|
+
isRef(value) ? "" : String(value || ""),
|
|
1202
|
+
(v: string) => commitAttr(v || undefined),
|
|
1203
|
+
);
|
|
1204
|
+
const slot = attrSlot(literalWidget);
|
|
1206
1205
|
return renderFieldRow({
|
|
1207
1206
|
hasValue: hasVal,
|
|
1208
1207
|
label: attrLabel(entry, attr),
|
|
1208
|
+
labelExtra: slot.modeButton,
|
|
1209
1209
|
onClear: () => transactDoc(activeTab.value, (t) => mutateUpdateAttribute(t, path, attr)),
|
|
1210
1210
|
prop: attr,
|
|
1211
|
-
widget:
|
|
1212
|
-
transactDoc(activeTab.value!, (t) => mutateUpdateAttribute(t, path, attr, v || undefined)),
|
|
1213
|
-
),
|
|
1211
|
+
widget: slot.widget,
|
|
1214
1212
|
});
|
|
1215
1213
|
}
|
|
1216
1214
|
|
|
@@ -1280,6 +1278,24 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1280
1278
|
|
|
1281
1279
|
// ── Build section templates ─────────────────────────────────────────
|
|
1282
1280
|
|
|
1281
|
+
const textSlot = renderDynamicSlot({
|
|
1282
|
+
caps: ["literal", "ref", "template"],
|
|
1283
|
+
extraSignals: mapSignals,
|
|
1284
|
+
fieldKey: `prop|${path.join("/")}|textContent`,
|
|
1285
|
+
onChange: (v?: JsonValue) =>
|
|
1286
|
+
transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "textContent", v)),
|
|
1287
|
+
staticWidget: spTextArea(
|
|
1288
|
+
"prop:textContent",
|
|
1289
|
+
typeof node.textContent === "string" ? node.textContent : "",
|
|
1290
|
+
(v: string) =>
|
|
1291
|
+
transactDoc(activeTab.value, (t) =>
|
|
1292
|
+
mutateUpdateProperty(t, path, "textContent", v || undefined),
|
|
1293
|
+
),
|
|
1294
|
+
),
|
|
1295
|
+
stateDefs: bindableSignals,
|
|
1296
|
+
value: node.textContent,
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1283
1299
|
const elemT = html`
|
|
1284
1300
|
<sp-accordion-item
|
|
1285
1301
|
label="Element"
|
|
@@ -1365,15 +1381,9 @@ export function renderPropertiesPanelTemplate(ctx: {
|
|
|
1365
1381
|
></span>`
|
|
1366
1382
|
: nothing}
|
|
1367
1383
|
<sp-field-label size="s">Text Content</sp-field-label>
|
|
1384
|
+
${textSlot.modeButton}
|
|
1368
1385
|
</div>
|
|
1369
|
-
${
|
|
1370
|
-
"prop:textContent",
|
|
1371
|
-
typeof node.textContent === "string" ? node.textContent : "",
|
|
1372
|
-
(v: string) =>
|
|
1373
|
-
transactDoc(activeTab.value, (t) =>
|
|
1374
|
-
mutateUpdateProperty(t, path, "textContent", v || undefined),
|
|
1375
|
-
),
|
|
1376
|
-
)}
|
|
1386
|
+
${textSlot.widget}
|
|
1377
1387
|
</div>
|
|
1378
1388
|
`
|
|
1379
1389
|
: nothing}
|
|
@@ -10,8 +10,7 @@ import { rightPanel, updateUi } from "../store";
|
|
|
10
10
|
import { effect, effectScope } from "../reactivity";
|
|
11
11
|
import { createPanelScheduler } from "./panel-scheduler";
|
|
12
12
|
import type { PanelScheduler } from "./panel-scheduler";
|
|
13
|
-
import { activeTab
|
|
14
|
-
import { consumePendingAgentPrompt, hasPendingAgentPrompt } from "../services/agent-seed";
|
|
13
|
+
import { activeTab } from "../workspace/workspace";
|
|
15
14
|
import { tabIcon } from "./activity-bar";
|
|
16
15
|
import { eventsSidebarTemplate } from "./events-panel";
|
|
17
16
|
import { isCustomElementDoc } from "./signals-panel";
|
|
@@ -21,12 +20,6 @@ import { renderStylePanelTemplate } from "./style-panel";
|
|
|
21
20
|
import { renderPropertiesPanelTemplate } from "./properties-panel";
|
|
22
21
|
|
|
23
22
|
import type { EffectScope } from "@vue/reactivity";
|
|
24
|
-
import {
|
|
25
|
-
renderAiPanelTemplate,
|
|
26
|
-
bindAiPanelHost,
|
|
27
|
-
mountAiPanel,
|
|
28
|
-
seedAssistantPrompt,
|
|
29
|
-
} from "./ai-panel";
|
|
30
23
|
|
|
31
24
|
interface RightPanelCtx {
|
|
32
25
|
navigateToComponent: (path: string) => void;
|
|
@@ -47,7 +40,6 @@ let _scheduler: PanelScheduler | null = null;
|
|
|
47
40
|
*/
|
|
48
41
|
export function mount(ctx: RightPanelCtx) {
|
|
49
42
|
_ctx = ctx;
|
|
50
|
-
mountAiPanel();
|
|
51
43
|
_scheduler = createPanelScheduler({
|
|
52
44
|
blockWhile: isColorPopoverOpen,
|
|
53
45
|
render: _doRender,
|
|
@@ -87,7 +79,6 @@ export function unmount() {
|
|
|
87
79
|
_propsContainer = null;
|
|
88
80
|
_eventsContainer = null;
|
|
89
81
|
_styleContainer = null;
|
|
90
|
-
_assistantContainer = null;
|
|
91
82
|
}
|
|
92
83
|
|
|
93
84
|
/**
|
|
@@ -101,7 +92,6 @@ export function render() {
|
|
|
101
92
|
let _propsContainer: HTMLElement | null = null;
|
|
102
93
|
let _eventsContainer: HTMLElement | null = null;
|
|
103
94
|
let _styleContainer: HTMLElement | null = null;
|
|
104
|
-
let _assistantContainer: HTMLElement | null = null;
|
|
105
95
|
|
|
106
96
|
function _ensureContainers() {
|
|
107
97
|
if (_propsContainer) {
|
|
@@ -113,12 +103,6 @@ function _ensureContainers() {
|
|
|
113
103
|
_eventsContainer.className = "panel-body";
|
|
114
104
|
_styleContainer = document.createElement("div");
|
|
115
105
|
_styleContainer.className = "panel-body";
|
|
116
|
-
_assistantContainer = document.createElement("div");
|
|
117
|
-
_assistantContainer.className = "panel-body";
|
|
118
|
-
_assistantContainer.style.cssText = "display:flex;flex-direction:column;overflow:hidden";
|
|
119
|
-
// The AI panel owns a focus-guard-free rAF render loop into this container so
|
|
120
|
-
// Streaming repaints while the composer is focused (see ai-panel.ts).
|
|
121
|
-
bindAiPanelHost(_assistantContainer);
|
|
122
106
|
}
|
|
123
107
|
|
|
124
108
|
function _doRender() {
|
|
@@ -138,20 +122,17 @@ function _doRender() {
|
|
|
138
122
|
selection: aTab.session.selection,
|
|
139
123
|
ui: aTab.session.ui,
|
|
140
124
|
};
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
const tab = S.ui.rightTab;
|
|
125
|
+
// Coerce stale values ("assistant" moved to the persistent chat sidebar; automation or a
|
|
126
|
+
// Restored session may still carry it).
|
|
127
|
+
const tab = ["properties", "events", "style"].includes(S.ui.rightTab)
|
|
128
|
+
? S.ui.rightTab
|
|
129
|
+
: "properties";
|
|
148
130
|
|
|
149
131
|
// Render tabs header
|
|
150
132
|
const panelTabs = [
|
|
151
133
|
{ icon: "sp-icon-properties", label: "Properties", value: "properties" },
|
|
152
134
|
{ icon: "sp-icon-event", label: "Events", value: "events" },
|
|
153
135
|
{ icon: "sp-icon-brush", label: "Style", value: "style" },
|
|
154
|
-
{ icon: "sp-icon-chat", label: "Assistant", value: "assistant" },
|
|
155
136
|
];
|
|
156
137
|
const tabsT = html`
|
|
157
138
|
<div class="panel-tabs">
|
|
@@ -178,21 +159,12 @@ function _doRender() {
|
|
|
178
159
|
`;
|
|
179
160
|
|
|
180
161
|
_ensureContainers();
|
|
181
|
-
const containers = [
|
|
182
|
-
|
|
183
|
-
_eventsContainer,
|
|
184
|
-
_styleContainer,
|
|
185
|
-
_assistantContainer,
|
|
186
|
-
] as HTMLElement[];
|
|
187
|
-
const tabKeys = ["properties", "events", "style", "assistant"];
|
|
162
|
+
const containers = [_propsContainer, _eventsContainer, _styleContainer] as HTMLElement[];
|
|
163
|
+
const tabKeys = ["properties", "events", "style"];
|
|
188
164
|
|
|
189
165
|
// Show/hide containers
|
|
190
166
|
for (let i = 0; i < containers.length; i++) {
|
|
191
|
-
|
|
192
|
-
containers[i]!.style.display = tabKeys[i] === "assistant" ? "flex" : "";
|
|
193
|
-
} else {
|
|
194
|
-
containers[i]!.style.display = "none";
|
|
195
|
-
}
|
|
167
|
+
containers[i]!.style.display = tabKeys[i] === tab ? "" : "none";
|
|
196
168
|
}
|
|
197
169
|
|
|
198
170
|
// Render tabs into the right panel, append containers
|
|
@@ -224,16 +196,6 @@ function _doRender() {
|
|
|
224
196
|
} catch (error) {
|
|
225
197
|
console.error("[renderStylePanelTemplate]", error);
|
|
226
198
|
}
|
|
227
|
-
} else if (tab === "assistant") {
|
|
228
|
-
litRender(renderAiPanelTemplate(), _assistantContainer!);
|
|
229
|
-
if (root) {
|
|
230
|
-
// Consume-on-read keeps repeated renders idempotent; seed after the panel template
|
|
231
|
-
// Has rendered so the assistant machinery is in place.
|
|
232
|
-
const prompt = consumePendingAgentPrompt(root);
|
|
233
|
-
if (prompt) {
|
|
234
|
-
requestAnimationFrame(() => void seedAssistantPrompt(prompt));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
199
|
}
|
|
238
200
|
} catch (error) {
|
|
239
201
|
console.error("right-panel render error:", error);
|