@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.
Files changed (105) hide show
  1. package/dist/studio.js +2231 -715
  2. package/dist/studio.js.map +121 -104
  3. package/package.json +8 -7
  4. package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
  5. package/src/browse/{browse.js → browse.ts} +110 -85
  6. package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
  7. package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
  8. package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
  9. package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
  10. package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
  11. package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
  12. package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
  13. package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
  14. package/src/editor/{context-menu.js → context-menu.ts} +38 -36
  15. package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
  16. package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
  17. package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
  18. package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
  19. package/src/editor/{inline-format.js → inline-format.ts} +46 -51
  20. package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
  21. package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
  22. package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
  23. package/src/files/{components.js → components.ts} +12 -15
  24. package/src/files/{file-ops.js → file-ops.ts} +36 -29
  25. package/src/files/{files.js → files.ts} +102 -84
  26. package/src/github/{github-auth.js → github-auth.ts} +3 -3
  27. package/src/github/{github-publish.js → github-publish.ts} +20 -18
  28. package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
  29. package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
  30. package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
  31. package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
  32. package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
  33. package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
  34. package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
  35. package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
  36. package/src/panels/dnd.ts +417 -0
  37. package/src/panels/{editors.js → editors.ts} +43 -50
  38. package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
  39. package/src/panels/{events-panel.js → events-panel.ts} +33 -25
  40. package/src/panels/{git-panel.js → git-panel.ts} +97 -83
  41. package/src/panels/{head-panel.js → head-panel.ts} +117 -139
  42. package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
  43. package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
  44. package/src/panels/{left-panel.js → left-panel.ts} +56 -63
  45. package/src/panels/{overlays.js → overlays.ts} +28 -36
  46. package/src/panels/{panel-events.js → panel-events.ts} +53 -51
  47. package/src/panels/{preview-render.js → preview-render.ts} +20 -15
  48. package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
  49. package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
  50. package/src/panels/{quick-search.js → quick-search.ts} +16 -17
  51. package/src/panels/{right-panel.js → right-panel.ts} +41 -59
  52. package/src/panels/{shared.js → shared.ts} +7 -5
  53. package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
  54. package/src/panels/{statusbar.js → statusbar.ts} +14 -14
  55. package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
  56. package/src/panels/{style-panel.js → style-panel.ts} +235 -290
  57. package/src/panels/{style-utils.js → style-utils.ts} +32 -31
  58. package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
  59. package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
  60. package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
  61. package/src/panels/{toolbar.js → toolbar.ts} +68 -70
  62. package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
  63. package/src/{platform.js → platform.ts} +5 -3
  64. package/src/platforms/{devserver.js → devserver.ts} +53 -44
  65. package/src/{recent-projects.js → recent-projects.ts} +17 -12
  66. package/src/{resize-edges.js → resize-edges.ts} +18 -13
  67. package/src/services/{cem-export.js → cem-export.ts} +14 -7
  68. package/src/services/{code-services.js → code-services.ts} +29 -20
  69. package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
  70. package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
  71. package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
  72. package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
  73. package/src/settings/{general-settings.js → general-settings.ts} +29 -28
  74. package/src/settings/{head-editor.js → head-editor.ts} +37 -35
  75. package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
  76. package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
  77. package/src/{site-context.js → site-context.ts} +35 -32
  78. package/src/{state.js → state.ts} +95 -78
  79. package/src/{store.js → store.ts} +60 -63
  80. package/src/{studio.js → studio.ts} +117 -119
  81. package/src/tabs/tab.ts +196 -0
  82. package/src/tabs/{transact.js → transact.ts} +107 -60
  83. package/src/types.ts +185 -0
  84. package/src/ui/{button-group.js → button-group.ts} +21 -21
  85. package/src/ui/{color-selector.js → color-selector.ts} +44 -37
  86. package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
  87. package/src/ui/{field-row.js → field-row.ts} +19 -2
  88. package/src/ui/{icons.js → icons.ts} +2 -2
  89. package/src/ui/{layers.js → layers.ts} +33 -24
  90. package/src/ui/{media-picker.js → media-picker.ts} +13 -9
  91. package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
  92. package/src/ui/{spectrum.js → spectrum.ts} +4 -4
  93. package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
  94. package/src/ui/{value-selector.js → value-selector.ts} +28 -28
  95. package/src/ui/{widgets.js → widgets.ts} +62 -15
  96. package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
  97. package/src/utils/{edit-display.js → edit-display.ts} +23 -20
  98. package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
  99. package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
  100. package/src/utils/{studio-utils.js → studio-utils.ts} +38 -26
  101. package/src/{view.js → view.ts} +53 -55
  102. package/src/workspace/{workspace.js → workspace.ts} +44 -33
  103. package/src/panels/dnd.js +0 -426
  104. package/src/tabs/tab.js +0 -192
  105. /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
- /** @type {Element[]} */
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(/** @type {Element} */ (walker.currentNode));
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(tag, range, editableRoot, matches) {
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
- /** @type {[Element, Element][]} */
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 = /** @type {Element} */ (walker.currentNode);
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
- /** @type {Element} */ (next).tagName === el.tagName &&
305
+ (next as Element).tagName === el.tagName &&
308
306
  FORMAT_TAGS.has(el.tagName.toLowerCase()) &&
309
- attributesMatch(el, /** @type {Element} */ (next))
307
+ attributesMatch(el, next as Element)
310
308
  ) {
311
- toMerge.push([el, /** @type {Element} */ (next)]);
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
- /** @type {Element[]} */
339
- const toCollapse = [];
336
+ const toCollapse: Element[] = [];
340
337
 
341
338
  while (walker.nextNode()) {
342
- const el = /** @type {Element} */ (walker.currentNode);
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
- /** @type {Element} */ (el.firstChild).tagName === el.tagName
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 = /** @type {Element} */ (el.firstChild);
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
- /** @type {Element[]} */
377
- const toRemove = [];
373
+ const toRemove: Element[] = [];
378
374
 
379
375
  while (walker.nextNode()) {
380
- const el = /** @type {Element} */ (walker.currentNode);
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 = /** @type {Element} */ (walker.currentNode);
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 = /** @type {Text} */ (op.el.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 = /** @type {Text} */ (op.el.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
- /** @type {Element[]} */
458
- const toUnwrap = [];
453
+ const toUnwrap: Element[] = [];
459
454
 
460
455
  while (walker.nextNode()) {
461
- const el = /** @type {Element} */ (walker.currentNode);
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.js";
9
- import { activeTab } from "../workspace/workspace.js";
10
- import { transactDoc, mutateInsertNode } from "../tabs/transact.js";
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
- // ─── Types ────────────────────────────────────────────────────────────────────
13
-
14
- /**
15
- * @typedef {Object} ObservableSubscription
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
- * @typedef {Object} ObservableElement
21
- * @property {(event: string, options?: Object) => ObservableSubscription} on - Creates an
22
- * observable for the given event.
23
- */
21
+ interface ObservableElement {
22
+ on: (event: string, options?: Record<string, unknown>) => ObservableSubscription;
23
+ }
24
24
 
25
- /**
26
- * @typedef {Object} CanvasPanel
27
- * @property {HTMLElement} canvas - The canvas content element.
28
- * @property {HTMLElement & ObservableElement} overlayClk - The overlay click-capture layer.
29
- * @property {HTMLElement} overlay - The overlay rendering layer.
30
- * @property {HTMLElement} viewport - The viewport container (containing block for anchor
31
- * positioning).
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
- /** @type {InsertionHelperContext | null} */
55
- let _ctx = null;
41
+ let _ctx: InsertionHelperContext | null = null;
56
42
 
57
- /** @type {HTMLElement | null} */
58
- let _helper = null;
43
+ let _helper: HTMLElement | null = null;
59
44
 
60
- /** @type {HTMLElement | null} */
61
- let _currentAnchor = null;
45
+ let _currentAnchor: HTMLElement | null = null;
62
46
 
63
- /** @type {{ edge: string; path: JxPath; parentPath: JxPath; idx: number } | null} */
64
- let _insertionPoint = null;
47
+ let _insertionPoint: { edge: string; path: JxPath; parentPath: JxPath; idx: number } | null = null;
65
48
 
66
- /** @type {AbortController | null} */
67
- let _abort = null;
49
+ let _abort: AbortController | null = null;
68
50
 
69
- /** @type {ReturnType<typeof setTimeout> | null} */
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 = /** @type {HTMLElement & ObservableElement} */ (panel.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 = /** @type {HTMLElement | null} */ (
140
- withPanelPointerEvents(() => document.elementFromPoint(e.clientX, e.clientY))
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 = /** @type {number} */ (_ctx.childIndex(path));
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
- /** @type {any} */ (el.style).anchorName = "--jx-insert";
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
- /** @type {any} */ (_currentAnchor.style).anchorName = "";
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(/** @type {MouseEvent} */ e) {
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(cmd, point) {
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;