@jxsuite/studio 0.13.0 → 0.14.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 +428 -617
- package/dist/studio.js.map +36 -36
- package/package.json +1 -1
- package/src/canvas/canvas-live-render.js +25 -0
- package/src/canvas/canvas-render.js +20 -2
- package/src/canvas/canvas-utils.js +1 -2
- package/src/editor/component-inline-edit.js +1 -3
- package/src/editor/content-inline-edit.js +1 -3
- package/src/editor/context-menu.js +26 -25
- package/src/editor/insertion-helper.js +0 -1
- package/src/editor/shortcuts.js +39 -45
- package/src/files/file-ops.js +57 -108
- package/src/files/files.js +13 -8
- package/src/panels/activity-bar.js +6 -4
- package/src/panels/canvas-dnd.js +4 -10
- package/src/panels/dnd.js +45 -7
- package/src/panels/editors.js +14 -16
- package/src/panels/elements-panel.js +13 -13
- package/src/panels/git-panel.js +2 -1
- package/src/panels/layers-panel.js +11 -12
- package/src/panels/left-panel.js +2 -2
- package/src/panels/panel-events.js +19 -25
- package/src/panels/preview-render.js +7 -3
- package/src/panels/pseudo-preview.js +9 -8
- package/src/panels/statusbar.js +8 -16
- package/src/panels/style-inputs.js +2 -3
- package/src/panels/style-utils.js +8 -6
- package/src/panels/stylebook-layers-panel.js +5 -5
- package/src/panels/stylebook-panel.js +16 -16
- package/src/panels/toolbar.js +2 -2
- package/src/state.js +2 -3
- package/src/store.js +2 -133
- package/src/studio.js +111 -239
- package/src/tabs/tab.js +19 -4
- package/src/ui/color-selector.js +4 -5
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +1 -0
package/package.json
CHANGED
|
@@ -389,6 +389,31 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
|
|
|
389
389
|
} else {
|
|
390
390
|
canvasEl.removeAttribute("data-content-mode");
|
|
391
391
|
}
|
|
392
|
+
|
|
393
|
+
// Resolve relative asset URLs through the PAL for environments that can't
|
|
394
|
+
// serve project files natively (e.g. ElectroBun's views:// scheme).
|
|
395
|
+
const platform = /** @type {any} */ (globalThis).__jxPlatform;
|
|
396
|
+
if (platform?.resolveAssetUrl && docBase) {
|
|
397
|
+
const mediaEls = el.querySelectorAll("img[src], video[src], source[src], video[poster]");
|
|
398
|
+
for (const node of mediaEls) {
|
|
399
|
+
for (const attr of ["src", "poster"]) {
|
|
400
|
+
const val = node.getAttribute(attr);
|
|
401
|
+
if (
|
|
402
|
+
val &&
|
|
403
|
+
!val.startsWith("data:") &&
|
|
404
|
+
!val.startsWith("blob:") &&
|
|
405
|
+
!val.startsWith("http")
|
|
406
|
+
) {
|
|
407
|
+
try {
|
|
408
|
+
const resolved = new URL(val, docBase).pathname.replace(/^\//, "");
|
|
409
|
+
const dataUrl = await platform.resolveAssetUrl(resolved);
|
|
410
|
+
if (dataUrl) node.setAttribute(attr, dataUrl);
|
|
411
|
+
} catch {}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
392
417
|
canvasEl.appendChild(el);
|
|
393
418
|
if (canvasMode === "design" || canvasMode === "edit") {
|
|
394
419
|
// Custom element connectedCallbacks render children asynchronously —
|
|
@@ -32,6 +32,8 @@ import { registerPanelDnD } from "../panels/canvas-dnd.js";
|
|
|
32
32
|
import { registerPanelEvents } from "../panels/panel-events.js";
|
|
33
33
|
import { computeDocumentDiff } from "./canvas-diff.js";
|
|
34
34
|
import { updateForcedPseudoPreview } from "../panels/pseudo-preview.js";
|
|
35
|
+
import { findCanvasElement } from "./canvas-helpers.js";
|
|
36
|
+
import { enterComponentInlineEdit } from "../editor/component-inline-edit.js";
|
|
35
37
|
import { renderStylebookMode } from "../panels/stylebook-panel.js";
|
|
36
38
|
import { dismissLinkPopover, dismissBlockActionBar } from "../panels/block-action-bar.js";
|
|
37
39
|
import { dismissContextMenu } from "../editor/context-menu.js";
|
|
@@ -481,8 +483,9 @@ function renderCanvasIntoPanel(
|
|
|
481
483
|
if (gitDiffState && docOverride) {
|
|
482
484
|
// Determine which document is original and which is current
|
|
483
485
|
const isOriginal = docOverride === (gitDiffState.originalDoc || gitDiffState.original);
|
|
484
|
-
const
|
|
485
|
-
const
|
|
486
|
+
const _tab = activeTab.value;
|
|
487
|
+
const origDoc = isOriginal ? docOverride : gitDiffState.currentDoc || _tab?.doc.document;
|
|
488
|
+
const currDoc = isOriginal ? gitDiffState.currentDoc || _tab?.doc.document : docOverride;
|
|
486
489
|
|
|
487
490
|
const { byPath: diffMap } = computeDocumentDiff(origDoc, currDoc);
|
|
488
491
|
|
|
@@ -501,6 +504,21 @@ function renderCanvasIntoPanel(
|
|
|
501
504
|
registerPanelEvents(panel);
|
|
502
505
|
renderOverlays();
|
|
503
506
|
updateForcedPseudoPreview();
|
|
507
|
+
|
|
508
|
+
// Process pending inline edit when canvas becomes ready
|
|
509
|
+
const currentTab = activeTab.value;
|
|
510
|
+
if (currentTab?.session.ui?.pendingInlineEdit) {
|
|
511
|
+
const { path, mediaName: mn } = /** @type {{ path: any; mediaName: string }} */ (
|
|
512
|
+
currentTab.session.ui.pendingInlineEdit
|
|
513
|
+
);
|
|
514
|
+
currentTab.session.ui.pendingInlineEdit = null;
|
|
515
|
+
const targetPanel =
|
|
516
|
+
canvasPanels.find((/** @type {any} */ p) => p.mediaName === mn) || canvasPanels[0];
|
|
517
|
+
if (targetPanel) {
|
|
518
|
+
const el = findCanvasElement(path, targetPanel.canvas);
|
|
519
|
+
if (el) enterComponentInlineEdit(el, path);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
504
522
|
});
|
|
505
523
|
}
|
|
506
524
|
|
|
@@ -168,8 +168,7 @@ export function applyTransform() {
|
|
|
168
168
|
if (!view.panzoomWrap) return;
|
|
169
169
|
const zoom = _ctx.getZoom();
|
|
170
170
|
view.panzoomWrap.style.transform = `translate(${view.panX}px, ${view.panY}px) scale(${zoom})`;
|
|
171
|
-
|
|
172
|
-
if (label) label.textContent = `${Math.round(zoom * 100)}%`;
|
|
171
|
+
renderZoomIndicator();
|
|
173
172
|
renderOnly("overlays");
|
|
174
173
|
if (_ctx.getCanvasMode() === "settings") _ctx.renderStylebookOverlays();
|
|
175
174
|
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
getState,
|
|
8
7
|
updateUi,
|
|
9
8
|
renderOnly,
|
|
10
9
|
getNodeAtPath,
|
|
@@ -48,8 +47,7 @@ export function enterComponentInlineEdit(el, path) {
|
|
|
48
47
|
return;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
const
|
|
52
|
-
const node = getNodeAtPath(S.document, path);
|
|
50
|
+
const node = getNodeAtPath(activeTab.value?.doc.document, path);
|
|
53
51
|
if (!node) return;
|
|
54
52
|
|
|
55
53
|
const tc = node.textContent;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
getState,
|
|
8
7
|
renderOnly,
|
|
9
8
|
getNodeAtPath,
|
|
10
9
|
parentElementPath,
|
|
@@ -44,8 +43,7 @@ export function enterInlineEdit(el, path) {
|
|
|
44
43
|
/** @type {any} */ children,
|
|
45
44
|
/** @type {any} */ textContent,
|
|
46
45
|
) {
|
|
47
|
-
const
|
|
48
|
-
const node = getNodeAtPath(S.document, commitPath);
|
|
46
|
+
const node = getNodeAtPath(activeTab.value?.doc.document, commitPath);
|
|
49
47
|
if (children) {
|
|
50
48
|
if (node && JSON.stringify(node.children) === JSON.stringify(children)) return;
|
|
51
49
|
transactDoc(activeTab.value, (t) => {
|
|
@@ -26,41 +26,42 @@ let clipboard = null;
|
|
|
26
26
|
|
|
27
27
|
// ─── Clipboard ────────────────────────────────────────────────────────────────
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!
|
|
32
|
-
const node = getNodeAtPath(
|
|
29
|
+
export function copyNode() {
|
|
30
|
+
const tab = activeTab.value;
|
|
31
|
+
if (!tab?.session.selection) return;
|
|
32
|
+
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
33
33
|
if (!node) return;
|
|
34
34
|
clipboard = structuredClone(node);
|
|
35
35
|
statusMessage("Copied");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!
|
|
41
|
-
const sel =
|
|
42
|
-
const node = getNodeAtPath(
|
|
38
|
+
export function cutNode() {
|
|
39
|
+
const tab = activeTab.value;
|
|
40
|
+
if (!tab?.session.selection || tab.session.selection.length < 2) return;
|
|
41
|
+
const sel = tab.session.selection;
|
|
42
|
+
const node = getNodeAtPath(tab.doc.document, sel);
|
|
43
43
|
if (!node) return;
|
|
44
44
|
clipboard = structuredClone(node);
|
|
45
|
-
transactDoc(
|
|
45
|
+
transactDoc(tab, (t) => mutateRemoveNode(t, sel));
|
|
46
46
|
statusMessage("Cut");
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
export function pasteNode(S) {
|
|
49
|
+
export function pasteNode() {
|
|
51
50
|
if (!clipboard) return;
|
|
51
|
+
const tab = activeTab.value;
|
|
52
|
+
if (!tab) return;
|
|
52
53
|
const clip = clipboard;
|
|
53
|
-
const pPath =
|
|
54
|
-
const parent = getNodeAtPath(
|
|
54
|
+
const pPath = tab.session.selection || [];
|
|
55
|
+
const parent = getNodeAtPath(tab.doc.document, pPath);
|
|
55
56
|
if (!parent) return;
|
|
56
57
|
|
|
57
|
-
if (
|
|
58
|
-
const pp = /** @type {JxPath} */ (parentElementPath(
|
|
59
|
-
const idx = /** @type {number} */ (childIndex(
|
|
60
|
-
transactDoc(
|
|
58
|
+
if (tab.session.selection && tab.session.selection.length >= 2) {
|
|
59
|
+
const pp = /** @type {JxPath} */ (parentElementPath(tab.session.selection));
|
|
60
|
+
const idx = /** @type {number} */ (childIndex(tab.session.selection));
|
|
61
|
+
transactDoc(tab, (t) => mutateInsertNode(t, pp, idx + 1, structuredClone(clip)));
|
|
61
62
|
} else {
|
|
62
63
|
const idx = parent.children ? parent.children.length : 0;
|
|
63
|
-
transactDoc(
|
|
64
|
+
transactDoc(tab, (t) => mutateInsertNode(t, pPath, idx, structuredClone(clip)));
|
|
64
65
|
}
|
|
65
66
|
statusMessage("Pasted");
|
|
66
67
|
}
|
|
@@ -85,25 +86,25 @@ export function dismissContextMenu() {
|
|
|
85
86
|
/**
|
|
86
87
|
* @param {MouseEvent} e
|
|
87
88
|
* @param {JxPath} path
|
|
88
|
-
* @param {StudioState} S
|
|
89
89
|
* @param {{ onEditComponent?: (path: string) => void }} [opts]
|
|
90
90
|
*/
|
|
91
|
-
export function showContextMenu(e, path,
|
|
91
|
+
export function showContextMenu(e, path, opts = {}) {
|
|
92
92
|
e.preventDefault();
|
|
93
93
|
ctxMenu.removeAttribute("open");
|
|
94
94
|
|
|
95
|
-
const
|
|
95
|
+
const tab = activeTab.value;
|
|
96
|
+
const node = getNodeAtPath(tab?.doc.document, path);
|
|
96
97
|
if (!node) return;
|
|
97
98
|
|
|
98
99
|
// Select the node
|
|
99
|
-
|
|
100
|
+
tab.session.selection = path;
|
|
100
101
|
|
|
101
102
|
/** @type {{ label: string; action?: () => void; danger?: boolean }[]} */
|
|
102
103
|
const items = [];
|
|
103
104
|
|
|
104
|
-
items.push({ label: "Copy", action: () => copyNode(
|
|
105
|
+
items.push({ label: "Copy", action: () => copyNode() });
|
|
105
106
|
if (path.length >= 2) {
|
|
106
|
-
items.push({ label: "Cut", action: () => cutNode(
|
|
107
|
+
items.push({ label: "Cut", action: () => cutNode() });
|
|
107
108
|
items.push({
|
|
108
109
|
label: "Duplicate",
|
|
109
110
|
action: () => transactDoc(activeTab.value, (t) => mutateDuplicateNode(t, path)),
|
|
@@ -33,7 +33,6 @@ import { transactDoc, mutateInsertNode } from "../tabs/transact.js";
|
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* @typedef {Object} InsertionHelperContext
|
|
36
|
-
* @property {() => any} getState - Returns the current editor state.
|
|
37
36
|
* @property {() => string} getCanvasMode - Returns the active canvas mode.
|
|
38
37
|
* @property {(fn: Function) => any} withPanelPointerEvents - Executes fn with pointer-events
|
|
39
38
|
* temporarily enabled on the canvas.
|
package/src/editor/shortcuts.js
CHANGED
|
@@ -18,18 +18,12 @@ import {
|
|
|
18
18
|
import { isEditing } from "./inline-edit.js";
|
|
19
19
|
import { copyNode, cutNode, pasteNode } from "./context-menu.js";
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {import("../state.js").StudioState} StudioState
|
|
23
|
-
*
|
|
24
|
-
* @typedef {import("../state.js").JxPath} JxPath
|
|
25
|
-
*/
|
|
21
|
+
/** @typedef {import("../state.js").JxPath} JxPath */
|
|
26
22
|
|
|
27
23
|
/**
|
|
28
24
|
* Initialise all keyboard (and wheel/pointer) shortcuts.
|
|
29
25
|
*
|
|
30
26
|
* @param {() => {
|
|
31
|
-
* S: StudioState;
|
|
32
|
-
* setS: (s: StudioState) => void;
|
|
33
27
|
* canvasMode: string;
|
|
34
28
|
* panX: number;
|
|
35
29
|
* panY: number;
|
|
@@ -47,7 +41,7 @@ export function initShortcuts(getContext) {
|
|
|
47
41
|
canvasWrap.addEventListener(
|
|
48
42
|
"wheel",
|
|
49
43
|
(/** @type {WheelEvent} */ e) => {
|
|
50
|
-
const {
|
|
44
|
+
const { canvasMode, panX, panY, setPan, applyTransform } = getContext();
|
|
51
45
|
// Edit (content) mode: let the scroll container handle scrolling natively
|
|
52
46
|
if (canvasMode === "edit") return;
|
|
53
47
|
e.preventDefault();
|
|
@@ -56,13 +50,13 @@ export function initShortcuts(getContext) {
|
|
|
56
50
|
const rect = canvasWrap.getBoundingClientRect();
|
|
57
51
|
const cursorX = e.clientX - rect.left;
|
|
58
52
|
const cursorY = e.clientY - rect.top;
|
|
59
|
-
const oldZoom =
|
|
53
|
+
const oldZoom = activeTab.value?.session.ui.zoom ?? 1;
|
|
60
54
|
const delta = -e.deltaY * 0.005;
|
|
61
55
|
const newZoom = Math.min(5.0, Math.max(0.05, oldZoom * (1 + delta)));
|
|
62
56
|
const ratio = newZoom / oldZoom;
|
|
63
57
|
// Adjust pan so the point under cursor stays stationary
|
|
64
58
|
setPan(cursorX - (cursorX - panX) * ratio, cursorY - (cursorY - panY) * ratio);
|
|
65
|
-
|
|
59
|
+
activeTab.value.session.ui.zoom = newZoom;
|
|
66
60
|
} else if (e.shiftKey) {
|
|
67
61
|
// Shift+scroll = horizontal pan
|
|
68
62
|
setPan(panX - e.deltaY, panY);
|
|
@@ -105,8 +99,6 @@ export function initShortcuts(getContext) {
|
|
|
105
99
|
|
|
106
100
|
document.addEventListener("keydown", (e) => {
|
|
107
101
|
const {
|
|
108
|
-
S,
|
|
109
|
-
setS,
|
|
110
102
|
canvasMode,
|
|
111
103
|
setPan,
|
|
112
104
|
applyTransform,
|
|
@@ -115,6 +107,7 @@ export function initShortcuts(getContext) {
|
|
|
115
107
|
openProject,
|
|
116
108
|
enterEditOnPath,
|
|
117
109
|
} = getContext();
|
|
110
|
+
const tab = activeTab.value;
|
|
118
111
|
const mod = e.ctrlKey || e.metaKey;
|
|
119
112
|
|
|
120
113
|
// Don't intercept when typing in inputs or contenteditable
|
|
@@ -182,27 +175,27 @@ export function initShortcuts(getContext) {
|
|
|
182
175
|
break;
|
|
183
176
|
case "d":
|
|
184
177
|
e.preventDefault();
|
|
185
|
-
if (
|
|
186
|
-
const sel =
|
|
187
|
-
transactDoc(
|
|
178
|
+
if (tab?.session.selection) {
|
|
179
|
+
const sel = tab.session.selection;
|
|
180
|
+
transactDoc(tab, (t) => mutateDuplicateNode(t, sel));
|
|
188
181
|
}
|
|
189
182
|
break;
|
|
190
183
|
case "c":
|
|
191
184
|
e.preventDefault();
|
|
192
|
-
copyNode(
|
|
185
|
+
copyNode();
|
|
193
186
|
break;
|
|
194
187
|
case "x":
|
|
195
188
|
e.preventDefault();
|
|
196
|
-
cutNode(
|
|
189
|
+
cutNode();
|
|
197
190
|
break;
|
|
198
191
|
case "v":
|
|
199
192
|
e.preventDefault();
|
|
200
|
-
pasteNode(
|
|
193
|
+
pasteNode();
|
|
201
194
|
break;
|
|
202
195
|
case "0":
|
|
203
196
|
if (canvasMode === "edit") break;
|
|
204
197
|
e.preventDefault();
|
|
205
|
-
|
|
198
|
+
activeTab.value.session.ui.zoom = 1;
|
|
206
199
|
setPan(16, 16);
|
|
207
200
|
applyTransform();
|
|
208
201
|
break;
|
|
@@ -210,13 +203,13 @@ export function initShortcuts(getContext) {
|
|
|
210
203
|
case "+":
|
|
211
204
|
if (canvasMode === "edit") break;
|
|
212
205
|
e.preventDefault();
|
|
213
|
-
|
|
206
|
+
activeTab.value.session.ui.zoom = Math.min(5.0, (tab?.session.ui.zoom ?? 1) * 1.2);
|
|
214
207
|
applyTransform();
|
|
215
208
|
break;
|
|
216
209
|
case "-":
|
|
217
210
|
if (canvasMode === "edit") break;
|
|
218
211
|
e.preventDefault();
|
|
219
|
-
|
|
212
|
+
activeTab.value.session.ui.zoom = Math.max(0.05, (tab?.session.ui.zoom ?? 1) / 1.2);
|
|
220
213
|
applyTransform();
|
|
221
214
|
break;
|
|
222
215
|
}
|
|
@@ -226,22 +219,22 @@ export function initShortcuts(getContext) {
|
|
|
226
219
|
switch (e.key) {
|
|
227
220
|
case "Delete":
|
|
228
221
|
case "Backspace":
|
|
229
|
-
if (
|
|
222
|
+
if (tab?.session.selection && tab.session.selection.length >= 2) {
|
|
230
223
|
e.preventDefault();
|
|
231
|
-
const sel =
|
|
232
|
-
transactDoc(
|
|
224
|
+
const sel = tab.session.selection;
|
|
225
|
+
transactDoc(tab, (t) => mutateRemoveNode(t, sel));
|
|
233
226
|
}
|
|
234
227
|
break;
|
|
235
228
|
case "Escape":
|
|
236
229
|
activeTab.value.session.selection = null;
|
|
237
230
|
break;
|
|
238
231
|
case "Enter":
|
|
239
|
-
if (
|
|
232
|
+
if (tab?.session.selection && tab.session.selection.length >= 2) {
|
|
240
233
|
e.preventDefault();
|
|
241
|
-
const pp = /** @type {JxPath} */ (parentElementPath(
|
|
242
|
-
const idx = /** @type {number} */ (childIndex(
|
|
234
|
+
const pp = /** @type {JxPath} */ (parentElementPath(tab.session.selection));
|
|
235
|
+
const idx = /** @type {number} */ (childIndex(tab.session.selection));
|
|
243
236
|
const newPath = [...pp, "children", idx + 1];
|
|
244
|
-
transactDoc(
|
|
237
|
+
transactDoc(tab, (t) => {
|
|
245
238
|
mutateInsertNode(t, pp, idx + 1, { tagName: "p", textContent: "" });
|
|
246
239
|
t.session.selection = newPath;
|
|
247
240
|
});
|
|
@@ -250,24 +243,24 @@ export function initShortcuts(getContext) {
|
|
|
250
243
|
break;
|
|
251
244
|
case "ArrowUp":
|
|
252
245
|
e.preventDefault();
|
|
253
|
-
navigateSelection(
|
|
246
|
+
navigateSelection(-1);
|
|
254
247
|
break;
|
|
255
248
|
case "ArrowDown":
|
|
256
249
|
e.preventDefault();
|
|
257
|
-
navigateSelection(
|
|
250
|
+
navigateSelection(1);
|
|
258
251
|
break;
|
|
259
252
|
case "ArrowLeft":
|
|
260
253
|
e.preventDefault();
|
|
261
|
-
if (
|
|
262
|
-
activeTab.value.session.selection = parentElementPath(
|
|
254
|
+
if (tab?.session.selection && tab.session.selection.length >= 2) {
|
|
255
|
+
activeTab.value.session.selection = parentElementPath(tab.session.selection);
|
|
263
256
|
}
|
|
264
257
|
break;
|
|
265
258
|
case "ArrowRight":
|
|
266
259
|
e.preventDefault();
|
|
267
|
-
if (
|
|
268
|
-
const node = getNodeAtPath(
|
|
260
|
+
if (tab?.session.selection) {
|
|
261
|
+
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
269
262
|
if (node?.children?.length > 0) {
|
|
270
|
-
activeTab.value.session.selection = [...
|
|
263
|
+
activeTab.value.session.selection = [...tab.session.selection, "children", 0];
|
|
271
264
|
}
|
|
272
265
|
}
|
|
273
266
|
break;
|
|
@@ -286,23 +279,24 @@ export function initShortcuts(getContext) {
|
|
|
286
279
|
);
|
|
287
280
|
}
|
|
288
281
|
|
|
289
|
-
/**
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
function navigateSelection(S, direction = -1) {
|
|
294
|
-
if (!S.selection) {
|
|
282
|
+
/** @param {number} [direction] */
|
|
283
|
+
function navigateSelection(direction = -1) {
|
|
284
|
+
const tab = activeTab.value;
|
|
285
|
+
if (!tab?.session.selection) {
|
|
295
286
|
activeTab.value.session.selection = [];
|
|
296
287
|
return;
|
|
297
288
|
}
|
|
298
|
-
if (
|
|
289
|
+
if (tab.session.selection.length < 2) return;
|
|
299
290
|
|
|
300
|
-
const parent = getNodeAtPath(
|
|
301
|
-
|
|
291
|
+
const parent = getNodeAtPath(
|
|
292
|
+
tab.doc.document,
|
|
293
|
+
/** @type {JxPath} */ (parentElementPath(tab.session.selection)),
|
|
294
|
+
);
|
|
295
|
+
const idx = /** @type {number} */ (childIndex(tab.session.selection));
|
|
302
296
|
const newIdx = idx + direction;
|
|
303
297
|
if (parent?.children && newIdx >= 0 && newIdx < parent.children.length) {
|
|
304
298
|
activeTab.value.session.selection = [
|
|
305
|
-
.../** @type {JxPath} */ (parentElementPath(
|
|
299
|
+
.../** @type {JxPath} */ (parentElementPath(tab.session.selection)),
|
|
306
300
|
"children",
|
|
307
301
|
newIdx,
|
|
308
302
|
];
|