@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
package/src/panels/left-panel.ts
CHANGED
|
@@ -11,26 +11,36 @@ import { html, render as litRender, nothing } from "lit-html";
|
|
|
11
11
|
import type { TemplateResult } from "lit-html";
|
|
12
12
|
import { leftPanel, updateSession } from "../store";
|
|
13
13
|
import { effect, effectScope } from "../reactivity";
|
|
14
|
-
import { createPanelScheduler
|
|
14
|
+
import { createPanelScheduler } from "./panel-scheduler";
|
|
15
|
+
import type { PanelScheduler } from "./panel-scheduler";
|
|
15
16
|
import { activeTab } from "../workspace/workspace";
|
|
16
17
|
import { view } from "../view";
|
|
17
|
-
import {
|
|
18
|
+
import { mutateUpdateFrontmatter, transact } from "../tabs/transact";
|
|
18
19
|
import type { GitDiffState, JsonValue } from "../types";
|
|
20
|
+
import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
|
|
19
21
|
|
|
20
22
|
import { renderLayersTemplate } from "./layers-panel";
|
|
21
23
|
import { renderStylebookLayersTemplate } from "./stylebook-layers-panel";
|
|
22
24
|
import { renderElementsTemplate } from "./elements-panel";
|
|
23
25
|
import { selectStylebookTag, stylebookMeta } from "./stylebook-panel";
|
|
26
|
+
import type { renderImportsTemplate } from "./imports-panel";
|
|
27
|
+
import type { renderSignalsTemplate } from "./signals-panel";
|
|
28
|
+
import type { renderDataExplorerTemplate } from "./data-explorer";
|
|
29
|
+
import type { renderHeadTemplate } from "./head-panel";
|
|
30
|
+
import type { renderGitPanel } from "./git-panel";
|
|
31
|
+
import type { EffectScope } from "@vue/reactivity";
|
|
24
32
|
|
|
25
33
|
interface LeftPanelCtx {
|
|
26
34
|
getCanvasMode: () => string;
|
|
27
35
|
setCanvasMode: (mode: string) => void;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
// Renderers injected from studio.ts (dependency inversion avoids circular imports);
|
|
37
|
+
// Typed against their implementations so call sites stay checked.
|
|
38
|
+
renderImportsTemplate: typeof renderImportsTemplate;
|
|
39
|
+
renderFilesTemplate: () => TemplateResult;
|
|
40
|
+
renderSignalsTemplate: typeof renderSignalsTemplate;
|
|
41
|
+
renderDataExplorerTemplate: typeof renderDataExplorerTemplate;
|
|
42
|
+
renderHeadTemplate: typeof renderHeadTemplate;
|
|
43
|
+
renderGitPanel: typeof renderGitPanel;
|
|
34
44
|
renderCanvas: () => void;
|
|
35
45
|
defCategory: (def: unknown) => string;
|
|
36
46
|
defBadgeLabel: (def: unknown) => string;
|
|
@@ -48,7 +58,7 @@ interface LeftPanelCtx {
|
|
|
48
58
|
|
|
49
59
|
let _ctx: LeftPanelCtx | null = null;
|
|
50
60
|
|
|
51
|
-
let _scope:
|
|
61
|
+
let _scope: EffectScope | null = null;
|
|
52
62
|
|
|
53
63
|
let _scheduler: PanelScheduler | null = null;
|
|
54
64
|
|
|
@@ -59,7 +69,7 @@ let _scheduler: PanelScheduler | null = null;
|
|
|
59
69
|
*/
|
|
60
70
|
export function mount(ctx: LeftPanelCtx) {
|
|
61
71
|
_ctx = ctx;
|
|
62
|
-
_scheduler = createPanelScheduler({
|
|
72
|
+
_scheduler = createPanelScheduler({ render: _doRender, root: leftPanel });
|
|
63
73
|
_scheduler.bindFocus();
|
|
64
74
|
_scope = effectScope();
|
|
65
75
|
_scope.run(() => {
|
|
@@ -98,27 +108,42 @@ export function render() {
|
|
|
98
108
|
|
|
99
109
|
/** Actual DOM paint, invoked by the scheduler. Includes a Lit-marker-corruption recovery retry. */
|
|
100
110
|
function _doRender() {
|
|
101
|
-
if (!_ctx)
|
|
111
|
+
if (!_ctx) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
102
114
|
try {
|
|
103
115
|
_render();
|
|
104
|
-
} catch (
|
|
105
|
-
console.error("left-panel render error:",
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("left-panel render error:", error);
|
|
106
118
|
try {
|
|
107
119
|
leftPanel.textContent = "";
|
|
108
|
-
// @ts-
|
|
120
|
+
// @ts-expect-error — clear Lit's internal state to recover from marker corruption
|
|
109
121
|
delete leftPanel["_$litPart$"];
|
|
110
122
|
_render();
|
|
111
|
-
} catch (
|
|
112
|
-
console.error("left-panel retry failed:",
|
|
123
|
+
} catch (retryError) {
|
|
124
|
+
console.error("left-panel retry failed:", retryError);
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
if (view.leftTab === "layers") {
|
|
117
129
|
const sel = leftPanel.querySelector(".layer-row.selected");
|
|
118
|
-
if (sel)
|
|
130
|
+
if (sel) {
|
|
131
|
+
sel.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
132
|
+
}
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
135
|
|
|
136
|
+
/** Overlay content-mode frontmatter title/$head onto the document for the head panel. */
|
|
137
|
+
function buildHeadDoc(doc: JxMutableNode, fm: Record<string, unknown>): JxMutableNode {
|
|
138
|
+
const title = fm.title as string | undefined;
|
|
139
|
+
const $head = fm.$head as JxHeadEntry[] | undefined;
|
|
140
|
+
return {
|
|
141
|
+
...doc,
|
|
142
|
+
...(title !== undefined ? { title } : {}),
|
|
143
|
+
...($head !== undefined ? { $head } : {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
122
147
|
function _render() {
|
|
123
148
|
const ctx = _ctx as LeftPanelCtx;
|
|
124
149
|
const tab = view.leftTab;
|
|
@@ -128,23 +153,25 @@ function _render() {
|
|
|
128
153
|
if (tab === "files") {
|
|
129
154
|
litRender(html`<div class="panel-body">${ctx.renderFilesTemplate()}</div>`, leftPanel);
|
|
130
155
|
const tree = leftPanel.querySelector(".file-tree") as HTMLElement | null;
|
|
131
|
-
if (tree)
|
|
156
|
+
if (tree) {
|
|
157
|
+
ctx.setupTreeKeyboard(tree);
|
|
158
|
+
}
|
|
132
159
|
ctx.registerFileTreeDnD({ renderLeftPanel: render });
|
|
133
160
|
return;
|
|
134
161
|
}
|
|
135
162
|
|
|
136
163
|
if (tab === "git") {
|
|
137
164
|
const aTab = activeTab.value;
|
|
138
|
-
const
|
|
139
|
-
litRender(html`<div class="panel-body">${ctx.renderGitPanel(
|
|
165
|
+
const ui: Parameters<LeftPanelCtx["renderGitPanel"]>[0]["ui"] = aTab ? aTab.session.ui : {};
|
|
166
|
+
litRender(html`<div class="panel-body">${ctx.renderGitPanel({ ui }, ctx)}</div>`, leftPanel);
|
|
140
167
|
return;
|
|
141
168
|
}
|
|
142
169
|
|
|
143
170
|
if (tab === "blocks") {
|
|
144
171
|
const content = renderElementsTemplate({
|
|
145
|
-
webdata: ctx.webdata,
|
|
146
172
|
defaultDef: ctx.defaultDef,
|
|
147
173
|
rerender: render,
|
|
174
|
+
webdata: ctx.webdata,
|
|
148
175
|
} as Parameters<typeof renderElementsTemplate>[0]);
|
|
149
176
|
litRender(html`<div class="panel-body">${content}</div>`, leftPanel);
|
|
150
177
|
ctx.registerElementsDnD();
|
|
@@ -171,18 +198,18 @@ function _render() {
|
|
|
171
198
|
* documentPath?: string;
|
|
172
199
|
* }}
|
|
173
200
|
*/ {
|
|
174
|
-
ui: aTab.session.ui,
|
|
175
|
-
document: aTab.doc.document,
|
|
176
|
-
mode: aTab.doc.mode,
|
|
177
|
-
selection: aTab.session.selection,
|
|
178
201
|
canvas: aTab.session.canvas,
|
|
179
202
|
content: aTab.doc.content,
|
|
203
|
+
document: aTab.doc.document,
|
|
180
204
|
documentPath: aTab.documentPath,
|
|
205
|
+
mode: aTab.doc.mode,
|
|
206
|
+
selection: aTab.session.selection,
|
|
207
|
+
ui: aTab.session.ui,
|
|
181
208
|
};
|
|
182
209
|
|
|
183
210
|
/** @type {TemplateResult | typeof nothing} */
|
|
184
211
|
let content;
|
|
185
|
-
if (tab === "layers")
|
|
212
|
+
if (tab === "layers") {
|
|
186
213
|
content =
|
|
187
214
|
ctx.getCanvasMode() === "stylebook"
|
|
188
215
|
? renderStylebookLayersTemplate({
|
|
@@ -193,59 +220,65 @@ function _render() {
|
|
|
193
220
|
navigateToComponent: ctx.navigateToComponent,
|
|
194
221
|
rerender: render,
|
|
195
222
|
});
|
|
196
|
-
else if (tab === "imports")
|
|
223
|
+
} else if (tab === "imports") {
|
|
197
224
|
content = ctx.renderImportsTemplate({
|
|
198
|
-
|
|
199
|
-
documentPath: S.documentPath,
|
|
200
|
-
documentElements: S.document.$elements || [],
|
|
201
|
-
applyMutation: (fn: (doc: object) => void) => {
|
|
225
|
+
applyMutation: (fn: (doc: JxMutableNode) => void) => {
|
|
202
226
|
transact(activeTab.value, fn);
|
|
203
227
|
},
|
|
228
|
+
documentElements: S.document.$elements || [],
|
|
229
|
+
documentPath: S.documentPath,
|
|
230
|
+
renderLeftPanel: render,
|
|
204
231
|
});
|
|
205
|
-
else if (tab === "state")
|
|
232
|
+
} else if (tab === "state") {
|
|
206
233
|
content = ctx.renderSignalsTemplate(S, {
|
|
207
|
-
renderLeftPanel: render,
|
|
208
234
|
renderCanvas: ctx.renderCanvas,
|
|
235
|
+
renderLeftPanel: render,
|
|
209
236
|
updateSession,
|
|
210
237
|
});
|
|
211
|
-
else if (tab === "data")
|
|
212
|
-
content = ctx.renderDataExplorerTemplate(S.document.state, S.canvas?.scope ?? null, {
|
|
238
|
+
} else if (tab === "data") {
|
|
239
|
+
content = ctx.renderDataExplorerTemplate(S.document.state ?? {}, S.canvas?.scope ?? null, {
|
|
240
|
+
defBadgeLabel: ctx.defBadgeLabel,
|
|
241
|
+
defCategory: ctx.defCategory,
|
|
213
242
|
renderCanvas: ctx.renderCanvas,
|
|
214
243
|
renderLeftPanel: render,
|
|
215
|
-
defCategory: ctx.defCategory,
|
|
216
|
-
defBadgeLabel: ctx.defBadgeLabel,
|
|
217
244
|
});
|
|
218
|
-
else if (tab === "head") {
|
|
245
|
+
} else if (tab === "head") {
|
|
219
246
|
const isContent = S.mode === "content";
|
|
220
247
|
const fm = S.content?.frontmatter ?? {};
|
|
221
|
-
const headDoc = isContent ?
|
|
248
|
+
const headDoc = isContent ? buildHeadDoc(S.document, fm) : S.document;
|
|
222
249
|
content = ctx.renderHeadTemplate({
|
|
223
|
-
document: headDoc,
|
|
224
250
|
applyMutation: isContent
|
|
225
|
-
? (fn: (doc:
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
const fmHead =
|
|
229
|
-
const tmp = {
|
|
230
|
-
title:
|
|
231
|
-
$head:
|
|
251
|
+
? (fn: (doc: JxMutableNode) => void) => {
|
|
252
|
+
const tabNow = activeTab.value!;
|
|
253
|
+
const fmNow = (tabNow.doc.content?.frontmatter ?? {}) as Record<string, unknown>;
|
|
254
|
+
const fmHead = fmNow.$head as JxHeadEntry[] | undefined;
|
|
255
|
+
const tmp: JxMutableNode = {
|
|
256
|
+
...(typeof fmNow.title === "string" ? { title: fmNow.title } : {}),
|
|
257
|
+
...(fmHead ? { $head: [...fmHead] } : {}),
|
|
232
258
|
};
|
|
233
259
|
fn(tmp);
|
|
234
|
-
if (tmp.title !==
|
|
235
|
-
mutateUpdateFrontmatter(
|
|
260
|
+
if (tmp.title !== fmNow.title) {
|
|
261
|
+
mutateUpdateFrontmatter(tabNow, "title", tmp.title as JsonValue);
|
|
262
|
+
}
|
|
236
263
|
const newHead = tmp.$head && tmp.$head.length > 0 ? tmp.$head : undefined;
|
|
237
|
-
|
|
264
|
+
// JxHeadEntry[] is JSON document content by construction.
|
|
265
|
+
mutateUpdateFrontmatter(tabNow, "$head", newHead as JsonValue);
|
|
238
266
|
render();
|
|
239
267
|
}
|
|
240
|
-
: (fn: (doc:
|
|
268
|
+
: (fn: (doc: JxMutableNode) => void) => {
|
|
241
269
|
transact(activeTab.value, fn);
|
|
242
270
|
},
|
|
271
|
+
document: headDoc,
|
|
243
272
|
renderLeftPanel: render,
|
|
244
273
|
});
|
|
245
|
-
} else
|
|
274
|
+
} else {
|
|
275
|
+
content = nothing;
|
|
276
|
+
}
|
|
246
277
|
|
|
247
278
|
litRender(html`<div class="panel-body">${content}</div>`, leftPanel);
|
|
248
279
|
|
|
249
280
|
// Post-render side effects
|
|
250
|
-
if (tab === "layers" && ctx.getCanvasMode() !== "stylebook")
|
|
281
|
+
if (tab === "layers" && ctx.getCanvasMode() !== "stylebook") {
|
|
282
|
+
ctx.registerLayersDnD();
|
|
283
|
+
}
|
|
251
284
|
}
|
package/src/panels/overlays.ts
CHANGED
|
@@ -10,8 +10,9 @@ import { canvasPanels, pathsEqual } from "../store";
|
|
|
10
10
|
import { effect, effectScope } from "../reactivity";
|
|
11
11
|
import { activeTab } from "../workspace/workspace";
|
|
12
12
|
import { view } from "../view";
|
|
13
|
-
import { findCanvasElement, getActivePanel
|
|
13
|
+
import { effectiveZoom, findCanvasElement, getActivePanel } from "../canvas/canvas-helpers";
|
|
14
14
|
import { layoutElements } from "../canvas/canvas-live-render";
|
|
15
|
+
import type { EffectScope } from "@vue/reactivity";
|
|
15
16
|
|
|
16
17
|
interface OverlayBox {
|
|
17
18
|
cls: string;
|
|
@@ -31,7 +32,7 @@ interface OverlaysCtx {
|
|
|
31
32
|
|
|
32
33
|
let _ctx: OverlaysCtx | null = null;
|
|
33
34
|
|
|
34
|
-
let _scope:
|
|
35
|
+
let _scope: EffectScope | null = null;
|
|
35
36
|
|
|
36
37
|
let _scheduled = false;
|
|
37
38
|
|
|
@@ -46,7 +47,9 @@ export function mount(ctx: OverlaysCtx) {
|
|
|
46
47
|
_scope.run(() => {
|
|
47
48
|
effect(() => {
|
|
48
49
|
const tab = activeTab.value;
|
|
49
|
-
if (!tab)
|
|
50
|
+
if (!tab) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
50
53
|
// Track selection, hover, and mode
|
|
51
54
|
void tab.session.selection;
|
|
52
55
|
void tab.session.hover;
|
|
@@ -63,7 +66,9 @@ export function unmount() {
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
export function render() {
|
|
66
|
-
if (!_ctx)
|
|
69
|
+
if (!_ctx) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
67
72
|
if (!_scheduled) {
|
|
68
73
|
_scheduled = true;
|
|
69
74
|
queueMicrotask(_flush);
|
|
@@ -72,9 +77,13 @@ export function render() {
|
|
|
72
77
|
|
|
73
78
|
function _flush() {
|
|
74
79
|
_scheduled = false;
|
|
75
|
-
if (!_ctx)
|
|
80
|
+
if (!_ctx) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
76
83
|
const tab = activeTab.value;
|
|
77
|
-
if (!tab)
|
|
84
|
+
if (!tab) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
78
87
|
const { selection, hover } = tab.session;
|
|
79
88
|
const { stylebookTab } = tab.session.ui;
|
|
80
89
|
const canvasMode = _ctx.getCanvasMode();
|
|
@@ -112,10 +121,12 @@ function _flush() {
|
|
|
112
121
|
const boxes: OverlayBox[] = [];
|
|
113
122
|
|
|
114
123
|
// Batch layout reads: read viewport geometry once per panel
|
|
115
|
-
if (!p.viewport)
|
|
124
|
+
if (!p.viewport) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
116
127
|
const vpRect = p.viewport.getBoundingClientRect();
|
|
117
|
-
const scrollTop = p.viewport
|
|
118
|
-
const scrollLeft = p.viewport
|
|
128
|
+
const { scrollTop } = p.viewport;
|
|
129
|
+
const { scrollLeft } = p.viewport;
|
|
119
130
|
const scale = effectiveZoom();
|
|
120
131
|
|
|
121
132
|
if (hover && !pathsEqual(hover, selection)) {
|
|
@@ -124,12 +135,14 @@ function _flush() {
|
|
|
124
135
|
const elRect = el.getBoundingClientRect();
|
|
125
136
|
const desc: OverlayBox = {
|
|
126
137
|
cls: "overlay-box overlay-hover",
|
|
127
|
-
|
|
138
|
+
height: `${elRect.height / scale}px`,
|
|
128
139
|
left: `${(elRect.left - vpRect.left + scrollLeft) / scale}px`,
|
|
140
|
+
top: `${(elRect.top - vpRect.top + scrollTop) / scale}px`,
|
|
129
141
|
width: `${elRect.width / scale}px`,
|
|
130
|
-
height: `${elRect.height / scale}px`,
|
|
131
142
|
};
|
|
132
|
-
if (layoutElements.has(el))
|
|
143
|
+
if (layoutElements.has(el)) {
|
|
144
|
+
desc.isLayout = true;
|
|
145
|
+
}
|
|
133
146
|
boxes.push(desc);
|
|
134
147
|
}
|
|
135
148
|
}
|
|
@@ -140,13 +153,17 @@ function _flush() {
|
|
|
140
153
|
const elRect = el.getBoundingClientRect();
|
|
141
154
|
const desc: OverlayBox = {
|
|
142
155
|
cls: "overlay-box overlay-selection",
|
|
143
|
-
|
|
156
|
+
height: `${elRect.height / scale}px`,
|
|
144
157
|
left: `${(elRect.left - vpRect.left + scrollLeft) / scale}px`,
|
|
158
|
+
top: `${(elRect.top - vpRect.top + scrollTop) / scale}px`,
|
|
145
159
|
width: `${elRect.width / scale}px`,
|
|
146
|
-
height: `${elRect.height / scale}px`,
|
|
147
160
|
};
|
|
148
|
-
if (view.componentInlineEdit || _ctx.isEditing())
|
|
149
|
-
|
|
161
|
+
if (view.componentInlineEdit || _ctx.isEditing()) {
|
|
162
|
+
desc.border = "none";
|
|
163
|
+
}
|
|
164
|
+
if (layoutElements.has(el)) {
|
|
165
|
+
desc.isLayout = true;
|
|
166
|
+
}
|
|
150
167
|
boxes.push(desc);
|
|
151
168
|
}
|
|
152
169
|
}
|
|
@@ -159,11 +176,11 @@ function _flush() {
|
|
|
159
176
|
<div
|
|
160
177
|
class="${b.cls}${b.isLayout ? " overlay-layout" : ""}"
|
|
161
178
|
style=${styleMap({
|
|
162
|
-
|
|
179
|
+
border: b.border,
|
|
180
|
+
height: b.height,
|
|
163
181
|
left: b.left,
|
|
182
|
+
top: b.top,
|
|
164
183
|
width: b.width,
|
|
165
|
-
height: b.height,
|
|
166
|
-
border: b.border,
|
|
167
184
|
})}
|
|
168
185
|
>
|
|
169
186
|
${b.isLayout ? html`<span class="overlay-layout-badge">Layout</span>` : nothing}
|
|
@@ -6,22 +6,27 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
updateUi,
|
|
10
|
-
elToPath,
|
|
11
|
-
pathsEqual,
|
|
12
|
-
parentElementPath,
|
|
13
9
|
childIndex,
|
|
10
|
+
elToPath,
|
|
14
11
|
getNodeAtPath,
|
|
12
|
+
parentElementPath,
|
|
13
|
+
pathsEqual,
|
|
15
14
|
renderOnly,
|
|
15
|
+
updateUi,
|
|
16
16
|
} from "../store";
|
|
17
17
|
import { activeTab } from "../workspace/workspace";
|
|
18
18
|
import { view } from "../view";
|
|
19
|
-
import {
|
|
19
|
+
import { isEditableBlock, isEditing, stopEditing } from "../editor/inline-edit";
|
|
20
20
|
import { showContextMenu } from "../editor/context-menu";
|
|
21
21
|
import * as insertionHelper from "../editor/insertion-helper";
|
|
22
22
|
import { defaultDef } from "../panels/shared";
|
|
23
|
-
import {
|
|
24
|
-
|
|
23
|
+
import {
|
|
24
|
+
bubbleInlinePath,
|
|
25
|
+
effectiveZoom,
|
|
26
|
+
findCanvasElement,
|
|
27
|
+
panelMediaToActiveMedia,
|
|
28
|
+
} from "../canvas/canvas-helpers";
|
|
29
|
+
import { activeLayoutPath, layoutElements } from "../canvas/canvas-live-render";
|
|
25
30
|
import type { CanvasPanel } from "./canvas-dnd";
|
|
26
31
|
import type { JxPath } from "../state";
|
|
27
32
|
|
|
@@ -55,11 +60,15 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
55
60
|
/** @param {() => unknown} fn */
|
|
56
61
|
function withPanelPointerEvents(fn: () => unknown) {
|
|
57
62
|
const els = canvas.querySelectorAll("*") as NodeListOf<HTMLElement>;
|
|
58
|
-
for (const el of els)
|
|
63
|
+
for (const el of els) {
|
|
64
|
+
el.style.pointerEvents = "auto";
|
|
65
|
+
}
|
|
59
66
|
overlayClk.style.display = "none";
|
|
60
67
|
const result = fn();
|
|
61
68
|
overlayClk.style.display = "";
|
|
62
|
-
for (const el of els)
|
|
69
|
+
for (const el of els) {
|
|
70
|
+
el.style.pointerEvents = "none";
|
|
71
|
+
}
|
|
63
72
|
return result;
|
|
64
73
|
}
|
|
65
74
|
|
|
@@ -74,8 +83,9 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
74
83
|
e.clientX <= r.right &&
|
|
75
84
|
e.clientY >= r.top &&
|
|
76
85
|
e.clientY <= r.bottom
|
|
77
|
-
)
|
|
86
|
+
) {
|
|
78
87
|
return;
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
90
|
if (isEditing()) {
|
|
81
91
|
stopEditing();
|
|
@@ -88,7 +98,9 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
88
98
|
document.elementsFromPoint(e.clientX, e.clientY),
|
|
89
99
|
) as Element[];
|
|
90
100
|
|
|
91
|
-
if (!tab)
|
|
101
|
+
if (!tab) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
92
104
|
|
|
93
105
|
for (const el of elements) {
|
|
94
106
|
if (canvas.contains(el) && el !== canvas) {
|
|
@@ -103,8 +115,8 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
103
115
|
|
|
104
116
|
const originalPath = elToPath.get(el);
|
|
105
117
|
if (originalPath) {
|
|
106
|
-
|
|
107
|
-
const newMedia =
|
|
118
|
+
const path = bubbleInlinePath(tab.doc.document, originalPath);
|
|
119
|
+
const newMedia = panelMediaToActiveMedia(mediaName);
|
|
108
120
|
|
|
109
121
|
const resolvedEl = (
|
|
110
122
|
path === originalPath
|
|
@@ -123,7 +135,7 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
if (canvasMode === "design" && tab.doc.mode !== "content") {
|
|
126
|
-
updateUi("pendingInlineEdit", {
|
|
138
|
+
updateUi("pendingInlineEdit", { mediaName, path });
|
|
127
139
|
tab.session.ui.activeMedia = newMedia;
|
|
128
140
|
tab.session.selection = path;
|
|
129
141
|
return;
|
|
@@ -151,18 +163,23 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
151
163
|
e.clientX <= r.right &&
|
|
152
164
|
e.clientY >= r.top &&
|
|
153
165
|
e.clientY <= r.bottom
|
|
154
|
-
)
|
|
166
|
+
) {
|
|
155
167
|
return;
|
|
168
|
+
}
|
|
156
169
|
}
|
|
157
170
|
const canvasMode = ctx.getCanvasMode();
|
|
158
|
-
if (canvasMode !== "edit" && canvasMode !== "design")
|
|
171
|
+
if (canvasMode !== "edit" && canvasMode !== "design") {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
159
174
|
|
|
160
175
|
const tab = activeTab.value;
|
|
161
176
|
const elements = withPanelPointerEvents(() =>
|
|
162
177
|
document.elementsFromPoint(e.clientX, e.clientY),
|
|
163
178
|
) as Element[];
|
|
164
179
|
|
|
165
|
-
if (!tab)
|
|
180
|
+
if (!tab) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
166
183
|
|
|
167
184
|
for (const el of elements) {
|
|
168
185
|
if (canvas.contains(el) && el !== canvas) {
|
|
@@ -175,7 +192,7 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
175
192
|
: (findCanvasElement(path, canvas) as HTMLElement | null) || el
|
|
176
193
|
) as HTMLElement;
|
|
177
194
|
if (isEditableBlock(resolvedEl)) {
|
|
178
|
-
const newMedia =
|
|
195
|
+
const newMedia = panelMediaToActiveMedia(mediaName);
|
|
179
196
|
tab.session.ui.activeMedia = newMedia;
|
|
180
197
|
tab.session.selection = path;
|
|
181
198
|
ctx.enterInlineEdit(resolvedEl, path);
|
|
@@ -199,8 +216,9 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
199
216
|
e.clientX <= r.right &&
|
|
200
217
|
e.clientY >= r.top &&
|
|
201
218
|
e.clientY <= r.bottom
|
|
202
|
-
)
|
|
219
|
+
) {
|
|
203
220
|
return;
|
|
221
|
+
}
|
|
204
222
|
}
|
|
205
223
|
const tab = activeTab.value;
|
|
206
224
|
const elements = withPanelPointerEvents(() =>
|
|
@@ -234,11 +252,14 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
234
252
|
e.clientX <= r.right &&
|
|
235
253
|
e.clientY >= r.top &&
|
|
236
254
|
e.clientY <= r.bottom
|
|
237
|
-
)
|
|
255
|
+
) {
|
|
238
256
|
return;
|
|
257
|
+
}
|
|
239
258
|
}
|
|
240
259
|
const tab = activeTab.value;
|
|
241
|
-
if (!tab)
|
|
260
|
+
if (!tab) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
242
263
|
const el = withPanelPointerEvents(() =>
|
|
243
264
|
document.elementFromPoint(e.clientX, e.clientY),
|
|
244
265
|
) as Element | null;
|
|
@@ -271,15 +292,15 @@ export function registerPanelEvents(panel: CanvasPanel) {
|
|
|
271
292
|
);
|
|
272
293
|
|
|
273
294
|
insertionHelper.mount({
|
|
274
|
-
|
|
275
|
-
withPanelPointerEvents,
|
|
276
|
-
effectiveZoom: effectiveZoom,
|
|
295
|
+
childIndex: (path: JxPath) => childIndex(path) ?? 0,
|
|
277
296
|
defaultDef,
|
|
278
|
-
|
|
279
|
-
childIndex,
|
|
280
|
-
getNodeAtPath,
|
|
297
|
+
effectiveZoom,
|
|
281
298
|
elToPath,
|
|
299
|
+
getCanvasMode: ctx.getCanvasMode,
|
|
300
|
+
getNodeAtPath,
|
|
282
301
|
panel: panel as unknown as Parameters<typeof insertionHelper.mount>[0]["panel"],
|
|
302
|
+
parentElementPath,
|
|
303
|
+
withPanelPointerEvents,
|
|
283
304
|
});
|
|
284
305
|
view.canvasEventCleanups.push(() => insertionHelper.unmount());
|
|
285
306
|
}
|
|
@@ -17,25 +17,33 @@
|
|
|
17
17
|
* @returns {boolean}
|
|
18
18
|
*/
|
|
19
19
|
export function isTextInput(el: Element | null): boolean {
|
|
20
|
-
if (!el)
|
|
20
|
+
if (!el) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
21
23
|
const tag = el.tagName.toLowerCase();
|
|
22
|
-
if (tag === "input" || tag === "textarea")
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
if (tag === "input" || tag === "textarea") {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (tag === "sp-textfield" || tag === "sp-number-field" || tag === "sp-search") {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (el.shadowRoot?.activeElement) {
|
|
31
|
+
return isTextInput(el.shadowRoot.activeElement);
|
|
32
|
+
}
|
|
25
33
|
return false;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
export interface PanelScheduler {
|
|
29
37
|
/** Request a render. Coalesced; deferred while a text input in the panel is focused. */
|
|
30
|
-
schedule()
|
|
38
|
+
schedule: () => void;
|
|
31
39
|
/** True when a render would currently be deferred (text input focused or blockWhile()). */
|
|
32
|
-
isEditing()
|
|
40
|
+
isEditing: () => boolean;
|
|
33
41
|
/** Attach focusin/focusout listeners to the panel root. */
|
|
34
|
-
bindFocus()
|
|
42
|
+
bindFocus: () => void;
|
|
35
43
|
/** Detach listeners and cancel any pending frame. */
|
|
36
|
-
unbind()
|
|
44
|
+
unbind: () => void;
|
|
37
45
|
/** Render immediately, bypassing the rAF coalescing (still respects the focus guard). */
|
|
38
|
-
flushNow()
|
|
46
|
+
flushNow: () => void;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
/**
|
|
@@ -67,7 +75,9 @@ export function createPanelScheduler(opts: {
|
|
|
67
75
|
function flush() {
|
|
68
76
|
scheduled = false;
|
|
69
77
|
rafId = 0;
|
|
70
|
-
if (rendering)
|
|
78
|
+
if (rendering) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
71
81
|
// Defer while a field is focused — flushed later by focusout (or the next schedule()).
|
|
72
82
|
if (blocked()) {
|
|
73
83
|
pending = true;
|
|
@@ -83,7 +93,9 @@ export function createPanelScheduler(opts: {
|
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
function schedule() {
|
|
86
|
-
if (scheduled)
|
|
96
|
+
if (scheduled) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
87
99
|
scheduled = true;
|
|
88
100
|
rafId = requestAnimationFrame(flush);
|
|
89
101
|
}
|
|
@@ -94,33 +106,39 @@ export function createPanelScheduler(opts: {
|
|
|
94
106
|
|
|
95
107
|
function onFocusOut() {
|
|
96
108
|
// Clear the editing flag and schedule a flush. If focus is merely moving to another field in
|
|
97
|
-
//
|
|
98
|
-
//
|
|
109
|
+
// The same panel, the synchronous focusin that follows re-sets `editing` before the rAF runs,
|
|
110
|
+
// So the deferred render keeps waiting (no mid-edit clobber).
|
|
99
111
|
editing = false;
|
|
100
|
-
if (pending)
|
|
112
|
+
if (pending) {
|
|
113
|
+
schedule();
|
|
114
|
+
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
return {
|
|
104
|
-
schedule,
|
|
105
|
-
isEditing: () => blocked(),
|
|
106
118
|
bindFocus() {
|
|
107
119
|
root.addEventListener("focusin", onFocusIn);
|
|
108
120
|
root.addEventListener("focusout", onFocusOut);
|
|
109
121
|
},
|
|
122
|
+
flushNow() {
|
|
123
|
+
if (rafId) {
|
|
124
|
+
cancelAnimationFrame(rafId);
|
|
125
|
+
}
|
|
126
|
+
rafId = 0;
|
|
127
|
+
scheduled = false;
|
|
128
|
+
flush();
|
|
129
|
+
},
|
|
130
|
+
isEditing: () => blocked(),
|
|
131
|
+
schedule,
|
|
110
132
|
unbind() {
|
|
111
133
|
root.removeEventListener("focusin", onFocusIn);
|
|
112
134
|
root.removeEventListener("focusout", onFocusOut);
|
|
113
|
-
if (rafId)
|
|
135
|
+
if (rafId) {
|
|
136
|
+
cancelAnimationFrame(rafId);
|
|
137
|
+
}
|
|
114
138
|
scheduled = false;
|
|
115
139
|
pending = false;
|
|
116
140
|
editing = false;
|
|
117
141
|
rendering = false;
|
|
118
142
|
},
|
|
119
|
-
flushNow() {
|
|
120
|
-
if (rafId) cancelAnimationFrame(rafId);
|
|
121
|
-
rafId = 0;
|
|
122
|
-
scheduled = false;
|
|
123
|
-
flush();
|
|
124
|
-
},
|
|
125
143
|
};
|
|
126
144
|
}
|