@jxsuite/studio 0.28.5 → 0.30.1
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.css +98 -98
- package/dist/studio.js +9368 -6586
- package/dist/studio.js.map +109 -83
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +11 -10
- package/src/browse/browse-modal.ts +2 -2
- package/src/browse/browse.ts +20 -19
- package/src/canvas/canvas-diff.ts +3 -3
- package/src/canvas/canvas-helpers.ts +15 -2
- package/src/canvas/canvas-live-render.ts +168 -86
- package/src/canvas/canvas-patcher.ts +656 -0
- package/src/canvas/canvas-perf.ts +68 -0
- package/src/canvas/canvas-render.ts +135 -23
- package/src/canvas/canvas-subtree-render.ts +113 -0
- package/src/canvas/canvas-utils.ts +4 -0
- package/src/editor/component-inline-edit.ts +4 -35
- package/src/editor/context-menu.ts +96 -76
- package/src/editor/convert-to-component.ts +11 -2
- package/src/editor/convert-to-repeater.ts +4 -5
- package/src/editor/inline-edit.ts +9 -9
- package/src/editor/inline-format.ts +11 -11
- package/src/editor/merge-tags.ts +120 -0
- package/src/editor/shortcuts.ts +4 -4
- package/src/files/components.ts +37 -0
- package/src/files/file-ops.ts +28 -7
- package/src/files/files.ts +68 -24
- package/src/files/fs-events.ts +162 -0
- package/src/github/github-auth.ts +14 -2
- package/src/github/github-publish.ts +12 -2
- package/src/panels/activity-bar.ts +1 -1
- package/src/panels/ai-panel.ts +67 -39
- package/src/panels/block-action-bar.ts +72 -1
- package/src/panels/canvas-dnd.ts +55 -26
- package/src/panels/data-explorer.ts +5 -3
- package/src/panels/dnd.ts +14 -17
- package/src/panels/editors.ts +5 -23
- package/src/panels/elements-panel.ts +2 -12
- package/src/panels/events-panel.ts +1 -1
- package/src/panels/git-panel.ts +6 -6
- package/src/panels/head-panel.ts +1 -1
- package/src/panels/layers-panel.ts +177 -147
- package/src/panels/preview-render.ts +15 -0
- package/src/panels/properties-panel.ts +16 -27
- package/src/panels/quick-search.ts +2 -2
- package/src/panels/right-panel.ts +2 -6
- package/src/panels/shared.ts +3 -0
- package/src/panels/signals-panel.ts +43 -33
- package/src/panels/statusbar.ts +15 -6
- package/src/panels/style-panel.ts +3 -3
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-panel.ts +4 -4
- package/src/panels/tab-bar.ts +198 -0
- package/src/panels/tab-strip.ts +2 -2
- package/src/panels/toolbar.ts +6 -75
- package/src/platforms/devserver.ts +113 -35
- package/src/reactivity.ts +2 -0
- package/src/services/cem-export.ts +23 -2
- package/src/services/code-services.ts +16 -2
- package/src/services/monaco-setup.ts +2 -1
- package/src/settings/content-types-editor.ts +16 -16
- package/src/settings/css-vars-editor.ts +2 -2
- package/src/settings/defs-editor.ts +14 -14
- package/src/settings/general-settings.ts +6 -6
- package/src/settings/head-editor.ts +2 -2
- package/src/site-context.ts +1 -1
- package/src/state.ts +66 -12
- package/src/store.ts +15 -3
- package/src/studio.ts +88 -35
- package/src/tabs/patch-ops.ts +143 -0
- package/src/tabs/tab.ts +15 -1
- package/src/tabs/transact.ts +454 -27
- package/src/types.ts +71 -1
- package/src/ui/color-selector.ts +7 -7
- package/src/ui/icons.ts +0 -30
- package/src/ui/media-picker.ts +2 -2
- package/src/ui/panel-resize.ts +14 -8
- package/src/ui/unit-selector.ts +5 -2
- package/src/ui/value-selector.ts +3 -3
- package/src/utils/canvas-media.ts +6 -6
- package/src/utils/edit-display.ts +125 -83
- package/src/utils/google-fonts.ts +1 -1
- package/src/utils/inherited-style.ts +3 -2
- package/src/utils/studio-utils.ts +1 -1
- package/src/view.ts +4 -1
package/src/panels/ai-panel.ts
CHANGED
|
@@ -43,6 +43,14 @@ interface QuikChatInstance {
|
|
|
43
43
|
historyImport: (history: unknown[]) => void;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
type QuikChatCtor = new (
|
|
47
|
+
container: HTMLElement,
|
|
48
|
+
onSend: (chat: unknown, msg: string) => void,
|
|
49
|
+
opts: Record<string, unknown>,
|
|
50
|
+
) => QuikChatInstance;
|
|
51
|
+
|
|
52
|
+
const QuikChat = quikchat as unknown as QuikChatCtor;
|
|
53
|
+
|
|
46
54
|
let chatInstance: QuikChatInstance | null = null;
|
|
47
55
|
let chatContainerEl: Element | null = null;
|
|
48
56
|
let _quikChatEl: HTMLElement | null = null;
|
|
@@ -57,7 +65,7 @@ export function mountAiPanel() {
|
|
|
57
65
|
return;
|
|
58
66
|
}
|
|
59
67
|
mounted = true;
|
|
60
|
-
checkAuth();
|
|
68
|
+
void checkAuth();
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
const _g = globalThis as unknown as {
|
|
@@ -87,10 +95,10 @@ export function mountQuikChat() {
|
|
|
87
95
|
return;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
chatInstance = new
|
|
98
|
+
chatInstance = new QuikChat(
|
|
91
99
|
container,
|
|
92
100
|
(_chat: unknown, msg: string) => {
|
|
93
|
-
handleUserSend(msg);
|
|
101
|
+
void handleUserSend(msg);
|
|
94
102
|
},
|
|
95
103
|
{
|
|
96
104
|
messagesArea: { alternating: false },
|
|
@@ -197,14 +205,14 @@ function stop() {
|
|
|
197
205
|
return;
|
|
198
206
|
}
|
|
199
207
|
const plat = getPlatform();
|
|
200
|
-
plat.aiStopSession(sessionId);
|
|
208
|
+
void plat.aiStopSession(sessionId);
|
|
201
209
|
finishStream();
|
|
202
210
|
}
|
|
203
211
|
|
|
204
212
|
function newChat() {
|
|
205
213
|
if (sessionId) {
|
|
206
214
|
const plat = getPlatform();
|
|
207
|
-
plat.aiDeleteSession(sessionId);
|
|
215
|
+
void plat.aiDeleteSession(sessionId);
|
|
208
216
|
}
|
|
209
217
|
disconnectStream();
|
|
210
218
|
messages = [];
|
|
@@ -223,6 +231,31 @@ function newChat() {
|
|
|
223
231
|
|
|
224
232
|
// ─── SSE Stream ─────────────────────────────────────────────────────────────
|
|
225
233
|
|
|
234
|
+
/** SSE `stream_event` payload (subset used here). */
|
|
235
|
+
interface StreamEventData {
|
|
236
|
+
event?: { type?: string; delta?: { type?: string; text?: string } };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** SSE `result` payload (subset used here). */
|
|
240
|
+
interface ResultData {
|
|
241
|
+
result?: string;
|
|
242
|
+
is_error?: boolean;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** SSE `error` payload (subset used here). */
|
|
246
|
+
interface ErrorData {
|
|
247
|
+
error?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Parse SSE JSON into the expected shape. Returns null on malformed input. */
|
|
251
|
+
function parseSse<T>(raw: unknown): T | null {
|
|
252
|
+
try {
|
|
253
|
+
return JSON.parse(String(raw)) as T;
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
226
259
|
async function connectStream(id: string) {
|
|
227
260
|
disconnectStream();
|
|
228
261
|
const plat = getPlatform();
|
|
@@ -230,40 +263,37 @@ async function connectStream(id: string) {
|
|
|
230
263
|
eventSource = new EventSource(url);
|
|
231
264
|
|
|
232
265
|
eventSource.addEventListener("stream_event", (e) => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
chatInstance.messageAppendContent(currentStreamMsgId, token);
|
|
245
|
-
}
|
|
266
|
+
const data = parseSse<StreamEventData>((e as MessageEvent).data);
|
|
267
|
+
const evt = data?.event;
|
|
268
|
+
if (evt?.type === "content_block_delta" && evt.delta?.type === "text_delta") {
|
|
269
|
+
const token = evt.delta.text ?? "";
|
|
270
|
+
currentAssistantText += token;
|
|
271
|
+
if (chatInstance && currentStreamMsgId != null) {
|
|
272
|
+
if (!streamStarted) {
|
|
273
|
+
chatInstance.messageReplaceContent(currentStreamMsgId, currentAssistantText);
|
|
274
|
+
streamStarted = true;
|
|
275
|
+
} else {
|
|
276
|
+
chatInstance.messageAppendContent(currentStreamMsgId, token);
|
|
246
277
|
}
|
|
247
278
|
}
|
|
248
|
-
}
|
|
279
|
+
}
|
|
249
280
|
});
|
|
250
281
|
|
|
251
282
|
eventSource.addEventListener("assistant", (e) => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
283
|
+
const data = parseSse<AssistantMessageData>((e as MessageEvent).data);
|
|
284
|
+
if (data) {
|
|
285
|
+
handleAssistantMessage(data);
|
|
286
|
+
}
|
|
255
287
|
});
|
|
256
288
|
|
|
257
289
|
eventSource.addEventListener("result", (e) => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
chatInstance.messageReplaceContent(currentStreamMsgId, `Error: ${data.result}`);
|
|
263
|
-
}
|
|
264
|
-
currentAssistantText = `Error: ${data.result}`;
|
|
290
|
+
const data = parseSse<ResultData>((e as MessageEvent).data);
|
|
291
|
+
if (data?.result && data.is_error) {
|
|
292
|
+
if (chatInstance && currentStreamMsgId != null) {
|
|
293
|
+
chatInstance.messageReplaceContent(currentStreamMsgId, `Error: ${data.result}`);
|
|
265
294
|
}
|
|
266
|
-
|
|
295
|
+
currentAssistantText = `Error: ${data.result}`;
|
|
296
|
+
}
|
|
267
297
|
finishStream();
|
|
268
298
|
});
|
|
269
299
|
|
|
@@ -272,15 +302,13 @@ async function connectStream(id: string) {
|
|
|
272
302
|
});
|
|
273
303
|
|
|
274
304
|
eventSource.addEventListener("error", (e) => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
chatInstance.messageReplaceContent(currentStreamMsgId, `Error: ${data.error}`);
|
|
280
|
-
}
|
|
281
|
-
currentAssistantText = `Error: ${data.error}`;
|
|
305
|
+
const data = parseSse<ErrorData>((e as MessageEvent).data);
|
|
306
|
+
if (data?.error) {
|
|
307
|
+
if (chatInstance && currentStreamMsgId != null) {
|
|
308
|
+
chatInstance.messageReplaceContent(currentStreamMsgId, `Error: ${data.error}`);
|
|
282
309
|
}
|
|
283
|
-
|
|
310
|
+
currentAssistantText = `Error: ${data.error}`;
|
|
311
|
+
}
|
|
284
312
|
finishStream();
|
|
285
313
|
});
|
|
286
314
|
|
|
@@ -306,7 +334,7 @@ function finishStream() {
|
|
|
306
334
|
|
|
307
335
|
if (pendingFileReloads.size > 0) {
|
|
308
336
|
for (const fp of pendingFileReloads) {
|
|
309
|
-
reloadFileInTab(fp);
|
|
337
|
+
void reloadFileInTab(fp);
|
|
310
338
|
}
|
|
311
339
|
pendingFileReloads.clear();
|
|
312
340
|
}
|
|
@@ -9,13 +9,21 @@ import { styleMap } from "lit-html/directives/style-map.js";
|
|
|
9
9
|
import { ref } from "lit-html/directives/ref.js";
|
|
10
10
|
import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
childIndex,
|
|
14
|
+
childList,
|
|
15
|
+
elToScope,
|
|
16
|
+
getNodeAtPath,
|
|
17
|
+
nodeLabel,
|
|
18
|
+
parentElementPath,
|
|
19
|
+
} from "../store";
|
|
13
20
|
import { activeTab } from "../workspace/workspace";
|
|
14
21
|
import { mutateMoveNode, mutateUpdateProperty, transactDoc } from "../tabs/transact";
|
|
15
22
|
import { view } from "../view";
|
|
16
23
|
import { getActiveElement, getInlineActions, isEditing } from "../editor/inline-edit";
|
|
17
24
|
import type { InlineAction } from "../editor/inline-edit";
|
|
18
25
|
import { isTagActiveInSelection, toggleInlineFormat } from "../editor/inline-format";
|
|
26
|
+
import { buildMergeTags } from "../editor/merge-tags";
|
|
19
27
|
import { componentRegistry } from "../files/components";
|
|
20
28
|
import { convertToComponent } from "../editor/convert-to-component";
|
|
21
29
|
import { findCanvasElement, getActivePanel } from "../canvas/canvas-helpers";
|
|
@@ -224,6 +232,60 @@ function applyInlineFormat(action: InlineAction) {
|
|
|
224
232
|
requestAnimationFrame(() => renderBlockActionBar());
|
|
225
233
|
}
|
|
226
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Open the merge-tag menu — a searchable list of `${…}` template tokens for the data available in
|
|
237
|
+
* the current state. Reuses the shared slash-menu popover (filter + keyboard nav + dismiss).
|
|
238
|
+
*
|
|
239
|
+
* @param {MouseEvent} e
|
|
240
|
+
*/
|
|
241
|
+
function onMergeTagClick(e: MouseEvent) {
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
const anchorEl = e.currentTarget as HTMLElement;
|
|
244
|
+
const tab = activeTab.value;
|
|
245
|
+
const editable = getActiveElement();
|
|
246
|
+
const state = (tab?.doc.document.state ?? {}) as Record<string, unknown>;
|
|
247
|
+
const scope = getActivePanel()?.liveCtx?.scope ?? null;
|
|
248
|
+
const localScope = editable ? (elToScope.get(editable) ?? null) : null;
|
|
249
|
+
|
|
250
|
+
const commands = buildMergeTags(state, scope, localScope).map((t) => ({
|
|
251
|
+
description: t.hint,
|
|
252
|
+
label: t.label,
|
|
253
|
+
tag: t.token,
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
showSlashMenu(anchorEl, "", {
|
|
257
|
+
commands,
|
|
258
|
+
onSelect: (cmd) => insertMergeTag(cmd.tag),
|
|
259
|
+
showFilter: true,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Insert a `${token}` template expression at the saved selection inside the active contenteditable.
|
|
265
|
+
* Mirrors onFormatClick's range-restore flow and inserts via execCommand so it joins the
|
|
266
|
+
* contenteditable's native undo stack (like paste).
|
|
267
|
+
*
|
|
268
|
+
* @param {string} token
|
|
269
|
+
*/
|
|
270
|
+
function insertMergeTag(token: string) {
|
|
271
|
+
if (!view.savedRange) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const anchor = view.savedRange.startContainer;
|
|
275
|
+
const editableRoot = (
|
|
276
|
+
anchor?.nodeType === Node.ELEMENT_NODE ? (anchor as Element) : anchor?.parentElement
|
|
277
|
+
)?.closest("[contenteditable]");
|
|
278
|
+
if (!editableRoot) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const sel = window.getSelection();
|
|
282
|
+
(editableRoot as HTMLElement).focus();
|
|
283
|
+
sel?.removeAllRanges();
|
|
284
|
+
sel?.addRange(view.savedRange);
|
|
285
|
+
document.execCommand("insertText", false, `\${${token}}`);
|
|
286
|
+
requestAnimationFrame(() => renderBlockActionBar());
|
|
287
|
+
}
|
|
288
|
+
|
|
227
289
|
/** Dismiss the link popover if open. */
|
|
228
290
|
export function dismissLinkPopover() {
|
|
229
291
|
const host = getLayerSlot("popover", "link-popover");
|
|
@@ -515,6 +577,15 @@ export function renderBlockActionBar() {
|
|
|
515
577
|
`,
|
|
516
578
|
)}
|
|
517
579
|
</sp-action-group>
|
|
580
|
+
<sp-action-button
|
|
581
|
+
size="xs"
|
|
582
|
+
quiet
|
|
583
|
+
title="Insert data"
|
|
584
|
+
@mousedown=${captureSelectionRange}
|
|
585
|
+
@click=${onMergeTagClick}
|
|
586
|
+
>
|
|
587
|
+
<sp-icon-data slot="icon"></sp-icon-data>
|
|
588
|
+
</sp-action-button>
|
|
518
589
|
`
|
|
519
590
|
: nothing}
|
|
520
591
|
</div>
|
package/src/panels/canvas-dnd.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { CanvasPanel } from "../types";
|
|
|
20
20
|
|
|
21
21
|
import type { JxPath } from "../state";
|
|
22
22
|
|
|
23
|
-
export type { CanvasPanel };
|
|
23
|
+
export type { CanvasPanel } from "../types";
|
|
24
24
|
|
|
25
25
|
interface DropInstruction {
|
|
26
26
|
type: "reorder-above" | "reorder-below" | "make-child";
|
|
@@ -125,34 +125,63 @@ export function registerPanelDnD(panel: CanvasPanel) {
|
|
|
125
125
|
});
|
|
126
126
|
view.canvasDndCleanups.push(monitorCleanup);
|
|
127
127
|
|
|
128
|
-
const document = activeTab.value?.doc.document;
|
|
129
128
|
for (const el of allEls) {
|
|
130
|
-
|
|
131
|
-
if (!elPath) {
|
|
129
|
+
if (!elToPath.get(el)) {
|
|
132
130
|
continue;
|
|
133
131
|
}
|
|
132
|
+
registerElementDropTarget(el);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Register one canvas element as a drop target. Path and leaf-ness are read live from elToPath and
|
|
138
|
+
* the current document at drag time, so surgical patches that remap sibling paths never leave stale
|
|
139
|
+
* closures behind.
|
|
140
|
+
*
|
|
141
|
+
* @param {Element} el
|
|
142
|
+
*/
|
|
143
|
+
function registerElementDropTarget(el: Element) {
|
|
144
|
+
const cleanup = dropTargetForElements({
|
|
145
|
+
canDrop({ source }) {
|
|
146
|
+
const elPath = elToPath.get(el);
|
|
147
|
+
if (!elPath) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const srcPath = source.data.path as JxPath | undefined;
|
|
151
|
+
if (srcPath && isAncestor(srcPath, elPath)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
},
|
|
156
|
+
element: /** @type {HTMLElement} */ el,
|
|
157
|
+
getData() {
|
|
158
|
+
const elPath = elToPath.get(el) ?? [];
|
|
159
|
+
const document = activeTab.value?.doc.document;
|
|
160
|
+
const node = document ? getNodeAtPath(document, elPath) : undefined;
|
|
161
|
+
const tag = (node?.tagName || "div").toLowerCase();
|
|
162
|
+
const hasElementChildren =
|
|
163
|
+
Array.isArray(node?.children) &&
|
|
164
|
+
node.children.some((c: unknown) => c != null && typeof c === "object");
|
|
165
|
+
return { _isVoid: VOID_ELEMENTS.has(tag) || !hasElementChildren, path: elPath };
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
view.canvasDndCleanups.push(cleanup);
|
|
169
|
+
}
|
|
141
170
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Register drop targets for a freshly patched-in subtree (root plus descendants). Cleanups join
|
|
173
|
+
* view.canvasDndCleanups and are released by the next full render like all canvas DnD handlers.
|
|
174
|
+
*
|
|
175
|
+
* @param {HTMLElement} rootEl
|
|
176
|
+
*/
|
|
177
|
+
export function registerSubtreeDnD(rootEl: HTMLElement) {
|
|
178
|
+
if (elToPath.get(rootEl)) {
|
|
179
|
+
registerElementDropTarget(rootEl);
|
|
180
|
+
}
|
|
181
|
+
for (const el of rootEl.querySelectorAll("*")) {
|
|
182
|
+
if (elToPath.get(el)) {
|
|
183
|
+
registerElementDropTarget(el);
|
|
184
|
+
}
|
|
156
185
|
}
|
|
157
186
|
}
|
|
158
187
|
|
|
@@ -229,7 +258,7 @@ function nearestChildEdge(children: HTMLElement[], cursorY: number, parentPath:
|
|
|
229
258
|
let closestIdx = children.length - 1;
|
|
230
259
|
|
|
231
260
|
for (let i = 0; i < children.length; i++) {
|
|
232
|
-
const rect = children[i]
|
|
261
|
+
const rect = children[i]!.getBoundingClientRect();
|
|
233
262
|
const topDist = Math.abs(cursorY - rect.top);
|
|
234
263
|
const bottomDist = Math.abs(cursorY - rect.bottom);
|
|
235
264
|
|
|
@@ -248,7 +277,7 @@ function nearestChildEdge(children: HTMLElement[], cursorY: number, parentPath:
|
|
|
248
277
|
const childPath = [...parentPath, "children", closestIdx];
|
|
249
278
|
return {
|
|
250
279
|
instruction,
|
|
251
|
-
referenceEl: children[closestIdx]
|
|
280
|
+
referenceEl: children[closestIdx]!,
|
|
252
281
|
targetPath: childPath,
|
|
253
282
|
};
|
|
254
283
|
}
|
|
@@ -9,7 +9,7 @@ import type { TemplateResult } from "lit-html";
|
|
|
9
9
|
const expandedDataKeys = new Set();
|
|
10
10
|
|
|
11
11
|
/** Unwrap a Vue ref (has .value and .__v_isRef) to get the underlying value. */
|
|
12
|
-
function unwrapSignal(value: unknown) {
|
|
12
|
+
export function unwrapSignal(value: unknown) {
|
|
13
13
|
if (value && typeof value === "object" && (value as Record<string, unknown>).__v_isRef) {
|
|
14
14
|
return (value as Record<string, unknown>).value;
|
|
15
15
|
}
|
|
@@ -17,7 +17,7 @@ function unwrapSignal(value: unknown) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Type label for a signal value in the data explorer. */
|
|
20
|
-
function dataTypeLabel(value: unknown) {
|
|
20
|
+
export function dataTypeLabel(value: unknown) {
|
|
21
21
|
const v = unwrapSignal(value);
|
|
22
22
|
if (v === null) {
|
|
23
23
|
return "null";
|
|
@@ -164,7 +164,9 @@ export function renderDataTreeTemplate(
|
|
|
164
164
|
><span class="data-value data-${item === null ? "null" : typeof item}">${valText}</span>
|
|
165
165
|
</div>`;
|
|
166
166
|
}
|
|
167
|
-
const label = Array.isArray(item)
|
|
167
|
+
const label = Array.isArray(item)
|
|
168
|
+
? `Array(${item.length})`
|
|
169
|
+
: `{${Object.keys(item as object).length}}`;
|
|
168
170
|
return html`
|
|
169
171
|
<div class="data-branch" style="padding-left:${indent}">
|
|
170
172
|
<span class="data-key">[${i}] </span
|
package/src/panels/dnd.ts
CHANGED
|
@@ -27,7 +27,11 @@ import {
|
|
|
27
27
|
import { mutateInsertNode, mutateMoveNode, transact, transactDoc } from "../tabs/transact";
|
|
28
28
|
import { activeTab } from "../workspace/workspace";
|
|
29
29
|
import { view } from "../view";
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
buildComponentInstance,
|
|
32
|
+
componentRegistry,
|
|
33
|
+
computeRelativePath,
|
|
34
|
+
} from "../files/components";
|
|
31
35
|
import { renderComponentPreview } from "./stylebook-panel";
|
|
32
36
|
import { defaultDef, unsafeTags } from "./shared";
|
|
33
37
|
import type { JxPath } from "../state";
|
|
@@ -63,8 +67,8 @@ export function registerLayersDnD() {
|
|
|
63
67
|
for (const row of container.querySelectorAll("[data-dnd-row]") as NodeListOf<HTMLElement>) {
|
|
64
68
|
const rowPath = (row.dataset.path as string)
|
|
65
69
|
.split("/")
|
|
66
|
-
.map((s: string) => (/^\d+$/.test(s) ?
|
|
67
|
-
const rowDepth =
|
|
70
|
+
.map((s: string) => (/^\d+$/.test(s) ? Math.trunc(Number(s)) : s)) as JxPath;
|
|
71
|
+
const rowDepth = Math.trunc(Number(row.dataset.dndDepth as string)) || 0;
|
|
68
72
|
const isVoid = Object.hasOwn(row.dataset, "dndVoid");
|
|
69
73
|
const isExpanded = Object.hasOwn(row.dataset, "dndExpanded");
|
|
70
74
|
|
|
@@ -205,25 +209,13 @@ export function registerComponentsDnD() {
|
|
|
205
209
|
// Fill preview with live rendered component
|
|
206
210
|
const preview = row.querySelector(".element-card-preview");
|
|
207
211
|
if (preview && !preview.querySelector(tagName)) {
|
|
208
|
-
renderComponentPreview(comp).then((el: HTMLElement) => {
|
|
212
|
+
void renderComponentPreview(comp).then((el: HTMLElement) => {
|
|
209
213
|
preview.textContent = "";
|
|
210
214
|
preview.append(el);
|
|
211
215
|
});
|
|
212
216
|
}
|
|
213
217
|
|
|
214
|
-
const instanceDef =
|
|
215
|
-
$props: comp.props
|
|
216
|
-
? Object.fromEntries(
|
|
217
|
-
comp.props.map(
|
|
218
|
-
(/** @type {{ name: string; default?: unknown; [k: string]: unknown }} */ p) => [
|
|
219
|
-
p.name,
|
|
220
|
-
p.default !== undefined ? p.default : "",
|
|
221
|
-
],
|
|
222
|
-
),
|
|
223
|
-
)
|
|
224
|
-
: {},
|
|
225
|
-
tagName: comp.tagName,
|
|
226
|
-
};
|
|
218
|
+
const instanceDef = buildComponentInstance(comp);
|
|
227
219
|
const cleanup = draggable({
|
|
228
220
|
element: row,
|
|
229
221
|
getInitialData() {
|
|
@@ -336,6 +328,11 @@ export function clearLayerDropGap(container: HTMLElement) {
|
|
|
336
328
|
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
337
329
|
for (const r of rows) {
|
|
338
330
|
(r as HTMLElement).style.transform = "";
|
|
331
|
+
// Also clear `display:none` left by hideDescendantRows. The `.layer-row` div has no `style`
|
|
332
|
+
// Lit binding and rows aren't keyed, so lit reuses these DOM nodes positionally on the
|
|
333
|
+
// Post-drop re-render — a stale `display:none` would otherwise hide whichever row lands on the
|
|
334
|
+
// Reused node (e.g. a sibling of the moved subtree).
|
|
335
|
+
(r as HTMLElement).style.display = "";
|
|
339
336
|
}
|
|
340
337
|
}
|
|
341
338
|
|
package/src/panels/editors.ts
CHANGED
|
@@ -38,8 +38,7 @@ function getFunctionBody(editing: EditingTarget | null | undefined) {
|
|
|
38
38
|
return "";
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
41
|
+
export function renderFunctionEditor() {
|
|
43
42
|
const editing = activeTab.value?.session.ui.editingFunction as EditingTarget | null | undefined;
|
|
44
43
|
|
|
45
44
|
// If editor already exists and matches current target, just sync value
|
|
@@ -79,27 +78,10 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
79
78
|
canvasWrap.style.flexDirection = "column";
|
|
80
79
|
canvasWrap.style.alignItems = "stretch";
|
|
81
80
|
|
|
82
|
-
//
|
|
83
|
-
renderOnly("toolbar");
|
|
84
|
-
|
|
85
|
-
const tab = activeTab.value;
|
|
86
|
-
const docName = tab?.documentPath?.split("/").pop() || tab?.doc.document?.tagName || "document";
|
|
87
|
-
const ed = editing as EditingTarget;
|
|
88
|
-
const funcLabel = ed.type === "def" ? `ƒ ${ed.defName}` : `ƒ ${ed.eventKey}`;
|
|
89
|
-
|
|
90
|
-
// Editor container
|
|
81
|
+
// The tab bar renders the Back button + breadcrumb context for the function editor.
|
|
91
82
|
let editorContainer: HTMLDivElement | null = null;
|
|
92
83
|
litRender(
|
|
93
84
|
html`<div class="source-wrap">
|
|
94
|
-
<div class="source-toolbar">
|
|
95
|
-
<sp-action-button size="s" @click=${closeFunctionEditor}>
|
|
96
|
-
<sp-icon-back slot="icon"></sp-icon-back>
|
|
97
|
-
Back
|
|
98
|
-
</sp-action-button>
|
|
99
|
-
<span class="breadcrumb-item">${docName}</span>
|
|
100
|
-
<span class="breadcrumb-sep"> › </span>
|
|
101
|
-
<span class="breadcrumb-item current">${funcLabel}</span>
|
|
102
|
-
</div>
|
|
103
85
|
<div
|
|
104
86
|
class="source-editor"
|
|
105
87
|
${ref((el) => {
|
|
@@ -135,13 +117,13 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
135
117
|
const editor = view.functionEditor;
|
|
136
118
|
|
|
137
119
|
// Format on open — show pretty-printed code, then run initial lint
|
|
138
|
-
codeService("format", { args, code: body }).then((result) => {
|
|
120
|
+
void codeService("format", { args, code: body }).then((result) => {
|
|
139
121
|
if (result?.code != null && view.functionEditor) {
|
|
140
122
|
view.functionEditor._ignoreNextChange = true;
|
|
141
123
|
view.functionEditor.setValue(result.code);
|
|
142
124
|
}
|
|
143
125
|
});
|
|
144
|
-
codeService("lint", { args, code: body }).then((result) => {
|
|
126
|
+
void codeService("lint", { args, code: body }).then((result) => {
|
|
145
127
|
if (result?.diagnostics && view.functionEditor) {
|
|
146
128
|
setLintMarkers(view.functionEditor, result.diagnostics as OxLintDiagnostic[]);
|
|
147
129
|
}
|
|
@@ -183,7 +165,7 @@ export function renderFunctionEditor(closeFunctionEditor: () => void) {
|
|
|
183
165
|
lintDebounce = setTimeout(() => {
|
|
184
166
|
const gen = (lintGen += 1);
|
|
185
167
|
const currentCode = editor.getValue();
|
|
186
|
-
codeService("lint", { args, code: currentCode }).then((result) => {
|
|
168
|
+
void codeService("lint", { args, code: currentCode }).then((result) => {
|
|
187
169
|
if (gen !== lintGen) {
|
|
188
170
|
return;
|
|
189
171
|
}
|
|
@@ -7,7 +7,7 @@ import { activeTab } from "../workspace/workspace";
|
|
|
7
7
|
import { mutateInsertNode, transactDoc } from "../tabs/transact";
|
|
8
8
|
import { view } from "../view";
|
|
9
9
|
import { getEffectiveElements } from "../site-context";
|
|
10
|
-
import { componentRegistry } from "../files/components";
|
|
10
|
+
import { buildComponentInstance, componentRegistry } from "../files/components";
|
|
11
11
|
|
|
12
12
|
import type { ComponentEntry } from "../files/components";
|
|
13
13
|
import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
|
|
@@ -134,17 +134,7 @@ export function renderElementsTemplate(ctx: {
|
|
|
134
134
|
const parentPath = t?.session.selection || [];
|
|
135
135
|
const parent = getNodeAtPath(t!.doc.document, parentPath);
|
|
136
136
|
const idx = childList(parent).length;
|
|
137
|
-
const instanceDef =
|
|
138
|
-
$props: Object.fromEntries(
|
|
139
|
-
(comp.props || []).map(
|
|
140
|
-
(/** @type {{ name: string; default?: unknown }} */ p) => [
|
|
141
|
-
p.name,
|
|
142
|
-
p.default !== undefined ? p.default : "",
|
|
143
|
-
],
|
|
144
|
-
),
|
|
145
|
-
),
|
|
146
|
-
tagName: comp.tagName,
|
|
147
|
-
};
|
|
137
|
+
const instanceDef = buildComponentInstance(comp);
|
|
148
138
|
transactDoc(t!, (tr) =>
|
|
149
139
|
mutateInsertNode(tr, parentPath, idx, structuredClone(instanceDef)),
|
|
150
140
|
);
|
|
@@ -266,7 +266,7 @@ export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boole
|
|
|
266
266
|
if (functionDefs.length > 0) {
|
|
267
267
|
transactDoc(activeTab.value, (t) =>
|
|
268
268
|
mutateUpdateProperty(t, selection, evName, {
|
|
269
|
-
$ref: `#/state/${functionDefs[0][0]}`,
|
|
269
|
+
$ref: `#/state/${functionDefs[0]![0]}`,
|
|
270
270
|
}),
|
|
271
271
|
);
|
|
272
272
|
} else {
|
package/src/panels/git-panel.ts
CHANGED
|
@@ -132,7 +132,7 @@ async function gitAction(action: string, body?: unknown) {
|
|
|
132
132
|
updateUi("gitLoading", true);
|
|
133
133
|
updateUi("gitError", null);
|
|
134
134
|
try {
|
|
135
|
-
await plat[action](body);
|
|
135
|
+
await plat[action]!(body);
|
|
136
136
|
await refreshGitStatus();
|
|
137
137
|
} catch (error) {
|
|
138
138
|
updateUi("gitError", errorMessage(error));
|
|
@@ -191,7 +191,7 @@ export function renderGitPanel(
|
|
|
191
191
|
const loading = S.ui.gitLoading;
|
|
192
192
|
|
|
193
193
|
if (!status && !loading) {
|
|
194
|
-
refreshGitStatus();
|
|
194
|
+
void refreshGitStatus();
|
|
195
195
|
return html`<div class="git-panel">
|
|
196
196
|
<div class="git-loading">Loading...</div>
|
|
197
197
|
</div>`;
|
|
@@ -232,7 +232,7 @@ export function renderGitPanel(
|
|
|
232
232
|
if (!_pollTimer) {
|
|
233
233
|
_pollTimer = setInterval(() => {
|
|
234
234
|
if (view.leftTab === "git" && !S.ui.gitLoading) {
|
|
235
|
-
refreshGitStatus();
|
|
235
|
+
void refreshGitStatus();
|
|
236
236
|
}
|
|
237
237
|
}, 30_000);
|
|
238
238
|
}
|
|
@@ -417,7 +417,7 @@ export function renderGitPanel(
|
|
|
417
417
|
const switchTab = (tab: string) => {
|
|
418
418
|
_gitSubTab = tab;
|
|
419
419
|
if (tab === "history" && !S.ui.gitLogEntries) {
|
|
420
|
-
fetchGitLog();
|
|
420
|
+
void fetchGitLog();
|
|
421
421
|
}
|
|
422
422
|
renderOnly("leftPanel");
|
|
423
423
|
};
|
|
@@ -453,7 +453,7 @@ export function renderGitPanel(
|
|
|
453
453
|
@keydown=${(e: KeyboardEvent) => {
|
|
454
454
|
if (e.ctrlKey && e.key === "Enter") {
|
|
455
455
|
e.preventDefault();
|
|
456
|
-
doCommit();
|
|
456
|
+
void doCommit();
|
|
457
457
|
}
|
|
458
458
|
}}
|
|
459
459
|
></sp-textfield>
|
|
@@ -489,7 +489,7 @@ export function renderGitPanel(
|
|
|
489
489
|
"hidden",
|
|
490
490
|
"",
|
|
491
491
|
);
|
|
492
|
-
doCommit();
|
|
492
|
+
void doCommit();
|
|
493
493
|
}}
|
|
494
494
|
>
|
|
495
495
|
Commit (don't sync)
|
package/src/panels/head-panel.ts
CHANGED
|
@@ -334,7 +334,7 @@ export function renderHeadTemplate({
|
|
|
334
334
|
let layoutSection: TemplateResult | symbol = nothing;
|
|
335
335
|
if (isPage) {
|
|
336
336
|
if (layoutEntries === null) {
|
|
337
|
-
loadLayoutEntries();
|
|
337
|
+
void loadLayoutEntries();
|
|
338
338
|
} else {
|
|
339
339
|
const currentLayout = doc.$layout;
|
|
340
340
|
const defaultLayout = projectState?.projectConfig?.defaults?.layout;
|