@jxsuite/studio 0.11.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 +6517 -5870
- package/dist/studio.js.map +57 -51
- package/package.json +5 -3
- package/src/canvas/canvas-diff.js +184 -0
- package/src/canvas/canvas-helpers.js +10 -14
- package/src/canvas/canvas-live-render.js +28 -2
- package/src/canvas/canvas-render.js +170 -20
- package/src/canvas/canvas-utils.js +22 -25
- package/src/editor/component-inline-edit.js +55 -44
- package/src/editor/content-inline-edit.js +47 -50
- package/src/editor/context-menu.js +78 -53
- package/src/editor/convert-to-component.js +11 -14
- package/src/editor/insertion-helper.js +8 -11
- package/src/editor/shortcuts.js +88 -64
- package/src/files/components.js +15 -4
- package/src/files/file-ops.js +57 -108
- package/src/files/files.js +81 -28
- package/src/panels/activity-bar.js +31 -7
- package/src/panels/block-action-bar.js +104 -80
- package/src/panels/canvas-dnd.js +4 -10
- package/src/panels/dnd.js +77 -35
- package/src/panels/editors.js +17 -26
- package/src/panels/elements-panel.js +17 -11
- package/src/panels/events-panel.js +44 -39
- package/src/panels/git-panel.js +47 -4
- package/src/panels/layers-panel.js +25 -21
- package/src/panels/left-panel.js +109 -44
- package/src/panels/overlays.js +91 -41
- package/src/panels/panel-events.js +28 -37
- package/src/panels/preview-render.js +7 -3
- package/src/panels/properties-panel.js +179 -104
- package/src/panels/pseudo-preview.js +9 -8
- package/src/panels/right-panel.js +85 -37
- package/src/panels/shared.js +0 -22
- package/src/panels/signals-panel.js +125 -54
- package/src/panels/statusbar.js +26 -19
- package/src/panels/style-inputs.js +5 -4
- package/src/panels/style-panel.js +128 -105
- package/src/panels/style-utils.js +8 -6
- package/src/panels/stylebook-layers-panel.js +5 -5
- package/src/panels/stylebook-panel.js +27 -31
- package/src/panels/tab-strip.js +124 -0
- package/src/panels/toolbar.js +40 -10
- package/src/reactivity.js +28 -0
- package/src/settings/content-types-editor.js +56 -7
- package/src/settings/defs-editor.js +56 -7
- package/src/settings/schema-field-ui.js +60 -25
- package/src/state.js +2 -459
- package/src/store.js +61 -219
- package/src/studio.js +163 -300
- package/src/tabs/tab.js +168 -0
- package/src/tabs/transact.js +406 -0
- package/src/ui/color-selector.js +4 -5
- package/src/view.js +3 -0
- package/src/workspace/workspace.js +90 -0
|
@@ -6,18 +6,9 @@
|
|
|
6
6
|
import { html, render as litRender, nothing } from "lit-html";
|
|
7
7
|
import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
updateSession,
|
|
13
|
-
renderOnly,
|
|
14
|
-
selectNode,
|
|
15
|
-
moveNode,
|
|
16
|
-
getNodeAtPath,
|
|
17
|
-
nodeLabel,
|
|
18
|
-
parentElementPath,
|
|
19
|
-
childIndex,
|
|
20
|
-
} from "../store.js";
|
|
9
|
+
import { getNodeAtPath, nodeLabel, parentElementPath, childIndex } from "../store.js";
|
|
10
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
11
|
+
import { transactDoc, mutateMoveNode } from "../tabs/transact.js";
|
|
21
12
|
import { view } from "../view.js";
|
|
22
13
|
import { isEditing, getActiveElement, getInlineActions } from "../editor/inline-edit.js";
|
|
23
14
|
import { toggleInlineFormat, isTagActiveInSelection } from "../editor/inline-format.js";
|
|
@@ -25,7 +16,21 @@ import { componentRegistry } from "../files/components.js";
|
|
|
25
16
|
import { convertToComponent } from "../editor/convert-to-component.js";
|
|
26
17
|
import { findCanvasElement, getActivePanel } from "../canvas/canvas-helpers.js";
|
|
27
18
|
|
|
28
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {import("../state.js").StudioState} StudioState
|
|
21
|
+
*
|
|
22
|
+
* @typedef {import("../state.js").JxPath} JxPath
|
|
23
|
+
*
|
|
24
|
+
* @typedef {{ command: string; tag: string; label: string; icon: string; shortcut?: string }} InlineAction
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @type {{
|
|
29
|
+
* getCanvasMode: () => string;
|
|
30
|
+
* navigateToComponent: (path: string) => void;
|
|
31
|
+
* createFloatingContainer: () => HTMLElement;
|
|
32
|
+
* } | null}
|
|
33
|
+
*/
|
|
29
34
|
let _ctx = null;
|
|
30
35
|
|
|
31
36
|
/**
|
|
@@ -33,8 +38,8 @@ let _ctx = null;
|
|
|
33
38
|
*
|
|
34
39
|
* @param {{
|
|
35
40
|
* getCanvasMode: () => string;
|
|
36
|
-
* navigateToComponent:
|
|
37
|
-
* createFloatingContainer:
|
|
41
|
+
* navigateToComponent: (path: string) => void;
|
|
42
|
+
* createFloatingContainer: () => HTMLElement;
|
|
38
43
|
* }} ctx
|
|
39
44
|
*/
|
|
40
45
|
export function initBlockActionBar(ctx) {
|
|
@@ -45,7 +50,7 @@ export function initBlockActionBar(ctx) {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/** Pre-built icon templates for inline format buttons (avoids unsafeStatic) */
|
|
48
|
-
const formatIconMap = /** @type {Record<string,
|
|
53
|
+
const formatIconMap = /** @type {Record<string, import("lit-html").TemplateResult>} */ ({
|
|
49
54
|
"sp-icon-text-bold": html`<sp-icon-text-bold slot="icon"></sp-icon-text-bold>`,
|
|
50
55
|
"sp-icon-text-italic": html`<sp-icon-text-italic slot="icon"></sp-icon-text-italic>`,
|
|
51
56
|
"sp-icon-text-underline": html`<sp-icon-text-underline slot="icon"></sp-icon-text-underline>`,
|
|
@@ -63,11 +68,11 @@ const formatIconMap = /** @type {Record<string, any>} */ ({
|
|
|
63
68
|
/**
|
|
64
69
|
* Prevent the bar from stealing focus from contenteditable
|
|
65
70
|
*
|
|
66
|
-
* @param {
|
|
71
|
+
* @param {MouseEvent} e
|
|
67
72
|
*/
|
|
68
73
|
function onBarMousedown(e) {
|
|
69
|
-
if (e.target.closest("sp-textfield")) return;
|
|
70
|
-
if (e.target.closest(".bar-drag-handle")) return;
|
|
74
|
+
if (/** @type {HTMLElement} */ (e.target).closest("sp-textfield")) return;
|
|
75
|
+
if (/** @type {HTMLElement} */ (e.target).closest(".bar-drag-handle")) return;
|
|
71
76
|
e.preventDefault();
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -78,42 +83,48 @@ function captureSelectionRange() {
|
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
/**
|
|
81
|
-
* @param {
|
|
82
|
-
* @param {
|
|
86
|
+
* @param {MouseEvent} e
|
|
87
|
+
* @param {InlineAction} action
|
|
83
88
|
*/
|
|
84
89
|
function onFormatClick(e, action) {
|
|
85
90
|
e.stopPropagation();
|
|
86
91
|
if (action.command === "link") {
|
|
87
|
-
showLinkPopover(
|
|
92
|
+
showLinkPopover(
|
|
93
|
+
/** @type {HTMLElement} */ (
|
|
94
|
+
/** @type {HTMLElement} */ (e.target).closest("sp-action-button")
|
|
95
|
+
),
|
|
96
|
+
);
|
|
88
97
|
} else if (view.savedRange) {
|
|
89
|
-
const sel =
|
|
98
|
+
const sel = window.getSelection();
|
|
90
99
|
const anchor = view.savedRange.startContainer;
|
|
91
100
|
const editableRoot = (
|
|
92
|
-
anchor?.nodeType === Node.ELEMENT_NODE
|
|
101
|
+
anchor?.nodeType === Node.ELEMENT_NODE
|
|
102
|
+
? /** @type {Element} */ (anchor)
|
|
103
|
+
: anchor?.parentElement
|
|
93
104
|
)?.closest("[contenteditable]");
|
|
94
105
|
if (editableRoot) {
|
|
95
|
-
editableRoot.focus();
|
|
96
|
-
sel
|
|
97
|
-
sel
|
|
106
|
+
/** @type {HTMLElement} */ (editableRoot).focus();
|
|
107
|
+
sel?.removeAllRanges();
|
|
108
|
+
sel?.addRange(view.savedRange);
|
|
98
109
|
applyInlineFormat(action);
|
|
99
110
|
}
|
|
100
111
|
}
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
function renderParentSelector() {
|
|
104
|
-
const
|
|
105
|
-
|
|
115
|
+
const tab = activeTab.value;
|
|
116
|
+
if (!tab?.session.selection) return nothing;
|
|
117
|
+
const pPath = parentElementPath(tab.session.selection);
|
|
106
118
|
if (!pPath) return nothing;
|
|
107
|
-
const parentNode = getNodeAtPath(
|
|
119
|
+
const parentNode = getNodeAtPath(tab.doc.document, pPath);
|
|
108
120
|
return html`
|
|
109
121
|
<sp-action-button
|
|
110
122
|
size="xs"
|
|
111
123
|
quiet
|
|
112
124
|
title="Select parent: ${nodeLabel(parentNode)}"
|
|
113
|
-
@click=${(/** @type {
|
|
125
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
114
126
|
e.stopPropagation();
|
|
115
|
-
|
|
116
|
-
update(selectNode(S, pPath));
|
|
127
|
+
activeTab.value.session.selection = pPath;
|
|
117
128
|
}}
|
|
118
129
|
>
|
|
119
130
|
<sp-icon-back slot="icon"></sp-icon-back>
|
|
@@ -122,10 +133,12 @@ function renderParentSelector() {
|
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
function renderMoveArrows() {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const
|
|
136
|
+
const tab = activeTab.value;
|
|
137
|
+
if (!tab?.session.selection) return nothing;
|
|
138
|
+
const sel = tab.session.selection;
|
|
139
|
+
const idx = /** @type {number} */ (childIndex(sel));
|
|
140
|
+
const pPath = parentElementPath(sel);
|
|
141
|
+
const parentNode = getNodeAtPath(tab.doc.document, /** @type {JxPath} */ (pPath));
|
|
129
142
|
const siblings = parentNode?.children;
|
|
130
143
|
return html`
|
|
131
144
|
<sp-action-button
|
|
@@ -133,7 +146,7 @@ function renderMoveArrows() {
|
|
|
133
146
|
quiet
|
|
134
147
|
title="Move up"
|
|
135
148
|
?disabled=${idx <= 0}
|
|
136
|
-
@click=${(/** @type {
|
|
149
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
137
150
|
e.stopPropagation();
|
|
138
151
|
moveSelectionUp();
|
|
139
152
|
}}
|
|
@@ -145,7 +158,7 @@ function renderMoveArrows() {
|
|
|
145
158
|
quiet
|
|
146
159
|
title="Move down"
|
|
147
160
|
?disabled=${!siblings || idx >= siblings.length - 1}
|
|
148
|
-
@click=${(/** @type {
|
|
161
|
+
@click=${(/** @type {MouseEvent} */ e) => {
|
|
149
162
|
e.stopPropagation();
|
|
150
163
|
moveSelectionDown();
|
|
151
164
|
}}
|
|
@@ -158,10 +171,10 @@ function renderMoveArrows() {
|
|
|
158
171
|
/**
|
|
159
172
|
* Apply an inline format action.
|
|
160
173
|
*
|
|
161
|
-
* @param {
|
|
174
|
+
* @param {InlineAction} action
|
|
162
175
|
*/
|
|
163
176
|
function applyInlineFormat(action) {
|
|
164
|
-
/** @type {Record<string,
|
|
177
|
+
/** @type {Record<string, string>} */
|
|
165
178
|
const cmdToTag = {
|
|
166
179
|
bold: "strong",
|
|
167
180
|
italic: "em",
|
|
@@ -190,19 +203,22 @@ export function dismissBlockActionBar() {
|
|
|
190
203
|
if (view.blockActionBarEl) litRender(nothing, view.blockActionBarEl);
|
|
191
204
|
}
|
|
192
205
|
|
|
193
|
-
/** @param {
|
|
206
|
+
/** @param {HTMLElement} anchorBtn */
|
|
194
207
|
function showLinkPopover(anchorBtn) {
|
|
195
208
|
litRender(nothing, view.linkPopoverHost);
|
|
196
209
|
|
|
197
210
|
const sel = window.getSelection();
|
|
198
|
-
/** @type {
|
|
211
|
+
/** @type {HTMLAnchorElement | null} */
|
|
199
212
|
let existingLink = null;
|
|
200
213
|
if (sel?.rangeCount) {
|
|
201
|
-
/** @type {
|
|
214
|
+
/** @type {Node | null} */
|
|
202
215
|
let node = sel.anchorNode;
|
|
203
216
|
while (node && node !== document.body) {
|
|
204
|
-
if (
|
|
205
|
-
|
|
217
|
+
if (
|
|
218
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
219
|
+
/** @type {Element} */ (node).tagName.toLowerCase() === "a"
|
|
220
|
+
) {
|
|
221
|
+
existingLink = /** @type {HTMLAnchorElement} */ (node);
|
|
206
222
|
break;
|
|
207
223
|
}
|
|
208
224
|
node = node.parentNode;
|
|
@@ -212,8 +228,10 @@ function showLinkPopover(anchorBtn) {
|
|
|
212
228
|
const rect = anchorBtn.getBoundingClientRect();
|
|
213
229
|
|
|
214
230
|
const onApply = () => {
|
|
215
|
-
const field =
|
|
216
|
-
|
|
231
|
+
const field = /** @type {HTMLInputElement | null} */ (
|
|
232
|
+
view.linkPopoverHost.querySelector("sp-textfield")
|
|
233
|
+
);
|
|
234
|
+
const url = field?.value || "";
|
|
217
235
|
if (existingLink) {
|
|
218
236
|
existingLink.setAttribute("href", url);
|
|
219
237
|
} else if (url) {
|
|
@@ -224,6 +242,7 @@ function showLinkPopover(anchorBtn) {
|
|
|
224
242
|
};
|
|
225
243
|
|
|
226
244
|
const onRemove = () => {
|
|
245
|
+
if (!existingLink?.parentNode) return;
|
|
227
246
|
const frag = document.createDocumentFragment();
|
|
228
247
|
while (existingLink.firstChild) frag.appendChild(existingLink.firstChild);
|
|
229
248
|
existingLink.parentNode.replaceChild(frag, existingLink);
|
|
@@ -231,7 +250,7 @@ function showLinkPopover(anchorBtn) {
|
|
|
231
250
|
renderBlockActionBar();
|
|
232
251
|
};
|
|
233
252
|
|
|
234
|
-
const onKeydown = (/** @type {
|
|
253
|
+
const onKeydown = (/** @type {KeyboardEvent} */ e) => {
|
|
235
254
|
if (e.key === "Enter") onApply();
|
|
236
255
|
else if (e.key === "Escape") {
|
|
237
256
|
litRender(nothing, view.linkPopoverHost);
|
|
@@ -273,32 +292,33 @@ function showLinkPopover(anchorBtn) {
|
|
|
273
292
|
|
|
274
293
|
/** Move the selected node up (swap with previous sibling). */
|
|
275
294
|
function moveSelectionUp() {
|
|
276
|
-
const
|
|
277
|
-
if (!
|
|
278
|
-
const
|
|
295
|
+
const tab = activeTab.value;
|
|
296
|
+
if (!tab?.session.selection || tab.session.selection.length < 2) return;
|
|
297
|
+
const sel = tab.session.selection;
|
|
298
|
+
const idx = /** @type {number} */ (childIndex(sel));
|
|
279
299
|
if (idx <= 0) return;
|
|
280
|
-
const pPath = /** @type {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
renderOnly("overlays");
|
|
300
|
+
const pPath = /** @type {JxPath} */ (parentElementPath(sel));
|
|
301
|
+
transactDoc(tab, (t) => mutateMoveNode(t, sel, pPath, idx - 1));
|
|
302
|
+
tab.session.selection = [...pPath, "children", idx - 1];
|
|
284
303
|
}
|
|
285
304
|
|
|
286
305
|
/** Move the selected node down (swap with next sibling). */
|
|
287
306
|
function moveSelectionDown() {
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
290
|
-
const
|
|
291
|
-
const
|
|
292
|
-
const
|
|
307
|
+
const tab = activeTab.value;
|
|
308
|
+
if (!tab?.session.selection || tab.session.selection.length < 2) return;
|
|
309
|
+
const sel = tab.session.selection;
|
|
310
|
+
const idx = /** @type {number} */ (childIndex(sel));
|
|
311
|
+
const pPath = /** @type {JxPath} */ (parentElementPath(sel));
|
|
312
|
+
const parentNode = getNodeAtPath(tab.doc.document, pPath);
|
|
293
313
|
const siblings = parentNode?.children;
|
|
294
314
|
if (!siblings || idx >= siblings.length - 1) return;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
renderOnly("overlays");
|
|
315
|
+
transactDoc(tab, (t) => mutateMoveNode(t, sel, pPath, idx + 2));
|
|
316
|
+
tab.session.selection = [...pPath, "children", idx + 1];
|
|
298
317
|
}
|
|
299
318
|
|
|
300
319
|
/** Render the unified block action bar above the selected element. */
|
|
301
320
|
export function renderBlockActionBar() {
|
|
321
|
+
if (!_ctx) return;
|
|
302
322
|
if (!view.blockActionBarEl) {
|
|
303
323
|
view.blockActionBarEl = _ctx.createFloatingContainer();
|
|
304
324
|
}
|
|
@@ -308,21 +328,22 @@ export function renderBlockActionBar() {
|
|
|
308
328
|
view.selDragCleanup = null;
|
|
309
329
|
}
|
|
310
330
|
|
|
311
|
-
const
|
|
331
|
+
const tab = activeTab.value;
|
|
312
332
|
const canvasMode = _ctx.getCanvasMode();
|
|
313
333
|
|
|
314
|
-
if (!
|
|
334
|
+
if (!tab?.session.selection || (canvasMode !== "design" && canvasMode !== "edit")) {
|
|
315
335
|
litRender(nothing, view.blockActionBarEl);
|
|
316
336
|
return;
|
|
317
337
|
}
|
|
318
338
|
|
|
339
|
+
const selection = tab.session.selection;
|
|
319
340
|
const activePanel = getActivePanel();
|
|
320
341
|
if (!activePanel) {
|
|
321
342
|
litRender(nothing, view.blockActionBarEl);
|
|
322
343
|
return;
|
|
323
344
|
}
|
|
324
|
-
const el = findCanvasElement(
|
|
325
|
-
const node = el && getNodeAtPath(
|
|
345
|
+
const el = findCanvasElement(selection, activePanel.canvas);
|
|
346
|
+
const node = el && getNodeAtPath(tab.doc.document, selection);
|
|
326
347
|
if (!el || !node) {
|
|
327
348
|
litRender(nothing, view.blockActionBarEl);
|
|
328
349
|
return;
|
|
@@ -347,28 +368,31 @@ export function renderBlockActionBar() {
|
|
|
347
368
|
style="left:${elRect.left}px; top:${topPos}px"
|
|
348
369
|
@mousedown=${onBarMousedown}
|
|
349
370
|
>
|
|
350
|
-
${
|
|
371
|
+
${selection.length >= 2 ? renderParentSelector() : nothing}
|
|
351
372
|
|
|
352
373
|
<span class="bar-tag">${node.$id || (node.tagName ?? "div")}</span>
|
|
353
374
|
|
|
354
|
-
${
|
|
375
|
+
${selection.length >= 2
|
|
355
376
|
? html`<span class="bar-drag-handle" title="Drag to reorder">⡇</span>`
|
|
356
377
|
: nothing}
|
|
357
|
-
${
|
|
358
|
-
${
|
|
378
|
+
${selection.length >= 2 ? renderMoveArrows() : nothing}
|
|
379
|
+
${selection.length >= 2 && node.tagName
|
|
359
380
|
? (() => {
|
|
360
381
|
const isComp =
|
|
361
382
|
node.tagName.includes("-") &&
|
|
362
|
-
componentRegistry.some(
|
|
383
|
+
componentRegistry.some(
|
|
384
|
+
(/** @type {{ tagName: string }} */ c) => c.tagName === node.tagName,
|
|
385
|
+
);
|
|
363
386
|
if (isComp) {
|
|
364
387
|
const comp = componentRegistry.find(
|
|
365
|
-
(/** @type {
|
|
388
|
+
(/** @type {{ tagName: string; path: string }} */ c) =>
|
|
389
|
+
c.tagName === node.tagName,
|
|
366
390
|
);
|
|
367
391
|
return html`<sp-action-button
|
|
368
392
|
size="xs"
|
|
369
393
|
quiet
|
|
370
394
|
title="Edit Component"
|
|
371
|
-
@click=${() => _ctx
|
|
395
|
+
@click=${() => _ctx?.navigateToComponent(/** @type {string} */ (comp?.path))}
|
|
372
396
|
><sp-icon-edit slot="icon" size="xs"></sp-icon-edit
|
|
373
397
|
></sp-action-button>`;
|
|
374
398
|
}
|
|
@@ -376,7 +400,7 @@ export function renderBlockActionBar() {
|
|
|
376
400
|
size="xs"
|
|
377
401
|
quiet
|
|
378
402
|
title="Convert to Component"
|
|
379
|
-
@click=${() => convertToComponent(
|
|
403
|
+
@click=${() => convertToComponent()}
|
|
380
404
|
><sp-icon-box slot="icon" size="xs"></sp-icon-box
|
|
381
405
|
></sp-action-button>`;
|
|
382
406
|
})()
|
|
@@ -398,7 +422,7 @@ export function renderBlockActionBar() {
|
|
|
398
422
|
value=${action.tag}
|
|
399
423
|
title="${action.label}${action.shortcut ? ` (${action.shortcut})` : ""}"
|
|
400
424
|
@mousedown=${captureSelectionRange}
|
|
401
|
-
@click=${(/** @type {
|
|
425
|
+
@click=${(/** @type {MouseEvent} */ e) => onFormatClick(e, action)}
|
|
402
426
|
>
|
|
403
427
|
${formatIconMap[action.icon] ?? nothing}
|
|
404
428
|
</sp-action-button>
|
|
@@ -422,8 +446,8 @@ export function renderBlockActionBar() {
|
|
|
422
446
|
bar.style.left = `${Math.max(0, window.innerWidth - barRect.width)}px`;
|
|
423
447
|
}
|
|
424
448
|
// Attach drag handle
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
449
|
+
const currentTab = activeTab.value;
|
|
450
|
+
if (currentTab?.session.selection && currentTab.session.selection.length >= 2) {
|
|
427
451
|
const handle = bar.querySelector(".bar-drag-handle");
|
|
428
452
|
if (handle) {
|
|
429
453
|
if (view.selDragCleanup) {
|
|
@@ -432,7 +456,7 @@ export function renderBlockActionBar() {
|
|
|
432
456
|
}
|
|
433
457
|
view.selDragCleanup = draggable({
|
|
434
458
|
element: handle,
|
|
435
|
-
getInitialData: () => ({ type: "tree-node", path:
|
|
459
|
+
getInitialData: () => ({ type: "tree-node", path: activeTab.value?.session.selection }),
|
|
436
460
|
});
|
|
437
461
|
}
|
|
438
462
|
}
|
package/src/panels/canvas-dnd.js
CHANGED
|
@@ -8,14 +8,8 @@ import {
|
|
|
8
8
|
monitorForElements,
|
|
9
9
|
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
elToPath,
|
|
14
|
-
canvasPanels,
|
|
15
|
-
getNodeAtPath,
|
|
16
|
-
VOID_ELEMENTS,
|
|
17
|
-
isAncestor,
|
|
18
|
-
} from "../store.js";
|
|
11
|
+
import { elToPath, canvasPanels, getNodeAtPath, VOID_ELEMENTS, isAncestor } from "../store.js";
|
|
12
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
19
13
|
import { view } from "../view.js";
|
|
20
14
|
import { applyDropInstruction } from "../panels/dnd.js";
|
|
21
15
|
import { effectiveZoom } from "../canvas/canvas-helpers.js";
|
|
@@ -72,12 +66,12 @@ export function registerPanelDnD(panel) {
|
|
|
72
66
|
});
|
|
73
67
|
view.canvasDndCleanups.push(monitorCleanup);
|
|
74
68
|
|
|
75
|
-
const
|
|
69
|
+
const document = activeTab.value?.doc.document;
|
|
76
70
|
for (const el of allEls) {
|
|
77
71
|
const elPath = elToPath.get(el);
|
|
78
72
|
if (!elPath) continue;
|
|
79
73
|
|
|
80
|
-
const node = getNodeAtPath(
|
|
74
|
+
const node = getNodeAtPath(document, elPath);
|
|
81
75
|
const tag = (node?.tagName || "div").toLowerCase();
|
|
82
76
|
const hasElementChildren = node?.children?.some(
|
|
83
77
|
(/** @type {unknown} */ c) => c != null && typeof c === "object",
|
package/src/panels/dnd.js
CHANGED
|
@@ -15,17 +15,15 @@ import {
|
|
|
15
15
|
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
|
|
16
16
|
|
|
17
17
|
import {
|
|
18
|
-
getState,
|
|
19
|
-
update,
|
|
20
18
|
leftPanel,
|
|
21
|
-
moveNode,
|
|
22
|
-
insertNode,
|
|
23
|
-
applyMutation,
|
|
24
19
|
getNodeAtPath,
|
|
25
20
|
parentElementPath,
|
|
26
21
|
childIndex,
|
|
27
22
|
isAncestor,
|
|
23
|
+
renderOnly,
|
|
28
24
|
} from "../store.js";
|
|
25
|
+
import { transact, transactDoc, mutateMoveNode, mutateInsertNode } from "../tabs/transact.js";
|
|
26
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
29
27
|
import { view } from "../view.js";
|
|
30
28
|
import { componentRegistry, computeRelativePath } from "../files/components.js";
|
|
31
29
|
import { renderComponentPreview } from "./stylebook-panel.js";
|
|
@@ -44,6 +42,7 @@ export function registerLayersDnD() {
|
|
|
44
42
|
.map((/** @type {any} */ s) => (/^\d+$/.test(s) ? parseInt(s) : s));
|
|
45
43
|
const rowDepth = parseInt(/** @type {string} */ (row.dataset.dndDepth)) || 0;
|
|
46
44
|
const isVoid = row.hasAttribute("data-dnd-void");
|
|
45
|
+
const isExpanded = row.hasAttribute("data-dnd-expanded");
|
|
47
46
|
|
|
48
47
|
const cleanup = combine(
|
|
49
48
|
draggable({
|
|
@@ -61,9 +60,15 @@ export function registerLayersDnD() {
|
|
|
61
60
|
onDragStart() {
|
|
62
61
|
row.classList.add("dragging");
|
|
63
62
|
view.layerDragSourceHeight = row.offsetHeight;
|
|
63
|
+
if (isExpanded) {
|
|
64
|
+
hideDescendantRows(row, container);
|
|
65
|
+
}
|
|
64
66
|
},
|
|
65
67
|
onDrop() {
|
|
66
68
|
row.classList.remove("dragging");
|
|
69
|
+
if (isExpanded) {
|
|
70
|
+
renderOnly("leftPanel");
|
|
71
|
+
}
|
|
67
72
|
},
|
|
68
73
|
}),
|
|
69
74
|
dropTargetForElements({
|
|
@@ -81,6 +86,7 @@ export function registerLayersDnD() {
|
|
|
81
86
|
element,
|
|
82
87
|
currentLevel: rowDepth,
|
|
83
88
|
indentPerLevel: 16,
|
|
89
|
+
mode: isExpanded ? "expanded" : "standard",
|
|
84
90
|
block: isVoid ? ["make-child"] : [],
|
|
85
91
|
}),
|
|
86
92
|
);
|
|
@@ -113,7 +119,21 @@ export function registerLayersDnD() {
|
|
|
113
119
|
if (!instruction || instruction.type === "instruction-blocked") return;
|
|
114
120
|
const srcData = source.data;
|
|
115
121
|
const targetPath = target.data.path;
|
|
122
|
+
|
|
123
|
+
// If the source had children, persist collapse at the new location
|
|
124
|
+
const srcRow = srcData.type === "tree-node" && source.element;
|
|
125
|
+
const wasExpanded = srcRow && srcRow.hasAttribute("data-dnd-expanded");
|
|
126
|
+
|
|
116
127
|
applyDropInstruction(instruction, srcData, targetPath);
|
|
128
|
+
|
|
129
|
+
if (wasExpanded) {
|
|
130
|
+
const tab = activeTab.value;
|
|
131
|
+
const newPath = tab?.session.selection;
|
|
132
|
+
if (newPath) {
|
|
133
|
+
const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
|
|
134
|
+
collapsed.add(newPath.join("/"));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
117
137
|
},
|
|
118
138
|
});
|
|
119
139
|
view.dndCleanups.push(monitorCleanup);
|
|
@@ -144,12 +164,14 @@ export function registerComponentsDnD() {
|
|
|
144
164
|
|
|
145
165
|
const instanceDef = {
|
|
146
166
|
tagName: comp.tagName,
|
|
147
|
-
$props:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
$props: comp.props
|
|
168
|
+
? Object.fromEntries(
|
|
169
|
+
comp.props.map((/** @type {any} */ p) => [
|
|
170
|
+
p.name,
|
|
171
|
+
p.default !== undefined ? p.default : "",
|
|
172
|
+
]),
|
|
173
|
+
)
|
|
174
|
+
: {},
|
|
153
175
|
};
|
|
154
176
|
const cleanup = draggable({
|
|
155
177
|
element: row,
|
|
@@ -190,6 +212,22 @@ export function registerElementsDnD() {
|
|
|
190
212
|
});
|
|
191
213
|
}
|
|
192
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Hide descendant rows of the dragged item so it appears collapsed during drag.
|
|
217
|
+
*
|
|
218
|
+
* @param {any} parentRow
|
|
219
|
+
* @param {any} container
|
|
220
|
+
*/
|
|
221
|
+
function hideDescendantRows(parentRow, container) {
|
|
222
|
+
const prefix = parentRow.dataset.path + "/";
|
|
223
|
+
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
224
|
+
for (const r of rows) {
|
|
225
|
+
if (/** @type {any} */ (r).dataset.path?.startsWith(prefix)) {
|
|
226
|
+
/** @type {any} */ (r).style.display = "none";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
193
231
|
/**
|
|
194
232
|
* @param {any} rowEl
|
|
195
233
|
* @param {any} data
|
|
@@ -251,7 +289,7 @@ export function clearLayerDropGap(container) {
|
|
|
251
289
|
* @param {any} targetPath
|
|
252
290
|
*/
|
|
253
291
|
export function applyDropInstruction(instruction, srcData, targetPath) {
|
|
254
|
-
const
|
|
292
|
+
const document = activeTab.value?.doc.document;
|
|
255
293
|
if (srcData.type === "tree-node") {
|
|
256
294
|
const fromPath = srcData.path;
|
|
257
295
|
const targetParent = /** @type {any} */ (parentElementPath(targetPath));
|
|
@@ -259,15 +297,17 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
|
|
|
259
297
|
|
|
260
298
|
switch (instruction.type) {
|
|
261
299
|
case "reorder-above":
|
|
262
|
-
|
|
300
|
+
transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetParent, targetIdx));
|
|
263
301
|
break;
|
|
264
302
|
case "reorder-below":
|
|
265
|
-
|
|
303
|
+
transactDoc(activeTab.value, (t) =>
|
|
304
|
+
mutateMoveNode(t, fromPath, targetParent, targetIdx + 1),
|
|
305
|
+
);
|
|
266
306
|
break;
|
|
267
307
|
case "make-child": {
|
|
268
|
-
const target = getNodeAtPath(
|
|
308
|
+
const target = getNodeAtPath(document, targetPath);
|
|
269
309
|
const len = target?.children?.length || 0;
|
|
270
|
-
|
|
310
|
+
transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetPath, len));
|
|
271
311
|
break;
|
|
272
312
|
}
|
|
273
313
|
}
|
|
@@ -277,15 +317,21 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
|
|
|
277
317
|
|
|
278
318
|
switch (instruction.type) {
|
|
279
319
|
case "reorder-above":
|
|
280
|
-
|
|
320
|
+
transactDoc(activeTab.value, (t) =>
|
|
321
|
+
mutateInsertNode(t, targetParent, targetIdx, structuredClone(srcData.fragment)),
|
|
322
|
+
);
|
|
281
323
|
break;
|
|
282
324
|
case "reorder-below":
|
|
283
|
-
|
|
325
|
+
transactDoc(activeTab.value, (t) =>
|
|
326
|
+
mutateInsertNode(t, targetParent, targetIdx + 1, structuredClone(srcData.fragment)),
|
|
327
|
+
);
|
|
284
328
|
break;
|
|
285
329
|
case "make-child": {
|
|
286
|
-
const target = getNodeAtPath(
|
|
330
|
+
const target = getNodeAtPath(document, targetPath);
|
|
287
331
|
const len = target?.children?.length || 0;
|
|
288
|
-
|
|
332
|
+
transactDoc(activeTab.value, (t) =>
|
|
333
|
+
mutateInsertNode(t, targetPath, len, structuredClone(srcData.fragment)),
|
|
334
|
+
);
|
|
289
335
|
break;
|
|
290
336
|
}
|
|
291
337
|
}
|
|
@@ -295,20 +341,18 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
|
|
|
295
341
|
if (tag && tag.includes("-")) {
|
|
296
342
|
const comp = componentRegistry.find((/** @type {any} */ c) => c.tagName === tag);
|
|
297
343
|
if (comp) {
|
|
298
|
-
const
|
|
299
|
-
const elements =
|
|
344
|
+
const tab = activeTab.value;
|
|
345
|
+
const elements = tab?.doc.document?.$elements || [];
|
|
300
346
|
if (comp.source === "npm") {
|
|
301
347
|
const specifier = comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
|
|
302
348
|
const alreadyImported = elements.some(
|
|
303
349
|
(/** @type {any} */ e) => e === specifier || e === comp.package,
|
|
304
350
|
);
|
|
305
351
|
if (!alreadyImported) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}),
|
|
311
|
-
);
|
|
352
|
+
transact(activeTab.value, (/** @type {any} */ doc) => {
|
|
353
|
+
if (!doc.$elements) doc.$elements = [];
|
|
354
|
+
doc.$elements.push(specifier);
|
|
355
|
+
});
|
|
312
356
|
}
|
|
313
357
|
} else {
|
|
314
358
|
const alreadyImported = elements.some(
|
|
@@ -317,13 +361,11 @@ export function applyDropInstruction(instruction, srcData, targetPath) {
|
|
|
317
361
|
(e.$ref === `./${comp.path}` || e.$ref.endsWith(comp.path.split("/").pop())),
|
|
318
362
|
);
|
|
319
363
|
if (!alreadyImported) {
|
|
320
|
-
const relPath = computeRelativePath(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}),
|
|
326
|
-
);
|
|
364
|
+
const relPath = computeRelativePath(tab?.documentPath, comp.path);
|
|
365
|
+
transact(activeTab.value, (/** @type {any} */ doc) => {
|
|
366
|
+
if (!doc.$elements) doc.$elements = [];
|
|
367
|
+
doc.$elements.push({ $ref: relPath });
|
|
368
|
+
});
|
|
327
369
|
}
|
|
328
370
|
}
|
|
329
371
|
}
|