@jxsuite/studio 0.28.2 → 0.28.4
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 +86144 -83971
- package/dist/studio.js.map +229 -219
- package/package.json +5 -5
- package/src/browse/browse-modal.ts +9 -3
- package/src/browse/browse.ts +139 -82
- package/src/canvas/canvas-diff.ts +35 -18
- package/src/canvas/canvas-helpers.ts +55 -21
- package/src/canvas/canvas-live-render.ts +140 -89
- package/src/canvas/canvas-render.ts +127 -82
- package/src/canvas/canvas-utils.ts +68 -42
- package/src/canvas/nested-site-style.ts +15 -7
- package/src/editor/component-inline-edit.ts +90 -51
- package/src/editor/content-inline-edit.ts +75 -72
- package/src/editor/context-menu.ts +125 -71
- package/src/editor/convert-targets.ts +17 -13
- package/src/editor/convert-to-component.ts +51 -28
- package/src/editor/convert-to-repeater.ts +44 -19
- package/src/editor/inline-edit.ts +107 -52
- package/src/editor/inline-format.ts +128 -60
- package/src/editor/insertion-helper.ts +26 -14
- package/src/editor/shortcuts.ts +89 -43
- package/src/editor/slash-menu.ts +41 -26
- package/src/files/components.ts +9 -7
- package/src/files/file-ops.ts +53 -33
- package/src/files/files.ts +246 -143
- package/src/format/constraints.ts +25 -15
- package/src/format/format-host.ts +33 -10
- package/src/github/github-auth.ts +24 -14
- package/src/github/github-publish.ts +20 -14
- package/src/new-project/new-project-modal.ts +27 -18
- package/src/panels/activity-bar.ts +27 -26
- package/src/panels/ai-panel.ts +100 -44
- package/src/panels/block-action-bar.ts +84 -52
- package/src/panels/canvas-dnd.ts +47 -28
- package/src/panels/data-explorer.ts +24 -11
- package/src/panels/dnd.ts +148 -100
- package/src/panels/editors.ts +54 -37
- package/src/panels/elements-panel.ts +23 -13
- package/src/panels/events-panel.ts +48 -38
- package/src/panels/git-panel.ts +95 -60
- package/src/panels/head-panel.ts +138 -84
- package/src/panels/imports-panel.ts +66 -29
- package/src/panels/layers-panel.ts +80 -36
- package/src/panels/left-panel.ts +86 -53
- package/src/panels/overlays.ts +36 -19
- package/src/panels/panel-events.ts +48 -27
- package/src/panels/panel-scheduler.ts +41 -23
- package/src/panels/preview-render.ts +26 -26
- package/src/panels/properties-panel.ts +155 -118
- package/src/panels/pseudo-preview.ts +31 -15
- package/src/panels/quick-search.ts +28 -13
- package/src/panels/right-panel.ts +35 -24
- package/src/panels/shared.ts +46 -29
- package/src/panels/signals-panel.ts +303 -186
- package/src/panels/statusbar.ts +27 -12
- package/src/panels/style-inputs.ts +30 -24
- package/src/panels/style-panel.ts +130 -90
- package/src/panels/style-utils.ts +63 -23
- package/src/panels/stylebook-layers-panel.ts +27 -30
- package/src/panels/stylebook-panel.ts +140 -72
- package/src/panels/tab-strip.ts +21 -10
- package/src/panels/toolbar.ts +52 -40
- package/src/panels/welcome-screen.ts +1 -1
- package/src/platform.ts +2 -1
- package/src/platforms/devserver.ts +221 -105
- package/src/recent-projects.ts +15 -7
- package/src/resize-edges.ts +7 -5
- package/src/services/cem-export.ts +73 -57
- package/src/services/code-services.ts +52 -25
- package/src/services/monaco-setup.ts +5 -5
- package/src/settings/content-types-editor.ts +133 -62
- package/src/settings/css-vars-editor.ts +31 -14
- package/src/settings/defs-editor.ts +125 -63
- package/src/settings/general-settings.ts +11 -6
- package/src/settings/head-editor.ts +30 -15
- package/src/settings/schema-field-ui.ts +75 -35
- package/src/settings/settings-modal.ts +35 -17
- package/src/site-context.ts +80 -47
- package/src/state.ts +98 -65
- package/src/store.ts +42 -23
- package/src/studio.ts +165 -144
- package/src/tabs/tab.ts +51 -45
- package/src/tabs/transact.ts +230 -109
- package/src/types.ts +60 -51
- package/src/ui/button-group.ts +3 -2
- package/src/ui/color-selector.ts +33 -15
- package/src/ui/expression-editor.ts +139 -86
- package/src/ui/field-input.ts +35 -18
- package/src/ui/field-row.ts +1 -1
- package/src/ui/icons.ts +5 -6
- package/src/ui/layers.ts +33 -26
- package/src/ui/media-picker.ts +13 -7
- package/src/ui/panel-resize.ts +39 -20
- package/src/ui/spectrum.ts +4 -2
- package/src/ui/unit-selector.ts +20 -11
- package/src/ui/value-selector.ts +13 -7
- package/src/ui/widgets.ts +34 -18
- package/src/utils/canvas-media.ts +66 -33
- package/src/utils/edit-display.ts +43 -44
- package/src/utils/google-fonts.ts +10 -6
- package/src/utils/inherited-style.ts +22 -8
- package/src/utils/studio-utils.ts +88 -45
- package/src/view.ts +9 -5
- package/src/workspace/workspace.ts +49 -29
|
@@ -5,16 +5,18 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { html, render as litRender, nothing } from "lit-html";
|
|
8
|
+
import type { CanvasPanel } from "../types";
|
|
8
9
|
import { ref } from "lit-html/directives/ref.js";
|
|
9
10
|
import { classMap } from "lit-html/directives/class-map.js";
|
|
10
11
|
import { styleMap } from "lit-html/directives/style-map.js";
|
|
11
12
|
import { ifDefined } from "lit-html/directives/if-defined.js";
|
|
12
13
|
|
|
13
|
-
import {
|
|
14
|
+
import { canvasPanels, canvasWrap, renderOnly, updateUi } from "../store";
|
|
14
15
|
import { activeTab } from "../workspace/workspace";
|
|
15
16
|
import { view } from "../view";
|
|
16
17
|
import { getLayerSlot } from "../ui/layers";
|
|
17
|
-
import { getActivePanel,
|
|
18
|
+
import { findCanvasElement, getActivePanel, panelMediaToActiveMedia } from "./canvas-helpers";
|
|
19
|
+
import type { TemplateResult } from "lit-html";
|
|
18
20
|
|
|
19
21
|
let _ctx: {
|
|
20
22
|
getCanvasMode: () => string;
|
|
@@ -57,34 +59,28 @@ export function canvasPanelTemplate(
|
|
|
57
59
|
label: string | null,
|
|
58
60
|
fullWidth: boolean,
|
|
59
61
|
width: number | null = null,
|
|
60
|
-
) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
overlay: HTMLElement | null;
|
|
66
|
-
overlayClk: HTMLElement | null;
|
|
67
|
-
viewport: HTMLElement | null;
|
|
68
|
-
scrollContainer: HTMLElement | null;
|
|
69
|
-
dropLine: HTMLElement | null;
|
|
70
|
-
_width: number | null;
|
|
71
|
-
} = {
|
|
72
|
-
mediaName: mediaName || "",
|
|
73
|
-
element: null,
|
|
62
|
+
): { tpl: TemplateResult; panel: CanvasPanel } {
|
|
63
|
+
// The DOM fields start null and are wired by the template's ref() directives,
|
|
64
|
+
// Which lit runs synchronously during render — before any consumer reads them.
|
|
65
|
+
const panel = {
|
|
66
|
+
_width: width || null,
|
|
74
67
|
canvas: null,
|
|
68
|
+
dropLine: null,
|
|
69
|
+
element: null,
|
|
70
|
+
mediaName: mediaName || "",
|
|
75
71
|
overlay: null,
|
|
76
72
|
overlayClk: null,
|
|
77
|
-
viewport: null,
|
|
78
73
|
scrollContainer: null,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
74
|
+
viewport: null,
|
|
75
|
+
} as unknown as CanvasPanel;
|
|
82
76
|
const tpl = html`
|
|
83
77
|
<div
|
|
84
78
|
class=${classMap({ "canvas-panel": true, "full-width": fullWidth })}
|
|
85
79
|
data-media=${ifDefined(mediaName !== null ? mediaName : undefined)}
|
|
86
80
|
${ref((el) => {
|
|
87
|
-
if (el)
|
|
81
|
+
if (el) {
|
|
82
|
+
panel.element = el as HTMLElement;
|
|
83
|
+
}
|
|
88
84
|
})}
|
|
89
85
|
>
|
|
90
86
|
${label
|
|
@@ -92,7 +88,7 @@ export function canvasPanelTemplate(
|
|
|
92
88
|
<div
|
|
93
89
|
class="canvas-panel-header"
|
|
94
90
|
@click=${() => {
|
|
95
|
-
updateUi("activeMedia", mediaName
|
|
91
|
+
updateUi("activeMedia", panelMediaToActiveMedia(mediaName));
|
|
96
92
|
}}
|
|
97
93
|
>
|
|
98
94
|
${label}
|
|
@@ -103,45 +99,57 @@ export function canvasPanelTemplate(
|
|
|
103
99
|
class="canvas-panel-viewport"
|
|
104
100
|
style=${styleMap({ width: width && !fullWidth ? `${width}px` : "" })}
|
|
105
101
|
${ref((el) => {
|
|
106
|
-
if (el)
|
|
102
|
+
if (el) {
|
|
103
|
+
panel.viewport = el as HTMLElement;
|
|
104
|
+
}
|
|
107
105
|
})}
|
|
108
106
|
>
|
|
109
107
|
<div
|
|
110
108
|
class="canvas-panel-canvas"
|
|
111
109
|
style=${styleMap({ width: width ? `${width}px` : "" })}
|
|
112
110
|
${ref((el) => {
|
|
113
|
-
if (el)
|
|
111
|
+
if (el) {
|
|
112
|
+
panel.canvas = el as HTMLElement;
|
|
113
|
+
}
|
|
114
114
|
})}
|
|
115
115
|
></div>
|
|
116
116
|
<div
|
|
117
117
|
class="canvas-panel-overlay"
|
|
118
118
|
${ref((el) => {
|
|
119
|
-
if (el)
|
|
119
|
+
if (el) {
|
|
120
|
+
panel.overlay = el as HTMLElement;
|
|
121
|
+
}
|
|
120
122
|
})}
|
|
121
123
|
>
|
|
122
124
|
<div
|
|
123
125
|
class="canvas-drop-indicator"
|
|
124
126
|
style="display:none"
|
|
125
127
|
${ref((el) => {
|
|
126
|
-
if (el)
|
|
128
|
+
if (el) {
|
|
129
|
+
panel.dropLine = el as HTMLElement;
|
|
130
|
+
}
|
|
127
131
|
})}
|
|
128
132
|
></div>
|
|
129
133
|
</div>
|
|
130
134
|
<div
|
|
131
135
|
class="canvas-panel-click"
|
|
132
136
|
${ref((el) => {
|
|
133
|
-
if (el)
|
|
137
|
+
if (el) {
|
|
138
|
+
panel.overlayClk = el as HTMLElement;
|
|
139
|
+
}
|
|
134
140
|
})}
|
|
135
141
|
></div>
|
|
136
142
|
</div>
|
|
137
143
|
</div>
|
|
138
144
|
`;
|
|
139
|
-
return {
|
|
145
|
+
return { panel, tpl };
|
|
140
146
|
}
|
|
141
147
|
|
|
142
148
|
/** Center canvas horizontally in viewport, top-aligned vertically. */
|
|
143
149
|
export function centerCanvas() {
|
|
144
|
-
if (!view.panzoomWrap)
|
|
150
|
+
if (!view.panzoomWrap) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
145
153
|
const wrapWidth = canvasWrap.clientWidth;
|
|
146
154
|
const contentWidth = view.panzoomWrap.scrollWidth;
|
|
147
155
|
const zoom = _ctx.getZoom();
|
|
@@ -159,7 +167,9 @@ export function observeCenterUntilStable() {
|
|
|
159
167
|
view.centerObserver.disconnect();
|
|
160
168
|
view.centerObserver = null;
|
|
161
169
|
}
|
|
162
|
-
if (!view.panzoomWrap)
|
|
170
|
+
if (!view.panzoomWrap) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
163
173
|
view.needsCenter = true;
|
|
164
174
|
view.centerObserver = new ResizeObserver(() => {
|
|
165
175
|
if (!view.needsCenter) {
|
|
@@ -176,17 +186,23 @@ export function observeCenterUntilStable() {
|
|
|
176
186
|
|
|
177
187
|
/** Apply the current zoom + pan transform to the panzoom wrapper. */
|
|
178
188
|
export function applyTransform() {
|
|
179
|
-
if (!view.panzoomWrap)
|
|
189
|
+
if (!view.panzoomWrap) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
180
192
|
const zoom = _ctx.getZoom();
|
|
181
193
|
view.panzoomWrap.style.transform = `translate(${view.panX}px, ${view.panY}px) scale(${zoom})`;
|
|
182
194
|
renderZoomIndicator();
|
|
183
195
|
renderOnly("overlays");
|
|
184
|
-
if (_ctx.getCanvasMode() === "stylebook")
|
|
196
|
+
if (_ctx.getCanvasMode() === "stylebook") {
|
|
197
|
+
_ctx.renderStylebookOverlays();
|
|
198
|
+
}
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
/** Calculate zoom + pan to fit all panels within the viewport. */
|
|
188
202
|
export function fitToScreen() {
|
|
189
|
-
if (!view.panzoomWrap)
|
|
203
|
+
if (!view.panzoomWrap) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
190
206
|
const wrapWidth = canvasWrap.clientWidth;
|
|
191
207
|
const wrapHeight = canvasWrap.clientHeight;
|
|
192
208
|
const gap = 24;
|
|
@@ -204,7 +220,7 @@ export function fitToScreen() {
|
|
|
204
220
|
|
|
205
221
|
const fitZoomW = wrapWidth / totalPanelWidth;
|
|
206
222
|
const fitZoomH = wrapHeight / maxPanelHeight;
|
|
207
|
-
const fitZoom = Math.min(5
|
|
223
|
+
const fitZoom = Math.min(5, Math.max(0.05, Math.min(fitZoomW, fitZoomH)));
|
|
208
224
|
|
|
209
225
|
_ctx.setZoomDirect(fitZoom);
|
|
210
226
|
|
|
@@ -217,7 +233,9 @@ export function fitToScreen() {
|
|
|
217
233
|
|
|
218
234
|
/** Reset zoom to 100% and re-center horizontally. */
|
|
219
235
|
export function resetZoom() {
|
|
220
|
-
if (!view.panzoomWrap)
|
|
236
|
+
if (!view.panzoomWrap) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
221
239
|
_ctx.setZoomDirect(1);
|
|
222
240
|
centerCanvas();
|
|
223
241
|
applyTransform();
|
|
@@ -244,21 +262,23 @@ function _panToEl(el: HTMLElement, panel?: { scrollContainer?: HTMLElement | nul
|
|
|
244
262
|
|
|
245
263
|
if (panel?.scrollContainer) {
|
|
246
264
|
panel.scrollContainer.scrollTo({
|
|
247
|
-
top: panel.scrollContainer.scrollTop - offsetY,
|
|
248
265
|
behavior: "smooth",
|
|
266
|
+
top: panel.scrollContainer.scrollTop - offsetY,
|
|
249
267
|
});
|
|
250
268
|
} else {
|
|
251
269
|
const startY = view.panY;
|
|
252
270
|
const targetY = startY + offsetY;
|
|
253
271
|
const start = performance.now();
|
|
254
272
|
const duration = 250;
|
|
255
|
-
|
|
273
|
+
const step = (now: number) => {
|
|
256
274
|
const t = Math.min((now - start) / duration, 1);
|
|
257
275
|
const ease = t * (2 - t);
|
|
258
276
|
view.panY = startY + (targetY - startY) * ease;
|
|
259
277
|
applyTransform();
|
|
260
|
-
if (t < 1)
|
|
261
|
-
|
|
278
|
+
if (t < 1) {
|
|
279
|
+
requestAnimationFrame(step);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
262
282
|
requestAnimationFrame(step);
|
|
263
283
|
}
|
|
264
284
|
}
|
|
@@ -270,9 +290,13 @@ function _panToEl(el: HTMLElement, panel?: { scrollContainer?: HTMLElement | nul
|
|
|
270
290
|
*/
|
|
271
291
|
export function panToElement(path: (string | number)[]) {
|
|
272
292
|
const panel = getActivePanel();
|
|
273
|
-
if (!panel?.canvas)
|
|
293
|
+
if (!panel?.canvas) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
274
296
|
const el = findCanvasElement(path, panel.canvas);
|
|
275
|
-
if (!el)
|
|
297
|
+
if (!el) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
276
300
|
_panToEl(el, panel);
|
|
277
301
|
}
|
|
278
302
|
|
|
@@ -337,7 +361,9 @@ export function renderZoomIndicator() {
|
|
|
337
361
|
|
|
338
362
|
/** Position the zoom indicator relative to canvas-wrap bounds. */
|
|
339
363
|
export function positionZoomIndicator() {
|
|
340
|
-
if (!_zoomIndicatorEl)
|
|
364
|
+
if (!_zoomIndicatorEl) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
341
367
|
const rect = canvasWrap.getBoundingClientRect();
|
|
342
368
|
_zoomIndicatorEl.style.left = `${rect.left + rect.width / 2}px`;
|
|
343
369
|
_zoomIndicatorEl.style.top = `${rect.bottom - 32}px`;
|
|
@@ -2,21 +2,27 @@
|
|
|
2
2
|
* Generate scoped CSS from nested style objects (e.g. `table: { width: "100%", thead: { ... } }`).
|
|
3
3
|
* Returns a CSS string with rules scoped under the given selector.
|
|
4
4
|
*
|
|
5
|
-
* @param {
|
|
5
|
+
* @param {import("@jxsuite/schema/types").JxStyle} styleObj - The full style object (flat + nested)
|
|
6
6
|
* @param {string} scope - The scoping selector (e.g. `[data-jx-site]`)
|
|
7
7
|
* @returns {string} Generated CSS text
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
import type { JxStyle } from "@jxsuite/schema/types";
|
|
10
|
+
|
|
11
|
+
export function buildNestedSiteCSS(styleObj: JxStyle, scope: string) {
|
|
10
12
|
let css = "";
|
|
11
13
|
|
|
12
|
-
function emit(parentSel: string, rules:
|
|
14
|
+
function emit(parentSel: string, rules: JxStyle) {
|
|
13
15
|
const props = Object.entries(rules)
|
|
14
16
|
.filter(([, val]) => val === null || typeof val !== "object" || Array.isArray(val))
|
|
15
17
|
.map(([p, val]) => `${camelToKebab(p)}: ${val}`)
|
|
16
18
|
.join("; ");
|
|
17
|
-
if (props)
|
|
19
|
+
if (props) {
|
|
20
|
+
css += `${parentSel} { ${props} }\n`;
|
|
21
|
+
}
|
|
18
22
|
for (const [sel, sub] of Object.entries(rules)) {
|
|
19
|
-
if (sub === null || typeof sub !== "object" || Array.isArray(sub))
|
|
23
|
+
if (sub === null || typeof sub !== "object" || Array.isArray(sub)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
20
26
|
const resolved = sel.startsWith("&")
|
|
21
27
|
? sel.replace("&", parentSel)
|
|
22
28
|
: sel.startsWith("[") || sel.startsWith(":") || sel.startsWith(".")
|
|
@@ -27,7 +33,9 @@ export function buildNestedSiteCSS(styleObj: Record<string, any>, scope: string)
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
for (const [k, v] of Object.entries(styleObj)) {
|
|
30
|
-
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
36
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
31
39
|
const resolved = k.startsWith("&")
|
|
32
40
|
? k.replace("&", scope)
|
|
33
41
|
: k.startsWith("[") || k.startsWith(":") || k.startsWith(".")
|
|
@@ -46,5 +54,5 @@ export function buildNestedSiteCSS(styleObj: Record<string, any>, scope: string)
|
|
|
46
54
|
* @returns {string}
|
|
47
55
|
*/
|
|
48
56
|
function camelToKebab(s: string) {
|
|
49
|
-
return s.
|
|
57
|
+
return s.replaceAll(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
50
58
|
}
|
|
@@ -5,24 +5,25 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
-
updateUi,
|
|
9
|
-
renderOnly,
|
|
10
|
-
getNodeAtPath,
|
|
11
|
-
parentElementPath,
|
|
12
|
-
childIndex,
|
|
13
8
|
canvasPanels,
|
|
9
|
+
childIndex,
|
|
14
10
|
elToPath,
|
|
11
|
+
getNodeAtPath,
|
|
12
|
+
parentElementPath,
|
|
13
|
+
renderOnly,
|
|
14
|
+
updateUi,
|
|
15
15
|
} from "../store";
|
|
16
16
|
import { activeTab } from "../workspace/workspace";
|
|
17
17
|
import type { JxPath } from "../state";
|
|
18
18
|
import {
|
|
19
|
-
transactDoc,
|
|
20
|
-
mutateRemoveNode,
|
|
21
19
|
mutateInsertNode,
|
|
20
|
+
mutateRemoveNode,
|
|
22
21
|
mutateUpdateProperty,
|
|
22
|
+
transactDoc,
|
|
23
23
|
} from "../tabs/transact";
|
|
24
24
|
import { view } from "../view";
|
|
25
|
-
import {
|
|
25
|
+
import { panelMediaToActiveMedia } from "../canvas/canvas-helpers";
|
|
26
|
+
import { dismissSlashMenu, isSlashMenuOpen, showSlashMenu } from "./slash-menu";
|
|
26
27
|
import { renderBlockActionBar } from "../panels/block-action-bar";
|
|
27
28
|
import { defaultDef } from "../panels/shared";
|
|
28
29
|
|
|
@@ -64,15 +65,27 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const node = getNodeAtPath(activeTab.value!.doc.document, path);
|
|
67
|
-
if (!node)
|
|
68
|
+
if (!node) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
68
71
|
|
|
69
72
|
const tc = node.textContent;
|
|
70
|
-
if (node.$props && (node.tagName || "").includes("-"))
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
73
|
+
if (node.$props && (node.tagName || "").includes("-")) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(node.children) && node.children.length > 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (node.children && typeof node.children === "object") {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (tc && typeof tc === "object") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
74
85
|
const voids = new Set(["img", "input", "br", "hr", "video", "audio", "source", "embed", "slot"]);
|
|
75
|
-
if (voids.has(node.tagName || ""))
|
|
86
|
+
if (voids.has(node.tagName || "")) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
76
89
|
|
|
77
90
|
for (const p of canvasPanels) {
|
|
78
91
|
const boxes = p.overlay.querySelectorAll(".overlay-box") as NodeListOf<HTMLElement>;
|
|
@@ -94,9 +107,9 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
|
|
|
94
107
|
|
|
95
108
|
view.componentInlineEdit = {
|
|
96
109
|
el,
|
|
97
|
-
path,
|
|
98
|
-
originalText: rawText,
|
|
99
110
|
mediaName: canvasPanels.find((p) => p.canvas.contains(el))?.mediaName || null,
|
|
111
|
+
originalText: rawText,
|
|
112
|
+
path,
|
|
100
113
|
};
|
|
101
114
|
|
|
102
115
|
el.focus();
|
|
@@ -115,31 +128,43 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
|
|
|
115
128
|
document.removeEventListener("mousedown", outsideHandler, true);
|
|
116
129
|
return;
|
|
117
130
|
}
|
|
118
|
-
if (view.componentInlineEdit.el.contains(evt.target as Node))
|
|
119
|
-
|
|
120
|
-
|
|
131
|
+
if (view.componentInlineEdit.el.contains(evt.target as Node)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (isSlashMenuOpen()) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (view.blockActionBarEl && view.blockActionBarEl.contains(evt.target as Node)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
121
140
|
document.removeEventListener("mousedown", outsideHandler, true);
|
|
122
141
|
|
|
123
|
-
let
|
|
124
|
-
|
|
142
|
+
let hitMedia = null;
|
|
143
|
+
let hitPath: (string | number)[] | null = null;
|
|
125
144
|
for (const p of canvasPanels) {
|
|
126
145
|
const els = p.canvas.querySelectorAll("*") as NodeListOf<HTMLElement>;
|
|
127
|
-
for (const
|
|
146
|
+
for (const child of els) {
|
|
147
|
+
child.style.pointerEvents = "auto";
|
|
148
|
+
}
|
|
128
149
|
p.overlayClk.style.display = "none";
|
|
129
150
|
const found = document.elementsFromPoint(evt.clientX, evt.clientY);
|
|
130
151
|
p.overlayClk.style.display = "";
|
|
131
|
-
for (const
|
|
152
|
+
for (const child of els) {
|
|
153
|
+
child.style.pointerEvents = "none";
|
|
154
|
+
}
|
|
132
155
|
for (const hit of found) {
|
|
133
156
|
if (p.canvas.contains(hit) && hit !== p.canvas) {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
hitPath =
|
|
157
|
+
const hitElPath = elToPath.get(hit);
|
|
158
|
+
if (hitElPath) {
|
|
159
|
+
hitPath = hitElPath;
|
|
137
160
|
hitMedia = p.mediaName;
|
|
138
161
|
break;
|
|
139
162
|
}
|
|
140
163
|
}
|
|
141
164
|
}
|
|
142
|
-
if (hitPath)
|
|
165
|
+
if (hitPath) {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
143
168
|
}
|
|
144
169
|
|
|
145
170
|
const { el: editEl, path: editPath, originalText } = view.componentInlineEdit;
|
|
@@ -151,8 +176,8 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
|
|
|
151
176
|
|
|
152
177
|
if (hitPath) {
|
|
153
178
|
let hp = hitPath;
|
|
154
|
-
const media =
|
|
155
|
-
updateUi("pendingInlineEdit", {
|
|
179
|
+
const media = panelMediaToActiveMedia(hitMedia);
|
|
180
|
+
updateUi("pendingInlineEdit", { mediaName: hitMedia, path: hp });
|
|
156
181
|
activeTab.value!.session.ui.activeMedia = media;
|
|
157
182
|
if (isEmpty && pPath) {
|
|
158
183
|
transactDoc(activeTab.value, (t) => {
|
|
@@ -167,7 +192,7 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
|
|
|
167
192
|
hitIdx > removedIdx
|
|
168
193
|
) {
|
|
169
194
|
hp = [...pPath, "children", hitIdx - 1];
|
|
170
|
-
updateUi("pendingInlineEdit", {
|
|
195
|
+
updateUi("pendingInlineEdit", { mediaName: hitMedia, path: hp });
|
|
171
196
|
}
|
|
172
197
|
t.session.selection = hp;
|
|
173
198
|
});
|
|
@@ -179,17 +204,15 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
|
|
|
179
204
|
} else {
|
|
180
205
|
activeTab.value!.session.selection = hp;
|
|
181
206
|
}
|
|
207
|
+
} else if (isEmpty && pPath) {
|
|
208
|
+
transactDoc(activeTab.value, (t) => mutateRemoveNode(t, editPath));
|
|
209
|
+
} else if (newText !== originalText) {
|
|
210
|
+
transactDoc(activeTab.value, (t) =>
|
|
211
|
+
mutateUpdateProperty(t, editPath, "textContent", newText || undefined),
|
|
212
|
+
);
|
|
182
213
|
} else {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} else if (newText !== originalText) {
|
|
186
|
-
transactDoc(activeTab.value, (t) =>
|
|
187
|
-
mutateUpdateProperty(t, editPath, "textContent", newText || undefined),
|
|
188
|
-
);
|
|
189
|
-
} else {
|
|
190
|
-
renderOnly("canvas");
|
|
191
|
-
renderOnly("overlays");
|
|
192
|
-
}
|
|
214
|
+
renderOnly("canvas");
|
|
215
|
+
renderOnly("overlays");
|
|
193
216
|
}
|
|
194
217
|
};
|
|
195
218
|
document.addEventListener("mousedown", outsideHandler, true);
|
|
@@ -200,8 +223,8 @@ export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
|
|
|
200
223
|
|
|
201
224
|
/** @param {KeyboardEvent} e */
|
|
202
225
|
function componentInlineKeydown(e: KeyboardEvent) {
|
|
203
|
-
if (isSlashMenuOpen()) {
|
|
204
|
-
|
|
226
|
+
if (isSlashMenuOpen() && ["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(e.key)) {
|
|
227
|
+
return;
|
|
205
228
|
}
|
|
206
229
|
|
|
207
230
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
@@ -215,7 +238,9 @@ function componentInlineKeydown(e: KeyboardEvent) {
|
|
|
215
238
|
}
|
|
216
239
|
|
|
217
240
|
function splitParagraph() {
|
|
218
|
-
if (!view.componentInlineEdit)
|
|
241
|
+
if (!view.componentInlineEdit) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
219
244
|
const { el, path, mediaName } = view.componentInlineEdit;
|
|
220
245
|
|
|
221
246
|
const sel = el.ownerDocument.defaultView?.getSelection() as Selection | null;
|
|
@@ -235,7 +260,9 @@ function splitParagraph() {
|
|
|
235
260
|
const tag = "p";
|
|
236
261
|
const pPath = parentElementPath(path) as JxPath;
|
|
237
262
|
const idx = childIndex(path) as number;
|
|
238
|
-
if (!pPath)
|
|
263
|
+
if (!pPath) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
239
266
|
|
|
240
267
|
const newDef = { tagName: tag, textContent: textAfter };
|
|
241
268
|
const newPath = [...pPath, "children", idx + 1];
|
|
@@ -248,11 +275,13 @@ function splitParagraph() {
|
|
|
248
275
|
t.session.selection = newPath;
|
|
249
276
|
});
|
|
250
277
|
|
|
251
|
-
updateUi("pendingInlineEdit", { path: newPath
|
|
278
|
+
updateUi("pendingInlineEdit", { mediaName, path: newPath });
|
|
252
279
|
}
|
|
253
280
|
|
|
254
281
|
function _commitComponentInlineEdit() {
|
|
255
|
-
if (!view.componentInlineEdit)
|
|
282
|
+
if (!view.componentInlineEdit) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
256
285
|
const { el, path, originalText } = view.componentInlineEdit;
|
|
257
286
|
const newText = (el.textContent ?? "").trim();
|
|
258
287
|
|
|
@@ -272,7 +301,9 @@ function _commitComponentInlineEdit() {
|
|
|
272
301
|
}
|
|
273
302
|
|
|
274
303
|
function cancelComponentInlineEdit() {
|
|
275
|
-
if (!view.componentInlineEdit)
|
|
304
|
+
if (!view.componentInlineEdit) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
276
307
|
const { el } = view.componentInlineEdit;
|
|
277
308
|
cleanupComponentInlineEdit(el);
|
|
278
309
|
renderOnly("canvas");
|
|
@@ -305,7 +336,9 @@ function cleanupComponentInlineEdit(el: HTMLElement) {
|
|
|
305
336
|
// ─── Component-mode slash commands ──────────────────────────────────────────
|
|
306
337
|
|
|
307
338
|
function componentInlineInput() {
|
|
308
|
-
if (!view.componentInlineEdit)
|
|
339
|
+
if (!view.componentInlineEdit) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
309
342
|
const { el, originalText } = view.componentInlineEdit;
|
|
310
343
|
const text = el.textContent || "";
|
|
311
344
|
|
|
@@ -319,11 +352,15 @@ function componentInlineInput() {
|
|
|
319
352
|
|
|
320
353
|
/** @param {{ tag: string; label: string; description: string }} cmd */
|
|
321
354
|
function handleComponentSlashSelect(cmd: { tag: string; label: string; description: string }) {
|
|
322
|
-
if (!view.componentInlineEdit)
|
|
355
|
+
if (!view.componentInlineEdit) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
323
358
|
const { el, path, mediaName } = view.componentInlineEdit;
|
|
324
359
|
const pPath = parentElementPath(path);
|
|
325
360
|
const idx = childIndex(path) as number;
|
|
326
|
-
if (!pPath)
|
|
361
|
+
if (!pPath) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
327
364
|
|
|
328
365
|
cleanupComponentInlineEdit(el);
|
|
329
366
|
|
|
@@ -337,5 +374,7 @@ function handleComponentSlashSelect(cmd: { tag: string; label: string; descripti
|
|
|
337
374
|
});
|
|
338
375
|
|
|
339
376
|
const hasText = newDef.textContent != null;
|
|
340
|
-
if (hasText)
|
|
377
|
+
if (hasText) {
|
|
378
|
+
updateUi("pendingInlineEdit", { mediaName, path: newPath });
|
|
379
|
+
}
|
|
341
380
|
}
|