@jxsuite/studio 0.23.1 → 0.24.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 +2231 -715
- package/dist/studio.js.map +121 -104
- package/package.json +8 -7
- package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
- package/src/browse/{browse.js → browse.ts} +110 -85
- package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
- package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
- package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
- package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
- package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
- package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
- package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
- package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
- package/src/editor/{context-menu.js → context-menu.ts} +38 -36
- package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
- package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
- package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
- package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
- package/src/editor/{inline-format.js → inline-format.ts} +46 -51
- package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
- package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
- package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
- package/src/files/{components.js → components.ts} +12 -15
- package/src/files/{file-ops.js → file-ops.ts} +36 -29
- package/src/files/{files.js → files.ts} +102 -84
- package/src/github/{github-auth.js → github-auth.ts} +3 -3
- package/src/github/{github-publish.js → github-publish.ts} +20 -18
- package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
- package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
- package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
- package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
- package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
- package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
- package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
- package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
- package/src/panels/dnd.ts +417 -0
- package/src/panels/{editors.js → editors.ts} +43 -50
- package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
- package/src/panels/{events-panel.js → events-panel.ts} +33 -25
- package/src/panels/{git-panel.js → git-panel.ts} +97 -83
- package/src/panels/{head-panel.js → head-panel.ts} +117 -139
- package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
- package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
- package/src/panels/{left-panel.js → left-panel.ts} +56 -63
- package/src/panels/{overlays.js → overlays.ts} +28 -36
- package/src/panels/{panel-events.js → panel-events.ts} +53 -51
- package/src/panels/{preview-render.js → preview-render.ts} +20 -15
- package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
- package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
- package/src/panels/{quick-search.js → quick-search.ts} +16 -17
- package/src/panels/{right-panel.js → right-panel.ts} +41 -59
- package/src/panels/{shared.js → shared.ts} +7 -5
- package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
- package/src/panels/{statusbar.js → statusbar.ts} +14 -14
- package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
- package/src/panels/{style-panel.js → style-panel.ts} +235 -290
- package/src/panels/{style-utils.js → style-utils.ts} +32 -31
- package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
- package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
- package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
- package/src/panels/{toolbar.js → toolbar.ts} +68 -70
- package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
- package/src/{platform.js → platform.ts} +5 -3
- package/src/platforms/{devserver.js → devserver.ts} +53 -44
- package/src/{recent-projects.js → recent-projects.ts} +17 -12
- package/src/{resize-edges.js → resize-edges.ts} +18 -13
- package/src/services/{cem-export.js → cem-export.ts} +14 -7
- package/src/services/{code-services.js → code-services.ts} +29 -20
- package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
- package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
- package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
- package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
- package/src/settings/{general-settings.js → general-settings.ts} +29 -28
- package/src/settings/{head-editor.js → head-editor.ts} +37 -35
- package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
- package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
- package/src/{site-context.js → site-context.ts} +35 -32
- package/src/{state.js → state.ts} +95 -78
- package/src/{store.js → store.ts} +60 -63
- package/src/{studio.js → studio.ts} +117 -119
- package/src/tabs/tab.ts +196 -0
- package/src/tabs/{transact.js → transact.ts} +107 -60
- package/src/types.ts +185 -0
- package/src/ui/{button-group.js → button-group.ts} +21 -21
- package/src/ui/{color-selector.js → color-selector.ts} +44 -37
- package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
- package/src/ui/{field-row.js → field-row.ts} +19 -2
- package/src/ui/{icons.js → icons.ts} +2 -2
- package/src/ui/{layers.js → layers.ts} +33 -24
- package/src/ui/{media-picker.js → media-picker.ts} +13 -9
- package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
- package/src/ui/{spectrum.js → spectrum.ts} +4 -4
- package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
- package/src/ui/{value-selector.js → value-selector.ts} +28 -28
- package/src/ui/{widgets.js → widgets.ts} +62 -15
- package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
- package/src/utils/{edit-display.js → edit-display.ts} +23 -20
- package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
- package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
- package/src/utils/{studio-utils.js → studio-utils.ts} +38 -26
- package/src/{view.js → view.ts} +53 -55
- package/src/workspace/{workspace.js → workspace.ts} +44 -33
- package/src/panels/dnd.js +0 -426
- package/src/tabs/tab.js +0 -192
- /package/src/{reactivity.js → reactivity.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Inline-format.js — Inline formatting engine for contenteditable editing
|
|
3
4
|
*
|
|
@@ -30,7 +31,7 @@ const FORMAT_TAGS = new Set([
|
|
|
30
31
|
* @param {HTMLElement | null} editableRoot
|
|
31
32
|
* @returns {boolean}
|
|
32
33
|
*/
|
|
33
|
-
export function isTagActiveInSelection(tag, editableRoot) {
|
|
34
|
+
export function isTagActiveInSelection(tag: string, editableRoot: HTMLElement | null) {
|
|
34
35
|
if (!editableRoot) return false;
|
|
35
36
|
if (editableRoot.contentEditable === "plaintext-only") return false;
|
|
36
37
|
const sel = window.getSelection();
|
|
@@ -45,12 +46,9 @@ export function isTagActiveInSelection(tag, editableRoot) {
|
|
|
45
46
|
* @param {Node | null} node
|
|
46
47
|
* @returns {boolean}
|
|
47
48
|
*/
|
|
48
|
-
const hasTag = (node) => {
|
|
49
|
+
const hasTag = (node: Node | null) => {
|
|
49
50
|
while (node && node !== editableRoot) {
|
|
50
|
-
if (
|
|
51
|
-
node.nodeType === Node.ELEMENT_NODE &&
|
|
52
|
-
/** @type {Element} */ (node).tagName.toLowerCase() === tag
|
|
53
|
-
)
|
|
51
|
+
if (node.nodeType === Node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === tag)
|
|
54
52
|
return true;
|
|
55
53
|
node = node.parentNode;
|
|
56
54
|
}
|
|
@@ -67,7 +65,7 @@ export function isTagActiveInSelection(tag, editableRoot) {
|
|
|
67
65
|
* @param {string} tag
|
|
68
66
|
* @param {HTMLElement | null} editableRoot
|
|
69
67
|
*/
|
|
70
|
-
export function toggleInlineFormat(tag, editableRoot) {
|
|
68
|
+
export function toggleInlineFormat(tag: string, editableRoot: HTMLElement | null) {
|
|
71
69
|
if (!editableRoot) return;
|
|
72
70
|
const sel = window.getSelection();
|
|
73
71
|
if (!sel || !sel.rangeCount) return;
|
|
@@ -98,26 +96,22 @@ export function toggleInlineFormat(tag, editableRoot) {
|
|
|
98
96
|
* @param {HTMLElement} root
|
|
99
97
|
* @returns {Element[]}
|
|
100
98
|
*/
|
|
101
|
-
function findIntersectingElements(tag, range, root) {
|
|
102
|
-
|
|
103
|
-
const matches = [];
|
|
99
|
+
function findIntersectingElements(tag: string, range: Range, root: HTMLElement) {
|
|
100
|
+
const matches: Element[] = [];
|
|
104
101
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
|
105
102
|
/**
|
|
106
103
|
* @param {Node} node
|
|
107
104
|
* @returns {number}
|
|
108
105
|
*/
|
|
109
|
-
acceptNode(node) {
|
|
110
|
-
if (
|
|
111
|
-
/** @type {Element} */ (node).tagName.toLowerCase() === tag &&
|
|
112
|
-
range.intersectsNode(node)
|
|
113
|
-
) {
|
|
106
|
+
acceptNode(node: Node) {
|
|
107
|
+
if ((node as Element).tagName.toLowerCase() === tag && range.intersectsNode(node)) {
|
|
114
108
|
return NodeFilter.FILTER_ACCEPT;
|
|
115
109
|
}
|
|
116
110
|
return NodeFilter.FILTER_SKIP;
|
|
117
111
|
},
|
|
118
112
|
});
|
|
119
113
|
while (walker.nextNode()) {
|
|
120
|
-
matches.push(
|
|
114
|
+
matches.push(walker.currentNode as Element);
|
|
121
115
|
}
|
|
122
116
|
return matches;
|
|
123
117
|
}
|
|
@@ -131,7 +125,12 @@ function findIntersectingElements(tag, range, root) {
|
|
|
131
125
|
* @param {HTMLElement} editableRoot
|
|
132
126
|
* @param {Element[]} matches
|
|
133
127
|
*/
|
|
134
|
-
function unwrapTagInRange(
|
|
128
|
+
function unwrapTagInRange(
|
|
129
|
+
tag: string,
|
|
130
|
+
range: Range,
|
|
131
|
+
editableRoot: HTMLElement,
|
|
132
|
+
matches: Element[],
|
|
133
|
+
) {
|
|
135
134
|
// Process in reverse so DOM mutations don't shift later nodes
|
|
136
135
|
for (let i = matches.length - 1; i >= 0; i--) {
|
|
137
136
|
const el = matches[i];
|
|
@@ -144,7 +143,7 @@ function unwrapTagInRange(tag, range, editableRoot, matches) {
|
|
|
144
143
|
*
|
|
145
144
|
* @param {Element} el
|
|
146
145
|
*/
|
|
147
|
-
function unwrapElement(el) {
|
|
146
|
+
function unwrapElement(el: Element) {
|
|
148
147
|
const parent = el.parentNode;
|
|
149
148
|
if (!parent) return;
|
|
150
149
|
const frag = document.createDocumentFragment();
|
|
@@ -162,7 +161,7 @@ function unwrapElement(el) {
|
|
|
162
161
|
* @param {Range} range
|
|
163
162
|
* @param {HTMLElement} _editableRoot
|
|
164
163
|
*/
|
|
165
|
-
function wrapRangeInTag(tag, range, _editableRoot) {
|
|
164
|
+
function wrapRangeInTag(tag: string, range: Range, _editableRoot: HTMLElement) {
|
|
166
165
|
const contents = range.extractContents();
|
|
167
166
|
|
|
168
167
|
// Trim leading whitespace from the fragment
|
|
@@ -209,7 +208,7 @@ function wrapRangeInTag(tag, range, _editableRoot) {
|
|
|
209
208
|
* @param {DocumentFragment} frag
|
|
210
209
|
* @returns {string | null}
|
|
211
210
|
*/
|
|
212
|
-
function trimLeadingWhitespace(frag) {
|
|
211
|
+
function trimLeadingWhitespace(frag: DocumentFragment) {
|
|
213
212
|
const first = frag.firstChild;
|
|
214
213
|
if (!first || first.nodeType !== Node.TEXT_NODE) return null;
|
|
215
214
|
const text = first.textContent ?? "";
|
|
@@ -232,7 +231,7 @@ function trimLeadingWhitespace(frag) {
|
|
|
232
231
|
* @param {DocumentFragment} frag
|
|
233
232
|
* @returns {string | null}
|
|
234
233
|
*/
|
|
235
|
-
function trimTrailingWhitespace(frag) {
|
|
234
|
+
function trimTrailingWhitespace(frag: DocumentFragment) {
|
|
236
235
|
const last = frag.lastChild;
|
|
237
236
|
if (!last || last.nodeType !== Node.TEXT_NODE) return null;
|
|
238
237
|
const text = last.textContent ?? "";
|
|
@@ -256,7 +255,7 @@ function trimTrailingWhitespace(frag) {
|
|
|
256
255
|
*
|
|
257
256
|
* @param {HTMLElement | null} root
|
|
258
257
|
*/
|
|
259
|
-
export function normalizeInlineContent(root) {
|
|
258
|
+
export function normalizeInlineContent(root: HTMLElement | null) {
|
|
260
259
|
if (!root) return;
|
|
261
260
|
let changed = true;
|
|
262
261
|
let iterations = 0;
|
|
@@ -291,24 +290,23 @@ export function normalizeInlineContent(root) {
|
|
|
291
290
|
* @param {HTMLElement} root
|
|
292
291
|
* @returns {boolean}
|
|
293
292
|
*/
|
|
294
|
-
function mergeAdjacentSiblings(root) {
|
|
293
|
+
function mergeAdjacentSiblings(root: HTMLElement) {
|
|
295
294
|
let changed = false;
|
|
296
295
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
297
|
-
|
|
298
|
-
const toMerge = [];
|
|
296
|
+
const toMerge: [Element, Element][] = [];
|
|
299
297
|
|
|
300
298
|
// Collect pairs first to avoid mutation during walk
|
|
301
299
|
while (walker.nextNode()) {
|
|
302
|
-
const el =
|
|
300
|
+
const el = walker.currentNode as Element;
|
|
303
301
|
const next = el.nextSibling;
|
|
304
302
|
if (
|
|
305
303
|
next &&
|
|
306
304
|
next.nodeType === Node.ELEMENT_NODE &&
|
|
307
|
-
|
|
305
|
+
(next as Element).tagName === el.tagName &&
|
|
308
306
|
FORMAT_TAGS.has(el.tagName.toLowerCase()) &&
|
|
309
|
-
attributesMatch(el,
|
|
307
|
+
attributesMatch(el, next as Element)
|
|
310
308
|
) {
|
|
311
|
-
toMerge.push([el,
|
|
309
|
+
toMerge.push([el, next as Element]);
|
|
312
310
|
}
|
|
313
311
|
}
|
|
314
312
|
|
|
@@ -332,28 +330,27 @@ function mergeAdjacentSiblings(root) {
|
|
|
332
330
|
* @param {HTMLElement} root
|
|
333
331
|
* @returns {boolean}
|
|
334
332
|
*/
|
|
335
|
-
function collapseRedundantNesting(root) {
|
|
333
|
+
function collapseRedundantNesting(root: HTMLElement) {
|
|
336
334
|
let changed = false;
|
|
337
335
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
338
|
-
|
|
339
|
-
const toCollapse = [];
|
|
336
|
+
const toCollapse: Element[] = [];
|
|
340
337
|
|
|
341
338
|
while (walker.nextNode()) {
|
|
342
|
-
const el =
|
|
339
|
+
const el = walker.currentNode as Element;
|
|
343
340
|
if (!FORMAT_TAGS.has(el.tagName.toLowerCase())) continue;
|
|
344
341
|
// Check if only child is an element with the same tag
|
|
345
342
|
if (
|
|
346
343
|
el.childNodes.length === 1 &&
|
|
347
344
|
el.firstChild !== null &&
|
|
348
345
|
el.firstChild.nodeType === Node.ELEMENT_NODE &&
|
|
349
|
-
|
|
346
|
+
(el.firstChild as Element).tagName === el.tagName
|
|
350
347
|
) {
|
|
351
348
|
toCollapse.push(el);
|
|
352
349
|
}
|
|
353
350
|
}
|
|
354
351
|
|
|
355
352
|
for (const el of toCollapse) {
|
|
356
|
-
const inner =
|
|
353
|
+
const inner = el.firstChild as Element;
|
|
357
354
|
// Replace outer with inner's children
|
|
358
355
|
while (inner.firstChild) {
|
|
359
356
|
el.insertBefore(inner.firstChild, inner);
|
|
@@ -370,14 +367,13 @@ function collapseRedundantNesting(root) {
|
|
|
370
367
|
* @param {HTMLElement} root
|
|
371
368
|
* @returns {boolean}
|
|
372
369
|
*/
|
|
373
|
-
function removeEmptyInlines(root) {
|
|
370
|
+
function removeEmptyInlines(root: HTMLElement) {
|
|
374
371
|
let changed = false;
|
|
375
372
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
376
|
-
|
|
377
|
-
const toRemove = [];
|
|
373
|
+
const toRemove: Element[] = [];
|
|
378
374
|
|
|
379
375
|
while (walker.nextNode()) {
|
|
380
|
-
const el =
|
|
376
|
+
const el = walker.currentNode as Element;
|
|
381
377
|
if (!FORMAT_TAGS.has(el.tagName.toLowerCase())) continue;
|
|
382
378
|
if (el.childNodes.length === 0 && !el.textContent) {
|
|
383
379
|
toRemove.push(el);
|
|
@@ -398,14 +394,14 @@ function removeEmptyInlines(root) {
|
|
|
398
394
|
* @param {HTMLElement} root
|
|
399
395
|
* @returns {boolean}
|
|
400
396
|
*/
|
|
401
|
-
function liftEdgeWhitespace(root) {
|
|
397
|
+
function liftEdgeWhitespace(root: HTMLElement) {
|
|
402
398
|
let changed = false;
|
|
403
399
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
404
400
|
/** @type {{ type: string; el: Element; ws: string }[]} */
|
|
405
401
|
const ops = [];
|
|
406
402
|
|
|
407
403
|
while (walker.nextNode()) {
|
|
408
|
-
const el =
|
|
404
|
+
const el = walker.currentNode as Element;
|
|
409
405
|
if (!FORMAT_TAGS.has(el.tagName.toLowerCase())) continue;
|
|
410
406
|
if (el === root) continue;
|
|
411
407
|
|
|
@@ -430,12 +426,12 @@ function liftEdgeWhitespace(root) {
|
|
|
430
426
|
|
|
431
427
|
for (const op of ops) {
|
|
432
428
|
if (op.type === "lift-leading") {
|
|
433
|
-
const firstChild =
|
|
429
|
+
const firstChild = op.el.firstChild as Text;
|
|
434
430
|
firstChild.textContent = (firstChild.textContent ?? "").slice(op.ws.length);
|
|
435
431
|
op.el.parentNode?.insertBefore(document.createTextNode(op.ws), op.el);
|
|
436
432
|
changed = true;
|
|
437
433
|
} else if (op.type === "lift-trailing") {
|
|
438
|
-
const lastChild =
|
|
434
|
+
const lastChild = op.el.lastChild as Text;
|
|
439
435
|
lastChild.textContent = (lastChild.textContent ?? "").slice(0, -op.ws.length);
|
|
440
436
|
op.el.parentNode?.insertBefore(document.createTextNode(op.ws), op.el.nextSibling);
|
|
441
437
|
changed = true;
|
|
@@ -451,14 +447,13 @@ function liftEdgeWhitespace(root) {
|
|
|
451
447
|
* @param {HTMLElement} root
|
|
452
448
|
* @returns {boolean}
|
|
453
449
|
*/
|
|
454
|
-
function unwrapBareSpans(root) {
|
|
450
|
+
function unwrapBareSpans(root: HTMLElement) {
|
|
455
451
|
let changed = false;
|
|
456
452
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
457
|
-
|
|
458
|
-
const toUnwrap = [];
|
|
453
|
+
const toUnwrap: Element[] = [];
|
|
459
454
|
|
|
460
455
|
while (walker.nextNode()) {
|
|
461
|
-
const el =
|
|
456
|
+
const el = walker.currentNode as Element;
|
|
462
457
|
if (el.tagName.toLowerCase() !== "span") continue;
|
|
463
458
|
if (el === root) continue;
|
|
464
459
|
// Keep spans with class, style, or any attributes
|
|
@@ -481,7 +476,7 @@ function unwrapBareSpans(root) {
|
|
|
481
476
|
* @param {Element} b
|
|
482
477
|
* @returns {boolean}
|
|
483
478
|
*/
|
|
484
|
-
function attributesMatch(a, b) {
|
|
479
|
+
function attributesMatch(a: Element, b: Element) {
|
|
485
480
|
const tag = a.tagName.toLowerCase();
|
|
486
481
|
if (tag === "a") {
|
|
487
482
|
return a.getAttribute("href") === b.getAttribute("href");
|
|
@@ -502,7 +497,7 @@ function attributesMatch(a, b) {
|
|
|
502
497
|
*
|
|
503
498
|
* @param {Range} range
|
|
504
499
|
*/
|
|
505
|
-
export function expandRangeToTemplateExpressions(range) {
|
|
500
|
+
export function expandRangeToTemplateExpressions(range: Range) {
|
|
506
501
|
expandBoundary(range, true); // start
|
|
507
502
|
expandBoundary(range, false); // end
|
|
508
503
|
}
|
|
@@ -514,7 +509,7 @@ export function expandRangeToTemplateExpressions(range) {
|
|
|
514
509
|
* @param {Range} range
|
|
515
510
|
* @param {boolean} isStart
|
|
516
511
|
*/
|
|
517
|
-
function expandBoundary(range, isStart) {
|
|
512
|
+
function expandBoundary(range: Range, isStart: boolean) {
|
|
518
513
|
const node = isStart ? range.startContainer : range.endContainer;
|
|
519
514
|
const offset = isStart ? range.startOffset : range.endOffset;
|
|
520
515
|
if (node.nodeType !== Node.TEXT_NODE) return;
|
|
@@ -546,7 +541,7 @@ function expandBoundary(range, isStart) {
|
|
|
546
541
|
* @param {string} text
|
|
547
542
|
* @returns {{ start: number; end: number }[]}
|
|
548
543
|
*/
|
|
549
|
-
export function findTemplateExpressions(text) {
|
|
544
|
+
export function findTemplateExpressions(text: string) {
|
|
550
545
|
/** @type {{ start: number; end: number }[]} */
|
|
551
546
|
const results = [];
|
|
552
547
|
let i = 0;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Insertion-helper.js — Single floating "+" button for element insertion on the canvas.
|
|
3
4
|
*
|
|
@@ -5,69 +6,49 @@
|
|
|
5
6
|
* Observables (Chrome 135+) for declarative event handling.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { showSlashMenu } from "./slash-menu
|
|
9
|
-
import { activeTab } from "../workspace/workspace
|
|
10
|
-
import { transactDoc, mutateInsertNode } from "../tabs/transact
|
|
9
|
+
import { showSlashMenu } from "./slash-menu";
|
|
10
|
+
import { activeTab } from "../workspace/workspace";
|
|
11
|
+
import { transactDoc, mutateInsertNode } from "../tabs/transact";
|
|
12
|
+
import type { CanvasPanel } from "../types";
|
|
13
|
+
import type { JxPath } from "../state";
|
|
14
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* @property {(obj: { next: Function }) => void} subscribe - Subscribes to the observable stream.
|
|
17
|
-
*/
|
|
16
|
+
interface ObservableSubscription {
|
|
17
|
+
subscribe(observer: { next: (e: Event) => void }): void;
|
|
18
|
+
subscribe(observer: { next: (e: MouseEvent) => void }): void;
|
|
19
|
+
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* observable for the given event.
|
|
23
|
-
*/
|
|
21
|
+
interface ObservableElement {
|
|
22
|
+
on: (event: string, options?: Record<string, unknown>) => ObservableSubscription;
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
interface InsertionHelperContext {
|
|
26
|
+
getCanvasMode: () => string; // Returns the active canvas mode.
|
|
27
|
+
withPanelPointerEvents: (fn: () => unknown) => unknown; // Executes fn with pointer-events
|
|
28
|
+
effectiveZoom: () => number; // Returns the current zoom scale factor.
|
|
29
|
+
defaultDef: (tag: string) => object; // Creates a default element definition for a tag.
|
|
30
|
+
parentElementPath: (path: JxPath) => JxPath | null; // Returns the parent element path,
|
|
31
|
+
childIndex: (path: JxPath) => string | number; // Returns the child index within the
|
|
32
|
+
getNodeAtPath: (doc: JxMutableNode, path: JxPath) => JxMutableNode | null; // Retrieves
|
|
33
|
+
elToPath: WeakMap<Element, JxPath>; // Maps rendered DOM elements to their document
|
|
34
|
+
panel: CanvasPanel; // The active canvas panel.
|
|
35
|
+
}
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
* @typedef {Object} InsertionHelperContext
|
|
36
|
-
* @property {() => string} getCanvasMode - Returns the active canvas mode.
|
|
37
|
-
* @property {(fn: Function) => unknown} withPanelPointerEvents - Executes fn with pointer-events
|
|
38
|
-
* temporarily enabled on the canvas.
|
|
39
|
-
* @property {() => number} effectiveZoom - Returns the current zoom scale factor.
|
|
40
|
-
* @property {(tag: string) => Object} defaultDef - Creates a default element definition for a tag.
|
|
41
|
-
* @property {(path: JxPath) => JxPath | null} parentElementPath - Returns the parent element path,
|
|
42
|
-
* or null for root.
|
|
43
|
-
* @property {(path: JxPath) => string | number} childIndex - Returns the child index within the
|
|
44
|
-
* parent.
|
|
45
|
-
* @property {(doc: JxMutableNode, path: JxPath) => JxMutableNode | null} getNodeAtPath - Retrieves
|
|
46
|
-
* the node at a document path.
|
|
47
|
-
* @property {WeakMap<Element, JxPath>} elToPath - Maps rendered DOM elements to their document
|
|
48
|
-
* paths.
|
|
49
|
-
* @property {CanvasPanel} panel - The active canvas panel.
|
|
50
|
-
*/
|
|
37
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
51
38
|
|
|
52
39
|
// ─── State ───────────────────────────────────────────────────────────────────
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
let _ctx = null;
|
|
41
|
+
let _ctx: InsertionHelperContext | null = null;
|
|
56
42
|
|
|
57
|
-
|
|
58
|
-
let _helper = null;
|
|
43
|
+
let _helper: HTMLElement | null = null;
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
let _currentAnchor = null;
|
|
45
|
+
let _currentAnchor: HTMLElement | null = null;
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
let _insertionPoint = null;
|
|
47
|
+
let _insertionPoint: { edge: string; path: JxPath; parentPath: JxPath; idx: number } | null = null;
|
|
65
48
|
|
|
66
|
-
|
|
67
|
-
let _abort = null;
|
|
49
|
+
let _abort: AbortController | null = null;
|
|
68
50
|
|
|
69
|
-
|
|
70
|
-
let _hideTimer = null;
|
|
51
|
+
let _hideTimer: ReturnType<typeof setTimeout> | null = null;
|
|
71
52
|
|
|
72
53
|
// Edge detection threshold in pixels
|
|
73
54
|
const EDGE_THRESHOLD = 14;
|
|
@@ -82,7 +63,7 @@ const HIDE_DELAY = 300;
|
|
|
82
63
|
*
|
|
83
64
|
* @param {InsertionHelperContext} ctx
|
|
84
65
|
*/
|
|
85
|
-
export function mount(ctx) {
|
|
66
|
+
export function mount(ctx: InsertionHelperContext) {
|
|
86
67
|
_ctx = ctx;
|
|
87
68
|
const { panel } = ctx;
|
|
88
69
|
|
|
@@ -101,7 +82,7 @@ export function mount(ctx) {
|
|
|
101
82
|
_abort = new AbortController();
|
|
102
83
|
|
|
103
84
|
// Listen on viewport — overlayClk gets pointer-events:none during editing/selection
|
|
104
|
-
const viewport =
|
|
85
|
+
const viewport = panel.viewport as HTMLElement & ObservableElement;
|
|
105
86
|
if (typeof viewport.on === "function") {
|
|
106
87
|
viewport.on("mousemove", { signal: _abort.signal }).subscribe({ next: onMouseMove });
|
|
107
88
|
viewport.on("mouseleave", { signal: _abort.signal }).subscribe({ next: hide });
|
|
@@ -125,7 +106,7 @@ export function unmount() {
|
|
|
125
106
|
// ─── Detection ───────────────────────────────────────────────────────────────
|
|
126
107
|
|
|
127
108
|
/** @param {MouseEvent} e */
|
|
128
|
-
function onMouseMove(e) {
|
|
109
|
+
function onMouseMove(e: MouseEvent) {
|
|
129
110
|
if (!_ctx || !_helper) return;
|
|
130
111
|
|
|
131
112
|
const { getCanvasMode } = _ctx;
|
|
@@ -136,9 +117,9 @@ function onMouseMove(e) {
|
|
|
136
117
|
}
|
|
137
118
|
|
|
138
119
|
const { panel, withPanelPointerEvents, elToPath } = _ctx;
|
|
139
|
-
const el =
|
|
140
|
-
|
|
141
|
-
);
|
|
120
|
+
const el = withPanelPointerEvents(() =>
|
|
121
|
+
document.elementFromPoint(e.clientX, e.clientY),
|
|
122
|
+
) as HTMLElement | null;
|
|
142
123
|
|
|
143
124
|
if (!el || !panel.canvas.contains(el)) {
|
|
144
125
|
hide();
|
|
@@ -185,7 +166,7 @@ function onMouseMove(e) {
|
|
|
185
166
|
hide();
|
|
186
167
|
return;
|
|
187
168
|
}
|
|
188
|
-
const childIdx =
|
|
169
|
+
const childIdx = _ctx.childIndex(path) as number;
|
|
189
170
|
|
|
190
171
|
if (isRow) {
|
|
191
172
|
const relX = e.clientX - rect.left;
|
|
@@ -217,13 +198,13 @@ function onMouseMove(e) {
|
|
|
217
198
|
* @param {JxPath} parentPath
|
|
218
199
|
* @param {number} idx
|
|
219
200
|
*/
|
|
220
|
-
function showAt(el, edge, path, parentPath, idx) {
|
|
201
|
+
function showAt(el: HTMLElement, edge: string, path: JxPath, parentPath: JxPath, idx: number) {
|
|
221
202
|
if (!_helper) return;
|
|
222
203
|
|
|
223
204
|
// Set CSS anchor on target element
|
|
224
205
|
if (_currentAnchor !== el) {
|
|
225
206
|
clearAnchor();
|
|
226
|
-
|
|
207
|
+
(el.style as CSSStyleDeclaration & Record<string, string>).anchorName = "--jx-insert";
|
|
227
208
|
_currentAnchor = el;
|
|
228
209
|
}
|
|
229
210
|
|
|
@@ -259,14 +240,14 @@ function hideNow() {
|
|
|
259
240
|
|
|
260
241
|
function clearAnchor() {
|
|
261
242
|
if (_currentAnchor) {
|
|
262
|
-
|
|
243
|
+
(_currentAnchor.style as CSSStyleDeclaration & Record<string, string>).anchorName = "";
|
|
263
244
|
_currentAnchor = null;
|
|
264
245
|
}
|
|
265
246
|
}
|
|
266
247
|
|
|
267
248
|
// ─── Insertion ───────────────────────────────────────────────────────────────
|
|
268
249
|
|
|
269
|
-
function onHelperClick(
|
|
250
|
+
function onHelperClick(e: MouseEvent) {
|
|
270
251
|
e.stopPropagation();
|
|
271
252
|
e.preventDefault();
|
|
272
253
|
|
|
@@ -283,7 +264,10 @@ function onHelperClick(/** @type {MouseEvent} */ e) {
|
|
|
283
264
|
* @param {{ label: string; tag: string; description?: string }} cmd
|
|
284
265
|
* @param {{ edge: string; path: JxPath; parentPath: JxPath; idx: number }} point
|
|
285
266
|
*/
|
|
286
|
-
function onSlashSelect(
|
|
267
|
+
function onSlashSelect(
|
|
268
|
+
cmd: { label: string; tag: string; description?: string },
|
|
269
|
+
point: { edge: string; path: JxPath; parentPath: JxPath; idx: number },
|
|
270
|
+
) {
|
|
287
271
|
if (!_ctx) return;
|
|
288
272
|
|
|
289
273
|
const { defaultDef } = _ctx;
|