@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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/// <reference lib="dom.iterable" />
|
|
1
3
|
/**
|
|
2
4
|
* Canvas DnD — extracted from studio.js (Phase 4m). Registers canvas elements as drag-and-drop
|
|
3
5
|
* targets using @atlaskit/pragmatic-drag-and-drop.
|
|
@@ -8,41 +10,35 @@ import {
|
|
|
8
10
|
monitorForElements,
|
|
9
11
|
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
10
12
|
|
|
11
|
-
import { elToPath, canvasPanels, getNodeAtPath, VOID_ELEMENTS, isAncestor } from "../store
|
|
12
|
-
import { activeTab } from "../workspace/workspace
|
|
13
|
-
import { view } from "../view
|
|
14
|
-
import { applyDropInstruction } from "../panels/dnd
|
|
15
|
-
import { effectiveZoom } from "../canvas/canvas-helpers
|
|
13
|
+
import { elToPath, canvasPanels, getNodeAtPath, VOID_ELEMENTS, isAncestor } from "../store";
|
|
14
|
+
import { activeTab } from "../workspace/workspace";
|
|
15
|
+
import { view } from "../view";
|
|
16
|
+
import { applyDropInstruction } from "../panels/dnd";
|
|
17
|
+
import { effectiveZoom } from "../canvas/canvas-helpers";
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
* @typedef {{ type: "reorder-above" | "reorder-below" | "make-child" }} DropInstruction
|
|
33
|
-
*
|
|
34
|
-
* @typedef {{ instruction: DropInstruction; referenceEl: HTMLElement; targetPath: JxPath }} DropResult
|
|
35
|
-
*/
|
|
19
|
+
import type { CanvasPanel } from "../types";
|
|
20
|
+
export type { CanvasPanel };
|
|
21
|
+
|
|
22
|
+
import type { JxPath } from "../state";
|
|
23
|
+
|
|
24
|
+
interface DropInstruction {
|
|
25
|
+
type: "reorder-above" | "reorder-below" | "make-child";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface DropResult {
|
|
29
|
+
instruction: DropInstruction;
|
|
30
|
+
referenceEl: HTMLElement;
|
|
31
|
+
targetPath: JxPath;
|
|
32
|
+
}
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
let _activeDropEl = null;
|
|
34
|
+
let _activeDropEl: HTMLElement | null = null;
|
|
39
35
|
|
|
40
36
|
/**
|
|
41
37
|
* Register all canvas elements in a panel as DnD drop targets.
|
|
42
38
|
*
|
|
43
39
|
* @param {CanvasPanel} panel
|
|
44
40
|
*/
|
|
45
|
-
export function registerPanelDnD(panel) {
|
|
41
|
+
export function registerPanelDnD(panel: CanvasPanel) {
|
|
46
42
|
const { canvas, dropLine } = panel;
|
|
47
43
|
const allEls = canvas.querySelectorAll("*");
|
|
48
44
|
|
|
@@ -50,7 +46,7 @@ export function registerPanelDnD(panel) {
|
|
|
50
46
|
onDragStart({ location }) {
|
|
51
47
|
view.lastDragInput = location.current.input;
|
|
52
48
|
for (const el of canvas.querySelectorAll("*")) {
|
|
53
|
-
|
|
49
|
+
(el as HTMLElement).style.pointerEvents = "auto";
|
|
54
50
|
}
|
|
55
51
|
for (const p of canvasPanels) p.overlayClk.style.pointerEvents = "none";
|
|
56
52
|
},
|
|
@@ -65,7 +61,7 @@ export function registerPanelDnD(panel) {
|
|
|
65
61
|
}
|
|
66
62
|
view.lastDragInput = null;
|
|
67
63
|
for (const el of canvas.querySelectorAll("*")) {
|
|
68
|
-
|
|
64
|
+
(el as HTMLElement).style.pointerEvents = "none";
|
|
69
65
|
}
|
|
70
66
|
for (const p of canvasPanels) p.overlayClk.style.pointerEvents = "";
|
|
71
67
|
},
|
|
@@ -77,17 +73,17 @@ export function registerPanelDnD(panel) {
|
|
|
77
73
|
const elPath = elToPath.get(el);
|
|
78
74
|
if (!elPath) continue;
|
|
79
75
|
|
|
80
|
-
const node = getNodeAtPath(document
|
|
76
|
+
const node = getNodeAtPath(document!, elPath);
|
|
81
77
|
const tag = (node?.tagName || "div").toLowerCase();
|
|
82
78
|
const hasElementChildren =
|
|
83
79
|
Array.isArray(node?.children) &&
|
|
84
|
-
node.children.some((
|
|
80
|
+
node.children.some((c: unknown) => c != null && typeof c === "object");
|
|
85
81
|
const isLeaf = VOID_ELEMENTS.has(tag) || !hasElementChildren;
|
|
86
82
|
|
|
87
83
|
const cleanup = dropTargetForElements({
|
|
88
84
|
element: /** @type {HTMLElement} */ (el),
|
|
89
85
|
canDrop({ source }) {
|
|
90
|
-
const srcPath =
|
|
86
|
+
const srcPath = source.data.path as JxPath | undefined;
|
|
91
87
|
if (srcPath && isAncestor(srcPath, elPath)) return false;
|
|
92
88
|
return true;
|
|
93
89
|
},
|
|
@@ -99,23 +95,19 @@ export function registerPanelDnD(panel) {
|
|
|
99
95
|
if (_activeDropEl && _activeDropEl !== el) {
|
|
100
96
|
_activeDropEl.classList.remove("canvas-drop-target");
|
|
101
97
|
}
|
|
102
|
-
_activeDropEl =
|
|
103
|
-
showCanvasDropIndicator(
|
|
98
|
+
_activeDropEl = el as HTMLElement;
|
|
99
|
+
showCanvasDropIndicator(el as HTMLElement, elPath, isLeaf, panel);
|
|
104
100
|
},
|
|
105
101
|
onDrag({ location }) {
|
|
106
102
|
view.lastDragInput = location.current.input;
|
|
107
|
-
showCanvasDropIndicator(
|
|
103
|
+
showCanvasDropIndicator(el as HTMLElement, elPath, isLeaf, panel);
|
|
108
104
|
},
|
|
109
105
|
onDragLeave() {},
|
|
110
106
|
onDrop({ source }) {
|
|
111
107
|
dropLine.style.display = "none";
|
|
112
|
-
|
|
108
|
+
(el as HTMLElement).classList.remove("canvas-drop-target");
|
|
113
109
|
_activeDropEl = null;
|
|
114
|
-
const { instruction, targetPath } = getCanvasDropResult(
|
|
115
|
-
/** @type {HTMLElement} */ (el),
|
|
116
|
-
elPath,
|
|
117
|
-
isLeaf,
|
|
118
|
-
);
|
|
110
|
+
const { instruction, targetPath } = getCanvasDropResult(el as HTMLElement, elPath, isLeaf);
|
|
119
111
|
applyDropInstruction(instruction, source.data, targetPath);
|
|
120
112
|
},
|
|
121
113
|
});
|
|
@@ -129,13 +121,13 @@ export function registerPanelDnD(panel) {
|
|
|
129
121
|
* @param {boolean} isLeaf
|
|
130
122
|
* @returns {DropResult}
|
|
131
123
|
*/
|
|
132
|
-
function getCanvasDropResult(el, elPath, isLeaf) {
|
|
124
|
+
function getCanvasDropResult(el: HTMLElement, elPath: JxPath, isLeaf: boolean): DropResult {
|
|
133
125
|
if (!view.lastDragInput)
|
|
134
126
|
return { instruction: { type: "make-child" }, referenceEl: el, targetPath: elPath };
|
|
135
127
|
const y = view.lastDragInput.clientY;
|
|
136
128
|
|
|
137
129
|
if (elPath.length === 0) {
|
|
138
|
-
const children =
|
|
130
|
+
const children = Array.from(el.children) as HTMLElement[];
|
|
139
131
|
if (children.length === 0)
|
|
140
132
|
return { instruction: { type: "make-child" }, referenceEl: el, targetPath: elPath };
|
|
141
133
|
return nearestChildEdge(children, y, elPath);
|
|
@@ -146,9 +138,7 @@ function getCanvasDropResult(el, elPath, isLeaf) {
|
|
|
146
138
|
|
|
147
139
|
if (isLeaf) {
|
|
148
140
|
const instruction =
|
|
149
|
-
relY < 0.5
|
|
150
|
-
? { type: /** @type {const} */ ("reorder-above") }
|
|
151
|
-
: { type: /** @type {const} */ ("reorder-below") };
|
|
141
|
+
relY < 0.5 ? { type: "reorder-above" as const } : { type: "reorder-below" as const };
|
|
152
142
|
return { instruction, referenceEl: el, targetPath: elPath };
|
|
153
143
|
}
|
|
154
144
|
|
|
@@ -168,9 +158,9 @@ function getCanvasDropResult(el, elPath, isLeaf) {
|
|
|
168
158
|
* @param {JxPath} parentPath
|
|
169
159
|
* @returns {DropResult}
|
|
170
160
|
*/
|
|
171
|
-
function nearestChildEdge(children, cursorY, parentPath) {
|
|
161
|
+
function nearestChildEdge(children: HTMLElement[], cursorY: number, parentPath: JxPath) {
|
|
172
162
|
let closestDist = Infinity;
|
|
173
|
-
let instruction =
|
|
163
|
+
let instruction = { type: "reorder-below" } as DropInstruction;
|
|
174
164
|
let closestIdx = children.length - 1;
|
|
175
165
|
|
|
176
166
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -200,7 +190,12 @@ function nearestChildEdge(children, cursorY, parentPath) {
|
|
|
200
190
|
* @param {boolean} isLeaf
|
|
201
191
|
* @param {CanvasPanel} panel
|
|
202
192
|
*/
|
|
203
|
-
function showCanvasDropIndicator(
|
|
193
|
+
function showCanvasDropIndicator(
|
|
194
|
+
el: HTMLElement,
|
|
195
|
+
elPath: JxPath,
|
|
196
|
+
isLeaf: boolean,
|
|
197
|
+
panel: CanvasPanel,
|
|
198
|
+
) {
|
|
204
199
|
const { instruction, referenceEl } = getCanvasDropResult(el, elPath, isLeaf);
|
|
205
200
|
const { dropLine, viewport } = panel;
|
|
206
201
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
// ─── Data Explorer ──────────────────────────────────────────────────────────
|
|
2
3
|
|
|
3
4
|
import { html, nothing } from "lit-html";
|
|
@@ -7,18 +8,14 @@ import { classMap } from "lit-html/directives/class-map.js";
|
|
|
7
8
|
const expandedDataKeys = new Set();
|
|
8
9
|
|
|
9
10
|
/** Unwrap a Vue ref (has .value and .__v_isRef) to get the underlying value. */
|
|
10
|
-
function unwrapSignal(
|
|
11
|
-
if (
|
|
12
|
-
value
|
|
13
|
-
typeof value === "object" &&
|
|
14
|
-
/** @type {Record<string, unknown>} */ (value).__v_isRef
|
|
15
|
-
)
|
|
16
|
-
return /** @type {Record<string, unknown>} */ (value).value;
|
|
11
|
+
function unwrapSignal(value: unknown) {
|
|
12
|
+
if (value && typeof value === "object" && (value as Record<string, unknown>).__v_isRef)
|
|
13
|
+
return (value as Record<string, unknown>).value;
|
|
17
14
|
return value;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
/** Type label for a signal value in the data explorer. */
|
|
21
|
-
function dataTypeLabel(
|
|
18
|
+
function dataTypeLabel(value: unknown) {
|
|
22
19
|
const v = unwrapSignal(value);
|
|
23
20
|
if (v === null) return "null";
|
|
24
21
|
if (v === undefined) return "pending";
|
|
@@ -40,7 +37,16 @@ function dataTypeLabel(/** @type {unknown} */ value) {
|
|
|
40
37
|
* }} callbacks
|
|
41
38
|
* @returns {import("lit-html").TemplateResult}
|
|
42
39
|
*/
|
|
43
|
-
export function renderDataExplorerTemplate(
|
|
40
|
+
export function renderDataExplorerTemplate(
|
|
41
|
+
state: Record<string, unknown>,
|
|
42
|
+
liveScope: Record<string, unknown> | null,
|
|
43
|
+
callbacks: {
|
|
44
|
+
renderCanvas: () => void;
|
|
45
|
+
renderLeftPanel: () => void;
|
|
46
|
+
defCategory: (def: unknown) => string;
|
|
47
|
+
defBadgeLabel: (def: unknown) => string;
|
|
48
|
+
},
|
|
49
|
+
) {
|
|
44
50
|
const { renderCanvas, renderLeftPanel, defCategory, defBadgeLabel } = callbacks;
|
|
45
51
|
|
|
46
52
|
const defs = state || {};
|
|
@@ -99,10 +105,10 @@ export function renderDataExplorerTemplate(state, liveScope, callbacks) {
|
|
|
99
105
|
* @returns {import("lit-html").TemplateResult}
|
|
100
106
|
*/
|
|
101
107
|
export function renderDataTreeTemplate(
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
value: unknown,
|
|
109
|
+
depth: number,
|
|
104
110
|
maxDepth = 5,
|
|
105
|
-
) {
|
|
111
|
+
): import("lit-html").TemplateResult {
|
|
106
112
|
const indent = `${(depth + 1) * 12}px`;
|
|
107
113
|
|
|
108
114
|
if (depth > maxDepth) {
|
|
@@ -127,7 +133,7 @@ export function renderDataTreeTemplate(
|
|
|
127
133
|
|
|
128
134
|
if (Array.isArray(value)) {
|
|
129
135
|
const cap = 20;
|
|
130
|
-
const items = value.slice(0, cap).map((item, i) => {
|
|
136
|
+
const items: import("lit-html").TemplateResult[] = value.slice(0, cap).map((item, i) => {
|
|
131
137
|
if (item === null || item === undefined || typeof item !== "object") {
|
|
132
138
|
const valText =
|
|
133
139
|
typeof item === "string" && item.length > 80
|
|
@@ -155,10 +161,10 @@ export function renderDataTreeTemplate(
|
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
// Object
|
|
158
|
-
const obj =
|
|
164
|
+
const obj = value as Record<string, unknown>;
|
|
159
165
|
const keys = Object.keys(obj);
|
|
160
166
|
const cap = 30;
|
|
161
|
-
const items = keys.slice(0, cap).map((key) => {
|
|
167
|
+
const items: import("lit-html").TemplateResult[] = keys.slice(0, cap).map((key) => {
|
|
162
168
|
const v = obj[key];
|
|
163
169
|
if (v === null || v === undefined || typeof v !== "object") {
|
|
164
170
|
const valText =
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* DnD registration functions — extracted from studio.js (Phase 4). Registers drag-and-drop behavior
|
|
4
|
+
* on layer rows, component cards, and element cards.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
draggable,
|
|
9
|
+
dropTargetForElements,
|
|
10
|
+
monitorForElements,
|
|
11
|
+
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
12
|
+
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
13
|
+
import {
|
|
14
|
+
attachInstruction,
|
|
15
|
+
extractInstruction,
|
|
16
|
+
} from "@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
leftPanel,
|
|
20
|
+
getNodeAtPath,
|
|
21
|
+
parentElementPath,
|
|
22
|
+
childIndex,
|
|
23
|
+
isAncestor,
|
|
24
|
+
renderOnly,
|
|
25
|
+
} from "../store";
|
|
26
|
+
import { transact, transactDoc, mutateMoveNode, mutateInsertNode } from "../tabs/transact";
|
|
27
|
+
import { activeTab } from "../workspace/workspace";
|
|
28
|
+
import { view } from "../view";
|
|
29
|
+
import { componentRegistry, computeRelativePath } from "../files/components";
|
|
30
|
+
import { renderComponentPreview } from "./stylebook-panel";
|
|
31
|
+
import { defaultDef, unsafeTags } from "./shared";
|
|
32
|
+
import type { JxPath } from "../state";
|
|
33
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
34
|
+
|
|
35
|
+
interface DragCanDragArgs {
|
|
36
|
+
element: HTMLElement;
|
|
37
|
+
input: { clientX: number; clientY: number };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface DragDropSourceArgs {
|
|
41
|
+
source: { data: Record<string, unknown>; element: HTMLElement };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface DragSelfArgs {
|
|
45
|
+
self: { data: Record<string, unknown> };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface DragMonitorDropArgs {
|
|
49
|
+
source: { data: Record<string, unknown>; element: HTMLElement };
|
|
50
|
+
location: { current: { dropTargets: { data: Record<string, unknown> }[] } };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Register DnD on layer rows — called from left-panel.js after render */
|
|
54
|
+
export function registerLayersDnD() {
|
|
55
|
+
requestAnimationFrame(() => {
|
|
56
|
+
const container = leftPanel?.querySelector(".layers-container") as HTMLElement | null;
|
|
57
|
+
if (!container) return;
|
|
58
|
+
|
|
59
|
+
(container.querySelectorAll("[data-dnd-row]") as NodeListOf<HTMLElement>).forEach((row) => {
|
|
60
|
+
const rowPath = (row.dataset.path as string)
|
|
61
|
+
.split("/")
|
|
62
|
+
.map((s: string) => (/^\d+$/.test(s) ? parseInt(s) : s)) as JxPath;
|
|
63
|
+
const rowDepth = parseInt(row.dataset.dndDepth as string) || 0;
|
|
64
|
+
const isVoid = row.hasAttribute("data-dnd-void");
|
|
65
|
+
const isExpanded = row.hasAttribute("data-dnd-expanded");
|
|
66
|
+
|
|
67
|
+
const cleanup = combine(
|
|
68
|
+
draggable({
|
|
69
|
+
element: row,
|
|
70
|
+
canDrag({ element: _el, input }: DragCanDragArgs) {
|
|
71
|
+
const target = document.elementFromPoint(input.clientX, input.clientY) as HTMLElement;
|
|
72
|
+
if (target?.closest(".layer-actions")) return false;
|
|
73
|
+
return true;
|
|
74
|
+
},
|
|
75
|
+
getInitialData() {
|
|
76
|
+
return { type: "tree-node", path: rowPath };
|
|
77
|
+
},
|
|
78
|
+
onDragStart() {
|
|
79
|
+
row.classList.add("dragging");
|
|
80
|
+
view.layerDragSourceHeight = row.offsetHeight;
|
|
81
|
+
if (isExpanded) {
|
|
82
|
+
hideDescendantRows(row, container);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
onDrop() {
|
|
86
|
+
row.classList.remove("dragging");
|
|
87
|
+
if (isExpanded) {
|
|
88
|
+
renderOnly("leftPanel");
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
dropTargetForElements({
|
|
93
|
+
element: row,
|
|
94
|
+
canDrop({ source }: DragDropSourceArgs) {
|
|
95
|
+
const srcPath = source.data.path as JxPath | undefined;
|
|
96
|
+
if (srcPath && isAncestor(srcPath, rowPath)) return false;
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
getData({ input, element }: any) {
|
|
100
|
+
return 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
|
+
) as Record<string | symbol, unknown>;
|
|
111
|
+
},
|
|
112
|
+
onDragEnter({ self }: DragSelfArgs) {
|
|
113
|
+
showLayerDropGap(row, self.data, container);
|
|
114
|
+
},
|
|
115
|
+
onDrag({ self }: DragSelfArgs) {
|
|
116
|
+
showLayerDropGap(row, self.data, container);
|
|
117
|
+
},
|
|
118
|
+
onDragLeave() {
|
|
119
|
+
clearLayerDropGap(container);
|
|
120
|
+
},
|
|
121
|
+
onDrop() {
|
|
122
|
+
clearLayerDropGap(container);
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
view.dndCleanups.push(cleanup);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Global monitor
|
|
130
|
+
const monitorCleanup = monitorForElements({
|
|
131
|
+
onDrop({ source, location }: DragMonitorDropArgs) {
|
|
132
|
+
clearLayerDropGap(container);
|
|
133
|
+
const target = location.current.dropTargets[0];
|
|
134
|
+
if (!target) return;
|
|
135
|
+
const instruction = extractInstruction(target.data);
|
|
136
|
+
if (!instruction || instruction.type === "instruction-blocked") return;
|
|
137
|
+
const srcData = source.data;
|
|
138
|
+
const targetPath = target.data.path as JxPath;
|
|
139
|
+
|
|
140
|
+
// If the source had children, persist collapse at the new location
|
|
141
|
+
const srcRow = srcData.type === "tree-node" && source.element;
|
|
142
|
+
const wasExpanded = srcRow && srcRow.hasAttribute("data-dnd-expanded");
|
|
143
|
+
|
|
144
|
+
applyDropInstruction(instruction, srcData, targetPath);
|
|
145
|
+
|
|
146
|
+
if (wasExpanded) {
|
|
147
|
+
const tab = activeTab.value;
|
|
148
|
+
const newPath = tab?.session.selection;
|
|
149
|
+
if (newPath) {
|
|
150
|
+
const collapsed = view._layersCollapsed || (view._layersCollapsed = new Set());
|
|
151
|
+
collapsed.add(newPath.join("/"));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
view.dndCleanups.push(monitorCleanup);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Register DnD on component rows — called from renderLeftPanel when tab=components */
|
|
161
|
+
export function registerComponentsDnD() {
|
|
162
|
+
requestAnimationFrame(() => {
|
|
163
|
+
const container = leftPanel?.querySelector(".components-section") as HTMLElement | null;
|
|
164
|
+
if (!container) return;
|
|
165
|
+
|
|
166
|
+
(container.querySelectorAll("[data-component-tag]") as NodeListOf<HTMLElement>).forEach(
|
|
167
|
+
(row) => {
|
|
168
|
+
const tagName = row.dataset.componentTag;
|
|
169
|
+
if (!tagName) return;
|
|
170
|
+
const comp = componentRegistry.find(
|
|
171
|
+
(c: import("../files/components.js").ComponentEntry) => c.tagName === tagName,
|
|
172
|
+
);
|
|
173
|
+
if (!comp) return;
|
|
174
|
+
|
|
175
|
+
// Fill preview with live rendered component
|
|
176
|
+
const preview = row.querySelector(".element-card-preview");
|
|
177
|
+
if (preview && !preview.querySelector(tagName)) {
|
|
178
|
+
renderComponentPreview(comp).then((el: HTMLElement) => {
|
|
179
|
+
preview.textContent = "";
|
|
180
|
+
preview.appendChild(el);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const instanceDef = {
|
|
185
|
+
tagName: comp.tagName,
|
|
186
|
+
$props: comp.props
|
|
187
|
+
? Object.fromEntries(
|
|
188
|
+
comp.props.map(
|
|
189
|
+
(/** @type {{ name: string; default?: unknown; [k: string]: unknown }} */ p) => [
|
|
190
|
+
p.name,
|
|
191
|
+
p.default !== undefined ? p.default : "",
|
|
192
|
+
],
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
: {},
|
|
196
|
+
};
|
|
197
|
+
const cleanup = draggable({
|
|
198
|
+
element: row,
|
|
199
|
+
getInitialData() {
|
|
200
|
+
return { type: "block", fragment: structuredClone(instanceDef) };
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
view.dndCleanups.push(cleanup);
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Register DnD on element (HTML block) rows */
|
|
210
|
+
export function registerElementsDnD() {
|
|
211
|
+
requestAnimationFrame(() => {
|
|
212
|
+
const container = leftPanel?.querySelector(".panel-body") as HTMLElement | null;
|
|
213
|
+
if (!container) return;
|
|
214
|
+
(container.querySelectorAll("[data-block-tag]") as NodeListOf<HTMLElement>).forEach((row) => {
|
|
215
|
+
const tag = row.dataset.blockTag as string;
|
|
216
|
+
const preview = row.querySelector(".element-card-preview");
|
|
217
|
+
if (preview && !preview.firstChild) {
|
|
218
|
+
const el = document.createElement(unsafeTags.has(tag) ? "span" : tag);
|
|
219
|
+
el.textContent = tag;
|
|
220
|
+
preview.appendChild(el);
|
|
221
|
+
}
|
|
222
|
+
const def = defaultDef(tag);
|
|
223
|
+
const cleanup = draggable({
|
|
224
|
+
element: row,
|
|
225
|
+
getInitialData() {
|
|
226
|
+
return { type: "block", fragment: structuredClone(def) };
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
view.dndCleanups.push(cleanup);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Hide descendant rows of the dragged item so it appears collapsed during drag.
|
|
236
|
+
*
|
|
237
|
+
* @param {HTMLElement} parentRow
|
|
238
|
+
* @param {HTMLElement} container
|
|
239
|
+
*/
|
|
240
|
+
function hideDescendantRows(parentRow: HTMLElement, container: HTMLElement) {
|
|
241
|
+
const prefix = parentRow.dataset.path + "/";
|
|
242
|
+
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
243
|
+
for (const r of rows) {
|
|
244
|
+
if ((r as HTMLElement).dataset.path?.startsWith(prefix)) {
|
|
245
|
+
(r as HTMLElement).style.display = "none";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @param {HTMLElement} rowEl
|
|
252
|
+
* @param {Record<string, unknown>} data
|
|
253
|
+
* @param {HTMLElement} container
|
|
254
|
+
*/
|
|
255
|
+
export function showLayerDropGap(
|
|
256
|
+
rowEl: HTMLElement,
|
|
257
|
+
data: Record<string, unknown>,
|
|
258
|
+
container: HTMLElement,
|
|
259
|
+
) {
|
|
260
|
+
const instruction = extractInstruction(data);
|
|
261
|
+
|
|
262
|
+
// Clear previous drop-target highlight
|
|
263
|
+
if (view._currentDropTargetRow && view._currentDropTargetRow !== rowEl) {
|
|
264
|
+
view._currentDropTargetRow.classList.remove("drop-target");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!instruction || instruction.type === "instruction-blocked") {
|
|
268
|
+
clearLayerDropGap(container);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (instruction.type === "make-child") {
|
|
273
|
+
clearLayerDropGap(container);
|
|
274
|
+
rowEl.classList.add("drop-target");
|
|
275
|
+
view._currentDropTargetRow = rowEl;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
rowEl.classList.remove("drop-target");
|
|
280
|
+
view._currentDropTargetRow = rowEl;
|
|
281
|
+
|
|
282
|
+
// Shift rows to create gap
|
|
283
|
+
const rows = Array.from(container.querySelectorAll(".layers-tree .layer-row"));
|
|
284
|
+
const targetIdx = rows.indexOf(rowEl);
|
|
285
|
+
const gap = view.layerDragSourceHeight;
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < rows.length; i++) {
|
|
288
|
+
if ((rows[i] as HTMLElement).classList.contains("dragging")) continue;
|
|
289
|
+
if (instruction.type === "reorder-above") {
|
|
290
|
+
(rows[i] as HTMLElement).style.transform = i >= targetIdx ? `translateY(${gap}px)` : "";
|
|
291
|
+
} else {
|
|
292
|
+
(rows[i] as HTMLElement).style.transform = i > targetIdx ? `translateY(${gap}px)` : "";
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** @param {HTMLElement} container */
|
|
298
|
+
export function clearLayerDropGap(container: HTMLElement) {
|
|
299
|
+
if (view._currentDropTargetRow) {
|
|
300
|
+
view._currentDropTargetRow.classList.remove("drop-target");
|
|
301
|
+
view._currentDropTargetRow = null;
|
|
302
|
+
}
|
|
303
|
+
const rows = container.querySelectorAll(".layers-tree .layer-row");
|
|
304
|
+
for (const r of rows) (r as HTMLElement).style.transform = "";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Apply a DnD instruction to the state
|
|
309
|
+
*
|
|
310
|
+
* @param {{ type: string }} instruction
|
|
311
|
+
* @param {Record<string, unknown>} srcData
|
|
312
|
+
* @param {JxPath} targetPath
|
|
313
|
+
*/
|
|
314
|
+
export function applyDropInstruction(
|
|
315
|
+
instruction: { type: string },
|
|
316
|
+
srcData: Record<string, unknown>,
|
|
317
|
+
targetPath: JxPath,
|
|
318
|
+
) {
|
|
319
|
+
const doc = activeTab.value?.doc.document as JxMutableNode;
|
|
320
|
+
if (srcData.type === "tree-node") {
|
|
321
|
+
const fromPath = srcData.path as JxPath;
|
|
322
|
+
const targetParent = parentElementPath(targetPath) as JxPath;
|
|
323
|
+
const targetIdx = childIndex(targetPath) as number;
|
|
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 = parentElementPath(targetPath) as JxPath;
|
|
343
|
+
const targetIdx = childIndex(targetPath) as number;
|
|
344
|
+
|
|
345
|
+
switch (instruction.type) {
|
|
346
|
+
case "reorder-above":
|
|
347
|
+
transactDoc(activeTab.value, (t) =>
|
|
348
|
+
mutateInsertNode(
|
|
349
|
+
t,
|
|
350
|
+
targetParent,
|
|
351
|
+
targetIdx,
|
|
352
|
+
structuredClone(srcData.fragment as JxMutableNode),
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
break;
|
|
356
|
+
case "reorder-below":
|
|
357
|
+
transactDoc(activeTab.value, (t) =>
|
|
358
|
+
mutateInsertNode(
|
|
359
|
+
t,
|
|
360
|
+
targetParent,
|
|
361
|
+
targetIdx + 1,
|
|
362
|
+
structuredClone(srcData.fragment as JxMutableNode),
|
|
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(t, targetPath, len, structuredClone(srcData.fragment as JxMutableNode)),
|
|
371
|
+
);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Auto-import to $elements if the dropped block is a custom component
|
|
377
|
+
const fragment = srcData.fragment as JxMutableNode | undefined;
|
|
378
|
+
const tag = fragment?.tagName;
|
|
379
|
+
if (tag && tag.includes("-")) {
|
|
380
|
+
const comp = componentRegistry.find(
|
|
381
|
+
(c: import("../files/components.js").ComponentEntry) => c.tagName === tag,
|
|
382
|
+
);
|
|
383
|
+
if (comp) {
|
|
384
|
+
const tab = activeTab.value;
|
|
385
|
+
const elements = tab?.doc.document?.$elements || [];
|
|
386
|
+
if (comp.source === "npm") {
|
|
387
|
+
const specifier = comp.modulePath ? `${comp.package}/${comp.modulePath}` : comp.package;
|
|
388
|
+
if (!specifier) return;
|
|
389
|
+
const alreadyImported = elements.some(
|
|
390
|
+
(e: JxMutableNode | string | { $ref: string }) => e === specifier || e === comp.package,
|
|
391
|
+
);
|
|
392
|
+
if (!alreadyImported) {
|
|
393
|
+
transact(activeTab.value, (doc: JxMutableNode) => {
|
|
394
|
+
if (!doc.$elements) doc.$elements = [];
|
|
395
|
+
doc.$elements.push(specifier);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
const alreadyImported = elements.some((e: JxMutableNode | string | { $ref: string }) => {
|
|
400
|
+
const ref = typeof e === "object" && e !== null ? e.$ref : undefined;
|
|
401
|
+
return (
|
|
402
|
+
ref &&
|
|
403
|
+
(ref === `./${comp.path}` || ref.endsWith(comp.path.split("/").pop() as string))
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
if (!alreadyImported) {
|
|
407
|
+
const relPath = computeRelativePath(tab?.documentPath ?? null, comp.path);
|
|
408
|
+
transact(activeTab.value, (doc: JxMutableNode) => {
|
|
409
|
+
if (!doc.$elements) doc.$elements = [];
|
|
410
|
+
doc.$elements.push({ $ref: relPath });
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|