@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
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Dynamic-slot — the shared "make this dynamic" control for any bindable document position.
|
|
4
|
+
*
|
|
5
|
+
* Renders the panel-provided static widget plus a label-side mode button that cycles the value up
|
|
6
|
+
* the Rule of Least Power ladder (spec §2.2): literal → $ref pointer → ${} template → $expression.
|
|
7
|
+
* Capability-driven: each slot offers only the modes its schema position allows, so panels stay
|
|
8
|
+
* decoupled from which rungs are blessed where. Each mode's last value is remembered per field for
|
|
9
|
+
* the session, so cycling back to an earlier mode restores what the user had there.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { html, nothing } from "lit-html";
|
|
13
|
+
import { live } from "lit-html/directives/live.js";
|
|
14
|
+
import { isRef, isTemplateString } from "@jxsuite/schema/guards";
|
|
15
|
+
import { cloneValue } from "../tabs/doc-op-apply";
|
|
16
|
+
import { activeTab } from "../workspace/workspace";
|
|
17
|
+
import { renderExpressionEditor } from "./expression-editor";
|
|
18
|
+
|
|
19
|
+
import type { TemplateResult } from "lit-html";
|
|
20
|
+
import type { EditorPreview } from "./expression-editor";
|
|
21
|
+
import type { JxStateDefinition } from "@jxsuite/schema/types";
|
|
22
|
+
import type { JsonValue } from "../types";
|
|
23
|
+
|
|
24
|
+
export type SlotMode = "literal" | "ref" | "template" | "expression";
|
|
25
|
+
|
|
26
|
+
const MODE_LABELS: Record<SlotMode, string> = {
|
|
27
|
+
expression: "fx",
|
|
28
|
+
literal: "abc",
|
|
29
|
+
ref: "$ref",
|
|
30
|
+
template: "${}",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const MODE_NOUNS: Record<SlotMode, string> = {
|
|
34
|
+
expression: "expression",
|
|
35
|
+
literal: "static",
|
|
36
|
+
ref: "signal binding",
|
|
37
|
+
template: "template literal",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* Session memory of each field's last value per mode, so cycling modes round-trips user input.
|
|
42
|
+
* Keyed `${tabId}|${fieldKey}|${mode}`; the mode key is derived from the value at stash time, so a
|
|
43
|
+
* restore always lands a value whose slotMode() matches the target mode. `undefined` is a
|
|
44
|
+
* legitimate stash (cleared literal), hence Map.has() discrimination on recall.
|
|
45
|
+
*/
|
|
46
|
+
const slotModeMemory = new Map<string, JsonValue | undefined>();
|
|
47
|
+
|
|
48
|
+
/** Test hook: drop all remembered per-mode field values. */
|
|
49
|
+
export function resetSlotModeMemory(): void {
|
|
50
|
+
slotModeMemory.clear();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function memoryKey(fieldKey: string, mode: SlotMode): string {
|
|
54
|
+
return `${activeTab.value?.id ?? "-"}|${fieldKey}|${mode}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hasRefOptions(opts: DynamicSlotOpts): boolean {
|
|
58
|
+
return opts.stateDefs.length > 0 || Boolean(opts.extraSignals?.length);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SignalOption {
|
|
62
|
+
value: string;
|
|
63
|
+
label: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface DynamicSlotOpts {
|
|
67
|
+
/** Stable identity for per-mode value memory; must incorporate the node path. */
|
|
68
|
+
fieldKey: string;
|
|
69
|
+
/** Current raw document value at this position. */
|
|
70
|
+
value: unknown;
|
|
71
|
+
/** Write-through to the document; `undefined` clears the position. */
|
|
72
|
+
onChange: (v?: JsonValue) => void;
|
|
73
|
+
/** Panel-provided literal editor for this slot's type. */
|
|
74
|
+
staticWidget: unknown;
|
|
75
|
+
/** Modes this schema position allows, in ladder order. "literal" and "ref" are the floor. */
|
|
76
|
+
caps: SlotMode[];
|
|
77
|
+
/** State keys offered by the ref picker and expression editor. */
|
|
78
|
+
stateDefs: string[];
|
|
79
|
+
/** Full state defs map — lets expression mode resolve named-formula catalog entries. */
|
|
80
|
+
stateEntries?: Record<string, JxStateDefinition> | null;
|
|
81
|
+
/** Extra pointer options beyond #/state/* (e.g. $map/item within repeater templates). */
|
|
82
|
+
extraSignals?: SignalOption[] | null;
|
|
83
|
+
/** Offer event#/ refs inside expression mode (handler positions only). */
|
|
84
|
+
allowEventRef?: boolean;
|
|
85
|
+
/** Live values for expression mode badges (services/preview-eval.ts). */
|
|
86
|
+
preview?: EditorPreview | null;
|
|
87
|
+
/** Static value restored when de-escalating to literal mode. */
|
|
88
|
+
literalDefault?: JsonValue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Detect which rung of the ladder a raw document value occupies. */
|
|
92
|
+
export function slotMode(value: unknown): SlotMode {
|
|
93
|
+
if (isRef(value)) {
|
|
94
|
+
return "ref";
|
|
95
|
+
}
|
|
96
|
+
if (isTemplateString(value)) {
|
|
97
|
+
return "template";
|
|
98
|
+
}
|
|
99
|
+
if (value && typeof value === "object" && "$expression" in value) {
|
|
100
|
+
return "expression";
|
|
101
|
+
}
|
|
102
|
+
return "literal";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function defaultForSlotMode(mode: SlotMode, opts: DynamicSlotOpts): JsonValue | undefined {
|
|
106
|
+
switch (mode) {
|
|
107
|
+
case "ref": {
|
|
108
|
+
const [first] = opts.stateDefs;
|
|
109
|
+
return first ? { $ref: `#/state/${first}` } : { $ref: opts.extraSignals?.[0]?.value ?? "" };
|
|
110
|
+
}
|
|
111
|
+
case "template": {
|
|
112
|
+
const [first] = opts.stateDefs;
|
|
113
|
+
return first ? `\${state.${first}}` : "${}";
|
|
114
|
+
}
|
|
115
|
+
case "expression": {
|
|
116
|
+
return { $expression: { operator: "??", target: null, value: null } };
|
|
117
|
+
}
|
|
118
|
+
default: {
|
|
119
|
+
return opts.literalDefault;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderRefWidget(refVal: string, opts: DynamicSlotOpts) {
|
|
125
|
+
return html`
|
|
126
|
+
<sp-picker
|
|
127
|
+
size="s"
|
|
128
|
+
quiet
|
|
129
|
+
style="flex:1"
|
|
130
|
+
placeholder="— select signal —"
|
|
131
|
+
value=${refVal || nothing}
|
|
132
|
+
@change=${(e: Event) => {
|
|
133
|
+
const v = (e.target as HTMLInputElement).value;
|
|
134
|
+
if (v) {
|
|
135
|
+
opts.onChange({ $ref: v });
|
|
136
|
+
} else {
|
|
137
|
+
opts.onChange();
|
|
138
|
+
}
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
${opts.stateDefs.map(
|
|
142
|
+
(name) => html`<sp-menu-item value=${`#/state/${name}`}>${name}</sp-menu-item>`,
|
|
143
|
+
)}
|
|
144
|
+
${opts.extraSignals?.length
|
|
145
|
+
? html`
|
|
146
|
+
<sp-menu-divider></sp-menu-divider>
|
|
147
|
+
${opts.extraSignals.map(
|
|
148
|
+
(sig) => html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`,
|
|
149
|
+
)}
|
|
150
|
+
`
|
|
151
|
+
: nothing}
|
|
152
|
+
</sp-picker>
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function renderTemplateWidget(value: unknown, opts: DynamicSlotOpts) {
|
|
157
|
+
return html`
|
|
158
|
+
<sp-textfield
|
|
159
|
+
size="s"
|
|
160
|
+
style="flex:1;font-family:var(--spectrum-code-font-family, monospace)"
|
|
161
|
+
placeholder="\${state.…}"
|
|
162
|
+
.value=${live(String(value ?? ""))}
|
|
163
|
+
@change=${(e: Event) => opts.onChange((e.target as HTMLInputElement).value)}
|
|
164
|
+
></sp-textfield>
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface DynamicSlotParts {
|
|
169
|
+
/** Label-side mode-cycle button. */
|
|
170
|
+
modeButton: TemplateResult;
|
|
171
|
+
/** Active-mode widget, sized to fill the row's widget column. */
|
|
172
|
+
widget: TemplateResult;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The label-side field-mode button. Shows the current mode's glyph — quiet/gray while static and
|
|
177
|
+
* accent-tinted once the value is dynamic — and each click cycles to the next capped mode, stashing
|
|
178
|
+
* the outgoing mode's value and restoring the incoming mode's remembered one.
|
|
179
|
+
*/
|
|
180
|
+
function renderModeButton(mode: SlotMode, caps: SlotMode[], opts: DynamicSlotOpts): TemplateResult {
|
|
181
|
+
const color =
|
|
182
|
+
mode !== "literal"
|
|
183
|
+
? "color:var(--spectrum-accent-content-color-default, #5c9dff)"
|
|
184
|
+
: "color:var(--spectrum-gray-600, #808080)";
|
|
185
|
+
// A ref rung with nothing to point at is a dead end — drop it rather than trap the cycle there.
|
|
186
|
+
const cycle = caps.filter((m) => m !== "ref" || hasRefOptions(opts));
|
|
187
|
+
if (cycle.length < 2) {
|
|
188
|
+
const hint = `Field mode: ${MODE_NOUNS[mode]} (no other modes available)`;
|
|
189
|
+
return html`
|
|
190
|
+
<sp-action-button
|
|
191
|
+
size="xs"
|
|
192
|
+
quiet
|
|
193
|
+
disabled
|
|
194
|
+
class="dynamic-slot-mode"
|
|
195
|
+
style=${color}
|
|
196
|
+
title=${hint}
|
|
197
|
+
aria-label=${hint}
|
|
198
|
+
>${MODE_LABELS[mode]}</sp-action-button
|
|
199
|
+
>
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
// An uncapped current mode indexes to -1, so its next stop is cycle[0] (literal).
|
|
203
|
+
const next = cycle[(cycle.indexOf(mode) + 1) % cycle.length]!;
|
|
204
|
+
const hint = `Field mode: ${MODE_NOUNS[mode]} — click for ${MODE_NOUNS[next]}`;
|
|
205
|
+
return html`
|
|
206
|
+
<sp-action-button
|
|
207
|
+
size="xs"
|
|
208
|
+
quiet
|
|
209
|
+
class="dynamic-slot-mode"
|
|
210
|
+
style=${color}
|
|
211
|
+
title=${hint}
|
|
212
|
+
aria-label=${hint}
|
|
213
|
+
@click=${() => {
|
|
214
|
+
slotModeMemory.set(
|
|
215
|
+
memoryKey(opts.fieldKey, mode),
|
|
216
|
+
cloneValue(opts.value as JsonValue | undefined),
|
|
217
|
+
);
|
|
218
|
+
// Leaving ref seeds an empty literal stash with the signal's declared default.
|
|
219
|
+
// Unbind-restores-default thus survives multi-hop cycles (ref → template → literal).
|
|
220
|
+
if (mode === "ref" && opts.literalDefault !== undefined) {
|
|
221
|
+
const litKey = memoryKey(opts.fieldKey, "literal");
|
|
222
|
+
if (!slotModeMemory.has(litKey)) {
|
|
223
|
+
slotModeMemory.set(litKey, cloneValue(opts.literalDefault));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const key = memoryKey(opts.fieldKey, next);
|
|
227
|
+
opts.onChange(
|
|
228
|
+
slotModeMemory.has(key)
|
|
229
|
+
? cloneValue(slotModeMemory.get(key))
|
|
230
|
+
: defaultForSlotMode(next, opts),
|
|
231
|
+
);
|
|
232
|
+
}}
|
|
233
|
+
>${MODE_LABELS[mode]}</sp-action-button
|
|
234
|
+
>
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Render a bindable slot as two parts the panel places independently: the active mode's widget for
|
|
240
|
+
* the row's value column, and the mode-cycle button for the label side.
|
|
241
|
+
*/
|
|
242
|
+
export function renderDynamicSlot(opts: DynamicSlotOpts): DynamicSlotParts {
|
|
243
|
+
const caps: SlotMode[] = opts.caps.length > 0 ? opts.caps : ["literal", "ref"];
|
|
244
|
+
const mode = slotMode(opts.value);
|
|
245
|
+
|
|
246
|
+
const widget =
|
|
247
|
+
mode === "ref"
|
|
248
|
+
? renderRefWidget(isRef(opts.value) ? opts.value.$ref : "", opts)
|
|
249
|
+
: mode === "template"
|
|
250
|
+
? renderTemplateWidget(opts.value, opts)
|
|
251
|
+
: mode === "expression"
|
|
252
|
+
? renderExpressionEditor(
|
|
253
|
+
(opts.value as { $expression?: unknown }).$expression,
|
|
254
|
+
(node) => opts.onChange({ $expression: node } as JsonValue),
|
|
255
|
+
{
|
|
256
|
+
allowEventRef: opts.allowEventRef ?? false,
|
|
257
|
+
preview: opts.preview ?? null,
|
|
258
|
+
stateDefs: opts.stateDefs,
|
|
259
|
+
stateEntries: opts.stateEntries ?? null,
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
: opts.staticWidget;
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
modeButton: renderModeButton(mode, caps, opts),
|
|
266
|
+
widget: html`
|
|
267
|
+
<div class="dynamic-slot" style="display:flex;flex:1;min-width:0">
|
|
268
|
+
<div style="flex:1;min-width:0">${widget}</div>
|
|
269
|
+
</div>
|
|
270
|
+
`,
|
|
271
|
+
};
|
|
272
|
+
}
|