@jxsuite/studio 0.10.1 → 0.11.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/README.md +82 -0
- package/dist/studio.js +1105 -235
- package/dist/studio.js.map +35 -25
- package/package.json +30 -29
- package/src/browse/browse.js +48 -22
- package/src/canvas/canvas-live-render.js +140 -16
- package/src/canvas/canvas-render.js +10 -7
- package/src/files/files.js +2 -0
- package/src/panels/canvas-dnd.js +132 -50
- package/src/panels/imports-panel.js +2 -2
- package/src/panels/overlays.js +15 -3
- package/src/panels/panel-events.js +10 -0
- package/src/panels/properties-panel.js +302 -8
- package/src/panels/stylebook-panel.js +58 -22
- package/src/settings/{collections-editor.js → content-types-editor.js} +70 -49
- package/src/settings/schema-field-ui.js +58 -5
- package/src/site-context.js +105 -0
- package/src/state.js +1 -1
- package/src/store.js +9 -1
- package/src/studio.js +10 -4
- package/src/ui/spectrum.js +2 -0
- package/src/utils/studio-utils.js +5 -5
- package/src/view.js +3 -0
package/src/panels/canvas-dnd.js
CHANGED
|
@@ -20,19 +20,39 @@ import { view } from "../view.js";
|
|
|
20
20
|
import { applyDropInstruction } from "../panels/dnd.js";
|
|
21
21
|
import { effectiveZoom } from "../canvas/canvas-helpers.js";
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {{
|
|
25
|
+
* canvas: HTMLElement;
|
|
26
|
+
* overlayClk: HTMLElement;
|
|
27
|
+
* overlay: HTMLElement;
|
|
28
|
+
* viewport: HTMLElement;
|
|
29
|
+
* dropLine: HTMLElement;
|
|
30
|
+
* }} CanvasPanel
|
|
31
|
+
*
|
|
32
|
+
* @typedef {(string | number)[]} JxPath
|
|
33
|
+
*
|
|
34
|
+
* @typedef {{ type: "reorder-above" | "reorder-below" | "make-child" }} DropInstruction
|
|
35
|
+
*
|
|
36
|
+
* @typedef {{ instruction: DropInstruction; referenceEl: HTMLElement; targetPath: JxPath }} DropResult
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** @type {HTMLElement | null} */
|
|
40
|
+
let _activeDropEl = null;
|
|
41
|
+
|
|
23
42
|
/**
|
|
24
43
|
* Register all canvas elements in a panel as DnD drop targets.
|
|
25
44
|
*
|
|
26
|
-
* @param {
|
|
45
|
+
* @param {CanvasPanel} panel
|
|
27
46
|
*/
|
|
28
47
|
export function registerPanelDnD(panel) {
|
|
29
48
|
const { canvas, dropLine } = panel;
|
|
30
49
|
const allEls = canvas.querySelectorAll("*");
|
|
31
50
|
|
|
32
51
|
const monitorCleanup = monitorForElements({
|
|
33
|
-
onDragStart() {
|
|
52
|
+
onDragStart({ location }) {
|
|
53
|
+
view.lastDragInput = location.current.input;
|
|
34
54
|
for (const el of canvas.querySelectorAll("*")) {
|
|
35
|
-
/** @type {
|
|
55
|
+
/** @type {HTMLElement} */ (el).style.pointerEvents = "auto";
|
|
36
56
|
}
|
|
37
57
|
for (const p of canvasPanels) p.overlayClk.style.pointerEvents = "none";
|
|
38
58
|
},
|
|
@@ -40,10 +60,12 @@ export function registerPanelDnD(panel) {
|
|
|
40
60
|
view.lastDragInput = location.current.input;
|
|
41
61
|
},
|
|
42
62
|
onDrop() {
|
|
63
|
+
_activeDropEl?.classList.remove("canvas-drop-target");
|
|
64
|
+
_activeDropEl = null;
|
|
43
65
|
for (const p of canvasPanels) p.dropLine.style.display = "none";
|
|
44
66
|
view.lastDragInput = null;
|
|
45
67
|
for (const el of canvas.querySelectorAll("*")) {
|
|
46
|
-
/** @type {
|
|
68
|
+
/** @type {HTMLElement} */ (el).style.pointerEvents = "none";
|
|
47
69
|
}
|
|
48
70
|
for (const p of canvasPanels) p.overlayClk.style.pointerEvents = "";
|
|
49
71
|
},
|
|
@@ -56,34 +78,45 @@ export function registerPanelDnD(panel) {
|
|
|
56
78
|
if (!elPath) continue;
|
|
57
79
|
|
|
58
80
|
const node = getNodeAtPath(S.document, elPath);
|
|
59
|
-
const
|
|
81
|
+
const tag = (node?.tagName || "div").toLowerCase();
|
|
82
|
+
const hasElementChildren = node?.children?.some(
|
|
83
|
+
(/** @type {unknown} */ c) => c != null && typeof c === "object",
|
|
84
|
+
);
|
|
85
|
+
const isLeaf = VOID_ELEMENTS.has(tag) || !hasElementChildren;
|
|
60
86
|
|
|
61
87
|
const cleanup = dropTargetForElements({
|
|
62
|
-
element: el,
|
|
88
|
+
element: /** @type {HTMLElement} */ (el),
|
|
63
89
|
canDrop({ source }) {
|
|
64
|
-
const srcPath = source.data.path;
|
|
65
|
-
if (srcPath && isAncestor(
|
|
90
|
+
const srcPath = /** @type {JxPath | undefined} */ (source.data.path);
|
|
91
|
+
if (srcPath && isAncestor(srcPath, elPath)) return false;
|
|
66
92
|
return true;
|
|
67
93
|
},
|
|
68
94
|
getData() {
|
|
69
|
-
return { path: elPath, _isVoid:
|
|
70
|
-
},
|
|
71
|
-
onDragEnter() {
|
|
72
|
-
showCanvasDropIndicator(el, elPath, isVoid, panel);
|
|
95
|
+
return { path: elPath, _isVoid: isLeaf };
|
|
73
96
|
},
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
onDragEnter({ location }) {
|
|
98
|
+
view.lastDragInput = location.current.input;
|
|
99
|
+
if (_activeDropEl && _activeDropEl !== el) {
|
|
100
|
+
_activeDropEl.classList.remove("canvas-drop-target");
|
|
101
|
+
}
|
|
102
|
+
_activeDropEl = /** @type {HTMLElement} */ (el);
|
|
103
|
+
showCanvasDropIndicator(/** @type {HTMLElement} */ (el), elPath, isLeaf, panel);
|
|
76
104
|
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
el
|
|
105
|
+
onDrag({ location }) {
|
|
106
|
+
view.lastDragInput = location.current.input;
|
|
107
|
+
showCanvasDropIndicator(/** @type {HTMLElement} */ (el), elPath, isLeaf, panel);
|
|
80
108
|
},
|
|
109
|
+
onDragLeave() {},
|
|
81
110
|
onDrop({ source }) {
|
|
82
111
|
dropLine.style.display = "none";
|
|
83
|
-
el.classList.remove("canvas-drop-target");
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
112
|
+
/** @type {HTMLElement} */ (el).classList.remove("canvas-drop-target");
|
|
113
|
+
_activeDropEl = null;
|
|
114
|
+
const { instruction, targetPath } = getCanvasDropResult(
|
|
115
|
+
/** @type {HTMLElement} */ (el),
|
|
116
|
+
elPath,
|
|
117
|
+
isLeaf,
|
|
118
|
+
);
|
|
119
|
+
applyDropInstruction(instruction, source.data, targetPath);
|
|
87
120
|
},
|
|
88
121
|
});
|
|
89
122
|
view.canvasDndCleanups.push(cleanup);
|
|
@@ -91,49 +124,98 @@ export function registerPanelDnD(panel) {
|
|
|
91
124
|
}
|
|
92
125
|
|
|
93
126
|
/**
|
|
94
|
-
* @param {
|
|
95
|
-
* @param {
|
|
96
|
-
* @param {
|
|
127
|
+
* @param {HTMLElement} el
|
|
128
|
+
* @param {JxPath} elPath
|
|
129
|
+
* @param {boolean} isLeaf
|
|
130
|
+
* @returns {DropResult}
|
|
97
131
|
*/
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
132
|
+
function getCanvasDropResult(el, elPath, isLeaf) {
|
|
133
|
+
if (!view.lastDragInput)
|
|
134
|
+
return { instruction: { type: "make-child" }, referenceEl: el, targetPath: elPath };
|
|
101
135
|
const y = view.lastDragInput.clientY;
|
|
136
|
+
|
|
137
|
+
if (elPath.length === 0) {
|
|
138
|
+
const children = /** @type {HTMLElement[]} */ (Array.from(el.children));
|
|
139
|
+
if (children.length === 0)
|
|
140
|
+
return { instruction: { type: "make-child" }, referenceEl: el, targetPath: elPath };
|
|
141
|
+
return nearestChildEdge(children, y, elPath);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const rect = el.getBoundingClientRect();
|
|
102
145
|
const relY = (y - rect.top) / rect.height;
|
|
103
146
|
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
147
|
+
if (isLeaf) {
|
|
148
|
+
const instruction =
|
|
149
|
+
relY < 0.5
|
|
150
|
+
? { type: /** @type {const} */ ("reorder-above") }
|
|
151
|
+
: { type: /** @type {const} */ ("reorder-below") };
|
|
152
|
+
return { instruction, referenceEl: el, targetPath: elPath };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (relY < 0.25)
|
|
156
|
+
return { instruction: { type: "reorder-above" }, referenceEl: el, targetPath: elPath };
|
|
157
|
+
if (relY > 0.75)
|
|
158
|
+
return { instruction: { type: "reorder-below" }, referenceEl: el, targetPath: elPath };
|
|
159
|
+
return { instruction: { type: "make-child" }, referenceEl: el, targetPath: elPath };
|
|
109
160
|
}
|
|
110
161
|
|
|
111
162
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* @param {
|
|
163
|
+
* Find the nearest child edge to the cursor and return the appropriate instruction along with the
|
|
164
|
+
* reference child element and its path.
|
|
165
|
+
*
|
|
166
|
+
* @param {HTMLElement[]} children
|
|
167
|
+
* @param {number} cursorY
|
|
168
|
+
* @param {JxPath} parentPath
|
|
169
|
+
* @returns {DropResult}
|
|
116
170
|
*/
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
function nearestChildEdge(children, cursorY, parentPath) {
|
|
172
|
+
let closestDist = Infinity;
|
|
173
|
+
let instruction = /** @type {DropInstruction} */ ({ type: "reorder-below" });
|
|
174
|
+
let closestIdx = children.length - 1;
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < children.length; i++) {
|
|
177
|
+
const rect = children[i].getBoundingClientRect();
|
|
178
|
+
const topDist = Math.abs(cursorY - rect.top);
|
|
179
|
+
const bottomDist = Math.abs(cursorY - rect.bottom);
|
|
180
|
+
|
|
181
|
+
if (topDist < closestDist) {
|
|
182
|
+
closestDist = topDist;
|
|
183
|
+
instruction = { type: "reorder-above" };
|
|
184
|
+
closestIdx = i;
|
|
185
|
+
}
|
|
186
|
+
if (bottomDist < closestDist) {
|
|
187
|
+
closestDist = bottomDist;
|
|
188
|
+
instruction = { type: "reorder-below" };
|
|
189
|
+
closestIdx = i;
|
|
190
|
+
}
|
|
123
191
|
}
|
|
124
192
|
|
|
193
|
+
const childPath = [...parentPath, "children", closestIdx];
|
|
194
|
+
return { instruction, referenceEl: children[closestIdx], targetPath: childPath };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {HTMLElement} el
|
|
199
|
+
* @param {JxPath} elPath
|
|
200
|
+
* @param {boolean} isLeaf
|
|
201
|
+
* @param {CanvasPanel} panel
|
|
202
|
+
*/
|
|
203
|
+
function showCanvasDropIndicator(el, elPath, isLeaf, panel) {
|
|
204
|
+
const { instruction, referenceEl } = getCanvasDropResult(el, elPath, isLeaf);
|
|
205
|
+
const { dropLine, viewport } = panel;
|
|
206
|
+
|
|
125
207
|
const scale = effectiveZoom();
|
|
126
208
|
const wrapRect = viewport.getBoundingClientRect();
|
|
127
|
-
const
|
|
128
|
-
const left = (
|
|
129
|
-
const width =
|
|
209
|
+
const refRect = referenceEl.getBoundingClientRect();
|
|
210
|
+
const left = (refRect.left - wrapRect.left + viewport.scrollLeft) / scale;
|
|
211
|
+
const width = refRect.width / scale;
|
|
130
212
|
|
|
131
213
|
if (instruction.type === "make-child") {
|
|
132
214
|
dropLine.style.display = "block";
|
|
133
|
-
dropLine.style.top = `${(
|
|
215
|
+
dropLine.style.top = `${(refRect.top - wrapRect.top + viewport.scrollTop) / scale}px`;
|
|
134
216
|
dropLine.style.left = `${left}px`;
|
|
135
217
|
dropLine.style.width = `${width}px`;
|
|
136
|
-
dropLine.style.height = `${
|
|
218
|
+
dropLine.style.height = `${refRect.height / scale}px`;
|
|
137
219
|
dropLine.className = "canvas-drop-indicator inside";
|
|
138
220
|
el.classList.add("canvas-drop-target");
|
|
139
221
|
return;
|
|
@@ -142,13 +224,13 @@ function showCanvasDropIndicator(el, elPath, isVoid, panel) {
|
|
|
142
224
|
el.classList.remove("canvas-drop-target");
|
|
143
225
|
const top =
|
|
144
226
|
instruction.type === "reorder-above"
|
|
145
|
-
? (
|
|
146
|
-
: (
|
|
227
|
+
? (refRect.top - wrapRect.top + viewport.scrollTop) / scale
|
|
228
|
+
: (refRect.bottom - wrapRect.top + viewport.scrollTop) / scale;
|
|
147
229
|
|
|
148
230
|
dropLine.style.display = "block";
|
|
149
231
|
dropLine.style.top = `${top}px`;
|
|
150
232
|
dropLine.style.left = `${left}px`;
|
|
151
233
|
dropLine.style.width = `${width}px`;
|
|
152
|
-
dropLine.style.height = "
|
|
234
|
+
dropLine.style.height = "";
|
|
153
235
|
dropLine.className = "canvas-drop-indicator line";
|
|
154
236
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* When editing project.json: shows Class Imports, Dependencies (add/remove packages), and
|
|
5
5
|
* per-package component toggles for cherry-picking individual elements. When editing a
|
|
6
|
-
* page/layout/component/
|
|
7
|
-
* toggles.
|
|
6
|
+
* page/layout/component/content type: shows Component Imports ($ref picker) and per-package
|
|
7
|
+
* component toggles.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { html, nothing } from "lit-html";
|
package/src/panels/overlays.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getActivePanel,
|
|
12
12
|
overlayBoxDescriptor,
|
|
13
13
|
} from "../canvas/canvas-helpers.js";
|
|
14
|
+
import { layoutElements } from "../canvas/canvas-live-render.js";
|
|
14
15
|
|
|
15
16
|
/** @type {any} */
|
|
16
17
|
let _ctx = null;
|
|
@@ -85,7 +86,11 @@ export function render() {
|
|
|
85
86
|
|
|
86
87
|
if (S.hover && !pathsEqual(S.hover, S.selection)) {
|
|
87
88
|
const el = findCanvasElement(S.hover, p.canvas);
|
|
88
|
-
if (el)
|
|
89
|
+
if (el) {
|
|
90
|
+
const desc = overlayBoxDescriptor(el, "hover", p);
|
|
91
|
+
if (layoutElements.has(el)) /** @type {any} */ (desc).isLayout = true;
|
|
92
|
+
boxes.push(desc);
|
|
93
|
+
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
if (S.selection && p === getActivePanel()) {
|
|
@@ -93,6 +98,7 @@ export function render() {
|
|
|
93
98
|
if (el) {
|
|
94
99
|
const desc = overlayBoxDescriptor(el, "selection", p);
|
|
95
100
|
if (view.componentInlineEdit || _ctx.isEditing()) /** @type {any} */ (desc).border = "none";
|
|
101
|
+
if (layoutElements.has(el)) /** @type {any} */ (desc).isLayout = true;
|
|
96
102
|
boxes.push(desc);
|
|
97
103
|
}
|
|
98
104
|
}
|
|
@@ -103,11 +109,17 @@ export function render() {
|
|
|
103
109
|
${boxes.map(
|
|
104
110
|
(b) => html`
|
|
105
111
|
<div
|
|
106
|
-
class
|
|
112
|
+
class="${b.cls}${/** @type {any} */ (b).isLayout ? " overlay-layout" : ""}"
|
|
107
113
|
style="top:${b.top};left:${b.left};width:${b.width};height:${b.height}${b.border
|
|
108
114
|
? `;border:${b.border}`
|
|
109
115
|
: ""}"
|
|
110
|
-
|
|
116
|
+
>
|
|
117
|
+
${
|
|
118
|
+
/** @type {any} */ (b).isLayout
|
|
119
|
+
? html`<span class="overlay-layout-badge">Layout</span>`
|
|
120
|
+
: nothing
|
|
121
|
+
}
|
|
122
|
+
</div>
|
|
111
123
|
`,
|
|
112
124
|
)}
|
|
113
125
|
`,
|
|
@@ -23,6 +23,7 @@ import { showContextMenu } from "../editor/context-menu.js";
|
|
|
23
23
|
import * as insertionHelper from "../editor/insertion-helper.js";
|
|
24
24
|
import { defaultDef } from "../panels/shared.js";
|
|
25
25
|
import { bubbleInlinePath, findCanvasElement, effectiveZoom } from "../canvas/canvas-helpers.js";
|
|
26
|
+
import { layoutElements, activeLayoutPath } from "../canvas/canvas-live-render.js";
|
|
26
27
|
|
|
27
28
|
/** @type {any} */
|
|
28
29
|
let _ctx = null;
|
|
@@ -87,6 +88,15 @@ export function registerPanelEvents(panel) {
|
|
|
87
88
|
|
|
88
89
|
for (const el of elements) {
|
|
89
90
|
if (canvas.contains(el) && el !== canvas) {
|
|
91
|
+
// Layout element clicked — show layout info instead of selecting in page doc
|
|
92
|
+
if (layoutElements.has(el)) {
|
|
93
|
+
view.layoutSelection = { el, layoutPath: activeLayoutPath };
|
|
94
|
+
update(selectNode(S, null));
|
|
95
|
+
renderOnly("rightPanel");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
view.layoutSelection = null;
|
|
99
|
+
|
|
90
100
|
const originalPath = elToPath.get(el);
|
|
91
101
|
if (originalPath) {
|
|
92
102
|
let path = bubbleInlinePath(S.document, originalPath);
|