@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
|
@@ -32,25 +32,40 @@ const FORMAT_TAGS = new Set([
|
|
|
32
32
|
* @returns {boolean}
|
|
33
33
|
*/
|
|
34
34
|
export function isTagActiveInSelection(tag: string, editableRoot: HTMLElement | null) {
|
|
35
|
-
if (!editableRoot)
|
|
36
|
-
|
|
35
|
+
if (!editableRoot) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (editableRoot.contentEditable === "plaintext-only") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
37
41
|
const sel = window.getSelection();
|
|
38
|
-
if (!sel || !sel.rangeCount)
|
|
42
|
+
if (!sel || !sel.rangeCount) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
39
45
|
|
|
40
46
|
const anchor = sel.anchorNode;
|
|
41
47
|
const focus = sel.focusNode;
|
|
42
|
-
if (!anchor || !focus)
|
|
43
|
-
|
|
48
|
+
if (!anchor || !focus) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (!editableRoot.contains(anchor) || !editableRoot.contains(focus)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
44
54
|
|
|
45
55
|
/**
|
|
46
56
|
* @param {Node | null} node
|
|
47
57
|
* @returns {boolean}
|
|
48
58
|
*/
|
|
49
59
|
const hasTag = (node: Node | null) => {
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
let current = node;
|
|
61
|
+
while (current && current !== editableRoot) {
|
|
62
|
+
if (
|
|
63
|
+
current.nodeType === Node.ELEMENT_NODE &&
|
|
64
|
+
(current as Element).tagName.toLowerCase() === tag
|
|
65
|
+
) {
|
|
52
66
|
return true;
|
|
53
|
-
|
|
67
|
+
}
|
|
68
|
+
current = current.parentNode;
|
|
54
69
|
}
|
|
55
70
|
return false;
|
|
56
71
|
};
|
|
@@ -66,12 +81,20 @@ export function isTagActiveInSelection(tag: string, editableRoot: HTMLElement |
|
|
|
66
81
|
* @param {HTMLElement | null} editableRoot
|
|
67
82
|
*/
|
|
68
83
|
export function toggleInlineFormat(tag: string, editableRoot: HTMLElement | null) {
|
|
69
|
-
if (!editableRoot)
|
|
84
|
+
if (!editableRoot) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
70
87
|
const sel = window.getSelection();
|
|
71
|
-
if (!sel || !sel.rangeCount)
|
|
88
|
+
if (!sel || !sel.rangeCount) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
72
91
|
const range = sel.getRangeAt(0);
|
|
73
|
-
if (range.collapsed)
|
|
74
|
-
|
|
92
|
+
if (range.collapsed) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!editableRoot.contains(range.commonAncestorContainer)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
75
98
|
|
|
76
99
|
// Expand selection to fully include any partially-selected ${...} expressions
|
|
77
100
|
expandRangeToTemplateExpressions(range);
|
|
@@ -145,12 +168,14 @@ function unwrapTagInRange(
|
|
|
145
168
|
*/
|
|
146
169
|
function unwrapElement(el: Element) {
|
|
147
170
|
const parent = el.parentNode;
|
|
148
|
-
if (!parent)
|
|
171
|
+
if (!parent) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
149
174
|
const frag = document.createDocumentFragment();
|
|
150
175
|
while (el.firstChild) {
|
|
151
|
-
frag.
|
|
176
|
+
frag.append(el.firstChild);
|
|
152
177
|
}
|
|
153
|
-
|
|
178
|
+
el.replaceWith(frag);
|
|
154
179
|
}
|
|
155
180
|
|
|
156
181
|
/**
|
|
@@ -171,7 +196,9 @@ function wrapRangeInTag(tag: string, range: Range, _editableRoot: HTMLElement) {
|
|
|
171
196
|
|
|
172
197
|
// If nothing left after trimming, re-insert everything and bail
|
|
173
198
|
if (!contents.hasChildNodes()) {
|
|
174
|
-
if (leadingWS)
|
|
199
|
+
if (leadingWS) {
|
|
200
|
+
range.insertNode(document.createTextNode(leadingWS));
|
|
201
|
+
}
|
|
175
202
|
if (trailingWS) {
|
|
176
203
|
const t = document.createTextNode(trailingWS);
|
|
177
204
|
range.collapse(false);
|
|
@@ -181,13 +208,17 @@ function wrapRangeInTag(tag: string, range: Range, _editableRoot: HTMLElement) {
|
|
|
181
208
|
}
|
|
182
209
|
|
|
183
210
|
const wrapper = document.createElement(tag);
|
|
184
|
-
wrapper.
|
|
211
|
+
wrapper.append(contents);
|
|
185
212
|
|
|
186
213
|
// Build the insertion fragment: [leadingWS] [wrapper] [trailingWS]
|
|
187
214
|
const frag = document.createDocumentFragment();
|
|
188
|
-
if (leadingWS)
|
|
189
|
-
|
|
190
|
-
|
|
215
|
+
if (leadingWS) {
|
|
216
|
+
frag.append(document.createTextNode(leadingWS));
|
|
217
|
+
}
|
|
218
|
+
frag.append(wrapper);
|
|
219
|
+
if (trailingWS) {
|
|
220
|
+
frag.append(document.createTextNode(trailingWS));
|
|
221
|
+
}
|
|
191
222
|
|
|
192
223
|
range.insertNode(frag);
|
|
193
224
|
|
|
@@ -210,14 +241,18 @@ function wrapRangeInTag(tag: string, range: Range, _editableRoot: HTMLElement) {
|
|
|
210
241
|
*/
|
|
211
242
|
function trimLeadingWhitespace(frag: DocumentFragment) {
|
|
212
243
|
const first = frag.firstChild;
|
|
213
|
-
if (!first || first.nodeType !== Node.TEXT_NODE)
|
|
244
|
+
if (!first || first.nodeType !== Node.TEXT_NODE) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
214
247
|
const text = first.textContent ?? "";
|
|
215
248
|
const match = text.match(/^(\s+)/);
|
|
216
|
-
if (!match)
|
|
217
|
-
|
|
249
|
+
if (!match) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const [, ws] = match;
|
|
218
253
|
if (text.length === ws.length) {
|
|
219
254
|
// Entire node is whitespace — remove it
|
|
220
|
-
|
|
255
|
+
first.remove();
|
|
221
256
|
} else {
|
|
222
257
|
first.textContent = text.slice(ws.length);
|
|
223
258
|
}
|
|
@@ -233,13 +268,17 @@ function trimLeadingWhitespace(frag: DocumentFragment) {
|
|
|
233
268
|
*/
|
|
234
269
|
function trimTrailingWhitespace(frag: DocumentFragment) {
|
|
235
270
|
const last = frag.lastChild;
|
|
236
|
-
if (!last || last.nodeType !== Node.TEXT_NODE)
|
|
271
|
+
if (!last || last.nodeType !== Node.TEXT_NODE) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
237
274
|
const text = last.textContent ?? "";
|
|
238
275
|
const match = text.match(/(\s+)$/);
|
|
239
|
-
if (!match)
|
|
240
|
-
|
|
276
|
+
if (!match) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const [, ws] = match;
|
|
241
280
|
if (text.length === ws.length) {
|
|
242
|
-
|
|
281
|
+
last.remove();
|
|
243
282
|
} else {
|
|
244
283
|
last.textContent = text.slice(0, -ws.length);
|
|
245
284
|
}
|
|
@@ -256,30 +295,42 @@ function trimTrailingWhitespace(frag: DocumentFragment) {
|
|
|
256
295
|
* @param {HTMLElement | null} root
|
|
257
296
|
*/
|
|
258
297
|
export function normalizeInlineContent(root: HTMLElement | null) {
|
|
259
|
-
if (!root)
|
|
298
|
+
if (!root) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
260
301
|
let changed = true;
|
|
261
302
|
let iterations = 0;
|
|
262
303
|
while (changed && iterations < 10) {
|
|
263
304
|
changed = false;
|
|
264
|
-
iterations
|
|
305
|
+
iterations += 1;
|
|
265
306
|
|
|
266
307
|
// 1. Merge adjacent text nodes
|
|
267
308
|
root.normalize();
|
|
268
309
|
|
|
269
310
|
// 2. Merge adjacent same-tag siblings
|
|
270
|
-
if (mergeAdjacentSiblings(root))
|
|
311
|
+
if (mergeAdjacentSiblings(root)) {
|
|
312
|
+
changed = true;
|
|
313
|
+
}
|
|
271
314
|
|
|
272
315
|
// 3. Collapse redundant nesting (strong > strong → strong)
|
|
273
|
-
if (collapseRedundantNesting(root))
|
|
316
|
+
if (collapseRedundantNesting(root)) {
|
|
317
|
+
changed = true;
|
|
318
|
+
}
|
|
274
319
|
|
|
275
320
|
// 4. Remove empty inline elements
|
|
276
|
-
if (removeEmptyInlines(root))
|
|
321
|
+
if (removeEmptyInlines(root)) {
|
|
322
|
+
changed = true;
|
|
323
|
+
}
|
|
277
324
|
|
|
278
325
|
// 5. Lift edge whitespace out of inline wrappers
|
|
279
|
-
if (liftEdgeWhitespace(root))
|
|
326
|
+
if (liftEdgeWhitespace(root)) {
|
|
327
|
+
changed = true;
|
|
328
|
+
}
|
|
280
329
|
|
|
281
330
|
// 6. Unwrap bare <span> elements (no class, style, or attributes)
|
|
282
|
-
if (unwrapBareSpans(root))
|
|
331
|
+
if (unwrapBareSpans(root)) {
|
|
332
|
+
changed = true;
|
|
333
|
+
}
|
|
283
334
|
}
|
|
284
335
|
}
|
|
285
336
|
|
|
@@ -315,7 +366,7 @@ function mergeAdjacentSiblings(root: HTMLElement) {
|
|
|
315
366
|
const [el, next] = toMerge[i];
|
|
316
367
|
// Move all children from next into el
|
|
317
368
|
while (next.firstChild) {
|
|
318
|
-
el.
|
|
369
|
+
el.append(next.firstChild);
|
|
319
370
|
}
|
|
320
371
|
next.remove();
|
|
321
372
|
changed = true;
|
|
@@ -337,7 +388,9 @@ function collapseRedundantNesting(root: HTMLElement) {
|
|
|
337
388
|
|
|
338
389
|
while (walker.nextNode()) {
|
|
339
390
|
const el = walker.currentNode as Element;
|
|
340
|
-
if (!FORMAT_TAGS.has(el.tagName.toLowerCase()))
|
|
391
|
+
if (!FORMAT_TAGS.has(el.tagName.toLowerCase())) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
341
394
|
// Check if only child is an element with the same tag
|
|
342
395
|
if (
|
|
343
396
|
el.childNodes.length === 1 &&
|
|
@@ -353,9 +406,9 @@ function collapseRedundantNesting(root: HTMLElement) {
|
|
|
353
406
|
const inner = el.firstChild as Element;
|
|
354
407
|
// Replace outer with inner's children
|
|
355
408
|
while (inner.firstChild) {
|
|
356
|
-
|
|
409
|
+
inner.before(inner.firstChild);
|
|
357
410
|
}
|
|
358
|
-
|
|
411
|
+
inner.remove();
|
|
359
412
|
changed = true;
|
|
360
413
|
}
|
|
361
414
|
return changed;
|
|
@@ -374,7 +427,9 @@ function removeEmptyInlines(root: HTMLElement) {
|
|
|
374
427
|
|
|
375
428
|
while (walker.nextNode()) {
|
|
376
429
|
const el = walker.currentNode as Element;
|
|
377
|
-
if (!FORMAT_TAGS.has(el.tagName.toLowerCase()))
|
|
430
|
+
if (!FORMAT_TAGS.has(el.tagName.toLowerCase())) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
378
433
|
if (el.childNodes.length === 0 && !el.textContent) {
|
|
379
434
|
toRemove.push(el);
|
|
380
435
|
}
|
|
@@ -402,15 +457,19 @@ function liftEdgeWhitespace(root: HTMLElement) {
|
|
|
402
457
|
|
|
403
458
|
while (walker.nextNode()) {
|
|
404
459
|
const el = walker.currentNode as Element;
|
|
405
|
-
if (!FORMAT_TAGS.has(el.tagName.toLowerCase()))
|
|
406
|
-
|
|
460
|
+
if (!FORMAT_TAGS.has(el.tagName.toLowerCase())) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (el === root) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
407
466
|
|
|
408
467
|
const first = el.firstChild;
|
|
409
468
|
if (first && first.nodeType === Node.TEXT_NODE) {
|
|
410
469
|
const text = first.textContent ?? "";
|
|
411
470
|
const m = text.match(/^(\s+)/);
|
|
412
471
|
if (m && text.length > m[1].length) {
|
|
413
|
-
ops.push({ type: "lift-leading",
|
|
472
|
+
ops.push({ el, type: "lift-leading", ws: m[1] });
|
|
414
473
|
}
|
|
415
474
|
}
|
|
416
475
|
|
|
@@ -419,7 +478,7 @@ function liftEdgeWhitespace(root: HTMLElement) {
|
|
|
419
478
|
const text = last.textContent ?? "";
|
|
420
479
|
const m = text.match(/(\s+)$/);
|
|
421
480
|
if (m && text.length > m[1].length) {
|
|
422
|
-
ops.push({ type: "lift-trailing",
|
|
481
|
+
ops.push({ el, type: "lift-trailing", ws: m[1] });
|
|
423
482
|
}
|
|
424
483
|
}
|
|
425
484
|
}
|
|
@@ -454,10 +513,16 @@ function unwrapBareSpans(root: HTMLElement) {
|
|
|
454
513
|
|
|
455
514
|
while (walker.nextNode()) {
|
|
456
515
|
const el = walker.currentNode as Element;
|
|
457
|
-
if (el.tagName.toLowerCase() !== "span")
|
|
458
|
-
|
|
516
|
+
if (el.tagName.toLowerCase() !== "span") {
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
if (el === root) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
459
522
|
// Keep spans with class, style, or any attributes
|
|
460
|
-
if (el.attributes.length > 0)
|
|
523
|
+
if (el.attributes.length > 0) {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
461
526
|
toUnwrap.push(el);
|
|
462
527
|
}
|
|
463
528
|
|
|
@@ -498,8 +563,8 @@ function attributesMatch(a: Element, b: Element) {
|
|
|
498
563
|
* @param {Range} range
|
|
499
564
|
*/
|
|
500
565
|
export function expandRangeToTemplateExpressions(range: Range) {
|
|
501
|
-
expandBoundary(range, true); //
|
|
502
|
-
expandBoundary(range, false); //
|
|
566
|
+
expandBoundary(range, true); // Start
|
|
567
|
+
expandBoundary(range, false); // End
|
|
503
568
|
}
|
|
504
569
|
|
|
505
570
|
/**
|
|
@@ -512,24 +577,24 @@ export function expandRangeToTemplateExpressions(range: Range) {
|
|
|
512
577
|
function expandBoundary(range: Range, isStart: boolean) {
|
|
513
578
|
const node = isStart ? range.startContainer : range.endContainer;
|
|
514
579
|
const offset = isStart ? range.startOffset : range.endOffset;
|
|
515
|
-
if (node.nodeType !== Node.TEXT_NODE)
|
|
580
|
+
if (node.nodeType !== Node.TEXT_NODE) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
516
583
|
|
|
517
584
|
const text = node.textContent ?? "";
|
|
518
585
|
|
|
519
586
|
// Find all ${...} expression spans in this text node (supporting nested braces)
|
|
520
587
|
const exprs = findTemplateExpressions(text);
|
|
521
588
|
for (const expr of exprs) {
|
|
522
|
-
//
|
|
589
|
+
// Expr = { start, end } — character indices of "$" and the closing "}" + 1
|
|
523
590
|
if (isStart) {
|
|
524
591
|
// If the range starts inside this expression, move it to include the whole expr
|
|
525
592
|
if (offset > expr.start && offset < expr.end) {
|
|
526
593
|
range.setStart(node, expr.start);
|
|
527
594
|
}
|
|
528
|
-
} else {
|
|
595
|
+
} else if (offset > expr.start && offset < expr.end) {
|
|
529
596
|
// If the range ends inside this expression, expand to include the whole expr
|
|
530
|
-
|
|
531
|
-
range.setEnd(node, expr.end);
|
|
532
|
-
}
|
|
597
|
+
range.setEnd(node, expr.end);
|
|
533
598
|
}
|
|
534
599
|
}
|
|
535
600
|
}
|
|
@@ -551,17 +616,20 @@ export function findTemplateExpressions(text: string) {
|
|
|
551
616
|
let depth = 1;
|
|
552
617
|
let j = i + 2;
|
|
553
618
|
while (j < text.length && depth > 0) {
|
|
554
|
-
if (text[j] === "{")
|
|
555
|
-
|
|
556
|
-
j
|
|
619
|
+
if (text[j] === "{") {
|
|
620
|
+
depth += 1;
|
|
621
|
+
} else if (text[j] === "}") {
|
|
622
|
+
depth -= 1;
|
|
623
|
+
}
|
|
624
|
+
j += 1;
|
|
557
625
|
}
|
|
558
626
|
if (depth === 0) {
|
|
559
|
-
results.push({
|
|
627
|
+
results.push({ end: j, start });
|
|
560
628
|
i = j;
|
|
561
629
|
continue;
|
|
562
630
|
}
|
|
563
631
|
}
|
|
564
|
-
i
|
|
632
|
+
i += 1;
|
|
565
633
|
}
|
|
566
634
|
return results;
|
|
567
635
|
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import { showSlashMenu } from "./slash-menu";
|
|
10
10
|
import { activeTab } from "../workspace/workspace";
|
|
11
|
-
import {
|
|
11
|
+
import { mutateInsertNode, transactDoc } from "../tabs/transact";
|
|
12
12
|
import type { CanvasPanel } from "../types";
|
|
13
13
|
import type { JxPath } from "../state";
|
|
14
14
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
15
15
|
|
|
16
16
|
interface ObservableSubscription {
|
|
17
|
-
subscribe(observer: { next: (e: Event) => void })
|
|
18
|
-
|
|
17
|
+
subscribe: ((observer: { next: (e: Event) => void }) => void) &
|
|
18
|
+
((observer: { next: (e: MouseEvent) => void }) => void);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
interface ObservableElement {
|
|
@@ -26,7 +26,7 @@ interface InsertionHelperContext {
|
|
|
26
26
|
getCanvasMode: () => string; // Returns the active canvas mode.
|
|
27
27
|
withPanelPointerEvents: (fn: () => unknown) => unknown; // Executes fn with pointer-events
|
|
28
28
|
effectiveZoom: () => number; // Returns the current zoom scale factor.
|
|
29
|
-
defaultDef: (tag: string) =>
|
|
29
|
+
defaultDef: (tag: string) => JxMutableNode; // Creates a default element definition for a tag.
|
|
30
30
|
parentElementPath: (path: JxPath) => JxPath | null; // Returns the parent element path,
|
|
31
31
|
childIndex: (path: JxPath) => string | number; // Returns the child index within the
|
|
32
32
|
getNodeAtPath: (doc: JxMutableNode, path: JxPath) => JxMutableNode | null; // Retrieves
|
|
@@ -82,7 +82,7 @@ export function mount(ctx: InsertionHelperContext) {
|
|
|
82
82
|
_helper.addEventListener("mouseleave", () => {
|
|
83
83
|
scheduleHide();
|
|
84
84
|
});
|
|
85
|
-
panel.viewport.
|
|
85
|
+
panel.viewport.append(_helper);
|
|
86
86
|
|
|
87
87
|
_abort = new AbortController();
|
|
88
88
|
|
|
@@ -105,7 +105,9 @@ export function unmount() {
|
|
|
105
105
|
_abort?.abort();
|
|
106
106
|
_abort = null;
|
|
107
107
|
cancelHide();
|
|
108
|
-
if (_helper?.parentElement)
|
|
108
|
+
if (_helper?.parentElement) {
|
|
109
|
+
_helper.remove();
|
|
110
|
+
}
|
|
109
111
|
clearAnchor();
|
|
110
112
|
_helper = null;
|
|
111
113
|
_ctx = null;
|
|
@@ -116,7 +118,9 @@ export function unmount() {
|
|
|
116
118
|
|
|
117
119
|
/** @param {MouseEvent} e */
|
|
118
120
|
function onMouseMove(e: MouseEvent) {
|
|
119
|
-
if (!_ctx || !_helper)
|
|
121
|
+
if (!_ctx || !_helper) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
120
124
|
|
|
121
125
|
const { getCanvasMode } = _ctx;
|
|
122
126
|
const mode = getCanvasMode();
|
|
@@ -161,7 +165,7 @@ function onMouseMove(e: MouseEvent) {
|
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
const parentStyle = getComputedStyle(parent);
|
|
164
|
-
const display = parentStyle
|
|
168
|
+
const { display } = parentStyle;
|
|
165
169
|
const isFlex = display === "flex" || display === "inline-flex";
|
|
166
170
|
const isGrid = display === "grid" || display === "inline-grid";
|
|
167
171
|
const isRow =
|
|
@@ -208,7 +212,9 @@ function onMouseMove(e: MouseEvent) {
|
|
|
208
212
|
* @param {number} idx
|
|
209
213
|
*/
|
|
210
214
|
function showAt(el: HTMLElement, edge: string, path: JxPath, parentPath: JxPath, idx: number) {
|
|
211
|
-
if (!_helper)
|
|
215
|
+
if (!_helper) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
212
218
|
|
|
213
219
|
// Set CSS anchor on target element
|
|
214
220
|
if (_currentAnchor !== el) {
|
|
@@ -219,7 +225,7 @@ function showAt(el: HTMLElement, edge: string, path: JxPath, parentPath: JxPath,
|
|
|
219
225
|
|
|
220
226
|
_helper.dataset.edge = edge;
|
|
221
227
|
_helper.classList.add("visible");
|
|
222
|
-
_insertionPoint = { edge,
|
|
228
|
+
_insertionPoint = { edge, idx, parentPath, path };
|
|
223
229
|
cancelHide();
|
|
224
230
|
}
|
|
225
231
|
|
|
@@ -241,7 +247,9 @@ function hide() {
|
|
|
241
247
|
|
|
242
248
|
function hideNow() {
|
|
243
249
|
_hideTimer = null;
|
|
244
|
-
if (!_helper)
|
|
250
|
+
if (!_helper) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
245
253
|
_helper.classList.remove("visible");
|
|
246
254
|
clearAnchor();
|
|
247
255
|
_insertionPoint = null;
|
|
@@ -260,12 +268,14 @@ function onHelperClick(e: MouseEvent) {
|
|
|
260
268
|
e.stopPropagation();
|
|
261
269
|
e.preventDefault();
|
|
262
270
|
|
|
263
|
-
if (!_ctx || !_helper || !_insertionPoint)
|
|
271
|
+
if (!_ctx || !_helper || !_insertionPoint) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
264
274
|
|
|
265
275
|
const captured = _insertionPoint;
|
|
266
276
|
showSlashMenu(_helper, "", {
|
|
267
|
-
showFilter: true,
|
|
268
277
|
onSelect: (cmd) => onSlashSelect(cmd, captured),
|
|
278
|
+
showFilter: true,
|
|
269
279
|
});
|
|
270
280
|
}
|
|
271
281
|
|
|
@@ -277,7 +287,9 @@ function onSlashSelect(
|
|
|
277
287
|
cmd: { label: string; tag: string; description?: string },
|
|
278
288
|
point: { edge: string; path: JxPath; parentPath: JxPath; idx: number },
|
|
279
289
|
) {
|
|
280
|
-
if (!_ctx)
|
|
290
|
+
if (!_ctx) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
281
293
|
|
|
282
294
|
const { defaultDef } = _ctx;
|
|
283
295
|
const { parentPath, idx, edge } = point;
|