@jxsuite/studio 0.23.2 → 0.25.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 +2311 -718
- package/dist/studio.js.map +121 -104
- package/package.json +8 -7
- package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
- package/src/browse/{browse.js → browse.ts} +109 -84
- package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
- package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
- package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
- package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
- package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
- package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
- package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
- package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
- package/src/editor/{context-menu.js → context-menu.ts} +38 -36
- package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
- package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
- package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
- package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
- package/src/editor/{inline-format.js → inline-format.ts} +46 -51
- package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
- package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
- package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
- package/src/files/{components.js → components.ts} +12 -15
- package/src/files/{file-ops.js → file-ops.ts} +36 -29
- package/src/files/{files.js → files.ts} +102 -84
- package/src/github/{github-auth.js → github-auth.ts} +3 -3
- package/src/github/{github-publish.js → github-publish.ts} +20 -18
- package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
- package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
- package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
- package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
- package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
- package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
- package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
- package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
- package/src/panels/dnd.ts +417 -0
- package/src/panels/{editors.js → editors.ts} +43 -50
- package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
- package/src/panels/{events-panel.js → events-panel.ts} +33 -25
- package/src/panels/{git-panel.js → git-panel.ts} +97 -83
- package/src/panels/{head-panel.js → head-panel.ts} +117 -139
- package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
- package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
- package/src/panels/{left-panel.js → left-panel.ts} +56 -63
- package/src/panels/{overlays.js → overlays.ts} +28 -36
- package/src/panels/{panel-events.js → panel-events.ts} +53 -51
- package/src/panels/{preview-render.js → preview-render.ts} +20 -15
- package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
- package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
- package/src/panels/{quick-search.js → quick-search.ts} +16 -17
- package/src/panels/{right-panel.js → right-panel.ts} +41 -59
- package/src/panels/{shared.js → shared.ts} +8 -6
- package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
- package/src/panels/{statusbar.js → statusbar.ts} +14 -14
- package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
- package/src/panels/{style-panel.js → style-panel.ts} +235 -290
- package/src/panels/{style-utils.js → style-utils.ts} +32 -31
- package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
- package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
- package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
- package/src/panels/{toolbar.js → toolbar.ts} +68 -70
- package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
- package/src/{platform.js → platform.ts} +5 -3
- package/src/platforms/{devserver.js → devserver.ts} +53 -44
- package/src/{recent-projects.js → recent-projects.ts} +17 -12
- package/src/{resize-edges.js → resize-edges.ts} +18 -13
- package/src/services/{cem-export.js → cem-export.ts} +14 -7
- package/src/services/{code-services.js → code-services.ts} +29 -20
- package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
- package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
- package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
- package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
- package/src/settings/{general-settings.js → general-settings.ts} +29 -28
- package/src/settings/{head-editor.js → head-editor.ts} +37 -35
- package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
- package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
- package/src/{site-context.js → site-context.ts} +35 -32
- package/src/{state.js → state.ts} +95 -78
- package/src/{store.js → store.ts} +60 -63
- package/src/{studio.js → studio.ts} +117 -119
- package/src/tabs/tab.ts +196 -0
- package/src/tabs/{transact.js → transact.ts} +107 -60
- package/src/types.ts +185 -0
- package/src/ui/{button-group.js → button-group.ts} +21 -21
- package/src/ui/{color-selector.js → color-selector.ts} +44 -37
- package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
- package/src/ui/{field-row.js → field-row.ts} +19 -2
- package/src/ui/{icons.js → icons.ts} +2 -2
- package/src/ui/{layers.js → layers.ts} +33 -24
- package/src/ui/{media-picker.js → media-picker.ts} +13 -9
- package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
- package/src/ui/{spectrum.js → spectrum.ts} +4 -4
- package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
- package/src/ui/{value-selector.js → value-selector.ts} +28 -28
- package/src/ui/{widgets.js → widgets.ts} +62 -15
- package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
- package/src/utils/edit-display.ts +298 -0
- package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
- package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
- package/src/utils/{studio-utils.js → studio-utils.ts} +35 -23
- package/src/{view.js → view.ts} +53 -55
- package/src/workspace/{workspace.js → workspace.ts} +44 -33
- package/src/panels/dnd.js +0 -426
- package/src/tabs/tab.js +0 -192
- package/src/utils/edit-display.js +0 -203
- /package/src/{reactivity.js → reactivity.ts} +0 -0
package/src/{view.js → view.ts}
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* View.js — Transient view state for Jx Studio
|
|
3
4
|
*
|
|
@@ -6,62 +7,59 @@
|
|
|
6
7
|
* store.js) makes renderer dependencies explicit.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* }} ViewState
|
|
61
|
-
*/
|
|
10
|
+
interface ViewState {
|
|
11
|
+
panzoomWrap: HTMLElement | null;
|
|
12
|
+
renderGeneration: number;
|
|
13
|
+
centerObserver: ResizeObserver | null;
|
|
14
|
+
needsCenter: boolean;
|
|
15
|
+
panX: number;
|
|
16
|
+
panY: number;
|
|
17
|
+
prevCanvasMode: string | null;
|
|
18
|
+
monacoEditor:
|
|
19
|
+
| (import("monaco-editor").editor.IStandaloneCodeEditor & { _ignoreNextChange?: boolean })
|
|
20
|
+
| null;
|
|
21
|
+
functionEditor:
|
|
22
|
+
| (import("monaco-editor").editor.IStandaloneCodeEditor & {
|
|
23
|
+
_ignoreNextChange?: boolean;
|
|
24
|
+
_editingTarget?: string | null;
|
|
25
|
+
})
|
|
26
|
+
| null;
|
|
27
|
+
componentInlineEdit: {
|
|
28
|
+
el: HTMLElement;
|
|
29
|
+
path: (string | number)[];
|
|
30
|
+
originalText: string;
|
|
31
|
+
mediaName: string | null;
|
|
32
|
+
_outsideHandler?: ((e: MouseEvent) => void) | null;
|
|
33
|
+
[k: string]: unknown;
|
|
34
|
+
} | null;
|
|
35
|
+
inlineEditCleanup: (() => void) | null;
|
|
36
|
+
blockActionBarEl: HTMLElement | null;
|
|
37
|
+
selDragCleanup: (() => void) | null;
|
|
38
|
+
dndCleanups: (() => void)[];
|
|
39
|
+
canvasDndCleanups: (() => void)[];
|
|
40
|
+
canvasEventCleanups: (() => void)[];
|
|
41
|
+
forcedStyleTag: HTMLStyleElement | null;
|
|
42
|
+
forcedAttrEl: HTMLElement | null;
|
|
43
|
+
elementsCollapsed: Set<string>;
|
|
44
|
+
elementsFilter: string;
|
|
45
|
+
lastDragInput: { clientX: number; clientY: number; [k: string]: unknown } | null;
|
|
46
|
+
_currentDropTargetRow: HTMLElement | null;
|
|
47
|
+
layerDragSourceHeight: number;
|
|
48
|
+
savedRange: Range | null;
|
|
49
|
+
_completionRegistered: boolean;
|
|
50
|
+
stylebookElToTag: WeakMap<Element, string>;
|
|
51
|
+
showAddBreakpointForm: boolean;
|
|
52
|
+
addBreakpointPreview: string;
|
|
53
|
+
layoutSelection: unknown;
|
|
54
|
+
leftTab: string;
|
|
55
|
+
leftPanelCollapsed: boolean;
|
|
56
|
+
rightPanelCollapsed: boolean;
|
|
57
|
+
autosaveTimer: ReturnType<typeof setTimeout> | null;
|
|
58
|
+
_layersCollapsed: Set<string> | null;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
export const view = {
|
|
62
|
+
export const view: ViewState = {
|
|
65
63
|
// Canvas infrastructure
|
|
66
64
|
panzoomWrap: null,
|
|
67
65
|
renderGeneration: 0,
|
|
@@ -1,53 +1,50 @@
|
|
|
1
|
-
import { reactive, computed } from "../reactivity
|
|
2
|
-
import { createTab, disposeTab } from "../tabs/tab
|
|
1
|
+
import { reactive, computed } from "../reactivity";
|
|
2
|
+
import { createTab, disposeTab } from "../tabs/tab";
|
|
3
|
+
import type { Tab } from "../tabs/tab";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import type { ComponentEntry } from "../files/components";
|
|
6
|
+
import type { JxStyle, JxMutableNode } from "@jxsuite/schema/types";
|
|
7
|
+
|
|
8
|
+
interface FileEntry {
|
|
9
|
+
name: string;
|
|
10
|
+
path: string;
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
11
13
|
|
|
12
14
|
export const workspace = reactive({
|
|
13
15
|
/** @type {string | null} */
|
|
14
|
-
projectRoot: null,
|
|
16
|
+
projectRoot: null as string | null,
|
|
15
17
|
/** @type {object | null} */
|
|
16
|
-
projectConfig: null,
|
|
18
|
+
projectConfig: null as object | null,
|
|
17
19
|
/** @type {ComponentEntry[]} */
|
|
18
|
-
componentRegistry: [],
|
|
20
|
+
componentRegistry: [] as ComponentEntry[],
|
|
19
21
|
/** @type {JxMutableNode | null} */
|
|
20
|
-
clipboard: null,
|
|
22
|
+
clipboard: null as JxMutableNode | null,
|
|
21
23
|
/** @type {JxStyle | null} */
|
|
22
|
-
styleClipboard: null,
|
|
24
|
+
styleClipboard: null as JxStyle | null,
|
|
23
25
|
fileTree: {
|
|
24
26
|
/** @type {Map<string, FileEntry[]>} */
|
|
25
|
-
dirs: new Map(),
|
|
27
|
+
dirs: new Map<string, FileEntry[]>(),
|
|
26
28
|
/** @type {Set<string>} */
|
|
27
|
-
expanded: new Set(),
|
|
29
|
+
expanded: new Set<string>(),
|
|
28
30
|
/** @type {string | null} */
|
|
29
|
-
selectedPath: null,
|
|
31
|
+
selectedPath: null as string | null,
|
|
30
32
|
searchQuery: "",
|
|
31
33
|
},
|
|
32
34
|
/** @type {Map<string, Tab>} */
|
|
33
|
-
tabs: new Map(),
|
|
35
|
+
tabs: new Map<string, Tab>(),
|
|
34
36
|
/** @type {string[]} */
|
|
35
|
-
tabOrder: [],
|
|
37
|
+
tabOrder: [] as string[],
|
|
36
38
|
/** @type {string | null} */
|
|
37
|
-
activeTabId: null,
|
|
39
|
+
activeTabId: null as string | null,
|
|
38
40
|
ui: {
|
|
39
41
|
activityBar: "files",
|
|
40
42
|
},
|
|
41
43
|
});
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
computed(() =>
|
|
47
|
-
workspace.activeTabId ? (workspace.tabs.get(workspace.activeTabId) ?? null) : null,
|
|
48
|
-
)
|
|
49
|
-
)
|
|
50
|
-
);
|
|
45
|
+
export const activeTab = computed(() =>
|
|
46
|
+
workspace.activeTabId ? (workspace.tabs.get(workspace.activeTabId) ?? null) : null,
|
|
47
|
+
) as unknown as import("@vue/reactivity").ComputedRef<Tab | null>;
|
|
51
48
|
|
|
52
49
|
/**
|
|
53
50
|
* Open a new tab and make it active.
|
|
@@ -63,7 +60,15 @@ export const activeTab = /** @type {import("@vue/reactivity").ComputedRef<Tab>}
|
|
|
63
60
|
* }} opts
|
|
64
61
|
* @returns {Tab}
|
|
65
62
|
*/
|
|
66
|
-
export function openTab(opts
|
|
63
|
+
export function openTab(opts: {
|
|
64
|
+
id: string;
|
|
65
|
+
documentPath?: string | null;
|
|
66
|
+
fileHandle?: FileSystemFileHandle | null;
|
|
67
|
+
document: Record<string, unknown>;
|
|
68
|
+
frontmatter?: Record<string, unknown>;
|
|
69
|
+
sourceFormat?: string | null;
|
|
70
|
+
capabilities?: { modes?: string[] };
|
|
71
|
+
}) {
|
|
67
72
|
const tab = createTab(opts);
|
|
68
73
|
workspace.tabs.set(tab.id, tab);
|
|
69
74
|
workspace.tabOrder.push(tab.id);
|
|
@@ -76,7 +81,7 @@ export function openTab(opts) {
|
|
|
76
81
|
*
|
|
77
82
|
* @param {string} tabId
|
|
78
83
|
*/
|
|
79
|
-
export function closeTab(tabId) {
|
|
84
|
+
export function closeTab(tabId: string) {
|
|
80
85
|
const tab = workspace.tabs.get(tabId);
|
|
81
86
|
if (!tab) return;
|
|
82
87
|
disposeTab(tab);
|
|
@@ -111,7 +116,13 @@ export function closeAllTabs() {
|
|
|
111
116
|
* }} newTabOpts
|
|
112
117
|
* @returns {import("../tabs/tab.js").Tab}
|
|
113
118
|
*/
|
|
114
|
-
export function replaceAllTabs(newTabOpts
|
|
119
|
+
export function replaceAllTabs(newTabOpts: {
|
|
120
|
+
id: string;
|
|
121
|
+
documentPath?: string | null;
|
|
122
|
+
document: Record<string, unknown>;
|
|
123
|
+
frontmatter?: Record<string, unknown>;
|
|
124
|
+
sourceFormat?: string | null;
|
|
125
|
+
}) {
|
|
115
126
|
const oldIds = [...workspace.tabs.keys()];
|
|
116
127
|
const oldTabs = [...workspace.tabs.values()];
|
|
117
128
|
|
|
@@ -136,7 +147,7 @@ export function replaceAllTabs(newTabOpts) {
|
|
|
136
147
|
*
|
|
137
148
|
* @param {string} tabId
|
|
138
149
|
*/
|
|
139
|
-
export function activateTab(tabId) {
|
|
150
|
+
export function activateTab(tabId: string) {
|
|
140
151
|
if (workspace.tabs.has(tabId)) workspace.activeTabId = tabId;
|
|
141
152
|
}
|
|
142
153
|
|
|
@@ -148,7 +159,7 @@ export function activateTab(tabId) {
|
|
|
148
159
|
* @param {string} newId
|
|
149
160
|
* @param {string} newDocumentPath
|
|
150
161
|
*/
|
|
151
|
-
export function renameTab(oldId, newId, newDocumentPath) {
|
|
162
|
+
export function renameTab(oldId: string, newId: string, newDocumentPath: string) {
|
|
152
163
|
const tab = workspace.tabs.get(oldId);
|
|
153
164
|
if (!tab) return;
|
|
154
165
|
tab.id = newId;
|
package/src/panels/dnd.js
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DnD registration functions — extracted from studio.js (Phase 4). Registers drag-and-drop behavior
|
|
3
|
-
* on layer rows, component cards, and element cards.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {{ element: HTMLElement; input: { clientX: number; clientY: number } }} DragCanDragArgs
|
|
8
|
-
*
|
|
9
|
-
* @typedef {{ source: { data: Record<string, unknown>; element: HTMLElement } }} DragDropSourceArgs
|
|
10
|
-
*
|
|
11
|
-
* @typedef {{ self: { data: Record<string, unknown> } }} DragSelfArgs
|
|
12
|
-
*
|
|
13
|
-
* @typedef {{
|
|
14
|
-
* source: { data: Record<string, unknown>; element: HTMLElement };
|
|
15
|
-
* location: { current: { dropTargets: { data: Record<string, unknown> }[] } };
|
|
16
|
-
* }} DragMonitorDropArgs
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
draggable,
|
|
21
|
-
dropTargetForElements,
|
|
22
|
-
monitorForElements,
|
|
23
|
-
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
24
|
-
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
25
|
-
import {
|
|
26
|
-
attachInstruction,
|
|
27
|
-
extractInstruction,
|
|
28
|
-
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
leftPanel,
|
|
32
|
-
getNodeAtPath,
|
|
33
|
-
parentElementPath,
|
|
34
|
-
childIndex,
|
|
35
|
-
isAncestor,
|
|
36
|
-
renderOnly,
|
|
37
|
-
} from "../store.js";
|
|
38
|
-
import { transact, transactDoc, mutateMoveNode, mutateInsertNode } from "../tabs/transact.js";
|
|
39
|
-
import { activeTab } from "../workspace/workspace.js";
|
|
40
|
-
import { view } from "../view.js";
|
|
41
|
-
import { componentRegistry, computeRelativePath } from "../files/components.js";
|
|
42
|
-
import { renderComponentPreview } from "./stylebook-panel.js";
|
|
43
|
-
import { defaultDef, unsafeTags } from "./shared.js";
|
|
44
|
-
|
|
45
|
-
/** Register DnD on layer rows — called from left-panel.js after render */
|
|
46
|
-
export function registerLayersDnD() {
|
|
47
|
-
requestAnimationFrame(() => {
|
|
48
|
-
const container = /** @type {HTMLElement | null} */ (
|
|
49
|
-
leftPanel?.querySelector(".layers-container")
|
|
50
|
-
);
|
|
51
|
-
if (!container) return;
|
|
52
|
-
|
|
53
|
-
/** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll("[data-dnd-row]")).forEach(
|
|
54
|
-
(row) => {
|
|
55
|
-
const rowPath = /** @type {JxPath} */ (
|
|
56
|
-
/** @type {string} */ (row.dataset.path)
|
|
57
|
-
.split("/")
|
|
58
|
-
.map((/** @type {string} */ s) => (/^\d+$/.test(s) ? parseInt(s) : s))
|
|
59
|
-
);
|
|
60
|
-
const rowDepth = parseInt(/** @type {string} */ (row.dataset.dndDepth)) || 0;
|
|
61
|
-
const isVoid = row.hasAttribute("data-dnd-void");
|
|
62
|
-
const isExpanded = row.hasAttribute("data-dnd-expanded");
|
|
63
|
-
|
|
64
|
-
const cleanup = combine(
|
|
65
|
-
draggable({
|
|
66
|
-
element: row,
|
|
67
|
-
canDrag(/** @type {DragCanDragArgs} */ { element: _el, input }) {
|
|
68
|
-
const target = /** @type {HTMLElement} */ (
|
|
69
|
-
document.elementFromPoint(input.clientX, input.clientY)
|
|
70
|
-
);
|
|
71
|
-
if (target?.closest(".layer-actions")) return false;
|
|
72
|
-
return true;
|
|
73
|
-
},
|
|
74
|
-
getInitialData() {
|
|
75
|
-
return { type: "tree-node", path: rowPath };
|
|
76
|
-
},
|
|
77
|
-
onDragStart() {
|
|
78
|
-
row.classList.add("dragging");
|
|
79
|
-
view.layerDragSourceHeight = row.offsetHeight;
|
|
80
|
-
if (isExpanded) {
|
|
81
|
-
hideDescendantRows(row, container);
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
onDrop() {
|
|
85
|
-
row.classList.remove("dragging");
|
|
86
|
-
if (isExpanded) {
|
|
87
|
-
renderOnly("leftPanel");
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
}),
|
|
91
|
-
dropTargetForElements({
|
|
92
|
-
element: row,
|
|
93
|
-
canDrop(/** @type {DragDropSourceArgs} */ { source }) {
|
|
94
|
-
const srcPath = /** @type {JxPath | undefined} */ (source.data.path);
|
|
95
|
-
if (srcPath && isAncestor(srcPath, rowPath)) return false;
|
|
96
|
-
return true;
|
|
97
|
-
},
|
|
98
|
-
getData(/** @type {any} */ { input, element }) {
|
|
99
|
-
return /** @type {Record<string | symbol, unknown>} */ (
|
|
100
|
-
attachInstruction(
|
|
101
|
-
{ path: rowPath },
|
|
102
|
-
{
|
|
103
|
-
input,
|
|
104
|
-
element,
|
|
105
|
-
currentLevel: rowDepth,
|
|
106
|
-
indentPerLevel: 16,
|
|
107
|
-
mode: isExpanded ? "expanded" : "standard",
|
|
108
|
-
block: isVoid ? ["make-child"] : [],
|
|
109
|
-
},
|
|
110
|
-
)
|
|
111
|
-
);
|
|
112
|
-
},
|
|
113
|
-
onDragEnter(/** @type {DragSelfArgs} */ { self }) {
|
|
114
|
-
showLayerDropGap(row, self.data, container);
|
|
115
|
-
},
|
|
116
|
-
onDrag(/** @type {DragSelfArgs} */ { self }) {
|
|
117
|
-
showLayerDropGap(row, self.data, container);
|
|
118
|
-
},
|
|
119
|
-
onDragLeave() {
|
|
120
|
-
clearLayerDropGap(container);
|
|
121
|
-
},
|
|
122
|
-
onDrop() {
|
|
123
|
-
clearLayerDropGap(container);
|
|
124
|
-
},
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
127
|
-
view.dndCleanups.push(cleanup);
|
|
128
|
-
},
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// Global monitor
|
|
132
|
-
const monitorCleanup = monitorForElements({
|
|
133
|
-
onDrop(/** @type {DragMonitorDropArgs} */ { source, location }) {
|
|
134
|
-
clearLayerDropGap(container);
|
|
135
|
-
const target = location.current.dropTargets[0];
|
|
136
|
-
if (!target) return;
|
|
137
|
-
const instruction = extractInstruction(target.data);
|
|
138
|
-
if (!instruction || instruction.type === "instruction-blocked") return;
|
|
139
|
-
const srcData = source.data;
|
|
140
|
-
const targetPath = /** @type {JxPath} */ (target.data.path);
|
|
141
|
-
|
|
142
|
-
// If the source had children, persist collapse at the new location
|
|
143
|
-
const srcRow = srcData.type === "tree-node" && source.element;
|
|
144
|
-
const wasExpanded = srcRow && srcRow.hasAttribute("data-dnd-expanded");
|
|
145
|
-
|
|
146
|
-
applyDropInstruction(instruction, srcData, targetPath);
|
|
147
|
-
|
|
148
|
-
if (wasExpanded) {
|
|
149
|
-
const tab = activeTab.value;
|
|
150
|
-
const newPath = tab?.session.selection;
|
|
151
|
-
if (newPath) {
|
|
152
|
-
const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
|
|
153
|
-
collapsed.add(newPath.join("/"));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
view.dndCleanups.push(monitorCleanup);
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** Register DnD on component rows — called from renderLeftPanel when tab=components */
|
|
163
|
-
export function registerComponentsDnD() {
|
|
164
|
-
requestAnimationFrame(() => {
|
|
165
|
-
const container = /** @type {HTMLElement | null} */ (
|
|
166
|
-
leftPanel?.querySelector(".components-section")
|
|
167
|
-
);
|
|
168
|
-
if (!container) return;
|
|
169
|
-
|
|
170
|
-
/** @type {NodeListOf<HTMLElement>} */ (
|
|
171
|
-
container.querySelectorAll("[data-component-tag]")
|
|
172
|
-
).forEach((row) => {
|
|
173
|
-
const tagName = row.dataset.componentTag;
|
|
174
|
-
if (!tagName) return;
|
|
175
|
-
const comp = componentRegistry.find(
|
|
176
|
-
(/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tagName,
|
|
177
|
-
);
|
|
178
|
-
if (!comp) return;
|
|
179
|
-
|
|
180
|
-
// Fill preview with live rendered component
|
|
181
|
-
const preview = row.querySelector(".element-card-preview");
|
|
182
|
-
if (preview && !preview.querySelector(tagName)) {
|
|
183
|
-
renderComponentPreview(comp).then((/** @type {HTMLElement} */ el) => {
|
|
184
|
-
preview.textContent = "";
|
|
185
|
-
preview.appendChild(el);
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const instanceDef = {
|
|
190
|
-
tagName: comp.tagName,
|
|
191
|
-
$props: comp.props
|
|
192
|
-
? Object.fromEntries(
|
|
193
|
-
comp.props.map(
|
|
194
|
-
(/** @type {{ name: string; default?: unknown; [k: string]: unknown }} */ p) => [
|
|
195
|
-
p.name,
|
|
196
|
-
p.default !== undefined ? p.default : "",
|
|
197
|
-
],
|
|
198
|
-
),
|
|
199
|
-
)
|
|
200
|
-
: {},
|
|
201
|
-
};
|
|
202
|
-
const cleanup = draggable({
|
|
203
|
-
element: row,
|
|
204
|
-
getInitialData() {
|
|
205
|
-
return { type: "block", fragment: structuredClone(instanceDef) };
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
view.dndCleanups.push(cleanup);
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/** Register DnD on element (HTML block) rows */
|
|
214
|
-
export function registerElementsDnD() {
|
|
215
|
-
requestAnimationFrame(() => {
|
|
216
|
-
const container = /** @type {HTMLElement | null} */ (leftPanel?.querySelector(".panel-body"));
|
|
217
|
-
if (!container) return;
|
|
218
|
-
/** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll("[data-block-tag]")).forEach(
|
|
219
|
-
(row) => {
|
|
220
|
-
const tag = /** @type {string} */ (row.dataset.blockTag);
|
|
221
|
-
const preview = row.querySelector(".element-card-preview");
|
|
222
|
-
if (preview && !preview.firstChild) {
|
|
223
|
-
const el = document.createElement(unsafeTags.has(tag) ? "span" : tag);
|
|
224
|
-
el.textContent = tag;
|
|
225
|
-
preview.appendChild(el);
|
|
226
|
-
}
|
|
227
|
-
const def = defaultDef(tag);
|
|
228
|
-
const cleanup = draggable({
|
|
229
|
-
element: row,
|
|
230
|
-
getInitialData() {
|
|
231
|
-
return { type: "block", fragment: structuredClone(def) };
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
view.dndCleanups.push(cleanup);
|
|
235
|
-
},
|
|
236
|
-
);
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Hide descendant rows of the dragged item so it appears collapsed during drag.
|
|
242
|
-
*
|
|
243
|
-
* @param {HTMLElement} parentRow
|
|
244
|
-
* @param {HTMLElement} container
|
|
245
|
-
*/
|
|
246
|
-
function hideDescendantRows(parentRow, container) {
|
|
247
|
-
const prefix = parentRow.dataset.path + "/";
|
|
248
|
-
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
249
|
-
for (const r of rows) {
|
|
250
|
-
if (/** @type {HTMLElement} */ (r).dataset.path?.startsWith(prefix)) {
|
|
251
|
-
/** @type {HTMLElement} */ (r).style.display = "none";
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* @param {HTMLElement} rowEl
|
|
258
|
-
* @param {Record<string, unknown>} data
|
|
259
|
-
* @param {HTMLElement} container
|
|
260
|
-
*/
|
|
261
|
-
export function showLayerDropGap(rowEl, data, container) {
|
|
262
|
-
const instruction = extractInstruction(data);
|
|
263
|
-
|
|
264
|
-
// Clear previous drop-target highlight
|
|
265
|
-
if (view._currentDropTargetRow && view._currentDropTargetRow !== rowEl) {
|
|
266
|
-
view._currentDropTargetRow.classList.remove("drop-target");
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (!instruction || instruction.type === "instruction-blocked") {
|
|
270
|
-
clearLayerDropGap(container);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (instruction.type === "make-child") {
|
|
275
|
-
clearLayerDropGap(container);
|
|
276
|
-
rowEl.classList.add("drop-target");
|
|
277
|
-
view._currentDropTargetRow = rowEl;
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
rowEl.classList.remove("drop-target");
|
|
282
|
-
view._currentDropTargetRow = rowEl;
|
|
283
|
-
|
|
284
|
-
// Shift rows to create gap
|
|
285
|
-
const rows = Array.from(container.querySelectorAll(".layers-tree .layer-row"));
|
|
286
|
-
const targetIdx = rows.indexOf(rowEl);
|
|
287
|
-
const gap = view.layerDragSourceHeight;
|
|
288
|
-
|
|
289
|
-
for (let i = 0; i < rows.length; i++) {
|
|
290
|
-
if (/** @type {HTMLElement} */ (rows[i]).classList.contains("dragging")) continue;
|
|
291
|
-
if (instruction.type === "reorder-above") {
|
|
292
|
-
/** @type {HTMLElement} */ (rows[i]).style.transform =
|
|
293
|
-
i >= targetIdx ? `translateY(${gap}px)` : "";
|
|
294
|
-
} else {
|
|
295
|
-
/** @type {HTMLElement} */ (rows[i]).style.transform =
|
|
296
|
-
i > targetIdx ? `translateY(${gap}px)` : "";
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/** @param {HTMLElement} container */
|
|
302
|
-
export function clearLayerDropGap(container) {
|
|
303
|
-
if (view._currentDropTargetRow) {
|
|
304
|
-
view._currentDropTargetRow.classList.remove("drop-target");
|
|
305
|
-
view._currentDropTargetRow = null;
|
|
306
|
-
}
|
|
307
|
-
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
308
|
-
for (const r of rows) /** @type {HTMLElement} */ (r).style.transform = "";
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Apply a DnD instruction to the state
|
|
313
|
-
*
|
|
314
|
-
* @param {{ type: string }} instruction
|
|
315
|
-
* @param {Record<string, unknown>} srcData
|
|
316
|
-
* @param {JxPath} targetPath
|
|
317
|
-
*/
|
|
318
|
-
export function applyDropInstruction(instruction, srcData, targetPath) {
|
|
319
|
-
const doc = /** @type {JxMutableNode} */ (activeTab.value?.doc.document);
|
|
320
|
-
if (srcData.type === "tree-node") {
|
|
321
|
-
const fromPath = /** @type {JxPath} */ (srcData.path);
|
|
322
|
-
const targetParent = /** @type {JxPath} */ (parentElementPath(targetPath));
|
|
323
|
-
const targetIdx = /** @type {number} */ (childIndex(targetPath));
|
|
324
|
-
|
|
325
|
-
switch (instruction.type) {
|
|
326
|
-
case "reorder-above":
|
|
327
|
-
transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetParent, targetIdx));
|
|
328
|
-
break;
|
|
329
|
-
case "reorder-below":
|
|
330
|
-
transactDoc(activeTab.value, (t) =>
|
|
331
|
-
mutateMoveNode(t, fromPath, targetParent, targetIdx + 1),
|
|
332
|
-
);
|
|
333
|
-
break;
|
|
334
|
-
case "make-child": {
|
|
335
|
-
const target = getNodeAtPath(doc, targetPath);
|
|
336
|
-
const len = target?.children?.length || 0;
|
|
337
|
-
transactDoc(activeTab.value, (t) => mutateMoveNode(t, fromPath, targetPath, len));
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
} else if (srcData.type === "block") {
|
|
342
|
-
const targetParent = /** @type {JxPath} */ (parentElementPath(targetPath));
|
|
343
|
-
const targetIdx = /** @type {number} */ (childIndex(targetPath));
|
|
344
|
-
|
|
345
|
-
switch (instruction.type) {
|
|
346
|
-
case "reorder-above":
|
|
347
|
-
transactDoc(activeTab.value, (t) =>
|
|
348
|
-
mutateInsertNode(
|
|
349
|
-
t,
|
|
350
|
-
targetParent,
|
|
351
|
-
targetIdx,
|
|
352
|
-
structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
|
|
353
|
-
),
|
|
354
|
-
);
|
|
355
|
-
break;
|
|
356
|
-
case "reorder-below":
|
|
357
|
-
transactDoc(activeTab.value, (t) =>
|
|
358
|
-
mutateInsertNode(
|
|
359
|
-
t,
|
|
360
|
-
targetParent,
|
|
361
|
-
targetIdx + 1,
|
|
362
|
-
structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
|
|
363
|
-
),
|
|
364
|
-
);
|
|
365
|
-
break;
|
|
366
|
-
case "make-child": {
|
|
367
|
-
const target = getNodeAtPath(doc, targetPath);
|
|
368
|
-
const len = target?.children?.length || 0;
|
|
369
|
-
transactDoc(activeTab.value, (t) =>
|
|
370
|
-
mutateInsertNode(
|
|
371
|
-
t,
|
|
372
|
-
targetPath,
|
|
373
|
-
len,
|
|
374
|
-
structuredClone(/** @type {JxMutableNode} */ (srcData.fragment)),
|
|
375
|
-
),
|
|
376
|
-
);
|
|
377
|
-
break;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Auto-import to $elements if the dropped block is a custom component
|
|
382
|
-
const fragment = /** @type {JxMutableNode | undefined} */ (srcData.fragment);
|
|
383
|
-
const tag = fragment?.tagName;
|
|
384
|
-
if (tag && tag.includes("-")) {
|
|
385
|
-
const comp = componentRegistry.find(
|
|
386
|
-
(/** @type {import("../files/components.js").ComponentEntry} */ c) => c.tagName === tag,
|
|
387
|
-
);
|
|
388
|
-
if (comp) {
|
|
389
|
-
const tab = activeTab.value;
|
|
390
|
-
const elements = tab?.doc.document?.$elements || [];
|
|
391
|
-
if (comp.source === "npm") {
|
|
392
|
-
const specifier = comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
|
|
393
|
-
if (!specifier) return;
|
|
394
|
-
const alreadyImported = elements.some(
|
|
395
|
-
(/** @type {JxMutableNode | string | { $ref: string }} */ e) =>
|
|
396
|
-
e === specifier || e === comp.package,
|
|
397
|
-
);
|
|
398
|
-
if (!alreadyImported) {
|
|
399
|
-
transact(activeTab.value, (/** @type {JxMutableNode} */ doc) => {
|
|
400
|
-
if (!doc.$elements) doc.$elements = [];
|
|
401
|
-
doc.$elements.push(specifier);
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
} else {
|
|
405
|
-
const alreadyImported = elements.some(
|
|
406
|
-
(/** @type {JxMutableNode | string | { $ref: string }} */ e) => {
|
|
407
|
-
const ref = typeof e === "object" && e !== null ? e.$ref : undefined;
|
|
408
|
-
return (
|
|
409
|
-
ref &&
|
|
410
|
-
(ref === `./${comp.path}` ||
|
|
411
|
-
ref.endsWith(/** @type {string} */ (comp.path.split("/").pop())))
|
|
412
|
-
);
|
|
413
|
-
},
|
|
414
|
-
);
|
|
415
|
-
if (!alreadyImported) {
|
|
416
|
-
const relPath = computeRelativePath(tab?.documentPath, comp.path);
|
|
417
|
-
transact(activeTab.value, (/** @type {JxMutableNode} */ doc) => {
|
|
418
|
-
if (!doc.$elements) doc.$elements = [];
|
|
419
|
-
doc.$elements.push({ $ref: relPath });
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|