@jxsuite/studio 0.8.0 → 0.9.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 +120246 -120127
- package/dist/studio.js.map +71 -63
- package/package.json +1 -1
- package/src/canvas/canvas-utils.js +313 -0
- package/src/editor/component-inline-edit.js +316 -0
- package/src/editor/content-inline-edit.js +220 -0
- package/src/panels/canvas-dnd.js +165 -0
- package/src/panels/panel-events.js +263 -0
- package/src/panels/preview-render.js +103 -0
- package/src/panels/pseudo-preview.js +64 -0
- package/src/studio.js +59 -1536
- package/src/utils/edit-display.js +197 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content inline edit bridge — extracted from studio.js (Phase 4k). Rich-text editing entry point
|
|
3
|
+
* for edit/content mode. Bridges startEditing() with Jx document state mutations.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
getState,
|
|
8
|
+
update,
|
|
9
|
+
renderOnly,
|
|
10
|
+
selectNode,
|
|
11
|
+
insertNode,
|
|
12
|
+
updateProperty,
|
|
13
|
+
parentElementPath,
|
|
14
|
+
childIndex,
|
|
15
|
+
canvasPanels,
|
|
16
|
+
} from "../store.js";
|
|
17
|
+
import { view } from "../view.js";
|
|
18
|
+
import { startEditing, isEditableBlock } from "./inline-edit.js";
|
|
19
|
+
import { restoreTemplateExpressions } from "../utils/edit-display.js";
|
|
20
|
+
import { renderBlockActionBar } from "../panels/block-action-bar.js";
|
|
21
|
+
import { defaultDef } from "../panels/shared.js";
|
|
22
|
+
|
|
23
|
+
/** @type {any} */
|
|
24
|
+
let _ctx = null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize the content inline edit module.
|
|
28
|
+
*
|
|
29
|
+
* @param {{ findCanvasElement: Function; getActivePanel: Function }} ctx
|
|
30
|
+
*/
|
|
31
|
+
export function initContentInlineEdit(ctx) {
|
|
32
|
+
_ctx = ctx;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Enter rich-text inline editing on a canvas element (edit/content mode).
|
|
37
|
+
*
|
|
38
|
+
* @param {any} el
|
|
39
|
+
* @param {any} path
|
|
40
|
+
*/
|
|
41
|
+
export function enterInlineEdit(el, path) {
|
|
42
|
+
// Restore raw template expressions before editing.
|
|
43
|
+
// prepareForEditMode renders ${expr} as ❪ expr ❫ for display;
|
|
44
|
+
// revert so the user edits the real syntax and commits it back intact.
|
|
45
|
+
restoreTemplateExpressions(el);
|
|
46
|
+
|
|
47
|
+
// Hide overlays while editing
|
|
48
|
+
for (const p of canvasPanels) {
|
|
49
|
+
p.overlay.style.display = "none";
|
|
50
|
+
p.overlayClk.style.pointerEvents = "none";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
startEditing(el, path, {
|
|
54
|
+
onCommit(
|
|
55
|
+
/** @type {any} */ commitPath,
|
|
56
|
+
/** @type {any} */ children,
|
|
57
|
+
/** @type {any} */ textContent,
|
|
58
|
+
) {
|
|
59
|
+
const S = getState();
|
|
60
|
+
if (children) {
|
|
61
|
+
let s = updateProperty(S, commitPath, "textContent", undefined);
|
|
62
|
+
s = updateProperty(s, commitPath, "children", children);
|
|
63
|
+
update(s);
|
|
64
|
+
} else if (textContent != null) {
|
|
65
|
+
let s = updateProperty(S, commitPath, "children", undefined);
|
|
66
|
+
s = updateProperty(s, commitPath, "textContent", textContent);
|
|
67
|
+
update(s);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
onSplit(/** @type {any} */ splitPath, /** @type {any} */ before, /** @type {any} */ after) {
|
|
72
|
+
const tag = "p";
|
|
73
|
+
let s = getState();
|
|
74
|
+
|
|
75
|
+
if (before.textContent != null) {
|
|
76
|
+
s = updateProperty(s, splitPath, "children", undefined);
|
|
77
|
+
s = updateProperty(s, splitPath, "textContent", before.textContent);
|
|
78
|
+
} else if (before.children) {
|
|
79
|
+
s = updateProperty(s, splitPath, "textContent", undefined);
|
|
80
|
+
s = updateProperty(s, splitPath, "children", before.children);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Insert new element after with "after" content
|
|
84
|
+
const parentPath = /** @type {any} */ (parentElementPath(splitPath));
|
|
85
|
+
const idx = /** @type {number} */ (childIndex(splitPath));
|
|
86
|
+
/** @type {any} */
|
|
87
|
+
const newNode = { tagName: tag };
|
|
88
|
+
if (after.textContent != null) {
|
|
89
|
+
newNode.textContent = after.textContent;
|
|
90
|
+
} else if (after.children) {
|
|
91
|
+
newNode.children = after.children;
|
|
92
|
+
} else {
|
|
93
|
+
newNode.textContent = "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
s = insertNode(s, parentPath, idx + 1, newNode);
|
|
97
|
+
const newPath = [...parentPath, "children", idx + 1];
|
|
98
|
+
s = selectNode(s, newPath);
|
|
99
|
+
update(s);
|
|
100
|
+
|
|
101
|
+
// Re-enter editing on the new element after render
|
|
102
|
+
requestAnimationFrame(() => {
|
|
103
|
+
const activePanel = _ctx.getActivePanel();
|
|
104
|
+
if (activePanel) {
|
|
105
|
+
const newEl = _ctx.findCanvasElement(newPath, activePanel.canvas);
|
|
106
|
+
if (newEl && isEditableBlock(newEl)) {
|
|
107
|
+
enterInlineEdit(newEl, newPath);
|
|
108
|
+
// Place cursor at start of new element
|
|
109
|
+
const sel = window.getSelection();
|
|
110
|
+
const range = document.createRange();
|
|
111
|
+
range.selectNodeContents(newEl);
|
|
112
|
+
range.collapse(true);
|
|
113
|
+
sel?.removeAllRanges();
|
|
114
|
+
sel?.addRange(range);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
onInsert(/** @type {any} */ afterPath, /** @type {any} */ cmd, /** @type {any} */ commitData) {
|
|
121
|
+
const isEmpty =
|
|
122
|
+
!commitData ||
|
|
123
|
+
(commitData.textContent != null && commitData.textContent.trim() === "") ||
|
|
124
|
+
(commitData.children &&
|
|
125
|
+
(commitData.children.length === 0 ||
|
|
126
|
+
(commitData.children.length === 1 &&
|
|
127
|
+
typeof commitData.children[0] === "string" &&
|
|
128
|
+
commitData.children[0].trim() === "") ||
|
|
129
|
+
(commitData.children.length === 1 &&
|
|
130
|
+
typeof commitData.children[0] === "object" &&
|
|
131
|
+
commitData.children[0]?.tagName === "br")));
|
|
132
|
+
|
|
133
|
+
// If the element is empty, swap its tagName instead of inserting after
|
|
134
|
+
if (isEmpty) {
|
|
135
|
+
let s = getState();
|
|
136
|
+
s = updateProperty(s, afterPath, "tagName", cmd.tag);
|
|
137
|
+
s = updateProperty(s, afterPath, "children", undefined);
|
|
138
|
+
const def = defaultDef(cmd.tag);
|
|
139
|
+
if (def.textContent && def.textContent !== "Paragraph text") {
|
|
140
|
+
s = updateProperty(s, afterPath, "textContent", def.textContent);
|
|
141
|
+
} else {
|
|
142
|
+
s = updateProperty(s, afterPath, "textContent", undefined);
|
|
143
|
+
}
|
|
144
|
+
s = selectNode(s, afterPath);
|
|
145
|
+
update(s);
|
|
146
|
+
|
|
147
|
+
requestAnimationFrame(() => {
|
|
148
|
+
const activePanel = _ctx.getActivePanel();
|
|
149
|
+
if (activePanel) {
|
|
150
|
+
const el = _ctx.findCanvasElement(afterPath, activePanel.canvas);
|
|
151
|
+
if (el && isEditableBlock(el)) {
|
|
152
|
+
enterInlineEdit(el, afterPath);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const elementDef = defaultDef(cmd.tag);
|
|
160
|
+
const parentPath = /** @type {any} */ (parentElementPath(afterPath));
|
|
161
|
+
const idx = /** @type {number} */ (childIndex(afterPath));
|
|
162
|
+
|
|
163
|
+
// Apply pending commit from inline edit first (batched to avoid double render)
|
|
164
|
+
let s = getState();
|
|
165
|
+
if (commitData) {
|
|
166
|
+
if (commitData.children) {
|
|
167
|
+
s = updateProperty(s, afterPath, "textContent", undefined);
|
|
168
|
+
s = updateProperty(s, afterPath, "children", commitData.children);
|
|
169
|
+
} else if (commitData.textContent != null) {
|
|
170
|
+
s = updateProperty(s, afterPath, "children", undefined);
|
|
171
|
+
s = updateProperty(s, afterPath, "textContent", commitData.textContent);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
s = insertNode(s, parentPath, idx + 1, structuredClone(elementDef));
|
|
176
|
+
const newPath = [...parentPath, "children", idx + 1];
|
|
177
|
+
s = selectNode(s, newPath);
|
|
178
|
+
update(s);
|
|
179
|
+
|
|
180
|
+
// If the inserted element is editable, enter editing
|
|
181
|
+
requestAnimationFrame(() => {
|
|
182
|
+
const activePanel = _ctx.getActivePanel();
|
|
183
|
+
if (activePanel) {
|
|
184
|
+
const newEl = _ctx.findCanvasElement(newPath, activePanel.canvas);
|
|
185
|
+
if (newEl && isEditableBlock(newEl)) {
|
|
186
|
+
enterInlineEdit(newEl, newPath);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
onEnd() {
|
|
193
|
+
if (view.inlineEditCleanup) {
|
|
194
|
+
view.inlineEditCleanup();
|
|
195
|
+
view.inlineEditCleanup = null;
|
|
196
|
+
}
|
|
197
|
+
for (const p of canvasPanels) {
|
|
198
|
+
p.overlay.style.display = "";
|
|
199
|
+
p.overlayClk.style.pointerEvents = "";
|
|
200
|
+
}
|
|
201
|
+
renderOnly("overlays");
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Show the block action bar (with inline formatting buttons) on the viewport
|
|
206
|
+
requestAnimationFrame(() => renderBlockActionBar());
|
|
207
|
+
|
|
208
|
+
// Re-render action bar when selection changes inside contenteditable
|
|
209
|
+
const selectionHandler = () => renderBlockActionBar();
|
|
210
|
+
document.addEventListener("selectionchange", selectionHandler);
|
|
211
|
+
el.addEventListener("mouseup", selectionHandler);
|
|
212
|
+
el.addEventListener("keyup", selectionHandler);
|
|
213
|
+
|
|
214
|
+
const inlineEditCleanup = () => {
|
|
215
|
+
document.removeEventListener("selectionchange", selectionHandler);
|
|
216
|
+
el.removeEventListener("mouseup", selectionHandler);
|
|
217
|
+
el.removeEventListener("keyup", selectionHandler);
|
|
218
|
+
};
|
|
219
|
+
view.inlineEditCleanup = inlineEditCleanup;
|
|
220
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas DnD — extracted from studio.js (Phase 4m). Registers canvas elements as drag-and-drop
|
|
3
|
+
* targets using @atlaskit/pragmatic-drag-and-drop.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
dropTargetForElements,
|
|
8
|
+
monitorForElements,
|
|
9
|
+
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getState,
|
|
13
|
+
elToPath,
|
|
14
|
+
canvasPanels,
|
|
15
|
+
getNodeAtPath,
|
|
16
|
+
VOID_ELEMENTS,
|
|
17
|
+
isAncestor,
|
|
18
|
+
} from "../store.js";
|
|
19
|
+
import { view } from "../view.js";
|
|
20
|
+
import { applyDropInstruction } from "../panels/dnd.js";
|
|
21
|
+
|
|
22
|
+
/** @type {any} */
|
|
23
|
+
let _ctx = null;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the canvas DnD module.
|
|
27
|
+
*
|
|
28
|
+
* @param {{ effectiveZoom: () => number }} ctx
|
|
29
|
+
*/
|
|
30
|
+
export function initCanvasDnD(ctx) {
|
|
31
|
+
_ctx = ctx;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register all canvas elements in a panel as DnD drop targets.
|
|
36
|
+
*
|
|
37
|
+
* @param {any} panel
|
|
38
|
+
*/
|
|
39
|
+
export function registerPanelDnD(panel) {
|
|
40
|
+
const { canvas, dropLine } = panel;
|
|
41
|
+
const allEls = canvas.querySelectorAll("*");
|
|
42
|
+
|
|
43
|
+
const monitorCleanup = monitorForElements({
|
|
44
|
+
onDragStart() {
|
|
45
|
+
for (const el of canvas.querySelectorAll("*")) {
|
|
46
|
+
/** @type {any} */ (el).style.pointerEvents = "auto";
|
|
47
|
+
}
|
|
48
|
+
for (const p of canvasPanels) p.overlayClk.style.pointerEvents = "none";
|
|
49
|
+
},
|
|
50
|
+
onDrag({ location }) {
|
|
51
|
+
view.lastDragInput = location.current.input;
|
|
52
|
+
},
|
|
53
|
+
onDrop() {
|
|
54
|
+
for (const p of canvasPanels) p.dropLine.style.display = "none";
|
|
55
|
+
view.lastDragInput = null;
|
|
56
|
+
for (const el of canvas.querySelectorAll("*")) {
|
|
57
|
+
/** @type {any} */ (el).style.pointerEvents = "none";
|
|
58
|
+
}
|
|
59
|
+
for (const p of canvasPanels) p.overlayClk.style.pointerEvents = "";
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
view.canvasDndCleanups.push(monitorCleanup);
|
|
63
|
+
|
|
64
|
+
const S = getState();
|
|
65
|
+
for (const el of allEls) {
|
|
66
|
+
const elPath = elToPath.get(el);
|
|
67
|
+
if (!elPath) continue;
|
|
68
|
+
|
|
69
|
+
const node = getNodeAtPath(S.document, elPath);
|
|
70
|
+
const isVoid = VOID_ELEMENTS.has((node?.tagName || "div").toLowerCase());
|
|
71
|
+
|
|
72
|
+
const cleanup = dropTargetForElements({
|
|
73
|
+
element: el,
|
|
74
|
+
canDrop({ source }) {
|
|
75
|
+
const srcPath = source.data.path;
|
|
76
|
+
if (srcPath && isAncestor(/** @type {any} */ (srcPath), elPath)) return false;
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
getData() {
|
|
80
|
+
return { path: elPath, _isVoid: isVoid };
|
|
81
|
+
},
|
|
82
|
+
onDragEnter() {
|
|
83
|
+
showCanvasDropIndicator(el, elPath, isVoid, panel);
|
|
84
|
+
},
|
|
85
|
+
onDrag() {
|
|
86
|
+
showCanvasDropIndicator(el, elPath, isVoid, panel);
|
|
87
|
+
},
|
|
88
|
+
onDragLeave() {
|
|
89
|
+
dropLine.style.display = "none";
|
|
90
|
+
el.classList.remove("canvas-drop-target");
|
|
91
|
+
},
|
|
92
|
+
onDrop({ source }) {
|
|
93
|
+
dropLine.style.display = "none";
|
|
94
|
+
el.classList.remove("canvas-drop-target");
|
|
95
|
+
const instruction = getCanvasDropInstruction(el, elPath, isVoid);
|
|
96
|
+
if (!instruction) return;
|
|
97
|
+
applyDropInstruction(instruction, source.data, elPath);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
view.canvasDndCleanups.push(cleanup);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {any} el
|
|
106
|
+
* @param {any} elPath
|
|
107
|
+
* @param {any} isVoid
|
|
108
|
+
*/
|
|
109
|
+
function getCanvasDropInstruction(el, elPath, isVoid) {
|
|
110
|
+
const rect = el.getBoundingClientRect();
|
|
111
|
+
if (!view.lastDragInput) return null;
|
|
112
|
+
const y = view.lastDragInput.clientY;
|
|
113
|
+
const relY = (y - rect.top) / rect.height;
|
|
114
|
+
|
|
115
|
+
if (elPath.length === 0) return { type: "make-child" };
|
|
116
|
+
if (isVoid) return relY < 0.5 ? { type: "reorder-above" } : { type: "reorder-below" };
|
|
117
|
+
if (relY < 0.25) return { type: "reorder-above" };
|
|
118
|
+
if (relY > 0.75) return { type: "reorder-below" };
|
|
119
|
+
return { type: "make-child" };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {any} el
|
|
124
|
+
* @param {any} elPath
|
|
125
|
+
* @param {any} isVoid
|
|
126
|
+
* @param {any} panel
|
|
127
|
+
*/
|
|
128
|
+
function showCanvasDropIndicator(el, elPath, isVoid, panel) {
|
|
129
|
+
const instruction = getCanvasDropInstruction(el, elPath, isVoid);
|
|
130
|
+
const { dropLine, viewport } = panel;
|
|
131
|
+
if (!instruction) {
|
|
132
|
+
dropLine.style.display = "none";
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const scale = _ctx.effectiveZoom();
|
|
137
|
+
const wrapRect = viewport.getBoundingClientRect();
|
|
138
|
+
const elRect = el.getBoundingClientRect();
|
|
139
|
+
const left = (elRect.left - wrapRect.left + viewport.scrollLeft) / scale;
|
|
140
|
+
const width = elRect.width / scale;
|
|
141
|
+
|
|
142
|
+
if (instruction.type === "make-child") {
|
|
143
|
+
dropLine.style.display = "block";
|
|
144
|
+
dropLine.style.top = `${(elRect.top - wrapRect.top + viewport.scrollTop) / scale}px`;
|
|
145
|
+
dropLine.style.left = `${left}px`;
|
|
146
|
+
dropLine.style.width = `${width}px`;
|
|
147
|
+
dropLine.style.height = `${elRect.height / scale}px`;
|
|
148
|
+
dropLine.className = "canvas-drop-indicator inside";
|
|
149
|
+
el.classList.add("canvas-drop-target");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
el.classList.remove("canvas-drop-target");
|
|
154
|
+
const top =
|
|
155
|
+
instruction.type === "reorder-above"
|
|
156
|
+
? (elRect.top - wrapRect.top + viewport.scrollTop) / scale
|
|
157
|
+
: (elRect.bottom - wrapRect.top + viewport.scrollTop) / scale;
|
|
158
|
+
|
|
159
|
+
dropLine.style.display = "block";
|
|
160
|
+
dropLine.style.top = `${top}px`;
|
|
161
|
+
dropLine.style.left = `${left}px`;
|
|
162
|
+
dropLine.style.width = `${width}px`;
|
|
163
|
+
dropLine.style.height = "2px";
|
|
164
|
+
dropLine.className = "canvas-drop-indicator line";
|
|
165
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel events — extracted from studio.js (Phase 4m). Unified event handler system for canvas
|
|
3
|
+
* panels: click-to-select, double-click inline edit, context menu, hover tracking, insertion
|
|
4
|
+
* helper.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
update,
|
|
9
|
+
selectNode,
|
|
10
|
+
hoverNode,
|
|
11
|
+
elToPath,
|
|
12
|
+
pathsEqual,
|
|
13
|
+
insertNode,
|
|
14
|
+
parentElementPath,
|
|
15
|
+
childIndex,
|
|
16
|
+
getNodeAtPath,
|
|
17
|
+
renderOnly,
|
|
18
|
+
} from "../store.js";
|
|
19
|
+
import { view } from "../view.js";
|
|
20
|
+
import { stopEditing, isEditing, isEditableBlock } from "../editor/inline-edit.js";
|
|
21
|
+
import { showContextMenu } from "../editor/context-menu.js";
|
|
22
|
+
import * as insertionHelper from "../editor/insertion-helper.js";
|
|
23
|
+
import { defaultDef } from "../panels/shared.js";
|
|
24
|
+
|
|
25
|
+
/** @type {any} */
|
|
26
|
+
let _ctx = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the panel events module.
|
|
30
|
+
*
|
|
31
|
+
* @param {{
|
|
32
|
+
* getState: () => any;
|
|
33
|
+
* setState: (s: any) => void;
|
|
34
|
+
* getCanvasMode: () => string;
|
|
35
|
+
* bubbleInlinePath: (doc: any, path: any) => any;
|
|
36
|
+
* findCanvasElement: (path: any, canvasEl: any) => any;
|
|
37
|
+
* enterInlineEdit: (el: any, path: any) => void;
|
|
38
|
+
* navigateToComponent: (path: any) => void;
|
|
39
|
+
* effectiveZoom: () => number;
|
|
40
|
+
* }} ctx
|
|
41
|
+
*/
|
|
42
|
+
export function initPanelEvents(ctx) {
|
|
43
|
+
_ctx = ctx;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @param {any} panel */
|
|
47
|
+
export function registerPanelEvents(panel) {
|
|
48
|
+
const { canvas, overlayClk, mediaName } = panel;
|
|
49
|
+
const ac = new AbortController();
|
|
50
|
+
const opts = { signal: ac.signal };
|
|
51
|
+
view.canvasEventCleanups.push(() => ac.abort());
|
|
52
|
+
|
|
53
|
+
/** @param {any} fn */
|
|
54
|
+
function withPanelPointerEvents(fn) {
|
|
55
|
+
const els = canvas.querySelectorAll("*");
|
|
56
|
+
for (const el of els) el.style.pointerEvents = "auto";
|
|
57
|
+
overlayClk.style.display = "none";
|
|
58
|
+
const result = fn();
|
|
59
|
+
overlayClk.style.display = "";
|
|
60
|
+
for (const el of els) el.style.pointerEvents = "none";
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
overlayClk.addEventListener(
|
|
65
|
+
"click",
|
|
66
|
+
(/** @type {any} */ e) => {
|
|
67
|
+
const barInner = view.blockActionBarEl?.firstElementChild;
|
|
68
|
+
if (barInner) {
|
|
69
|
+
const r = barInner.getBoundingClientRect();
|
|
70
|
+
if (
|
|
71
|
+
e.clientX >= r.left &&
|
|
72
|
+
e.clientX <= r.right &&
|
|
73
|
+
e.clientY >= r.top &&
|
|
74
|
+
e.clientY <= r.bottom
|
|
75
|
+
)
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (isEditing()) {
|
|
79
|
+
stopEditing();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const S = _ctx.getState();
|
|
83
|
+
const canvasMode = _ctx.getCanvasMode();
|
|
84
|
+
|
|
85
|
+
const elements = withPanelPointerEvents(() =>
|
|
86
|
+
document.elementsFromPoint(e.clientX, e.clientY),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
for (const el of elements) {
|
|
90
|
+
if (canvas.contains(el) && el !== canvas) {
|
|
91
|
+
const originalPath = elToPath.get(el);
|
|
92
|
+
if (originalPath) {
|
|
93
|
+
let path = _ctx.bubbleInlinePath(S.document, originalPath);
|
|
94
|
+
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
95
|
+
const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
|
|
96
|
+
|
|
97
|
+
const resolvedEl =
|
|
98
|
+
path === originalPath ? el : _ctx.findCanvasElement(path, canvas) || el;
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
pathsEqual(path, S.selection) &&
|
|
102
|
+
isEditableBlock(resolvedEl) &&
|
|
103
|
+
(canvasMode === "edit" || S.mode === "content")
|
|
104
|
+
) {
|
|
105
|
+
_ctx.setState(withMedia);
|
|
106
|
+
_ctx.enterInlineEdit(resolvedEl, path);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (canvasMode === "design" && S.mode !== "content") {
|
|
111
|
+
view.pendingInlineEdit = { path, mediaName };
|
|
112
|
+
update(selectNode(withMedia, path));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
update(selectNode(withMedia, path));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
update(selectNode(S, null));
|
|
122
|
+
},
|
|
123
|
+
opts,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
overlayClk.addEventListener(
|
|
127
|
+
"dblclick",
|
|
128
|
+
(/** @type {any} */ e) => {
|
|
129
|
+
const barInner = view.blockActionBarEl?.firstElementChild;
|
|
130
|
+
if (barInner) {
|
|
131
|
+
const r = barInner.getBoundingClientRect();
|
|
132
|
+
if (
|
|
133
|
+
e.clientX >= r.left &&
|
|
134
|
+
e.clientX <= r.right &&
|
|
135
|
+
e.clientY >= r.top &&
|
|
136
|
+
e.clientY <= r.bottom
|
|
137
|
+
)
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const canvasMode = _ctx.getCanvasMode();
|
|
141
|
+
if (canvasMode !== "edit" && canvasMode !== "design") return;
|
|
142
|
+
|
|
143
|
+
const S = _ctx.getState();
|
|
144
|
+
const elements = withPanelPointerEvents(() =>
|
|
145
|
+
document.elementsFromPoint(e.clientX, e.clientY),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
for (const el of elements) {
|
|
149
|
+
if (canvas.contains(el) && el !== canvas) {
|
|
150
|
+
const originalPath = elToPath.get(el);
|
|
151
|
+
if (originalPath) {
|
|
152
|
+
const path = _ctx.bubbleInlinePath(S.document, originalPath);
|
|
153
|
+
const resolvedEl =
|
|
154
|
+
path === originalPath ? el : _ctx.findCanvasElement(path, canvas) || el;
|
|
155
|
+
if (isEditableBlock(resolvedEl)) {
|
|
156
|
+
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
157
|
+
const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
|
|
158
|
+
update(selectNode(withMedia, path));
|
|
159
|
+
_ctx.enterInlineEdit(resolvedEl, path);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
opts,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
overlayClk.addEventListener(
|
|
170
|
+
"contextmenu",
|
|
171
|
+
(/** @type {any} */ e) => {
|
|
172
|
+
const barInner = view.blockActionBarEl?.firstElementChild;
|
|
173
|
+
if (barInner) {
|
|
174
|
+
const r = barInner.getBoundingClientRect();
|
|
175
|
+
if (
|
|
176
|
+
e.clientX >= r.left &&
|
|
177
|
+
e.clientX <= r.right &&
|
|
178
|
+
e.clientY >= r.top &&
|
|
179
|
+
e.clientY <= r.bottom
|
|
180
|
+
)
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const S = _ctx.getState();
|
|
184
|
+
const elements = withPanelPointerEvents(() =>
|
|
185
|
+
document.elementsFromPoint(e.clientX, e.clientY),
|
|
186
|
+
);
|
|
187
|
+
for (const el of elements) {
|
|
188
|
+
if (canvas.contains(el) && el !== canvas) {
|
|
189
|
+
let path = elToPath.get(el);
|
|
190
|
+
if (path) {
|
|
191
|
+
path = _ctx.bubbleInlinePath(S.document, path);
|
|
192
|
+
showContextMenu(e, path, S, { onEditComponent: _ctx.navigateToComponent });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
},
|
|
199
|
+
opts,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
overlayClk.addEventListener(
|
|
203
|
+
"mousemove",
|
|
204
|
+
(/** @type {any} */ e) => {
|
|
205
|
+
const barInner = view.blockActionBarEl?.firstElementChild;
|
|
206
|
+
if (barInner) {
|
|
207
|
+
const r = barInner.getBoundingClientRect();
|
|
208
|
+
if (
|
|
209
|
+
e.clientX >= r.left &&
|
|
210
|
+
e.clientX <= r.right &&
|
|
211
|
+
e.clientY >= r.top &&
|
|
212
|
+
e.clientY <= r.bottom
|
|
213
|
+
)
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
let S = _ctx.getState();
|
|
217
|
+
const el = withPanelPointerEvents(() => document.elementFromPoint(e.clientX, e.clientY));
|
|
218
|
+
if (el && canvas.contains(el) && el !== canvas) {
|
|
219
|
+
let path = elToPath.get(el);
|
|
220
|
+
if (path) {
|
|
221
|
+
path = _ctx.bubbleInlinePath(S.document, path);
|
|
222
|
+
if (!pathsEqual(path, S.hover)) {
|
|
223
|
+
_ctx.setState(hoverNode(S, path));
|
|
224
|
+
renderOnly("overlays");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} else if (S.hover) {
|
|
228
|
+
_ctx.setState(hoverNode(S, null));
|
|
229
|
+
renderOnly("overlays");
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
opts,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
overlayClk.addEventListener(
|
|
236
|
+
"mouseleave",
|
|
237
|
+
() => {
|
|
238
|
+
const S = _ctx.getState();
|
|
239
|
+
if (S.hover) {
|
|
240
|
+
_ctx.setState(hoverNode(S, null));
|
|
241
|
+
renderOnly("overlays");
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
opts,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
insertionHelper.mount({
|
|
248
|
+
getState: _ctx.getState,
|
|
249
|
+
update,
|
|
250
|
+
getCanvasMode: _ctx.getCanvasMode,
|
|
251
|
+
withPanelPointerEvents,
|
|
252
|
+
effectiveZoom: _ctx.effectiveZoom,
|
|
253
|
+
defaultDef,
|
|
254
|
+
insertNode,
|
|
255
|
+
selectNode,
|
|
256
|
+
parentElementPath,
|
|
257
|
+
childIndex,
|
|
258
|
+
getNodeAtPath,
|
|
259
|
+
elToPath,
|
|
260
|
+
panel,
|
|
261
|
+
});
|
|
262
|
+
view.canvasEventCleanups.push(() => insertionHelper.unmount());
|
|
263
|
+
}
|