@dxos/ui-editor 0.8.4-main.4a85c3132b → 0.8.4-main.4f23b4e393

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 (153) hide show
  1. package/dist/lib/browser/index.mjs +1563 -775
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/types/index.mjs +26 -6
  5. package/dist/lib/browser/types/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/index.mjs +1562 -773
  7. package/dist/lib/node-esm/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/meta.json +1 -1
  9. package/dist/lib/node-esm/types/index.mjs +27 -6
  10. package/dist/lib/node-esm/types/index.mjs.map +4 -4
  11. package/dist/types/src/defaults.d.ts +4 -10
  12. package/dist/types/src/defaults.d.ts.map +1 -1
  13. package/dist/types/src/extensions/annotations.d.ts.map +1 -1
  14. package/dist/types/src/extensions/auto-scroll.d.ts +14 -2
  15. package/dist/types/src/extensions/auto-scroll.d.ts.map +1 -1
  16. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -1
  17. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -1
  18. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +5 -2
  19. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -1
  20. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  25. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  26. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  27. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
  28. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  29. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  30. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  31. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  32. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  33. package/dist/types/src/extensions/factories.d.ts +4 -3
  34. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  35. package/dist/types/src/extensions/factories.test.d.ts +2 -0
  36. package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
  37. package/dist/types/src/extensions/focus.d.ts +1 -1
  38. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  39. package/dist/types/src/extensions/index.d.ts +2 -1
  40. package/dist/types/src/extensions/index.d.ts.map +1 -1
  41. package/dist/types/src/extensions/json.d.ts.map +1 -1
  42. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -1
  44. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -0
  45. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  46. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  51. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  53. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
  54. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  55. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  56. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -1
  57. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
  58. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -1
  59. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
  60. package/dist/types/src/extensions/preview/preview.d.ts +2 -0
  61. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  62. package/dist/types/src/extensions/replacer.d.ts.map +1 -1
  63. package/dist/types/src/extensions/scroll-past-end.d.ts +3 -0
  64. package/dist/types/src/extensions/scroll-past-end.d.ts.map +1 -0
  65. package/dist/types/src/extensions/scroller.d.ts +12 -10
  66. package/dist/types/src/extensions/scroller.d.ts.map +1 -1
  67. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  68. package/dist/types/src/extensions/snippets.d.ts +10 -0
  69. package/dist/types/src/extensions/snippets.d.ts.map +1 -0
  70. package/dist/types/src/extensions/submit.d.ts.map +1 -1
  71. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -1
  72. package/dist/types/src/extensions/tags/fader.d.ts +12 -0
  73. package/dist/types/src/extensions/tags/fader.d.ts.map +1 -0
  74. package/dist/types/src/extensions/tags/index.d.ts +4 -1
  75. package/dist/types/src/extensions/tags/index.d.ts.map +1 -1
  76. package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
  77. package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
  78. package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
  79. package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
  80. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
  81. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
  82. package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
  83. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
  84. package/dist/types/src/extensions/tags/xml-tags.d.ts +28 -8
  85. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  86. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -1
  87. package/dist/types/src/index.d.ts +0 -1
  88. package/dist/types/src/index.d.ts.map +1 -1
  89. package/dist/types/src/styles/theme.d.ts +2 -2
  90. package/dist/types/src/styles/theme.d.ts.map +1 -1
  91. package/dist/types/src/types/types.d.ts +4 -4
  92. package/dist/types/src/types/types.d.ts.map +1 -1
  93. package/dist/types/src/util/cursor.d.ts.map +1 -1
  94. package/dist/types/src/util/debug.d.ts.map +1 -1
  95. package/dist/types/src/util/decorations.d.ts.map +1 -1
  96. package/dist/types/src/util/dom.d.ts.map +1 -1
  97. package/dist/types/src/util/facet.d.ts.map +1 -1
  98. package/dist/types/src/util/util.d.ts.map +1 -1
  99. package/dist/types/tsconfig.tsbuildinfo +1 -1
  100. package/package.json +34 -37
  101. package/src/defaults.ts +33 -20
  102. package/src/extensions/auto-scroll.ts +120 -12
  103. package/src/extensions/autocomplete/placeholder.ts +37 -18
  104. package/src/extensions/automerge/automerge.test.tsx +35 -9
  105. package/src/extensions/automerge/automerge.ts +5 -7
  106. package/src/extensions/comments.ts +0 -1
  107. package/src/extensions/factories.test.ts +88 -0
  108. package/src/extensions/factories.ts +26 -8
  109. package/src/extensions/folding.ts +3 -20
  110. package/src/extensions/index.ts +2 -1
  111. package/src/extensions/markdown/action.ts +0 -1
  112. package/src/extensions/markdown/bundle.ts +23 -9
  113. package/src/extensions/markdown/decorate.ts +11 -9
  114. package/src/extensions/markdown/highlight.ts +4 -10
  115. package/src/extensions/markdown/parser.test.ts +0 -1
  116. package/src/extensions/markdown/styles.ts +37 -4
  117. package/src/extensions/markdown/table.ts +24 -2
  118. package/src/extensions/outliner/outliner.test.ts +0 -1
  119. package/src/extensions/outliner/outliner.ts +1 -2
  120. package/src/extensions/outliner/tree.test.ts +0 -1
  121. package/src/extensions/preview/preview.ts +54 -7
  122. package/src/extensions/scroll-past-end.ts +32 -0
  123. package/src/extensions/scroller.ts +49 -25
  124. package/src/extensions/selection.ts +1 -1
  125. package/src/extensions/snippets.ts +67 -0
  126. package/src/extensions/tags/extended-markdown.test.ts +120 -2
  127. package/src/extensions/tags/extended-markdown.ts +80 -1
  128. package/src/extensions/tags/fader.ts +195 -0
  129. package/src/extensions/tags/index.ts +4 -1
  130. package/src/extensions/tags/testing/text.md +36 -0
  131. package/src/extensions/tags/testing/text.txt +35 -0
  132. package/src/extensions/tags/typewriter.test.ts +65 -0
  133. package/src/extensions/tags/typewriter.ts +594 -0
  134. package/src/extensions/tags/xml-block-decoration.ts +123 -0
  135. package/src/extensions/tags/xml-formatting.ts +125 -0
  136. package/src/extensions/tags/xml-tags.ts +179 -31
  137. package/src/extensions/tags/xml-util.test.ts +199 -24
  138. package/src/extensions/tags/xml-util.ts +62 -5
  139. package/src/index.ts +0 -1
  140. package/src/styles/theme.ts +45 -31
  141. package/src/types/types.ts +10 -2
  142. package/src/typings.d.ts +8 -0
  143. package/src/util/cursor.ts +0 -1
  144. package/dist/lib/browser/chunk-HL3YF6WC.mjs +0 -22
  145. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +0 -7
  146. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs +0 -24
  147. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +0 -7
  148. package/dist/types/src/extensions/tags/streamer.d.ts +0 -12
  149. package/dist/types/src/extensions/tags/streamer.d.ts.map +0 -1
  150. package/dist/types/src/extensions/typewriter.d.ts +0 -10
  151. package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
  152. package/src/extensions/tags/streamer.ts +0 -243
  153. package/src/extensions/typewriter.ts +0 -68
@@ -1,29 +1,42 @@
1
- import {
2
- EditorInputMode,
3
- EditorInputModes,
4
- EditorViewMode,
5
- EditorViewModes
6
- } from "./chunk-HL3YF6WC.mjs";
7
-
8
1
  // src/index.ts
9
- import { EditorState as EditorState3 } from "@codemirror/state";
10
- import { EditorView as EditorView30, keymap as keymap15 } from "@codemirror/view";
2
+ import { EditorState as EditorState4 } from "@codemirror/state";
3
+ import { EditorView as EditorView33, keymap as keymap15 } from "@codemirror/view";
11
4
  import { tags as tags2 } from "@lezer/highlight";
12
5
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
13
6
 
14
7
  // src/defaults.ts
15
8
  import { mx } from "@dxos/ui-theme";
16
- var editorWidth = "!mx-auto w-full max-w-[min(50rem,100%-4rem)]";
17
- var editorSlots = {
18
- scroll: {
19
- className: "pt-2"
20
- },
9
+ var editorClassNames = (role) => mx("dx-attention-surface p-0.5 data-[toolbar=disabled]:pt-2 dx-focus-ring-inset", role === "section" ? "[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-h-24" : "dx-container overflow-hidden");
10
+ var documentSlots = {
11
+ content: {
12
+ /**
13
+ * CodeMirror content width.
14
+ * 40rem = 640px. Corresponds to initial plank width (Google docs, Stashpad, etc.)
15
+ * 50rem = 800px. Maximum content width for solo mode.
16
+ * NOTE: Max width - 4rem = 2rem left/right margin (or 2rem gutter plus 1rem left/right margin).
17
+ */
18
+ className: mx(
19
+ // Inline-size container for widget sizing (children use `max-w-[100cqi]`).
20
+ // NOTE: Use inline-size, not full size containment — `container-type: size` on the
21
+ // editor content breaks CodeMirror's viewport measurement, leaving blank gaps during
22
+ // scroll until a click forces a re-measure.
23
+ "dx-inline-size-container",
24
+ // Wider margin for web (vs. mobile).
25
+ "pointer-fine:max-w-[min(50rem,100%-4rem)] pointer-coarse:max-w-[min(50rem,100%-2rem)]",
26
+ "mx-auto! w-full"
27
+ )
28
+ }
29
+ };
30
+ var compactSlots = {
31
+ content: {
32
+ className: "mx-2!"
33
+ }
34
+ };
35
+ var mobileSlots = {
21
36
  content: {
22
- className: editorWidth
37
+ className: "mx-2!"
23
38
  }
24
39
  };
25
- var editorWithToolbarLayout = "grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden";
26
- var stackItemContentEditorClassNames = (role) => mx("dx-attention-surface p-0.5 dx-focus-ring-inset data-[toolbar=disabled]:pt-2", role === "section" ? "[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-h-24" : "dx-container overflow-hidden");
27
40
 
28
41
  // src/extensions/annotations.ts
29
42
  import { RangeSetBuilder } from "@codemirror/state";
@@ -207,7 +220,7 @@ var singleValueFacet = (defaultValue) => Facet.define({
207
220
  var overlap = (a, b) => a.from <= b.to && a.to >= b.from;
208
221
  var defaultCursorConverter = {
209
222
  toCursor: (position) => position.toString(),
210
- fromCursor: (cursor2) => parseInt(cursor2)
223
+ fromCursor: (cursor) => parseInt(cursor)
211
224
  };
212
225
  var Cursor = class _Cursor {
213
226
  static converter = singleValueFacet(defaultCursorConverter);
@@ -220,9 +233,9 @@ var Cursor = class _Cursor {
220
233
  to
221
234
  ].join(":");
222
235
  };
223
- static getRangeFromCursor = (state, cursor2) => {
236
+ static getRangeFromCursor = (state, cursor) => {
224
237
  const cursorConverter2 = state.facet(_Cursor.converter);
225
- const parts = cursor2.split(":");
238
+ const parts = cursor.split(":");
226
239
  const from = cursorConverter2.fromCursor(parts[0]);
227
240
  const to = cursorConverter2.fromCursor(parts[1]);
228
241
  return from !== void 0 && to !== void 0 ? {
@@ -258,12 +271,7 @@ var wrapWithCatch = (fn, label) => {
258
271
  } catch (err) {
259
272
  log.catch(err, {
260
273
  label
261
- }, {
262
- F: __dxlog_file,
263
- L: 20,
264
- S: void 0,
265
- C: (f, a) => f(...a)
266
- });
274
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 13, S: void 0 });
267
275
  }
268
276
  };
269
277
  };
@@ -289,12 +297,7 @@ var logChanges = (trs) => {
289
297
  if (changes.length) {
290
298
  log("changes", {
291
299
  changes
292
- }, {
293
- F: __dxlog_file,
294
- L: 54,
295
- S: void 0,
296
- C: (f, a) => f(...a)
297
- });
300
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 44, S: void 0 });
298
301
  }
299
302
  };
300
303
 
@@ -360,30 +363,37 @@ var insertAtLineStart = (view, from, insert) => {
360
363
  };
361
364
 
362
365
  // src/extensions/autocomplete/placeholder.ts
363
- var placeholder = ({ content, delay = 3e3 }) => {
366
+ var placeholder = ({ content, delay = 3e3, focusOnly = false }) => {
364
367
  const plugin = ViewPlugin3.fromClass(class {
365
368
  _timeout;
366
369
  _decorations = Decoration3.none;
367
370
  update(update2) {
371
+ if (!update2.docChanged && !update2.selectionSet && !update2.focusChanged) {
372
+ return;
373
+ }
368
374
  if (this._timeout) {
369
375
  window.clearTimeout(this._timeout);
370
376
  this._timeout = void 0;
371
377
  }
378
+ this._decorations = Decoration3.none;
379
+ if (focusOnly && !update2.view.hasFocus) {
380
+ return;
381
+ }
372
382
  const activeLine = update2.view.state.doc.lineAt(update2.view.state.selection.main.head);
373
- const isEmpty = activeLine.text.trim() === "";
374
- if (isEmpty) {
375
- const lineStart = activeLine.from;
376
- this._timeout = setTimeout(() => {
377
- this._decorations = Decoration3.set([
378
- Decoration3.widget({
379
- widget: new PlaceholderWidget(content),
380
- side: 1
381
- }).range(lineStart)
382
- ]);
383
- update2.view.update([]);
384
- }, delay);
383
+ if (activeLine.text.trim() !== "") {
384
+ return;
385
385
  }
386
- this._decorations = Decoration3.none;
386
+ const lineStart = activeLine.from;
387
+ const view = update2.view;
388
+ this._timeout = setTimeout(() => {
389
+ this._decorations = Decoration3.set([
390
+ Decoration3.widget({
391
+ widget: new PlaceholderWidget(content),
392
+ side: 1
393
+ }).range(lineStart)
394
+ ]);
395
+ view.update([]);
396
+ }, delay);
387
397
  }
388
398
  destroy() {
389
399
  if (this._timeout) {
@@ -502,9 +512,11 @@ var typeahead = ({ onComplete } = {}) => {
502
512
  };
503
513
 
504
514
  // src/extensions/auto-scroll.ts
515
+ import { StateEffect as StateEffect2 } from "@codemirror/state";
505
516
  import { EditorView as EditorView5, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
506
517
  import { addEventListener, combine, throttle } from "@dxos/async";
507
518
  import { Domino } from "@dxos/ui";
519
+ import { getSize } from "@dxos/ui-theme";
508
520
 
509
521
  // src/extensions/scroller.ts
510
522
  import { StateEffect } from "@codemirror/state";
@@ -561,15 +573,14 @@ var scroller = ({ overScroll = 0 } = {}) => {
561
573
  }
562
574
  requestAnimationFrame(() => {
563
575
  this.view.scrollDOM.scrollTo({
564
- top: targetScrollTop,
565
- behavior
576
+ top: targetScrollTop
566
577
  });
567
578
  });
568
579
  }
569
580
  });
570
581
  return [
571
582
  scrollPlugin,
572
- // Listen for effect.s
583
+ // Listen for effect.
573
584
  EditorView4.updateListener.of((update2) => {
574
585
  update2.transactions.forEach((transaction) => {
575
586
  try {
@@ -584,23 +595,19 @@ var scroller = ({ overScroll = 0 } = {}) => {
584
595
  }
585
596
  }
586
597
  } catch (err) {
587
- log2.catch(err, void 0, {
588
- F: __dxlog_file2,
589
- L: 145,
590
- S: void 0,
591
- C: (f, a) => f(...a)
592
- });
598
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 91, S: void 0 });
593
599
  }
594
600
  });
595
601
  }),
596
602
  // Styles.
597
603
  EditorView4.theme({
598
- ".cm-content": {
599
- paddingBottom: `${overScroll}px`
600
- },
601
604
  ".cm-scroller": {
602
- overflowAnchor: "none",
603
- paddingBottom: "0"
605
+ overflowY: "scroll",
606
+ // Browser scroll-anchoring: when widgets above the viewport resize (e.g. tool blocks
607
+ // expanding their TogglePanel), the browser picks a stable element near the viewport
608
+ // top and adjusts `scrollTop` so the user's view doesn't jump. Auto-scroll's pinning
609
+ // logic still has the final word when pinned (forces scrollTop to scrollHeight).
610
+ overflowAnchor: "auto"
604
611
  },
605
612
  ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
606
613
  display: "none"
@@ -612,6 +619,16 @@ var scroller = ({ overScroll = 0 } = {}) => {
612
619
  "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
613
620
  background: "var(--color-scrollbar-thumb)"
614
621
  },
622
+ // Spacer below the last text line. Implemented as a real block pseudo-element
623
+ // (rather than `padding-bottom` on `.cm-content`) so it materializes in the
624
+ // scroller's `scrollHeight` regardless of how `padding` is reset by the base
625
+ // theme or downstream classes — this is what gives auto-scroll its head-room
626
+ // so the last line stays `overScroll` px above the viewport bottom.
627
+ ".cm-content::after": {
628
+ content: '""',
629
+ display: "block",
630
+ height: `${overScroll}px`
631
+ },
615
632
  ".cm-scroll-button": {
616
633
  position: "absolute",
617
634
  bottom: "0.5rem",
@@ -620,22 +637,28 @@ var scroller = ({ overScroll = 0 } = {}) => {
620
637
  })
621
638
  ];
622
639
  };
623
- function createCrawler(view, k = 0.3, maxStep = 2, targetDelta = 0.5) {
640
+ function createCrawler(view, omega = 5, snapThreshold = 5, snapVelocity = 50) {
624
641
  const el = view.scrollDOM;
625
642
  let currentTop = 0;
643
+ let velocity = 0;
626
644
  let rafId = null;
627
- function frame() {
645
+ let lastTime = 0;
646
+ function frame(now) {
647
+ const dt = lastTime === 0 ? 1 / 60 : Math.min(0.1, (now - lastTime) / 1e3);
648
+ lastTime = now;
628
649
  const targetTop = el.scrollHeight - el.clientHeight;
629
650
  const delta = targetTop - currentTop;
630
- const absDelta = Math.abs(delta);
631
- if (absDelta < targetDelta) {
651
+ if (Math.abs(delta) < snapThreshold && Math.abs(velocity) < snapVelocity) {
632
652
  el.scrollTop = targetTop;
633
653
  currentTop = targetTop;
654
+ velocity = 0;
634
655
  rafId = null;
656
+ lastTime = 0;
635
657
  return;
636
658
  }
637
- const step = Math.sign(delta) * Math.min(absDelta, Math.max(1, Math.min(absDelta * k, maxStep)));
638
- currentTop += step;
659
+ const accel = omega * omega * delta - 2 * omega * velocity;
660
+ velocity += accel * dt;
661
+ currentTop += velocity * dt;
639
662
  el.scrollTop = currentTop;
640
663
  rafId = requestAnimationFrame(frame);
641
664
  }
@@ -643,12 +666,15 @@ function createCrawler(view, k = 0.3, maxStep = 2, targetDelta = 0.5) {
643
666
  scroll: () => {
644
667
  if (rafId === null) {
645
668
  currentTop = el.scrollTop;
669
+ lastTime = 0;
646
670
  rafId = requestAnimationFrame(frame);
647
671
  }
648
672
  },
649
673
  cancel: () => {
650
674
  if (rafId !== null) {
651
675
  cancelAnimationFrame(rafId);
676
+ velocity = 0;
677
+ lastTime = 0;
652
678
  rafId = null;
653
679
  }
654
680
  }
@@ -656,26 +682,64 @@ function createCrawler(view, k = 0.3, maxStep = 2, targetDelta = 0.5) {
656
682
  }
657
683
 
658
684
  // src/extensions/auto-scroll.ts
659
- var autoScroll = (_ = {}) => {
685
+ var autoScrollEffect = StateEffect2.define();
686
+ var autoScroll = ({ scrollOnResize = true } = {}) => {
660
687
  let buttonContainer;
661
688
  let isPinned = true;
689
+ let jumpPending = false;
690
+ let enabled = true;
691
+ let firstUpdate = true;
662
692
  const setPinned = (pinned) => {
663
693
  buttonContainer?.classList.toggle("opacity-0", pinned);
664
694
  isPinned = pinned;
665
695
  };
666
696
  return [
667
- // Update listener for logging when scrolling is needed.
668
- EditorView5.updateListener.of(({ view, heightChanged, state }) => {
697
+ // Update listener for scrolling when content changes.
698
+ EditorView5.updateListener.of((update2) => {
699
+ const { view, heightChanged, state, startState } = update2;
700
+ for (const tr of update2.transactions) {
701
+ for (const effect of tr.effects) {
702
+ if (effect.is(autoScrollEffect)) {
703
+ enabled = effect.value;
704
+ if (enabled) {
705
+ setPinned(true);
706
+ view.dispatch({
707
+ effects: scrollerCrawlEffect.of(true)
708
+ });
709
+ } else {
710
+ view.dispatch({
711
+ effects: scrollerCrawlEffect.of(false)
712
+ });
713
+ }
714
+ }
715
+ }
716
+ }
717
+ if (!enabled) {
718
+ return;
719
+ }
720
+ if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
721
+ firstUpdate = false;
722
+ jumpPending = true;
723
+ requestAnimationFrame(() => {
724
+ view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
725
+ jumpPending = false;
726
+ });
727
+ return;
728
+ }
729
+ firstUpdate = false;
730
+ if (jumpPending) {
731
+ return;
732
+ }
669
733
  if (heightChanged) {
670
734
  if (isPinned) {
671
735
  const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
672
736
  const delta = scrollHeight - scrollTop - clientHeight;
673
- if (delta > 0 && scrollTop > 0) {
737
+ if (delta > 0) {
674
738
  setPinned(true);
675
739
  view.dispatch({
676
740
  effects: scrollerCrawlEffect.of(true)
677
741
  });
678
- } else if (delta < 0) {
742
+ } else if (delta < -1) {
679
743
  setPinned(false);
680
744
  }
681
745
  } else {
@@ -685,6 +749,46 @@ var autoScroll = (_ = {}) => {
685
749
  }
686
750
  }
687
751
  }),
752
+ // Re-pin and jump to bottom when the scroll container itself resizes (e.g. sidebar toggle,
753
+ // window resize). Doc-driven height changes are handled by the updateListener above; this
754
+ // observer covers the case where the viewport changes while the doc length is unchanged.
755
+ scrollOnResize ? ViewPlugin6.fromClass(class {
756
+ observer;
757
+ firstObservation = true;
758
+ destroyed = false;
759
+ constructor(view) {
760
+ const onResize = throttle(() => {
761
+ if (this.destroyed || !enabled) {
762
+ return;
763
+ }
764
+ setPinned(true);
765
+ requestAnimationFrame(() => {
766
+ if (this.destroyed) {
767
+ return;
768
+ }
769
+ view.scrollDOM.scrollTo({
770
+ top: view.scrollDOM.scrollHeight,
771
+ behavior: "instant"
772
+ });
773
+ view.dispatch({
774
+ effects: scrollerCrawlEffect.of(false)
775
+ });
776
+ });
777
+ }, 50);
778
+ this.observer = new ResizeObserver(() => {
779
+ if (this.firstObservation) {
780
+ this.firstObservation = false;
781
+ return;
782
+ }
783
+ onResize();
784
+ });
785
+ this.observer.observe(view.scrollDOM);
786
+ }
787
+ destroy() {
788
+ this.destroyed = true;
789
+ this.observer.disconnect();
790
+ }
791
+ }) : [],
688
792
  // Detect user scroll and unpin (or re-pin if scrolled to the bottom).
689
793
  ViewPlugin6.fromClass(class {
690
794
  cleanup;
@@ -710,12 +814,12 @@ var autoScroll = (_ = {}) => {
710
814
  // Scroll button.
711
815
  ViewPlugin6.fromClass(class {
712
816
  constructor(view) {
713
- const icon = Domino.of("dx-icon").attributes({
817
+ const icon = Domino.of("dx-icon").classNames(getSize(4)).attributes({
714
818
  icon: "ph--arrow-down--regular"
715
819
  });
716
820
  const button = Domino.of("button").classNames("dx-button bg-accent-surface").attributes({
717
821
  "data-density": "fine"
718
- }).children(icon).on("click", () => {
822
+ }).append(icon).on("click", () => {
719
823
  setPinned(true);
720
824
  view.dispatch({
721
825
  effects: scrollerLineEffect.of({
@@ -725,7 +829,7 @@ var autoScroll = (_ = {}) => {
725
829
  })
726
830
  });
727
831
  });
728
- buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").children(button).root;
832
+ buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").append(button).root;
729
833
  view.scrollDOM.parentElement.appendChild(buttonContainer);
730
834
  }
731
835
  })
@@ -760,35 +864,25 @@ var cursorConverter = (accessor) => ({
760
864
  try {
761
865
  return toCursor(accessor, pos, assoc);
762
866
  } catch (err) {
763
- log3.catch(err, void 0, {
764
- F: __dxlog_file3,
765
- L: 15,
766
- S: void 0,
767
- C: (f, a) => f(...a)
768
- });
867
+ log3.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 11, S: void 0 });
769
868
  return "";
770
869
  }
771
870
  },
772
- fromCursor: (cursor2) => {
871
+ fromCursor: (cursor) => {
773
872
  try {
774
- return fromCursor(accessor, cursor2);
873
+ return fromCursor(accessor, cursor);
775
874
  } catch (err) {
776
- log3.catch(err, void 0, {
777
- F: __dxlog_file3,
778
- L: 24,
779
- S: void 0,
780
- C: (f, a) => f(...a)
781
- });
875
+ log3.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 19, S: void 0 });
782
876
  return 0;
783
877
  }
784
878
  }
785
879
  });
786
880
 
787
881
  // src/extensions/automerge/defs.ts
788
- import { Annotation, StateEffect as StateEffect2 } from "@codemirror/state";
882
+ import { Annotation, StateEffect as StateEffect3 } from "@codemirror/state";
789
883
  var getPath = (state, field) => state.field(field).path;
790
884
  var getLastHeads = (state, field) => state.field(field).lastHeads;
791
- var updateHeadsEffect = StateEffect2.define({});
885
+ var updateHeadsEffect = StateEffect3.define({});
792
886
  var updateHeads = (newHeads) => updateHeadsEffect.of({
793
887
  newHeads
794
888
  });
@@ -963,12 +1057,7 @@ var Syncer = class {
963
1057
  this._pending = false;
964
1058
  }
965
1059
  onEditorChange(view) {
966
- log4("onEditorChange", void 0, {
967
- F: __dxlog_file4,
968
- L: 45,
969
- S: this,
970
- C: (f, a) => f(...a)
971
- });
1060
+ log4("onEditorChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 35, S: this });
972
1061
  const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
973
1062
  const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
974
1063
  if (newHeads) {
@@ -979,12 +1068,7 @@ var Syncer = class {
979
1068
  }
980
1069
  }
981
1070
  onAutomergeChange(view) {
982
- log4("onAutomergeChange", void 0, {
983
- F: __dxlog_file4,
984
- L: 60,
985
- S: this,
986
- C: (f, a) => f(...a)
987
- });
1071
+ log4("onAutomergeChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 47, S: this });
988
1072
  const oldHeads = getLastHeads(view.state, this._state);
989
1073
  const newHeads = A2.getHeads(this._handle.doc());
990
1074
  const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.doc(), oldHeads, newHeads);
@@ -1046,6 +1130,17 @@ var automerge = (accessor) => {
1046
1130
  const value = DocAccessor.getValue(accessor);
1047
1131
  const current = this._view.state.doc.toString();
1048
1132
  if (value !== current) {
1133
+ this._view.dispatch({
1134
+ changes: {
1135
+ from: 0,
1136
+ to: this._view.state.doc.length,
1137
+ insert: value
1138
+ },
1139
+ annotations: [
1140
+ initialSync,
1141
+ reconcileAnnotation.of(true)
1142
+ ]
1143
+ });
1049
1144
  }
1050
1145
  });
1051
1146
  }
@@ -1096,10 +1191,7 @@ var awareness = (provider = dummyProvider) => {
1096
1191
  ];
1097
1192
  };
1098
1193
  var RemoteSelectionsDecorator = class {
1099
- _ctx = new Context(void 0, {
1100
- F: __dxlog_file5,
1101
- L: 80
1102
- });
1194
+ _ctx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 33 });
1103
1195
  _cursorConverter;
1104
1196
  _provider;
1105
1197
  _lastAnchor;
@@ -1330,10 +1422,7 @@ var SpaceAwarenessProvider = class {
1330
1422
  this._info = info;
1331
1423
  }
1332
1424
  open() {
1333
- this._ctx = new Context2(void 0, {
1334
- F: __dxlog_file6,
1335
- L: 57
1336
- });
1425
+ this._ctx = new Context2(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 28 });
1337
1426
  this._postTask = new DeferredTask(this._ctx, async () => {
1338
1427
  if (this._localState) {
1339
1428
  await this._messenger.postMessage(this._channel, {
@@ -1360,12 +1449,7 @@ var SpaceAwarenessProvider = class {
1360
1449
  }).catch((err) => {
1361
1450
  log5.debug("failed to query awareness", {
1362
1451
  err
1363
- }, {
1364
- F: __dxlog_file6,
1365
- L: 91,
1366
- S: this,
1367
- C: (f, a) => f(...a)
1368
- });
1452
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 57, S: this });
1369
1453
  });
1370
1454
  }
1371
1455
  close() {
@@ -1377,15 +1461,7 @@ var SpaceAwarenessProvider = class {
1377
1461
  return Array.from(this._remoteStates.values());
1378
1462
  }
1379
1463
  update(position) {
1380
- invariant(this._postTask, void 0, {
1381
- F: __dxlog_file6,
1382
- L: 106,
1383
- S: this,
1384
- A: [
1385
- "this._postTask",
1386
- ""
1387
- ]
1388
- });
1464
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 71, S: this, A: ["this._postTask", ""] });
1389
1465
  this._localState = {
1390
1466
  peerId: this._peerId,
1391
1467
  position,
@@ -1394,27 +1470,11 @@ var SpaceAwarenessProvider = class {
1394
1470
  this._postTask.schedule();
1395
1471
  }
1396
1472
  _handleQueryMessage() {
1397
- invariant(this._postTask, void 0, {
1398
- F: __dxlog_file6,
1399
- L: 117,
1400
- S: this,
1401
- A: [
1402
- "this._postTask",
1403
- ""
1404
- ]
1405
- });
1473
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 80, S: this, A: ["this._postTask", ""] });
1406
1474
  this._postTask.schedule();
1407
1475
  }
1408
1476
  _handlePostMessage(message) {
1409
- invariant(message.kind === "post", void 0, {
1410
- F: __dxlog_file6,
1411
- L: 122,
1412
- S: this,
1413
- A: [
1414
- "message.kind === 'post'",
1415
- ""
1416
- ]
1417
- });
1477
+ invariant(message.kind === "post", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 84, S: this, A: ["message.kind === 'post'", ""] });
1418
1478
  this._remoteStates.set(message.state.peerId, message.state);
1419
1479
  this.remoteStateChange.emit();
1420
1480
  }
@@ -1543,15 +1603,7 @@ var Blaster = class {
1543
1603
  return this._node;
1544
1604
  }
1545
1605
  initialize() {
1546
- invariant2(!this._canvas && !this._ctx, void 0, {
1547
- F: __dxlog_file7,
1548
- L: 142,
1549
- S: this,
1550
- A: [
1551
- "!this._canvas && !this._ctx",
1552
- ""
1553
- ]
1554
- });
1606
+ invariant2(!this._canvas && !this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 134, S: this, A: ["!this._canvas && !this._ctx", ""] });
1555
1607
  this._canvas = document.createElement("canvas");
1556
1608
  this._canvas.id = "code-blast-canvas";
1557
1609
  this._canvas.style.position = "absolute";
@@ -1580,15 +1632,7 @@ var Blaster = class {
1580
1632
  }
1581
1633
  }
1582
1634
  start() {
1583
- invariant2(this._canvas && this._ctx, void 0, {
1584
- F: __dxlog_file7,
1585
- L: 181,
1586
- S: this,
1587
- A: [
1588
- "this._canvas && this._ctx",
1589
- ""
1590
- ]
1591
- });
1635
+ invariant2(this._canvas && this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 166, S: this, A: ["this._canvas && this._ctx", ""] });
1592
1636
  this._running = true;
1593
1637
  this.loop();
1594
1638
  }
@@ -1823,13 +1867,13 @@ var blocks = () => [
1823
1867
  ];
1824
1868
 
1825
1869
  // src/extensions/bookmarks.ts
1826
- import { Prec as Prec3, StateEffect as StateEffect3, StateField as StateField2 } from "@codemirror/state";
1870
+ import { Prec as Prec3, StateEffect as StateEffect4, StateField as StateField2 } from "@codemirror/state";
1827
1871
  import { keymap as keymap4 } from "@codemirror/view";
1828
1872
  import { log as log6 } from "@dxos/log";
1829
1873
  var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/bookmarks.ts";
1830
- var addBookmark = StateEffect3.define();
1831
- var removeBookmark = StateEffect3.define();
1832
- var clearBookmarks = StateEffect3.define();
1874
+ var addBookmark = StateEffect4.define();
1875
+ var removeBookmark = StateEffect4.define();
1876
+ var clearBookmarks = StateEffect4.define();
1833
1877
  var bookmarks = () => {
1834
1878
  return [
1835
1879
  bookmarksField,
@@ -1838,12 +1882,7 @@ var bookmarks = () => {
1838
1882
  key: "Mod-ArrowUp",
1839
1883
  run: (view) => {
1840
1884
  const bookmarks2 = view.state.field(bookmarksField);
1841
- log6("up", bookmarks2, {
1842
- F: __dxlog_file8,
1843
- L: 29,
1844
- S: void 0,
1845
- C: (f, a) => f(...a)
1846
- });
1885
+ log6("up", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 18, S: void 0 });
1847
1886
  return true;
1848
1887
  }
1849
1888
  },
@@ -1851,12 +1890,7 @@ var bookmarks = () => {
1851
1890
  key: "Mod-ArrowDown",
1852
1891
  run: (view) => {
1853
1892
  const bookmarks2 = view.state.field(bookmarksField);
1854
- log6("down", bookmarks2, {
1855
- F: __dxlog_file8,
1856
- L: 37,
1857
- S: void 0,
1858
- C: (f, a) => f(...a)
1859
- });
1893
+ log6("down", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 26, S: void 0 });
1860
1894
  return true;
1861
1895
  }
1862
1896
  }
@@ -1893,7 +1927,7 @@ var bookmarksField = StateField2.define({
1893
1927
 
1894
1928
  // src/extensions/comments.ts
1895
1929
  import { invertedEffects } from "@codemirror/commands";
1896
- import { StateEffect as StateEffect4, StateField as StateField3 } from "@codemirror/state";
1930
+ import { StateEffect as StateEffect5, StateField as StateField3 } from "@codemirror/state";
1897
1931
  import { Decoration as Decoration7, EditorView as EditorView11, ViewPlugin as ViewPlugin10, hoverTooltip, keymap as keymap6 } from "@codemirror/view";
1898
1932
  import sortBy from "lodash.sortby";
1899
1933
  import { debounce as debounce2 } from "@dxos/async";
@@ -1908,7 +1942,7 @@ import { invariant as invariant3 } from "@dxos/invariant";
1908
1942
  import { isTruthy } from "@dxos/util";
1909
1943
  var __dxlog_file9 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/selection.ts";
1910
1944
  var documentId = singleValueFacet();
1911
- var stateRestoreAnnotation = "dxos.org/cm/state-restore";
1945
+ var stateRestoreAnnotation = "org.dxos.cm.state-restore";
1912
1946
  var createEditorStateTransaction = ({ scrollTo, selection }) => {
1913
1947
  return {
1914
1948
  selection,
@@ -1921,28 +1955,12 @@ var createEditorStateTransaction = ({ scrollTo, selection }) => {
1921
1955
  };
1922
1956
  var createEditorStateStore = (keyPrefix) => ({
1923
1957
  getState: (id) => {
1924
- invariant3(id, void 0, {
1925
- F: __dxlog_file9,
1926
- L: 47,
1927
- S: void 0,
1928
- A: [
1929
- "id",
1930
- ""
1931
- ]
1932
- });
1958
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 26, S: void 0, A: ["id", ""] });
1933
1959
  const state = localStorage.getItem(`${keyPrefix}/${id}`);
1934
1960
  return state ? JSON.parse(state) : void 0;
1935
1961
  },
1936
1962
  setState: (id, state) => {
1937
- invariant3(id, void 0, {
1938
- F: __dxlog_file9,
1939
- L: 53,
1940
- S: void 0,
1941
- A: [
1942
- "id",
1943
- ""
1944
- ]
1945
- });
1963
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 31, S: void 0, A: ["id", ""] });
1946
1964
  localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
1947
1965
  }
1948
1966
  });
@@ -1995,9 +2013,9 @@ var selectionState = ({ getState, setState } = {}) => {
1995
2013
 
1996
2014
  // src/extensions/comments.ts
1997
2015
  var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
1998
- var setComments = StateEffect4.define();
1999
- var setSelection = StateEffect4.define();
2000
- var setCommentState = StateEffect4.define();
2016
+ var setComments = StateEffect5.define();
2017
+ var setSelection = StateEffect5.define();
2018
+ var setCommentState = StateEffect5.define();
2001
2019
  var commentsState = StateField3.define({
2002
2020
  create: (state) => ({
2003
2021
  id: state.facet(documentId),
@@ -2064,12 +2082,7 @@ var commentsDecorations = EditorView11.decorations.compute([
2064
2082
  const decorations2 = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
2065
2083
  const range = comment.range;
2066
2084
  if (!range) {
2067
- log7.warn("Invalid range:", range, {
2068
- F: __dxlog_file10,
2069
- L: 140,
2070
- S: void 0,
2071
- C: (f, a) => f(...a)
2072
- });
2085
+ log7.warn("Invalid range:", range, { "~LogMeta": "~LogMeta", F: __dxlog_file10, L: 93, S: void 0 });
2073
2086
  return void 0;
2074
2087
  } else if (range.from === range.to) {
2075
2088
  return void 0;
@@ -2079,7 +2092,7 @@ var commentsDecorations = EditorView11.decorations.compute([
2079
2092
  }).filter(isNonNullable);
2080
2093
  return Decoration7.set(decorations2);
2081
2094
  });
2082
- var commentClickedEffect = StateEffect4.define();
2095
+ var commentClickedEffect = StateEffect5.define();
2083
2096
  var handleCommentClick = EditorView11.domEventHandlers({
2084
2097
  click: (event, view) => {
2085
2098
  let target = event.target;
@@ -2182,10 +2195,10 @@ var trackPastedComments = (onUpdate) => {
2182
2195
  const { comments: comments2 } = update2.startState.field(commentsState);
2183
2196
  const exists = comments2.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
2184
2197
  if (!exists) {
2185
- const cursor2 = Cursor.getCursorFromRange(update2.state, comment);
2198
+ const cursor = Cursor.getCursorFromRange(update2.state, comment);
2186
2199
  onUpdate({
2187
2200
  id: comment.id,
2188
- cursor: cursor2
2201
+ cursor
2189
2202
  });
2190
2203
  }
2191
2204
  }
@@ -2197,7 +2210,7 @@ var mapTrackedComment = (comment, changes) => ({
2197
2210
  from: changes.mapPos(comment.from, 1),
2198
2211
  to: changes.mapPos(comment.to, 1)
2199
2212
  });
2200
- var restoreCommentEffect = StateEffect4.define({
2213
+ var restoreCommentEffect = StateEffect5.define({
2201
2214
  map: mapTrackedComment
2202
2215
  });
2203
2216
  var createComment = (view) => {
@@ -2214,13 +2227,13 @@ var createComment = (view) => {
2214
2227
  }
2215
2228
  });
2216
2229
  }
2217
- const cursor2 = Cursor.getCursorFromRange(view.state, {
2230
+ const cursor = Cursor.getCursorFromRange(view.state, {
2218
2231
  from,
2219
2232
  to
2220
2233
  });
2221
- if (cursor2) {
2234
+ if (cursor) {
2222
2235
  options.onCreate?.({
2223
- cursor: cursor2,
2236
+ cursor,
2224
2237
  from,
2225
2238
  location: view.coordsAtPos(from)
2226
2239
  });
@@ -2461,7 +2474,7 @@ import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap }
2461
2474
  import { HighlightStyle, bracketMatching, syntaxHighlighting } from "@codemirror/language";
2462
2475
  import { searchKeymap } from "@codemirror/search";
2463
2476
  import { EditorState } from "@codemirror/state";
2464
- import { EditorView as EditorView15, ViewPlugin as ViewPlugin11, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap7, lineNumbers, placeholder as placeholder2, scrollPastEnd } from "@codemirror/view";
2477
+ import { EditorView as EditorView16, ViewPlugin as ViewPlugin12, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap7, lineNumbers, placeholder as placeholder2 } from "@codemirror/view";
2465
2478
  import { vscodeDarkStyle, vscodeLightStyle } from "@uiw/codemirror-theme-vscode";
2466
2479
  import defaultsDeep2 from "lodash.defaultsdeep";
2467
2480
  import { generateName } from "@dxos/display-name";
@@ -2473,28 +2486,28 @@ import { EditorView as EditorView13 } from "@codemirror/view";
2473
2486
  import { mx as mx3 } from "@dxos/ui-theme";
2474
2487
  var headings = {
2475
2488
  1: {
2476
- className: "text-4xl",
2477
- fontSize: "var(--text-4xl)",
2489
+ className: "text-3xl",
2490
+ fontSize: "var(--text-3xl)",
2478
2491
  lineHeight: "var(--text-4xl--line-height)"
2479
2492
  },
2480
2493
  2: {
2481
- className: "text-3xl",
2482
- fontSize: "var(--text-3xl)",
2494
+ className: "text-2xl",
2495
+ fontSize: "var(--text-2xl)",
2483
2496
  lineHeight: "var(--text-3xl--line-height)"
2484
2497
  },
2485
2498
  3: {
2486
- className: "text-2xl",
2487
- fontSize: "var(--text-2xl)",
2499
+ className: "text-xl",
2500
+ fontSize: "var(--text-xl)",
2488
2501
  lineHeight: "var(--text-2xl--line-height)"
2489
2502
  },
2490
2503
  4: {
2491
- className: "text-xl",
2492
- fontSize: "var(--text-xl)",
2504
+ className: "text-lg",
2505
+ fontSize: "var(--text-lg)",
2493
2506
  lineHeight: "var(--text-xl--line-height)"
2494
2507
  },
2495
2508
  5: {
2496
- className: "text-lg",
2497
- fontSize: "var(--text-lg)",
2509
+ className: "text-base",
2510
+ fontSize: "var(--text-base)",
2498
2511
  lineHeight: "var(--text-lg--line-height)"
2499
2512
  },
2500
2513
  6: {
@@ -2503,20 +2516,20 @@ var headings = {
2503
2516
  lineHeight: "var(--text-base--line-height)"
2504
2517
  }
2505
2518
  };
2519
+ var fontBody = '"Inter Variable", ui-sans-serif, system-ui, sans-serif';
2520
+ var fontMono = '"JetBrains Mono Variable", ui-monospace, "Cascadia Code", "Source Code Pro", monospace';
2506
2521
  var markdownTheme = {
2507
- code: "font-mono no-underline! text-cm-code",
2508
- codeMark: "font-mono text-cm-code-mark",
2509
- mark: "opacity-50",
2522
+ code: "font-mono! cm-code-inline",
2523
+ codeMark: "font-mono! cm-code-mark",
2524
+ mark: "font-mono!",
2510
2525
  heading: (level) => ({
2511
- className: mx3(headings[level].className, "font-light text-cm-heading"),
2526
+ className: mx3(headings[level].className, "font-light text-(--color-cm-heading-number)"),
2512
2527
  color: "var(--color-cm-heading) !important",
2513
2528
  lineHeight: headings[level].lineHeight,
2514
2529
  fontSize: headings[level].fontSize,
2515
2530
  fontWeight: "100 !important"
2516
2531
  })
2517
2532
  };
2518
- var fontBody = "Inter Variable, ui-sans-serif, system-ui, sans-serif";
2519
- var fontMono = "JetBrains Mono Variable, ui-monospace, Cascadia Code, Source Code Pro, monospace";
2520
2533
  var baseTheme = EditorView13.baseTheme({
2521
2534
  /**
2522
2535
  * Outer frame.
@@ -2529,12 +2542,21 @@ var baseTheme = EditorView13.baseTheme({
2529
2542
  * Scroller
2530
2543
  */
2531
2544
  ".cm-scroller": {
2532
- overflowAnchor: "none"
2545
+ // Browser scroll-anchoring: see comment in `scroller.ts`. `auto` lets the browser pin a
2546
+ // stable element near the viewport top so widget resizes (e.g. tool-block TogglePanel
2547
+ // open/close) don't jump the user's view.
2548
+ overflowAnchor: "auto"
2533
2549
  },
2534
2550
  ".cm-scroller::-webkit-scrollbar": {
2535
- width: "8px"
2551
+ width: "var(--scrollbar-size,8px)",
2552
+ height: "var(--scrollbar-size,8px)"
2553
+ },
2554
+ ".cm-scroller::-webkit-scrollbar-corner": {
2555
+ background: "transparent"
2556
+ },
2557
+ ".cm-scroller::-webkit-scrollbar-track": {
2558
+ background: "transparent"
2536
2559
  },
2537
- ".cm-scroller::-webkit-scrollbar-track": {},
2538
2560
  ".cm-scroller::-webkit-scrollbar-thumb": {
2539
2561
  background: "transparent",
2540
2562
  transition: "background 0.15s"
@@ -2548,7 +2570,6 @@ var baseTheme = EditorView13.baseTheme({
2548
2570
  */
2549
2571
  ".cm-content": {
2550
2572
  padding: "unset",
2551
- lineHeight: "24px",
2552
2573
  color: "unset"
2553
2574
  },
2554
2575
  /**
@@ -2579,9 +2600,16 @@ var baseTheme = EditorView13.baseTheme({
2579
2600
  * Line.
2580
2601
  */
2581
2602
  ".cm-line": {
2582
- lineHeight: "24px",
2603
+ lineHeight: 1.5,
2583
2604
  paddingInline: 0
2584
2605
  },
2606
+ /**
2607
+ * Force all inline children to inherit line-height to prevent monospace font metrics
2608
+ * (JetBrains Mono ascent/descent) from inflating the line box beyond 24px.
2609
+ */
2610
+ ".cm-line *": {
2611
+ lineHeight: "inherit"
2612
+ },
2585
2613
  ".cm-activeLine": {
2586
2614
  background: "var(--color-cm-active-line)"
2587
2615
  },
@@ -2663,12 +2691,12 @@ var baseTheme = EditorView13.baseTheme({
2663
2691
  padding: "4px"
2664
2692
  },
2665
2693
  ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {
2666
- background: "var(--color-active-surface)",
2667
- color: "var(--color-base-surface-text)"
2694
+ background: "var(--color-current-surface)",
2695
+ color: "var(--color-base-foreground)"
2668
2696
  },
2669
2697
  ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
2670
2698
  paddingLeft: "4px !important",
2671
- color: "var(--color-base-surface-text)"
2699
+ color: "var(--color-base-foreground)"
2672
2700
  },
2673
2701
  /**
2674
2702
  * Completion info.
@@ -2687,7 +2715,7 @@ var baseTheme = EditorView13.baseTheme({
2687
2715
  padding: "0 4px"
2688
2716
  },
2689
2717
  ".cm-completionMatchedText": {
2690
- color: "var(--color-base-surface-text)",
2718
+ color: "var(--color-base-foreground)",
2691
2719
  textDecoration: "none !important"
2692
2720
  },
2693
2721
  /**
@@ -2722,7 +2750,7 @@ var baseTheme = EditorView13.baseTheme({
2722
2750
  backgroundColor: "var(--color-input-surface)"
2723
2751
  },
2724
2752
  ".cm-panel input:focus, .cm-panel button:focus": {
2725
- outline: "1px solid var(--color-neutral-focus-indicator)"
2753
+ outline: "1px solid var(--color-focus-ring-subtle)"
2726
2754
  },
2727
2755
  ".cm-panel label": {
2728
2756
  display: "inline-flex",
@@ -2735,7 +2763,7 @@ var baseTheme = EditorView13.baseTheme({
2735
2763
  height: "8px",
2736
2764
  marginRight: "6px !important",
2737
2765
  padding: "2px !important",
2738
- color: "var(--color-neutral-focus-indicator)"
2766
+ color: "var(--color-focus-ring-subtle)"
2739
2767
  },
2740
2768
  ".cm-panel button": {
2741
2769
  "&:hover": {
@@ -2759,10 +2787,9 @@ var editorGutter = EditorView13.theme({
2759
2787
  }
2760
2788
  });
2761
2789
  var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2762
- // Set metrics on the scroller (this is often what CM uses for layout).
2790
+ // Main content.
2763
2791
  ".cm-scroller": {
2764
- fontFamily: monospace ? fontMono : fontBody,
2765
- fontSize: "16px"
2792
+ fontFamily: monospace ? fontMono : fontBody
2766
2793
  },
2767
2794
  // Maintain defaults for UI components.
2768
2795
  ".cm-content, .cm-gutters, .cm-panel": {
@@ -2772,9 +2799,9 @@ var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2772
2799
  });
2773
2800
 
2774
2801
  // src/extensions/focus.ts
2775
- import { StateEffect as StateEffect5, StateField as StateField5 } from "@codemirror/state";
2802
+ import { StateEffect as StateEffect6, StateField as StateField5 } from "@codemirror/state";
2776
2803
  import { EditorView as EditorView14 } from "@codemirror/view";
2777
- var focusEffect = StateEffect5.define();
2804
+ var focusEffect = StateEffect6.define();
2778
2805
  var focusField = StateField5.define({
2779
2806
  create: () => false,
2780
2807
  update: (value, tr) => {
@@ -2802,9 +2829,32 @@ var focus = [
2802
2829
  })
2803
2830
  ];
2804
2831
 
2832
+ // src/extensions/scroll-past-end.ts
2833
+ import { EditorView as EditorView15, ViewPlugin as ViewPlugin11 } from "@codemirror/view";
2834
+ var scrollPastEndPlugin = ViewPlugin11.fromClass(class {
2835
+ height = 1e3;
2836
+ attrs = {
2837
+ style: "padding-bottom: 1000px"
2838
+ };
2839
+ update({ view }) {
2840
+ const lastLineBlock = view.lineBlockAt(view.state.doc.length);
2841
+ const height = view.dom.clientHeight - lastLineBlock.height - view.documentPadding.top - 0.5;
2842
+ if (height >= 0 && height !== this.height) {
2843
+ this.height = height;
2844
+ this.attrs = {
2845
+ style: `padding-bottom: ${height}px`
2846
+ };
2847
+ }
2848
+ }
2849
+ });
2850
+ var scrollPastEnd = () => [
2851
+ scrollPastEndPlugin,
2852
+ EditorView15.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?.attrs ?? null)
2853
+ ];
2854
+
2805
2855
  // src/extensions/factories.ts
2806
2856
  var __dxlog_file11 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/factories.ts";
2807
- var tabbable = EditorView15.contentAttributes.of({
2857
+ var tabbable = EditorView16.contentAttributes.of({
2808
2858
  tabindex: "0"
2809
2859
  });
2810
2860
  var filterChars = (chars) => {
@@ -2857,13 +2907,8 @@ var createBasicExtensions = (propsProp) => {
2857
2907
  const props = defaultsDeep2({}, propsProp, defaultBasicOptions);
2858
2908
  return [
2859
2909
  // NOTE: Doesn't catch errors in keymap functions.
2860
- EditorView15.exceptionSink.of((err) => {
2861
- log8.catch(err, void 0, {
2862
- F: __dxlog_file11,
2863
- L: 131,
2864
- S: void 0,
2865
- C: (f, a) => f(...a)
2866
- });
2910
+ EditorView16.exceptionSink.of((err) => {
2911
+ log8.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file11, L: 79, S: void 0 });
2867
2912
  }),
2868
2913
  props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
2869
2914
  props.bracketMatching && bracketMatching(),
@@ -2872,7 +2917,7 @@ var createBasicExtensions = (propsProp) => {
2872
2917
  props.drawSelection && drawSelection({
2873
2918
  cursorBlinkRate: 1200
2874
2919
  }),
2875
- props.editable !== void 0 && EditorView15.editable.of(props.editable),
2920
+ props.editable !== void 0 && EditorView16.editable.of(props.editable),
2876
2921
  props.focus && focus,
2877
2922
  props.highlightActiveLine && highlightActiveLine(),
2878
2923
  props.history && history(),
@@ -2880,9 +2925,16 @@ var createBasicExtensions = (propsProp) => {
2880
2925
  lineNumbers(),
2881
2926
  editorGutter
2882
2927
  ],
2883
- props.lineWrapping && EditorView15.lineWrapping,
2928
+ props.lineWrapping && EditorView16.lineWrapping,
2884
2929
  props.placeholder && placeholder2(props.placeholder),
2885
2930
  props.readOnly !== void 0 && EditorState.readOnly.of(props.readOnly),
2931
+ // `EditorState.readOnly` is advisory — CodeMirror doesn't auto-reject doc-changing
2932
+ // transactions. Some extensions (e.g. `@codemirror/lang-markdown`'s Enter handler that
2933
+ // continues a list) dispatch programmatic edits regardless. Drop user-initiated edits
2934
+ // (`input` / `delete` keymap dispatches plus `undo` / `redo` from the history extension)
2935
+ // but pass programmatic dispatches — streaming `MarkdownStream` and similar consumers
2936
+ // depend on being able to populate the doc themselves.
2937
+ props.readOnly && EditorState.transactionFilter.of((tr) => tr.docChanged && (tr.isUserEvent("input") || tr.isUserEvent("delete") || tr.isUserEvent("undo") || tr.isUserEvent("redo")) ? [] : tr),
2886
2938
  props.scrollPastEnd && scrollPastEnd(),
2887
2939
  props.tabbable && tabbable,
2888
2940
  props.tabSize && EditorState.tabSize.of(props.tabSize),
@@ -2925,24 +2977,29 @@ var defaultStyles = {
2925
2977
  dark: vscodeDarkStyle,
2926
2978
  light: vscodeLightStyle
2927
2979
  };
2928
- var createThemeExtensions = ({ monospace, themeMode, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp } = {}) => {
2980
+ var createThemeExtensions = ({ monospace, scrollbarThin, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp, themeMode } = {}) => {
2929
2981
  const slots = defaultsDeep2({}, slotsProp, defaultThemeSlots);
2930
2982
  return [
2931
2983
  baseTheme,
2932
- EditorView15.darkTheme.of(themeMode === "dark"),
2984
+ EditorView16.darkTheme.of(themeMode === "dark"),
2933
2985
  createFontTheme({
2934
2986
  monospace
2935
2987
  }),
2936
2988
  syntaxHighlightingProp && syntaxHighlighting(HighlightStyle.define(themeMode === "dark" ? defaultStyles.dark : defaultStyles.light)),
2937
- slots.editor?.className && EditorView15.editorAttributes.of({
2989
+ slots.editor?.className && EditorView16.editorAttributes.of({
2938
2990
  class: slots.editor.className
2939
2991
  }),
2940
- slots.content?.className && EditorView15.contentAttributes.of({
2992
+ slots.content?.className && EditorView16.contentAttributes.of({
2941
2993
  class: slots.content.className
2942
2994
  }),
2943
- slots.scroll?.className && ViewPlugin11.fromClass(class {
2995
+ (slots.scroller?.className || scrollbarThin) && ViewPlugin12.fromClass(class {
2944
2996
  constructor(view) {
2945
- view.scrollDOM.classList.add(...slots.scroll.className.split(/\s+/));
2997
+ if (slots.scroller?.className) {
2998
+ view.scrollDOM.classList.add(...slots.scroller.className.split(/\s+/));
2999
+ }
3000
+ if (scrollbarThin) {
3001
+ view.scrollDOM.style.setProperty("--scrollbar-size", "4px");
3002
+ }
2946
3003
  }
2947
3004
  })
2948
3005
  ].filter(isTruthy2);
@@ -2971,7 +3028,7 @@ var createDataExtensions = ({ id, text, messenger, identity }) => {
2971
3028
 
2972
3029
  // src/extensions/folding.ts
2973
3030
  import { codeFolding, foldGutter } from "@codemirror/language";
2974
- import { EditorView as EditorView16 } from "@codemirror/view";
3031
+ import { EditorView as EditorView17 } from "@codemirror/view";
2975
3032
  import { Domino as Domino2, mx as mx4 } from "@dxos/ui";
2976
3033
  var folding = () => {
2977
3034
  return [
@@ -2979,13 +3036,14 @@ var folding = () => {
2979
3036
  placeholderDOM: () => Domino2.of("span").root
2980
3037
  }),
2981
3038
  foldGutter({
3039
+ // NOTE: We can't animate since the element is remounted on state change.
2982
3040
  markerDOM: (open) => {
2983
- return Domino2.of("div").classNames("flex h-full justify-center items-center").children(Domino2.of("svg", Domino2.SVG).classNames(mx4("w-4 h-4 cursor-pointer", open && "rotate-90")).children(Domino2.of("use", Domino2.SVG).attributes({
3041
+ return Domino2.of("div").classNames("flex h-full justify-center items-center").append(Domino2.of("svg", Domino2.SVG).classNames(mx4("w-4 h-4 cursor-pointer", open && "rotate-90")).append(Domino2.of("use", Domino2.SVG).attributes({
2984
3042
  href: Domino2.icon("ph--caret-right--regular")
2985
3043
  }))).root;
2986
3044
  }
2987
3045
  }),
2988
- EditorView16.theme({
3046
+ EditorView17.theme({
2989
3047
  ".cm-foldGutter": {
2990
3048
  opacity: 0.3,
2991
3049
  transition: "opacity 0.3s",
@@ -2999,7 +3057,7 @@ var folding = () => {
2999
3057
  };
3000
3058
 
3001
3059
  // src/extensions/hashtag.ts
3002
- import { Decoration as Decoration8, EditorView as EditorView17, MatchDecorator, ViewPlugin as ViewPlugin12, WidgetType as WidgetType4 } from "@codemirror/view";
3060
+ import { Decoration as Decoration8, EditorView as EditorView18, MatchDecorator, ViewPlugin as ViewPlugin13, WidgetType as WidgetType4 } from "@codemirror/view";
3003
3061
  import { getHashStyles, mx as mx5 } from "@dxos/ui-theme";
3004
3062
  var TagWidget = class extends WidgetType4 {
3005
3063
  _text;
@@ -3020,7 +3078,7 @@ var tagMatcher = new MatchDecorator({
3020
3078
  })
3021
3079
  });
3022
3080
  var hashtag = () => [
3023
- ViewPlugin12.fromClass(class {
3081
+ ViewPlugin13.fromClass(class {
3024
3082
  tags;
3025
3083
  constructor(view) {
3026
3084
  this.tags = tagMatcher.createDeco(view);
@@ -3030,11 +3088,11 @@ var hashtag = () => [
3030
3088
  }
3031
3089
  }, {
3032
3090
  decorations: (instance) => instance.tags,
3033
- provide: (plugin) => EditorView17.atomicRanges.of((view) => {
3091
+ provide: (plugin) => EditorView18.atomicRanges.of((view) => {
3034
3092
  return view.plugin(plugin)?.tags || Decoration8.none;
3035
3093
  })
3036
3094
  }),
3037
- EditorView17.theme({
3095
+ EditorView18.theme({
3038
3096
  ".cm-tag": {
3039
3097
  borderRadius: "4px",
3040
3098
  marginRight: "6px",
@@ -3089,18 +3147,18 @@ var schemaLinter = (validate) => (view) => {
3089
3147
  };
3090
3148
 
3091
3149
  // src/extensions/listener.ts
3092
- import { EditorView as EditorView18 } from "@codemirror/view";
3150
+ import { EditorView as EditorView19 } from "@codemirror/view";
3093
3151
  import { isNonNullable as isNonNullable2 } from "@dxos/util";
3094
3152
  var listener = ({ onFocus, onChange }) => {
3095
3153
  return [
3096
- onFocus && EditorView18.focusChangeEffect.of((state, focusing) => {
3154
+ onFocus && EditorView19.focusChangeEffect.of((state, focusing) => {
3097
3155
  onFocus({
3098
3156
  id: state.facet(documentId),
3099
3157
  focusing
3100
3158
  });
3101
3159
  return null;
3102
3160
  }),
3103
- onChange && EditorView18.updateListener.of(({ state, docChanged }) => {
3161
+ onChange && EditorView19.updateListener.of(({ state, docChanged }) => {
3104
3162
  if (docChanged) {
3105
3163
  onChange({
3106
3164
  id: state.facet(documentId),
@@ -3115,7 +3173,7 @@ var listener = ({ onFocus, onChange }) => {
3115
3173
  import { snippet } from "@codemirror/autocomplete";
3116
3174
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
3117
3175
  import { EditorSelection as EditorSelection2 } from "@codemirror/state";
3118
- import { EditorView as EditorView19, keymap as keymap8 } from "@codemirror/view";
3176
+ import { EditorView as EditorView20, keymap as keymap8 } from "@codemirror/view";
3119
3177
  import { debounceAndThrottle } from "@dxos/async";
3120
3178
  var formattingEquals = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockQuote === b.blockQuote;
3121
3179
  var Inline = /* @__PURE__ */ (function(Inline2) {
@@ -4204,7 +4262,7 @@ var getFormatting = (state) => {
4204
4262
  };
4205
4263
  };
4206
4264
  var formattingListener = (onStateChange, delay = 100) => {
4207
- return EditorView19.updateListener.of(debounceAndThrottle((update2) => {
4265
+ return EditorView20.updateListener.of(debounceAndThrottle((update2) => {
4208
4266
  if (update2.docChanged || update2.selectionSet) {
4209
4267
  onStateChange(getFormatting(update2.state));
4210
4268
  }
@@ -4265,8 +4323,7 @@ import { completionKeymap } from "@codemirror/autocomplete";
4265
4323
  import { defaultKeymap as defaultKeymap2, indentWithTab as indentWithTab2 } from "@codemirror/commands";
4266
4324
  import { jsonLanguage } from "@codemirror/lang-json";
4267
4325
  import { markdown, markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
4268
- import { xml } from "@codemirror/lang-xml";
4269
- import { LanguageDescription, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4326
+ import { foldNodeProp, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4270
4327
  import { languages } from "@codemirror/language-data";
4271
4328
  import { keymap as keymap9 } from "@codemirror/view";
4272
4329
  import { isTruthy as isTruthy3 } from "@dxos/util";
@@ -4276,11 +4333,6 @@ import { markdownLanguage } from "@codemirror/lang-markdown";
4276
4333
  import { HighlightStyle as HighlightStyle2 } from "@codemirror/language";
4277
4334
  import { Tag, styleTags, tags } from "@lezer/highlight";
4278
4335
  import { Table } from "@lezer/markdown";
4279
- var styles4 = {
4280
- code: "font-mono no-underline! text-cm-code",
4281
- codeMark: "font-mono text-cm-code-mark",
4282
- mark: "opacity-50"
4283
- };
4284
4336
  var markdownTags = {
4285
4337
  Blockquote: Tag.define(),
4286
4338
  CodeMark: Tag.define(),
@@ -4362,7 +4414,7 @@ var markdownHighlightStyle = (_options = {}) => {
4362
4414
  markdownTags.LinkReference,
4363
4415
  markdownTags.ListMark
4364
4416
  ],
4365
- class: styles4.mark
4417
+ class: markdownTheme.mark
4366
4418
  },
4367
4419
  // Markdown marks.
4368
4420
  {
@@ -4373,7 +4425,7 @@ var markdownHighlightStyle = (_options = {}) => {
4373
4425
  markdownTags.QuoteMark,
4374
4426
  markdownTags.EmphasisMark
4375
4427
  ],
4376
- class: styles4.mark
4428
+ class: markdownTheme.mark
4377
4429
  },
4378
4430
  // E.g., code block language (after ```).
4379
4431
  {
@@ -4382,7 +4434,7 @@ var markdownHighlightStyle = (_options = {}) => {
4382
4434
  tags.function(tags.variableName),
4383
4435
  tags.labelName
4384
4436
  ],
4385
- class: styles4.codeMark
4437
+ class: markdownTheme.codeMark
4386
4438
  },
4387
4439
  // Fonts.
4388
4440
  {
@@ -4448,7 +4500,7 @@ var markdownHighlightStyle = (_options = {}) => {
4448
4500
  markdownTags.CodeText,
4449
4501
  markdownTags.InlineCode
4450
4502
  ],
4451
- class: styles4.code
4503
+ class: markdownTheme.code
4452
4504
  },
4453
4505
  {
4454
4506
  tag: [
@@ -4478,15 +4530,23 @@ var createMarkdownExtensions = (options = {}) => {
4478
4530
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
4479
4531
  base: markdownLanguage2,
4480
4532
  // Languages for syntax highlighting fenced code blocks.
4533
+ // Caller-supplied languages are checked first so they can override defaults.
4481
4534
  defaultCodeLanguage: jsonLanguage,
4482
- codeLanguages: languages,
4535
+ codeLanguages: [
4536
+ ...options.codeLanguages ?? [],
4537
+ ...languages
4538
+ ],
4483
4539
  // Don't complete HTML tags.
4484
4540
  completeHTMLTags: false,
4485
4541
  // Parser extensions.
4486
4542
  extensions: [
4487
4543
  // GFM provided by default.
4488
4544
  markdownTagsExtensions,
4489
- ...options.extensions ?? defaultExtensions()
4545
+ ...options.extensions ?? defaultExtensions(),
4546
+ // Disable folding for fenced code blocks by overriding foldNodeProp.
4547
+ // Note: returning null from foldService does not prevent syntaxFolding fallback,
4548
+ // so we must override the node prop directly on the FencedCode node type.
4549
+ noFencedCodeFolding
4490
4550
  ]
4491
4551
  }),
4492
4552
  // Custom styles.
@@ -4501,18 +4561,13 @@ var createMarkdownExtensions = (options = {}) => {
4501
4561
  ].filter(isTruthy3))
4502
4562
  ];
4503
4563
  };
4504
- var xmlLanguageDesc = LanguageDescription.of({
4505
- name: "xml",
4506
- alias: [
4507
- "html",
4508
- "xhtml"
4509
- ],
4510
- extensions: [
4511
- "xml",
4512
- "xhtml"
4513
- ],
4514
- load: async () => xml()
4515
- });
4564
+ var noFencedCodeFolding = {
4565
+ props: [
4566
+ foldNodeProp.add({
4567
+ FencedCode: () => null
4568
+ })
4569
+ ]
4570
+ };
4516
4571
  var defaultExtensions = () => [
4517
4572
  noSetExtHeading,
4518
4573
  noHtml
@@ -4532,19 +4587,19 @@ var debugTree = (cb) => StateField6.define({
4532
4587
  update: (value, tr) => cb(convertTreeToJson(tr.state))
4533
4588
  });
4534
4589
  var convertTreeToJson = (state) => {
4535
- const treeToJson = (cursor2) => {
4590
+ const treeToJson = (cursor) => {
4536
4591
  const node = {
4537
- type: cursor2.type.name,
4538
- from: cursor2.from,
4539
- to: cursor2.to,
4540
- text: state.doc.slice(cursor2.from, cursor2.to).toString(),
4592
+ type: cursor.type.name,
4593
+ from: cursor.from,
4594
+ to: cursor.to,
4595
+ text: state.doc.slice(cursor.from, cursor.to).toString(),
4541
4596
  children: []
4542
4597
  };
4543
- if (cursor2.firstChild()) {
4598
+ if (cursor.firstChild()) {
4544
4599
  do {
4545
- node.children.push(treeToJson(cursor2));
4546
- } while (cursor2.nextSibling());
4547
- cursor2.parent();
4600
+ node.children.push(treeToJson(cursor));
4601
+ } while (cursor.nextSibling());
4602
+ cursor.parent();
4548
4603
  }
4549
4604
  return node;
4550
4605
  };
@@ -4553,17 +4608,16 @@ var convertTreeToJson = (state) => {
4553
4608
 
4554
4609
  // src/extensions/markdown/decorate.ts
4555
4610
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
4556
- import { Prec as Prec4, RangeSetBuilder as RangeSetBuilder5, StateEffect as StateEffect6 } from "@codemirror/state";
4557
- import { Decoration as Decoration11, EditorView as EditorView23, ViewPlugin as ViewPlugin14, WidgetType as WidgetType7 } from "@codemirror/view";
4611
+ import { Prec as Prec4, RangeSetBuilder as RangeSetBuilder5, StateEffect as StateEffect7 } from "@codemirror/state";
4612
+ import { Decoration as Decoration11, EditorView as EditorView24, ViewPlugin as ViewPlugin15, WidgetType as WidgetType7 } from "@codemirror/view";
4558
4613
  import { invariant as invariant4 } from "@dxos/invariant";
4559
- import { mx as mx6 } from "@dxos/ui-theme";
4560
4614
 
4561
4615
  // src/extensions/markdown/changes.ts
4562
4616
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
4563
4617
  import { Transaction as Transaction4 } from "@codemirror/state";
4564
- import { ViewPlugin as ViewPlugin13 } from "@codemirror/view";
4618
+ import { ViewPlugin as ViewPlugin14 } from "@codemirror/view";
4565
4619
  var adjustChanges = () => {
4566
- return ViewPlugin13.fromClass(class {
4620
+ return ViewPlugin14.fromClass(class {
4567
4621
  update(update2) {
4568
4622
  const tree = syntaxTree4(update2.state);
4569
4623
  const adjustments = [];
@@ -4705,7 +4759,7 @@ var getValidUrl = (str) => {
4705
4759
  // src/extensions/markdown/image.ts
4706
4760
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
4707
4761
  import { StateField as StateField7 } from "@codemirror/state";
4708
- import { Decoration as Decoration9, EditorView as EditorView20, WidgetType as WidgetType5 } from "@codemirror/view";
4762
+ import { Decoration as Decoration9, EditorView as EditorView21, WidgetType as WidgetType5 } from "@codemirror/view";
4709
4763
  var image = (_options = {}) => {
4710
4764
  return [
4711
4765
  StateField7.define({
@@ -4716,10 +4770,10 @@ var image = (_options = {}) => {
4716
4770
  if (!tr.docChanged && !tr.selection) {
4717
4771
  return value;
4718
4772
  }
4719
- const cursor2 = tr.state.selection.main.head;
4773
+ const cursor = tr.state.selection.main.head;
4720
4774
  const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
4721
- let from = Math.min(cursor2, oldCursor);
4722
- let to = Math.max(cursor2, oldCursor);
4775
+ let from = Math.min(cursor, oldCursor);
4776
+ let to = Math.max(cursor, oldCursor);
4723
4777
  tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
4724
4778
  from = Math.min(from, fromB);
4725
4779
  to = Math.max(to, toB);
@@ -4733,19 +4787,19 @@ var image = (_options = {}) => {
4733
4787
  add: buildDecorations(tr.state, from, to)
4734
4788
  });
4735
4789
  },
4736
- provide: (field) => EditorView20.decorations.from(field)
4790
+ provide: (field) => EditorView21.decorations.from(field)
4737
4791
  })
4738
4792
  ];
4739
4793
  };
4740
4794
  var buildDecorations = (state, from, to) => {
4741
4795
  const decorations2 = [];
4742
- const cursor2 = state.selection.main.head;
4796
+ const cursor = state.selection.main.head;
4743
4797
  syntaxTree5(state).iterate({
4744
4798
  enter: (node) => {
4745
4799
  if (node.name === "Image") {
4746
4800
  const urlNode = node.node.getChild("URL");
4747
4801
  if (urlNode) {
4748
- const hide2 = state.readOnly || cursor2 < node.from || cursor2 > node.to || !state.field(focusField);
4802
+ const hide2 = state.readOnly || cursor < node.from || cursor > node.to || !state.field(focusField);
4749
4803
  const url = state.sliceDoc(urlNode.from, urlNode.to);
4750
4804
  if (url.match(/^https?:\/\//) === null && url.match(/^file?:\/\//) === null) {
4751
4805
  return;
@@ -4793,10 +4847,10 @@ var ImageWidget = class extends WidgetType5 {
4793
4847
  };
4794
4848
 
4795
4849
  // src/extensions/markdown/styles.ts
4796
- import { EditorView as EditorView21 } from "@codemirror/view";
4850
+ import { EditorView as EditorView22 } from "@codemirror/view";
4797
4851
  var bulletListIndentationWidth = 24;
4798
4852
  var orderedListIndentationWidth = 36;
4799
- var formattingStyles = EditorView21.theme({
4853
+ var formattingStyles = EditorView22.theme({
4800
4854
  /**
4801
4855
  * Horizontal rule.
4802
4856
  */
@@ -4831,13 +4885,38 @@ var formattingStyles = EditorView21.theme({
4831
4885
  background: "var(--color-cm-codeblock)",
4832
4886
  borderLeft: "2px solid var(--color-cm-separator)",
4833
4887
  paddingLeft: "1rem",
4834
- margin: "0"
4888
+ margin: 0
4835
4889
  },
4836
4890
  /**
4837
4891
  * Code and codeblocks.
4838
4892
  */
4893
+ "& code": {
4894
+ fontFamily: fontMono,
4895
+ color: "var(--color-cm-code)",
4896
+ whiteSpace: "nowrap"
4897
+ },
4839
4898
  "& .cm-code": {
4840
- fontFamily: fontMono
4899
+ fontFamily: fontMono,
4900
+ color: "var(--color-cm-code)"
4901
+ },
4902
+ // Inline code spans (triggered by backticks) use `cm-code-inline` + `font-mono`.
4903
+ // Different monospace font metrics can slightly overflow the fixed CodeMirror line box,
4904
+ // so constrain them to the target 24px height.
4905
+ "& .cm-code-inline": {
4906
+ fontFamily: fontMono,
4907
+ height: "24px",
4908
+ // display: 'inline-flex',
4909
+ alignItems: "center",
4910
+ overflow: "hidden",
4911
+ whiteSpace: "nowrap",
4912
+ color: "var(--color-cm-code-inline)"
4913
+ },
4914
+ "& .cm-code-mark": {
4915
+ fontFamily: fontMono,
4916
+ height: "24px",
4917
+ display: "inline-flex",
4918
+ alignItems: "center",
4919
+ overflow: "hidden"
4841
4920
  },
4842
4921
  "& .cm-codeblock-line": {
4843
4922
  background: "var(--color-cm-codeblock)",
@@ -4869,16 +4948,24 @@ var formattingStyles = EditorView21.theme({
4869
4948
  */
4870
4949
  ".cm-table *": {
4871
4950
  fontFamily: fontMono,
4951
+ lineHeight: 1.5,
4872
4952
  textDecoration: "none !important"
4873
4953
  },
4874
4954
  ".cm-table-head": {
4875
4955
  padding: "2px 16px 2px 0px",
4956
+ overflowWrap: "break-word",
4957
+ whiteSpace: "pre-wrap",
4958
+ wordBreak: "keep-all",
4876
4959
  textAlign: "left",
4877
- borderBottom: "1px solid var(--color-cm-separator)",
4878
- color: "var(--color-subdued)"
4960
+ color: "var(--color-subdued)",
4961
+ borderBottom: "1px solid var(--color-cm-separator)"
4879
4962
  },
4880
4963
  ".cm-table-cell": {
4881
- padding: "2px 16px 2px 0px"
4964
+ padding: "2px 16px 2px 0px",
4965
+ overflowWrap: "break-word",
4966
+ whiteSpace: "pre-wrap",
4967
+ wordBreak: "keep-all",
4968
+ verticalAlign: "top"
4882
4969
  },
4883
4970
  /**
4884
4971
  * Image.
@@ -4894,12 +4981,12 @@ var formattingStyles = EditorView21.theme({
4894
4981
  },
4895
4982
  ".cm-image-with-loader": {
4896
4983
  display: "block",
4897
- opacity: "0",
4984
+ opacity: 0,
4898
4985
  transitionDuration: "350ms",
4899
4986
  transitionProperty: "opacity"
4900
4987
  },
4901
4988
  ".cm-image-with-loader.cm-loaded-image": {
4902
- opacity: "1"
4989
+ opacity: 1
4903
4990
  },
4904
4991
  ".cm-image-wrapper": {
4905
4992
  "grid-template-columns": "1fr",
@@ -4918,17 +5005,17 @@ var formattingStyles = EditorView21.theme({
4918
5005
  // src/extensions/markdown/table.ts
4919
5006
  import { syntaxTree as syntaxTree6 } from "@codemirror/language";
4920
5007
  import { RangeSetBuilder as RangeSetBuilder4, StateField as StateField8 } from "@codemirror/state";
4921
- import { Decoration as Decoration10, EditorView as EditorView22, WidgetType as WidgetType6 } from "@codemirror/view";
5008
+ import { Decoration as Decoration10, EditorView as EditorView23, WidgetType as WidgetType6 } from "@codemirror/view";
4922
5009
  var table = (options = {}) => {
4923
5010
  return StateField8.define({
4924
5011
  create: (state) => update(state, options),
4925
5012
  update: (_, tr) => update(tr.state, options),
4926
- provide: (field) => EditorView22.decorations.from(field)
5013
+ provide: (field) => EditorView23.decorations.from(field)
4927
5014
  });
4928
5015
  };
4929
5016
  var update = (state, _options) => {
4930
5017
  const builder = new RangeSetBuilder4();
4931
- const cursor2 = state.selection.main.head;
5018
+ const cursor = state.selection.main.head;
4932
5019
  const tables = [];
4933
5020
  const getTable = () => tables[tables.length - 1];
4934
5021
  const getRow = () => {
@@ -4966,7 +5053,7 @@ var update = (state, _options) => {
4966
5053
  }
4967
5054
  });
4968
5055
  tables.forEach((table2) => {
4969
- const replace = state.readOnly || cursor2 < table2.from || cursor2 > table2.to;
5056
+ const replace = state.readOnly || cursor < table2.from || cursor > table2.to;
4970
5057
  if (replace) {
4971
5058
  builder.add(table2.from, table2.to, Decoration10.replace({
4972
5059
  block: true,
@@ -4980,6 +5067,26 @@ var update = (state, _options) => {
4980
5067
  });
4981
5068
  return builder.finish();
4982
5069
  };
5070
+ var renderCellContent = (el, text) => {
5071
+ const parts = text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*|__[^_\n]+__|\*[^*\n]+\*|_[^_\n]+_)/);
5072
+ for (const part of parts) {
5073
+ if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
5074
+ const code = document.createElement("code");
5075
+ code.textContent = part.slice(1, -1);
5076
+ el.appendChild(code);
5077
+ } else if (part.startsWith("**") && part.endsWith("**") || part.startsWith("__") && part.endsWith("__")) {
5078
+ const strong = document.createElement("strong");
5079
+ strong.textContent = part.slice(2, -2);
5080
+ el.appendChild(strong);
5081
+ } else if (part.startsWith("*") && part.endsWith("*") || part.startsWith("_") && part.endsWith("_")) {
5082
+ const em = document.createElement("em");
5083
+ em.textContent = part.slice(1, -1);
5084
+ el.appendChild(em);
5085
+ } else {
5086
+ el.appendChild(document.createTextNode(part));
5087
+ }
5088
+ }
5089
+ };
4983
5090
  var TableWidget = class extends WidgetType6 {
4984
5091
  _table;
4985
5092
  constructor(_table) {
@@ -4999,7 +5106,7 @@ var TableWidget = class extends WidgetType6 {
4999
5106
  this._table.header?.forEach((cell) => {
5000
5107
  const th = document.createElement("th");
5001
5108
  th.setAttribute("class", "cm-table-head");
5002
- tr.appendChild(th).textContent = cell;
5109
+ renderCellContent(tr.appendChild(th), cell);
5003
5110
  });
5004
5111
  const body = table2.appendChild(document.createElement("tbody"));
5005
5112
  this._table.rows?.forEach((row) => {
@@ -5007,7 +5114,7 @@ var TableWidget = class extends WidgetType6 {
5007
5114
  row.forEach((cell) => {
5008
5115
  const td = document.createElement("td");
5009
5116
  td.setAttribute("class", "cm-table-cell");
5010
- tr2.appendChild(td).textContent = cell;
5117
+ renderCellContent(tr2.appendChild(td), cell);
5011
5118
  });
5012
5119
  });
5013
5120
  return div;
@@ -5114,10 +5221,10 @@ var fencedCodeLine = Decoration11.line({
5114
5221
  class: "cm-code cm-codeblock-line"
5115
5222
  });
5116
5223
  var fencedCodeLineFirst = Decoration11.line({
5117
- class: mx6("cm-code cm-codeblock-line", "cm-codeblock-start")
5224
+ class: "cm-code cm-codeblock-line cm-codeblock-start"
5118
5225
  });
5119
5226
  var fencedCodeLineLast = Decoration11.line({
5120
- class: mx6("cm-code cm-codeblock-line", "cm-codeblock-end")
5227
+ class: "cm-code cm-codeblock-line cm-codeblock-end"
5121
5228
  });
5122
5229
  var commentBlockLine = fencedCodeLine;
5123
5230
  var commentBlockLineFirst = fencedCodeLineFirst;
@@ -5149,15 +5256,7 @@ var buildDecorations2 = (view, options, focus2) => {
5149
5256
  const { state } = view;
5150
5257
  const headerLevels = [];
5151
5258
  const getHeaderLevels = (node, level) => {
5152
- invariant4(level > 0, void 0, {
5153
- F: __dxlog_file12,
5154
- L: 179,
5155
- S: void 0,
5156
- A: [
5157
- "level > 0",
5158
- ""
5159
- ]
5160
- });
5259
+ invariant4(level > 0, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 160, S: void 0, A: ["level > 0", ""] });
5161
5260
  if (level > headerLevels.length) {
5162
5261
  const len = headerLevels.length;
5163
5262
  headerLevels.length = level;
@@ -5188,15 +5287,7 @@ var buildDecorations2 = (view, options, focus2) => {
5188
5287
  listLevels.pop();
5189
5288
  };
5190
5289
  const getCurrentListLevel = () => {
5191
- invariant4(listLevels.length, void 0, {
5192
- F: __dxlog_file12,
5193
- L: 201,
5194
- S: void 0,
5195
- A: [
5196
- "listLevels.length",
5197
- ""
5198
- ]
5199
- });
5290
+ invariant4(listLevels.length, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 192, S: void 0, A: ["listLevels.length", ""] });
5200
5291
  return listLevels[listLevels.length - 1];
5201
5292
  };
5202
5293
  const enterNode = (node) => {
@@ -5234,7 +5325,7 @@ var buildDecorations2 = (view, options, focus2) => {
5234
5325
  deco: hide
5235
5326
  });
5236
5327
  } else {
5237
- const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + " ";
5328
+ const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + "). ";
5238
5329
  if (num.length) {
5239
5330
  atomicDecoRanges.push({
5240
5331
  from: mark.from,
@@ -5417,11 +5508,11 @@ var buildDecorations2 = (view, options, focus2) => {
5417
5508
  }
5418
5509
  decoRanges.push({
5419
5510
  from: marks[0].to,
5420
- to: marks[1].from,
5511
+ to: !editing && options.renderLinkButton ? node.to : marks[1].from,
5421
5512
  deco: Decoration11.mark({
5422
5513
  tagName: "a",
5423
5514
  attributes: {
5424
- class: "cm-link",
5515
+ class: options.renderLinkButton ? "cm-link cm-link-with-button" : "cm-link",
5425
5516
  href: url,
5426
5517
  rel: "noreferrer",
5427
5518
  target: "_blank"
@@ -5499,18 +5590,21 @@ var buildDecorations2 = (view, options, focus2) => {
5499
5590
  deco.add(from, to, d);
5500
5591
  }
5501
5592
  const atomicDeco = new RangeSetBuilder5();
5502
- for (const { from, to, deco: d } of atomicDecoRanges) {
5503
- atomicDeco.add(from, to, d);
5593
+ for (const { from, to, deco: deco2 } of atomicDecoRanges) {
5594
+ if (from < to && state.doc.lineAt(from).number !== state.doc.lineAt(to).number) {
5595
+ continue;
5596
+ }
5597
+ atomicDeco.add(from, to, deco2);
5504
5598
  }
5505
5599
  return {
5506
5600
  deco: deco.finish(),
5507
5601
  atomicDeco: atomicDeco.finish()
5508
5602
  };
5509
5603
  };
5510
- var forceUpdate = StateEffect6.define();
5604
+ var forceUpdate = StateEffect7.define();
5511
5605
  var decorateMarkdown = (options = {}) => {
5512
5606
  return [
5513
- ViewPlugin14.fromClass(class {
5607
+ ViewPlugin15.fromClass(class {
5514
5608
  deco;
5515
5609
  atomicDeco;
5516
5610
  pendingUpdate;
@@ -5545,9 +5639,9 @@ var decorateMarkdown = (options = {}) => {
5545
5639
  }
5546
5640
  }, {
5547
5641
  provide: (plugin) => [
5548
- Prec4.low(EditorView23.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5549
- EditorView23.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5550
- EditorView23.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5642
+ Prec4.low(EditorView24.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5643
+ EditorView24.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5644
+ EditorView24.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5551
5645
  ]
5552
5646
  }),
5553
5647
  image(),
@@ -5615,12 +5709,7 @@ var mention = ({ debug, onSearch }) => {
5615
5709
  (context) => {
5616
5710
  log9.info("completion context", {
5617
5711
  context
5618
- }, {
5619
- F: __dxlog_file13,
5620
- L: 27,
5621
- S: void 0,
5622
- C: (f, a) => f(...a)
5623
- });
5712
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file13, L: 18, S: void 0 });
5624
5713
  const match = context.matchBefore(/@(\w+)?/);
5625
5714
  if (!match || match.from === match.to && !context.explicit) {
5626
5715
  return null;
@@ -5637,8 +5726,8 @@ var mention = ({ debug, onSearch }) => {
5637
5726
  };
5638
5727
 
5639
5728
  // src/extensions/modal.ts
5640
- import { StateEffect as StateEffect7, StateField as StateField9 } from "@codemirror/state";
5641
- var modalStateEffect = StateEffect7.define();
5729
+ import { StateEffect as StateEffect8, StateField as StateField9 } from "@codemirror/state";
5730
+ var modalStateEffect = StateEffect8.define();
5642
5731
  var modalStateField = StateField9.define({
5643
5732
  create: () => false,
5644
5733
  update: (value, tr) => {
@@ -5853,15 +5942,7 @@ var outlinerTree = (_options = {}) => {
5853
5942
  break;
5854
5943
  }
5855
5944
  case "BulletList": {
5856
- invariant5(current, void 0, {
5857
- F: __dxlog_file14,
5858
- L: 219,
5859
- S: void 0,
5860
- A: [
5861
- "current",
5862
- ""
5863
- ]
5864
- });
5945
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 169, S: void 0, A: ["current", ""] });
5865
5946
  parent = current;
5866
5947
  if (current) {
5867
5948
  current.lineRange.to = current.node.from;
@@ -5870,15 +5951,7 @@ var outlinerTree = (_options = {}) => {
5870
5951
  break;
5871
5952
  }
5872
5953
  case "ListItem": {
5873
- invariant5(parent, void 0, {
5874
- F: __dxlog_file14,
5875
- L: 228,
5876
- S: void 0,
5877
- A: [
5878
- "parent",
5879
- ""
5880
- ]
5881
- });
5954
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 179, S: void 0, A: ["parent", ""] });
5882
5955
  const nextSibling = node.node.nextSibling ?? node.node.parent?.nextSibling;
5883
5956
  const docRange = {
5884
5957
  from: state.doc.lineAt(node.from).from,
@@ -5912,42 +5985,18 @@ var outlinerTree = (_options = {}) => {
5912
5985
  break;
5913
5986
  }
5914
5987
  case "ListMark": {
5915
- invariant5(current, void 0, {
5916
- F: __dxlog_file14,
5917
- L: 272,
5918
- S: void 0,
5919
- A: [
5920
- "current",
5921
- ""
5922
- ]
5923
- });
5988
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 219, S: void 0, A: ["current", ""] });
5924
5989
  current.type = "bullet";
5925
5990
  current.contentRange.from = node.from + "- ".length;
5926
5991
  break;
5927
5992
  }
5928
5993
  case "Task": {
5929
- invariant5(current, void 0, {
5930
- F: __dxlog_file14,
5931
- L: 278,
5932
- S: void 0,
5933
- A: [
5934
- "current",
5935
- ""
5936
- ]
5937
- });
5994
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 226, S: void 0, A: ["current", ""] });
5938
5995
  current.type = "task";
5939
5996
  break;
5940
5997
  }
5941
5998
  case "TaskMarker": {
5942
- invariant5(current, void 0, {
5943
- F: __dxlog_file14,
5944
- L: 283,
5945
- S: void 0,
5946
- A: [
5947
- "current",
5948
- ""
5949
- ]
5950
- });
5999
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 232, S: void 0, A: ["current", ""] });
5951
6000
  current.contentRange.from = node.from + "[ ] ".length;
5952
6001
  break;
5953
6002
  }
@@ -5955,29 +6004,13 @@ var outlinerTree = (_options = {}) => {
5955
6004
  },
5956
6005
  leave: (node) => {
5957
6006
  if (node.name === "BulletList") {
5958
- invariant5(parent, void 0, {
5959
- F: __dxlog_file14,
5960
- L: 291,
5961
- S: void 0,
5962
- A: [
5963
- "parent",
5964
- ""
5965
- ]
5966
- });
6007
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 240, S: void 0, A: ["parent", ""] });
5967
6008
  prevSiblings[level--] = void 0;
5968
6009
  parent = parent.parent;
5969
6010
  }
5970
6011
  }
5971
6012
  });
5972
- invariant5(tree, void 0, {
5973
- F: __dxlog_file14,
5974
- L: 298,
5975
- S: void 0,
5976
- A: [
5977
- "tree",
5978
- ""
5979
- ]
5980
- });
6013
+ invariant5(tree, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 246, S: void 0, A: ["tree", ""] });
5981
6014
  return tree;
5982
6015
  };
5983
6016
  return [
@@ -6262,17 +6295,17 @@ var commands = () => keymap11.of([
6262
6295
 
6263
6296
  // src/extensions/outliner/outliner.ts
6264
6297
  import { Prec as Prec5 } from "@codemirror/state";
6265
- import { Decoration as Decoration12, EditorView as EditorView25, ViewPlugin as ViewPlugin17 } from "@codemirror/view";
6266
- import { mx as mx7 } from "@dxos/ui-theme";
6298
+ import { Decoration as Decoration12, EditorView as EditorView26, ViewPlugin as ViewPlugin18 } from "@codemirror/view";
6299
+ import { mx as mx6 } from "@dxos/ui-theme";
6267
6300
 
6268
6301
  // src/extensions/outliner/editor.ts
6269
6302
  import { EditorSelection as EditorSelection4, EditorState as EditorState2 } from "@codemirror/state";
6270
- import { ViewPlugin as ViewPlugin15 } from "@codemirror/view";
6303
+ import { ViewPlugin as ViewPlugin16 } from "@codemirror/view";
6271
6304
  import { log as log10 } from "@dxos/log";
6272
6305
  var __dxlog_file15 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/editor.ts";
6273
6306
  var LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
6274
6307
  var initialize = () => {
6275
- return ViewPlugin15.fromClass(class {
6308
+ return ViewPlugin16.fromClass(class {
6276
6309
  constructor(view) {
6277
6310
  const first = view.state.doc.lineAt(0);
6278
6311
  const text = view.state.sliceDoc(first.from, first.to);
@@ -6419,35 +6452,20 @@ var editor = () => [
6419
6452
  text: insert.toString(),
6420
6453
  length: insert.length
6421
6454
  }
6422
- }, {
6423
- F: __dxlog_file15,
6424
- L: 164,
6425
- S: void 0,
6426
- C: (f, a) => f(...a)
6427
- });
6455
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 174, S: void 0 });
6428
6456
  }
6429
6457
  });
6430
6458
  if (changes.length > 0) {
6431
6459
  log10("modified,", {
6432
6460
  changes
6433
- }, {
6434
- F: __dxlog_file15,
6435
- L: 175,
6436
- S: void 0,
6437
- C: (f, a) => f(...a)
6438
- });
6461
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 196, S: void 0 });
6439
6462
  return [
6440
6463
  {
6441
6464
  changes
6442
6465
  }
6443
6466
  ];
6444
6467
  } else if (cancel) {
6445
- log10("cancel", void 0, {
6446
- F: __dxlog_file15,
6447
- L: 178,
6448
- S: void 0,
6449
- C: (f, a) => f(...a)
6450
- });
6468
+ log10("cancel", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 205, S: void 0 });
6451
6469
  return [];
6452
6470
  }
6453
6471
  return tr;
@@ -6455,10 +6473,10 @@ var editor = () => [
6455
6473
  ];
6456
6474
 
6457
6475
  // src/extensions/outliner/menu.ts
6458
- import { EditorView as EditorView24, ViewPlugin as ViewPlugin16 } from "@codemirror/view";
6476
+ import { EditorView as EditorView25, ViewPlugin as ViewPlugin17 } from "@codemirror/view";
6459
6477
  import { addEventListener as addEventListener2 } from "@dxos/async";
6460
6478
  var menu = (options = {}) => [
6461
- ViewPlugin16.fromClass(class {
6479
+ ViewPlugin17.fromClass(class {
6462
6480
  view;
6463
6481
  tag;
6464
6482
  rafId;
@@ -6520,7 +6538,7 @@ var menu = (options = {}) => [
6520
6538
  this.rafId = requestAnimationFrame(this.updateButtonPosition.bind(this));
6521
6539
  }
6522
6540
  }),
6523
- EditorView24.theme({
6541
+ EditorView25.theme({
6524
6542
  ".cm-popover-trigger": {
6525
6543
  position: "fixed",
6526
6544
  padding: "0",
@@ -6556,12 +6574,12 @@ var outliner = (_options = {}) => [
6556
6574
  listPaddingLeft: 8
6557
6575
  }),
6558
6576
  // Researve space for menu.
6559
- EditorView25.contentAttributes.of({
6577
+ EditorView26.contentAttributes.of({
6560
6578
  class: "w-full !mr-[3rem]"
6561
6579
  })
6562
6580
  ];
6563
6581
  var decorations = () => [
6564
- ViewPlugin17.fromClass(class {
6582
+ ViewPlugin18.fromClass(class {
6565
6583
  decorations = Decoration12.none;
6566
6584
  constructor(view) {
6567
6585
  this.updateDecorations(view.state, view);
@@ -6586,7 +6604,7 @@ var decorations = () => [
6586
6604
  const lineTo = doc.lineAt(item.contentRange.to);
6587
6605
  const isSelected = selection.includes(item.index) || item === current;
6588
6606
  decorations2.push(Decoration12.line({
6589
- class: mx7("cm-list-item", lineFrom.number === line.number && "cm-list-item-start", lineTo.number === line.number && "cm-list-item-end", isSelected && (hasFocus ? "cm-list-item-focused" : "cm-list-item-selected"))
6607
+ class: mx6("cm-list-item", lineFrom.number === line.number && "cm-list-item-start", lineTo.number === line.number && "cm-list-item-end", isSelected && (hasFocus ? "cm-list-item-focused" : "cm-list-item-selected"))
6590
6608
  }).range(line.from, line.from));
6591
6609
  }
6592
6610
  }
@@ -6596,7 +6614,7 @@ var decorations = () => [
6596
6614
  decorations: (v) => v.decorations
6597
6615
  }),
6598
6616
  // Theme.
6599
- EditorView25.theme(Object.assign({
6617
+ EditorView26.theme(Object.assign({
6600
6618
  ".cm-list-item": {
6601
6619
  borderLeftWidth: "1px",
6602
6620
  borderRightWidth: "1px",
@@ -6621,7 +6639,7 @@ var decorations = () => [
6621
6639
  marginBottom: "2px"
6622
6640
  },
6623
6641
  ".cm-list-item-focused": {
6624
- borderColor: "var(--color-neutral-focus-indicator)"
6642
+ borderColor: "var(--color-focus-ring-subtle)"
6625
6643
  },
6626
6644
  "&:focus-within .cm-list-item-selected": {
6627
6645
  borderColor: "var(--color-separator)"
@@ -6631,28 +6649,57 @@ var decorations = () => [
6631
6649
 
6632
6650
  // src/extensions/preview/preview.ts
6633
6651
  import { syntaxTree as syntaxTree10 } from "@codemirror/language";
6634
- import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField11 } from "@codemirror/state";
6635
- import { Decoration as Decoration13, EditorView as EditorView26, WidgetType as WidgetType8 } from "@codemirror/view";
6652
+ import { RangeSetBuilder as RangeSetBuilder6, StateEffect as StateEffect9, StateField as StateField11 } from "@codemirror/state";
6653
+ import { Decoration as Decoration13, EditorView as EditorView27, ViewPlugin as ViewPlugin19, WidgetType as WidgetType8 } from "@codemirror/view";
6654
+ import { DXN, Entity } from "@dxos/echo";
6655
+ var labelResolvedEffect = StateEffect9.define();
6636
6656
  var preview = (options = {}) => {
6657
+ const viewRef = {
6658
+ current: void 0
6659
+ };
6637
6660
  return [
6638
6661
  // NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
6639
6662
  // "Block decorations may not be specified via plugins".
6640
6663
  StateField11.define({
6641
- create: (state) => buildDecorations3(state, options),
6664
+ create: (state) => buildDecorations3(state, options, viewRef),
6642
6665
  update: (decorations2, tr) => {
6643
- if (tr.docChanged) {
6644
- return buildDecorations3(tr.state, options);
6666
+ if (tr.docChanged || tr.effects.some((effect) => effect.is(labelResolvedEffect))) {
6667
+ return buildDecorations3(tr.state, options, viewRef);
6645
6668
  }
6646
6669
  return decorations2.map(tr.changes);
6647
6670
  },
6648
6671
  provide: (field) => [
6649
- EditorView26.decorations.from(field),
6650
- EditorView26.atomicRanges.of((view) => view.state.field(field))
6672
+ EditorView27.decorations.from(field),
6673
+ EditorView27.atomicRanges.of((view) => view.state.field(field))
6651
6674
  ]
6675
+ }),
6676
+ ViewPlugin19.define((view) => {
6677
+ viewRef.current = view;
6678
+ return {
6679
+ destroy() {
6680
+ viewRef.current = void 0;
6681
+ }
6682
+ };
6652
6683
  })
6653
6684
  ];
6654
6685
  };
6655
- var buildDecorations3 = (state, options) => {
6686
+ var resolveLabel = (db, dxnStr, viewRef) => {
6687
+ const dxn = DXN.tryParse(dxnStr);
6688
+ if (!dxn) {
6689
+ return;
6690
+ }
6691
+ const ref = db.makeRef(dxn);
6692
+ const target = ref.target;
6693
+ if (target) {
6694
+ return Entity.getLabel(target);
6695
+ }
6696
+ void ref.tryLoad().then(() => {
6697
+ viewRef.current?.dispatch({
6698
+ effects: labelResolvedEffect.of(void 0)
6699
+ });
6700
+ });
6701
+ };
6702
+ var buildDecorations3 = (state, options, viewRef) => {
6656
6703
  const builder = new RangeSetBuilder6();
6657
6704
  syntaxTree10(state).iterate({
6658
6705
  enter: (node) => {
@@ -6664,8 +6711,13 @@ var buildDecorations3 = (state, options) => {
6664
6711
  case "Link": {
6665
6712
  const link = getLinkRef(state, node.node);
6666
6713
  if (link) {
6714
+ const resolved = options.db ? resolveLabel(options.db, link.dxn, viewRef) : void 0;
6715
+ const displayLink = resolved ? {
6716
+ ...link,
6717
+ label: resolved
6718
+ } : link;
6667
6719
  builder.add(node.from, node.to, Decoration13.replace({
6668
- widget: new PreviewInlineWidget(options, link),
6720
+ widget: new PreviewInlineWidget(options, displayLink),
6669
6721
  side: 1
6670
6722
  }));
6671
6723
  }
@@ -6757,7 +6809,7 @@ var PreviewBlockWidget = class extends WidgetType8 {
6757
6809
  };
6758
6810
 
6759
6811
  // src/extensions/replacer.ts
6760
- import { EditorView as EditorView27 } from "@codemirror/view";
6812
+ import { EditorView as EditorView28 } from "@codemirror/view";
6761
6813
  var defaultReplacements = [
6762
6814
  {
6763
6815
  input: "--",
@@ -6820,7 +6872,7 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
6820
6872
  const sortedReplacements = [
6821
6873
  ...replacements
6822
6874
  ].sort((a, b) => b.input.length - a.input.length);
6823
- return EditorView27.inputHandler.of((view, from, to, insert) => {
6875
+ return EditorView28.inputHandler.of((view, from, to, insert) => {
6824
6876
  if (insert.length !== 1) {
6825
6877
  return false;
6826
6878
  }
@@ -6854,12 +6906,69 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
6854
6906
  });
6855
6907
  };
6856
6908
 
6909
+ // src/extensions/snippets.ts
6910
+ import { keymap as keymap12 } from "@codemirror/view";
6911
+ var defaultItems = [
6912
+ "hello world!",
6913
+ "this is a test.",
6914
+ "this is [DXOS](https://dxos.org)"
6915
+ ];
6916
+ var snippets2 = ({ delay = 75, items = defaultItems } = {}) => {
6917
+ let timer;
6918
+ let index = 0;
6919
+ return [
6920
+ keymap12.of([
6921
+ {
6922
+ // Reset.
6923
+ key: "alt-meta-'",
6924
+ run: () => {
6925
+ clearTimeout(timer);
6926
+ index = 0;
6927
+ return true;
6928
+ }
6929
+ },
6930
+ {
6931
+ // Next snippet.
6932
+ // TODO(burdon): Press 1-9 to select snippet?
6933
+ key: "Shift-Meta-'",
6934
+ run: (view) => {
6935
+ clearTimeout(timer);
6936
+ const text = items[index++];
6937
+ if (index === items?.length) {
6938
+ index = 0;
6939
+ }
6940
+ let offset = 0;
6941
+ const insert = (delayMs = 0) => {
6942
+ timer = setTimeout(() => {
6943
+ const pos = view.state.selection.main.head;
6944
+ view.dispatch({
6945
+ changes: {
6946
+ from: pos,
6947
+ insert: text[offset++]
6948
+ },
6949
+ selection: {
6950
+ anchor: pos + 1
6951
+ }
6952
+ });
6953
+ if (offset < text.length) {
6954
+ insert(Math.random() * delay * (text[offset] === " " ? 2 : 1));
6955
+ }
6956
+ }, delayMs);
6957
+ };
6958
+ insert();
6959
+ return true;
6960
+ }
6961
+ }
6962
+ ])
6963
+ ];
6964
+ };
6965
+
6857
6966
  // src/extensions/submit.ts
6858
6967
  import { Prec as Prec6 } from "@codemirror/state";
6859
- import { keymap as keymap12 } from "@codemirror/view";
6968
+ import { keymap as keymap13 } from "@codemirror/view";
6860
6969
  var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
6861
6970
  return [
6862
- Prec6.highest(keymap12.of([
6971
+ Prec6.highest(keymap13.of([
6863
6972
  {
6864
6973
  key: "Enter",
6865
6974
  preventDefault: true,
@@ -6904,6 +7013,7 @@ var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
6904
7013
  // src/extensions/tags/extended-markdown.ts
6905
7014
  import { xmlLanguage } from "@codemirror/lang-xml";
6906
7015
  import { parseMixed } from "@lezer/common";
7016
+ var escapeRegExpSource = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6907
7017
  var extendedMarkdown = ({ registry } = {}) => {
6908
7018
  return [
6909
7019
  createMarkdownExtensions({
@@ -6915,13 +7025,65 @@ var extendedMarkdown = ({ registry } = {}) => {
6915
7025
  {
6916
7026
  name: "SetextHeading",
6917
7027
  parse: () => false
6918
- }
7028
+ },
7029
+ // Custom XML block parser that keeps registered tags as a single HTMLBlock
7030
+ // even when their content contains blank lines.
7031
+ ...xmlBlockParsers(registry)
6919
7032
  ]
6920
7033
  }
6921
7034
  ]
6922
7035
  })
6923
7036
  ];
6924
7037
  };
7038
+ var xmlBlockParsers = (registry) => {
7039
+ const customTags = Object.keys(registry ?? {});
7040
+ if (customTags.length === 0) {
7041
+ return [];
7042
+ }
7043
+ const tagPattern = customTags.map(escapeRegExpSource).join("|");
7044
+ const selfClosePattern = new RegExp(`^\\s*<(${tagPattern})(\\s[^>]*)?\\/>\\s*$`);
7045
+ const openPattern = new RegExp(`^\\s*<(${tagPattern})(\\s[^>]*)?\\/?>`);
7046
+ return [
7047
+ {
7048
+ name: "XMLBlock",
7049
+ before: "HTMLBlock",
7050
+ parse: (cx, line) => {
7051
+ const match = openPattern.exec(line.text);
7052
+ if (!match) {
7053
+ return false;
7054
+ }
7055
+ if (selfClosePattern.test(line.text)) {
7056
+ const end2 = cx.lineStart + line.text.length;
7057
+ cx.addElement(cx.elt("HTMLBlock", cx.lineStart, end2));
7058
+ cx.nextLine();
7059
+ return true;
7060
+ }
7061
+ if (match[0].trimEnd().endsWith("/>")) {
7062
+ return false;
7063
+ }
7064
+ const tagName = match[1];
7065
+ const closeTag = `</${tagName}>`;
7066
+ const start = cx.lineStart;
7067
+ if (line.text.includes(closeTag)) {
7068
+ cx.addElement(cx.elt("HTMLBlock", start, start + line.text.length));
7069
+ cx.nextLine();
7070
+ return true;
7071
+ }
7072
+ let end = cx.lineStart + line.text.length;
7073
+ while (cx.nextLine()) {
7074
+ end = cx.lineStart + line.text.length;
7075
+ if (line.text.includes(closeTag)) {
7076
+ cx.addElement(cx.elt("HTMLBlock", start, end));
7077
+ cx.nextLine();
7078
+ return true;
7079
+ }
7080
+ }
7081
+ cx.addElement(cx.elt("HTMLBlock", start, end));
7082
+ return true;
7083
+ }
7084
+ }
7085
+ ];
7086
+ };
6925
7087
  var mixedParser = (registry) => {
6926
7088
  const customTags = Object.keys(registry ?? {});
6927
7089
  const tagPattern = new RegExp(`<(${customTags.join("|")})`);
@@ -6955,219 +7117,793 @@ var mixedParser = (registry) => {
6955
7117
  });
6956
7118
  };
6957
7119
 
6958
- // src/extensions/tags/streamer.ts
6959
- import { StateEffect as StateEffect8, StateField as StateField12 } from "@codemirror/state";
6960
- import { Decoration as Decoration14, EditorView as EditorView28, ViewPlugin as ViewPlugin18, WidgetType as WidgetType9 } from "@codemirror/view";
6961
- import { Domino as Domino3 } from "@dxos/ui";
6962
- import { isTruthy as isTruthy4 } from "@dxos/util";
6963
- var BLINK_RATE = 2e3;
6964
- var streamer = (options = {}) => {
6965
- return [
6966
- options.cursor && cursor(),
6967
- options.fadeIn && fadeIn(typeof options.fadeIn === "object" ? options.fadeIn : {})
6968
- ].filter(isTruthy4);
6969
- };
6970
- var cursor = () => {
6971
- const hideCursor = StateEffect8.define();
6972
- const showCursor = StateField12.define({
6973
- create: () => true,
6974
- update: (value, tr) => {
6975
- for (const effect of tr.effects) {
6976
- if (effect.is(hideCursor)) {
6977
- return false;
7120
+ // src/extensions/tags/fader.ts
7121
+ import { StateEffect as StateEffect10, StateField as StateField12 } from "@codemirror/state";
7122
+ import { Decoration as Decoration14, EditorView as EditorView29, ViewPlugin as ViewPlugin20 } from "@codemirror/view";
7123
+ var DEFAULT_REMOVAL_DELAY = 5e3;
7124
+ var DEFAULT_COALESCE_WINDOW = 100;
7125
+ var CLEANUP_INTERVAL = 1e3;
7126
+ var fader = (options = {}) => {
7127
+ const removalDelay = DEFAULT_REMOVAL_DELAY;
7128
+ const coalesceWindow = options.coalesce ?? DEFAULT_COALESCE_WINDOW;
7129
+ let lastCount = -1;
7130
+ const log12 = (expiries) => {
7131
+ if (expiries.length !== lastCount) {
7132
+ lastCount = expiries.length;
7133
+ }
7134
+ };
7135
+ const dequeue = StateEffect10.define();
7136
+ const fadeField = StateField12.define({
7137
+ create: () => ({
7138
+ decorations: Decoration14.none,
7139
+ expiries: [],
7140
+ batchStart: 0
7141
+ }),
7142
+ update: ({ decorations: decorations2, expiries, batchStart }, tr) => {
7143
+ for (const effect of tr.effects) {
7144
+ if (effect.is(dequeue)) {
7145
+ const now2 = effect.value;
7146
+ let removeCount = 0;
7147
+ while (removeCount < expiries.length && expiries[removeCount] <= now2) {
7148
+ removeCount++;
7149
+ }
7150
+ if (removeCount > 0) {
7151
+ expiries = expiries.slice(removeCount);
7152
+ let skipped = 0;
7153
+ decorations2 = decorations2.update({
7154
+ filter: () => {
7155
+ if (skipped < removeCount) {
7156
+ skipped++;
7157
+ return false;
7158
+ }
7159
+ return true;
7160
+ }
7161
+ });
7162
+ }
7163
+ }
7164
+ }
7165
+ if (!tr.docChanged) {
7166
+ log12(expiries);
7167
+ return {
7168
+ decorations: decorations2,
7169
+ expiries,
7170
+ batchStart
7171
+ };
7172
+ }
7173
+ let isReset = tr.state.doc.length === 0;
7174
+ if (!isReset && tr.startState.doc.length > 0) {
7175
+ tr.changes.iterChanges((fromA, toA) => {
7176
+ if (fromA === 0 && toA === tr.startState.doc.length) {
7177
+ isReset = true;
7178
+ }
7179
+ });
7180
+ }
7181
+ if (isReset) {
7182
+ log12([]);
7183
+ return {
7184
+ decorations: Decoration14.none,
7185
+ expiries: [],
7186
+ batchStart: 0
7187
+ };
7188
+ }
7189
+ const now = Date.now();
7190
+ const add = [];
7191
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7192
+ if (toA === tr.startState.doc.length && inserted.length > 0) {
7193
+ add.push({
7194
+ from: fromB,
7195
+ to: toB
7196
+ });
7197
+ }
7198
+ });
7199
+ if (add.length > 0) {
7200
+ const canCoalesce = expiries.length > 0 && batchStart > 0 && now - batchStart < coalesceWindow;
7201
+ if (canCoalesce) {
7202
+ let lastFrom = -1;
7203
+ let lastTo = -1;
7204
+ decorations2.between(0, tr.state.doc.length, (from, to) => {
7205
+ lastFrom = from;
7206
+ lastTo = to;
7207
+ });
7208
+ if (lastFrom >= 0) {
7209
+ decorations2 = decorations2.update({
7210
+ filter: (from, to) => !(from === lastFrom && to === lastTo)
7211
+ });
7212
+ const mergedFrom = Math.min(lastFrom, add[0].from);
7213
+ const mergedTo = add[add.length - 1].to;
7214
+ decorations2 = decorations2.update({
7215
+ add: [
7216
+ Decoration14.mark({
7217
+ class: "cm-fader"
7218
+ }).range(mergedFrom, mergedTo)
7219
+ ]
7220
+ });
7221
+ expiries = [
7222
+ ...expiries.slice(0, -1),
7223
+ now + removalDelay
7224
+ ];
7225
+ }
7226
+ } else {
7227
+ batchStart = now;
7228
+ expiries = [
7229
+ ...expiries,
7230
+ now + removalDelay
7231
+ ];
7232
+ decorations2 = decorations2.update({
7233
+ add: add.map(({ from, to }) => Decoration14.mark({
7234
+ class: "cm-fader"
7235
+ }).range(from, to))
7236
+ });
7237
+ }
7238
+ }
7239
+ log12(expiries);
7240
+ return {
7241
+ decorations: decorations2,
7242
+ expiries,
7243
+ batchStart
7244
+ };
7245
+ },
7246
+ provide: (f) => EditorView29.decorations.from(f, (value) => value.decorations)
7247
+ });
7248
+ const cleanup = ViewPlugin20.fromClass(class {
7249
+ view;
7250
+ #timer;
7251
+ constructor(view) {
7252
+ this.view = view;
7253
+ this.#schedule();
7254
+ }
7255
+ update() {
7256
+ this.#schedule();
7257
+ }
7258
+ #schedule() {
7259
+ const { expiries } = this.view.state.field(fadeField);
7260
+ if (expiries.length === 0) {
7261
+ clearTimeout(this.#timer);
7262
+ this.#timer = void 0;
7263
+ return;
7264
+ }
7265
+ if (this.#timer !== void 0) {
7266
+ return;
7267
+ }
7268
+ const delay = Math.max(CLEANUP_INTERVAL, expiries[0] - Date.now());
7269
+ this.#timer = setTimeout(() => {
7270
+ this.#timer = void 0;
7271
+ this.view.dispatch({
7272
+ effects: dequeue.of(Date.now())
7273
+ });
7274
+ }, delay);
7275
+ }
7276
+ destroy() {
7277
+ clearTimeout(this.#timer);
7278
+ }
7279
+ });
7280
+ return [
7281
+ fadeField,
7282
+ cleanup,
7283
+ EditorView29.theme({
7284
+ ".cm-fader": {
7285
+ animation: "fader 1s ease-out forwards"
7286
+ },
7287
+ "@keyframes fader": {
7288
+ "0%": {
7289
+ textShadow: "0 0 16px rgba(100, 200, 255, 1), 0 0 32px rgba(100, 200, 255, 0.6)"
7290
+ },
7291
+ "100%": {}
7292
+ }
7293
+ })
7294
+ ];
7295
+ };
7296
+
7297
+ // src/extensions/tags/typewriter.ts
7298
+ import { Annotation as Annotation3, ChangeSet as ChangeSet2, EditorState as EditorState3, StateEffect as StateEffect11, StateField as StateField13 } from "@codemirror/state";
7299
+ import { Decoration as Decoration15, EditorView as EditorView30, ViewPlugin as ViewPlugin21, WidgetType as WidgetType9 } from "@codemirror/view";
7300
+ import { Domino as Domino3 } from "@dxos/ui";
7301
+ var typewriterBypass = Annotation3.define();
7302
+ var typewriterDrainingEffect = StateEffect11.define();
7303
+ var CURSOR_LINGER = 3e3;
7304
+ var FRAME_BUDGET_MS = 4;
7305
+ var CHARS_PER_FRAME = 5;
7306
+ var FLUSH_THRESHOLD = 2e3;
7307
+ var COMPACT_HEAD_THRESHOLD = 4096;
7308
+ var typewriter = (options = {}) => {
7309
+ const streamingTags = options.streamingTags ?? /* @__PURE__ */ new Set();
7310
+ const flushThreshold = options.flushThreshold ?? FLUSH_THRESHOLD;
7311
+ const frameBudgetMs = options.frameBudgetMs ?? FRAME_BUDGET_MS;
7312
+ const charsPerFrame = options.charsPerFrame ?? CHARS_PER_FRAME;
7313
+ const suppressAppend = StateEffect11.define();
7314
+ const insertChunk = StateEffect11.define();
7315
+ const bufferField = StateField13.define({
7316
+ create: () => ({
7317
+ text: "",
7318
+ head: 0,
7319
+ insertAt: 0
7320
+ }),
7321
+ update: (value, tr) => {
7322
+ let { text, head, insertAt } = value;
7323
+ for (const effect of tr.effects) {
7324
+ if (effect.is(suppressAppend)) {
7325
+ if (text.length === head) {
7326
+ insertAt = effect.value.from;
7327
+ }
7328
+ text += effect.value.text;
7329
+ }
7330
+ if (effect.is(insertChunk)) {
7331
+ head += effect.value.text.length;
7332
+ insertAt = effect.value.from + effect.value.text.length;
7333
+ if (head >= COMPACT_HEAD_THRESHOLD || head > 0 && head * 2 >= text.length) {
7334
+ text = text.slice(head);
7335
+ head = 0;
7336
+ }
6978
7337
  }
6979
7338
  }
6980
7339
  if (tr.docChanged) {
6981
- return true;
7340
+ let isReset = tr.state.doc.length === 0;
7341
+ if (!isReset && tr.startState.doc.length > 0) {
7342
+ tr.changes.iterChanges((fromA, toA) => {
7343
+ if (fromA === 0 && toA === tr.startState.doc.length) {
7344
+ isReset = true;
7345
+ }
7346
+ });
7347
+ }
7348
+ if (isReset) {
7349
+ return {
7350
+ text: "",
7351
+ head: 0,
7352
+ insertAt: 0
7353
+ };
7354
+ }
7355
+ if (!tr.effects.some((effect) => effect.is(insertChunk))) {
7356
+ insertAt = tr.changes.mapPos(Math.min(insertAt, tr.startState.doc.length));
7357
+ }
6982
7358
  }
6983
- return value;
7359
+ return {
7360
+ text,
7361
+ head,
7362
+ insertAt
7363
+ };
6984
7364
  }
6985
7365
  });
6986
- const timerPlugin = ViewPlugin18.fromClass(class {
7366
+ const filter = EditorState3.transactionFilter.of((tr) => {
7367
+ if (!tr.docChanged) {
7368
+ return tr;
7369
+ }
7370
+ if (tr.annotation(typewriterBypass) || tr.effects.some((effect) => effect.is(insertChunk))) {
7371
+ return tr;
7372
+ }
7373
+ let appendedText = "";
7374
+ let appendFrom = -1;
7375
+ let isAppendOnly = true;
7376
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
7377
+ if (toA === tr.startState.doc.length && fromA === toA && inserted.length > 0) {
7378
+ appendedText += inserted.sliceString(0);
7379
+ if (appendFrom === -1) {
7380
+ appendFrom = fromA;
7381
+ }
7382
+ } else {
7383
+ isAppendOnly = false;
7384
+ }
7385
+ });
7386
+ if (!isAppendOnly || appendedText.length === 0) {
7387
+ return tr;
7388
+ }
7389
+ return {
7390
+ changes: ChangeSet2.empty(tr.startState.doc.length),
7391
+ effects: suppressAppend.of({
7392
+ from: appendFrom,
7393
+ text: appendedText
7394
+ })
7395
+ };
7396
+ });
7397
+ const drainPlugin = ViewPlugin21.fromClass(class {
6987
7398
  view;
6988
- timer;
7399
+ _raf;
7400
+ _activeStreamTag = null;
6989
7401
  constructor(view) {
6990
7402
  this.view = view;
6991
7403
  }
6992
7404
  update(update2) {
6993
- if (update2.docChanged) {
6994
- clearTimeout(this.timer);
6995
- this.timer = setTimeout(() => {
6996
- this.view.dispatch({
6997
- effects: hideCursor.of(null)
6998
- });
6999
- }, BLINK_RATE);
7405
+ const { text, head } = update2.state.field(bufferField);
7406
+ const pending = text.length - head;
7407
+ if (pending === 0) {
7408
+ this._activeStreamTag = null;
7409
+ }
7410
+ if (pending > 0 && this._raf === void 0) {
7411
+ this._start();
7000
7412
  }
7001
7413
  }
7414
+ _start() {
7415
+ queueMicrotask(() => {
7416
+ this.view.dispatch({
7417
+ effects: typewriterDrainingEffect.of(true),
7418
+ annotations: typewriterBypass.of(true)
7419
+ });
7420
+ });
7421
+ this._raf = requestAnimationFrame(this._tick);
7422
+ }
7423
+ _tick = () => {
7424
+ const { text, head, insertAt } = this.view.state.field(bufferField);
7425
+ const pending = text.length - head;
7426
+ if (pending === 0) {
7427
+ this.view.dispatch({
7428
+ effects: typewriterDrainingEffect.of(false),
7429
+ annotations: typewriterBypass.of(true)
7430
+ });
7431
+ this._raf = void 0;
7432
+ return;
7433
+ }
7434
+ if (pending > flushThreshold) {
7435
+ const chunk = text.slice(head);
7436
+ this._activeStreamTag = null;
7437
+ this.view.dispatch({
7438
+ changes: {
7439
+ from: insertAt,
7440
+ insert: chunk
7441
+ },
7442
+ effects: insertChunk.of({
7443
+ from: insertAt,
7444
+ text: chunk
7445
+ })
7446
+ });
7447
+ this._raf = requestAnimationFrame(this._tick);
7448
+ return;
7449
+ }
7450
+ const startTime = performance.now();
7451
+ let pos = head;
7452
+ let activeTag = this._activeStreamTag;
7453
+ let charsEmitted = 0;
7454
+ while (pos < text.length && performance.now() - startTime < frameBudgetMs) {
7455
+ const result = flushable(text, pos, streamingTags, activeTag);
7456
+ if (result.count === 0) {
7457
+ break;
7458
+ }
7459
+ if (charsEmitted > 0 && charsEmitted + result.count > charsPerFrame) {
7460
+ break;
7461
+ }
7462
+ if (result.enterTag) {
7463
+ activeTag = result.enterTag;
7464
+ }
7465
+ if (result.exitTag) {
7466
+ activeTag = null;
7467
+ }
7468
+ pos += result.count;
7469
+ charsEmitted += result.count;
7470
+ }
7471
+ const totalCount = pos - head;
7472
+ if (totalCount > 0) {
7473
+ const chunk = text.slice(head, head + totalCount);
7474
+ this._activeStreamTag = activeTag;
7475
+ this.view.dispatch({
7476
+ changes: {
7477
+ from: insertAt,
7478
+ insert: chunk
7479
+ },
7480
+ effects: insertChunk.of({
7481
+ from: insertAt,
7482
+ text: chunk
7483
+ })
7484
+ });
7485
+ }
7486
+ this._raf = requestAnimationFrame(this._tick);
7487
+ };
7002
7488
  destroy() {
7003
- clearTimeout(this.timer);
7489
+ if (this._raf !== void 0) {
7490
+ cancelAnimationFrame(this._raf);
7491
+ }
7004
7492
  }
7005
7493
  });
7006
- const cursorDecoration = StateField12.define({
7007
- create: () => Decoration14.none,
7008
- update: (_decorations, tr) => {
7009
- const show = tr.state.field(showCursor);
7010
- if (!show) {
7011
- return Decoration14.none;
7494
+ return [
7495
+ bufferField,
7496
+ filter,
7497
+ drainPlugin,
7498
+ options.cursor && typewriterCursor(bufferField)
7499
+ ].filter(Boolean);
7500
+ };
7501
+ var typewriterCursor = (bufferField) => {
7502
+ const hideCursor = StateEffect11.define();
7503
+ const visibilityField = StateField13.define({
7504
+ create: () => ({
7505
+ visible: false,
7506
+ insertAt: 0,
7507
+ lastNonWsAt: 0
7508
+ }),
7509
+ update: (value, tr) => {
7510
+ const { text, head, insertAt } = tr.state.field(bufferField);
7511
+ const pending = text.length - head;
7512
+ if (pending > 0) {
7513
+ let lastNonWsAt = tr.changes.mapPos(Math.min(value.lastNonWsAt, tr.startState.doc.length));
7514
+ if (tr.docChanged) {
7515
+ tr.changes.iterChanges((_fromA, _toA, _fromB, _toB, inserted) => {
7516
+ const chunk = inserted.sliceString(0);
7517
+ if (chunk.trim().length > 0) {
7518
+ lastNonWsAt = _fromB + chunk.length;
7519
+ }
7520
+ });
7521
+ }
7522
+ return {
7523
+ visible: true,
7524
+ insertAt,
7525
+ lastNonWsAt
7526
+ };
7012
7527
  }
7013
- const endPos = tr.state.doc.length;
7014
- return Decoration14.set([
7015
- Decoration14.widget({
7528
+ for (const effect of tr.effects) {
7529
+ if (effect.is(hideCursor)) {
7530
+ return {
7531
+ ...value,
7532
+ visible: false
7533
+ };
7534
+ }
7535
+ }
7536
+ return value;
7537
+ }
7538
+ });
7539
+ const decorationField = StateField13.define({
7540
+ create: () => Decoration15.none,
7541
+ update: (_decorations, tr) => {
7542
+ const { visible, insertAt, lastNonWsAt } = tr.state.field(visibilityField);
7543
+ if (!visible) {
7544
+ return Decoration15.none;
7545
+ }
7546
+ const { text, head } = tr.state.field(bufferField);
7547
+ const cursorAt = text.length > head ? insertAt : lastNonWsAt;
7548
+ const pos = Math.min(cursorAt, tr.state.doc.length);
7549
+ return Decoration15.set([
7550
+ Decoration15.widget({
7016
7551
  widget: new CursorWidget(),
7017
7552
  side: 1
7018
- }).range(endPos)
7553
+ }).range(pos)
7019
7554
  ]);
7020
7555
  },
7021
- provide: (f) => EditorView28.decorations.from(f)
7556
+ provide: (field) => EditorView30.decorations.from(field)
7557
+ });
7558
+ const timerPlugin = ViewPlugin21.fromClass(class {
7559
+ view;
7560
+ _timer;
7561
+ constructor(view) {
7562
+ this.view = view;
7563
+ }
7564
+ update(update2) {
7565
+ const { text, head } = update2.state.field(bufferField);
7566
+ const { visible } = update2.state.field(visibilityField);
7567
+ const pending = text.length - head;
7568
+ if (pending > 0) {
7569
+ clearTimeout(this._timer);
7570
+ this._timer = void 0;
7571
+ } else if (visible && this._timer === void 0) {
7572
+ this._timer = setTimeout(() => {
7573
+ this.view.dispatch({
7574
+ effects: hideCursor.of(null)
7575
+ });
7576
+ this._timer = void 0;
7577
+ }, CURSOR_LINGER);
7578
+ }
7579
+ }
7580
+ destroy() {
7581
+ clearTimeout(this._timer);
7582
+ }
7022
7583
  });
7023
7584
  return [
7024
- showCursor,
7025
- timerPlugin,
7026
- cursorDecoration
7585
+ visibilityField,
7586
+ decorationField,
7587
+ timerPlugin
7027
7588
  ];
7028
7589
  };
7029
- var CursorWidget = class extends WidgetType9 {
7590
+ var CursorWidget = class _CursorWidget extends WidgetType9 {
7591
+ // All instances are interchangeable — let CM reuse the existing DOM across drips so
7592
+ // the blink animation isn't restarted on every transaction.
7593
+ eq(other) {
7594
+ return other instanceof _CursorWidget;
7595
+ }
7030
7596
  toDOM() {
7031
- const inner = Domino3.of("span").text("\u258F").style({
7032
- animation: "blink 2s infinite"
7597
+ const inner = Domino3.of("span").text("\u2217").style({
7598
+ animation: "blink 1s infinite",
7599
+ animationDelay: "250ms"
7033
7600
  });
7034
7601
  return Domino3.of("span").style({
7035
7602
  opacity: "0.8"
7036
- }).children(inner).root;
7603
+ }).append(inner).root;
7037
7604
  }
7038
7605
  };
7039
- var fadeIn = (options = {}) => {
7040
- const FADE_IN_DURATION = 1e3;
7041
- const DEFAULT_REMOVAL_DELAY = 3e3;
7042
- const removalDelay = options.removalDelay ?? DEFAULT_REMOVAL_DELAY;
7043
- const removeDecoration = StateEffect8.define();
7044
- const fadeField = StateField12.define({
7045
- create: () => Decoration14.none,
7046
- update: (decorations2, tr) => {
7047
- let next = decorations2;
7048
- for (const effect of tr.effects) {
7049
- if (effect.is(removeDecoration)) {
7050
- const target = effect.value;
7051
- next = next.update({
7052
- filter: (from, to) => !(from === target.from && to === target.to)
7053
- });
7054
- }
7055
- }
7056
- if (!tr.docChanged) {
7057
- return next;
7606
+ var OPENING_TAG_NAME = /^<([a-zA-Z][\w-]*)/;
7607
+ var TAG_NAME_PROBE = 64;
7608
+ var escapeRegExpSource2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7609
+ var flushable = (buffer, start, streamingTags, activeStreamTag) => {
7610
+ if (start >= buffer.length) {
7611
+ return {
7612
+ count: 0
7613
+ };
7614
+ }
7615
+ if (activeStreamTag) {
7616
+ const closeTag = `</${activeStreamTag}>`;
7617
+ if (buffer.startsWith(closeTag, start)) {
7618
+ return {
7619
+ count: closeTag.length,
7620
+ exitTag: true
7621
+ };
7622
+ }
7623
+ if (buffer[start] === "<") {
7624
+ return {
7625
+ count: xmlElementLength(buffer, start)
7626
+ };
7627
+ }
7628
+ return {
7629
+ count: 1
7630
+ };
7631
+ }
7632
+ const ch = buffer[start];
7633
+ if (ch === "<") {
7634
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7635
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7636
+ if (nameMatch && streamingTags.has(nameMatch[1])) {
7637
+ const close = buffer.indexOf(">", start);
7638
+ if (close === -1) {
7639
+ return {
7640
+ count: 0
7641
+ };
7058
7642
  }
7059
- let isReset = tr.state.doc.length === 0;
7060
- if (!isReset) {
7061
- tr.changes.iterChanges((fromA, toA) => {
7062
- if (fromA === 0 && toA === tr.startState.doc.length) {
7063
- isReset = true;
7064
- }
7065
- });
7643
+ if (buffer[close - 1] === "/") {
7644
+ return {
7645
+ count: close + 1 - start
7646
+ };
7066
7647
  }
7067
- if (isReset) {
7068
- return Decoration14.none;
7648
+ return {
7649
+ count: close + 1 - start,
7650
+ enterTag: nameMatch[1]
7651
+ };
7652
+ }
7653
+ return {
7654
+ count: xmlElementLength(buffer, start)
7655
+ };
7656
+ }
7657
+ if (ch === "!" && buffer.length > start + 1 && buffer[start + 1] === "[") {
7658
+ return {
7659
+ count: linkLength(buffer, start, start + 1)
7660
+ };
7661
+ }
7662
+ if (ch === "[") {
7663
+ return {
7664
+ count: linkLength(buffer, start, start)
7665
+ };
7666
+ }
7667
+ return {
7668
+ count: 1
7669
+ };
7670
+ };
7671
+ var xmlElementLength = (buffer, start = 0) => {
7672
+ const close = buffer.indexOf(">", start);
7673
+ if (close === -1) {
7674
+ return 0;
7675
+ }
7676
+ if (buffer[close - 1] === "/") {
7677
+ return close + 1 - start;
7678
+ }
7679
+ if (buffer[start + 1] === "/") {
7680
+ return close + 1 - start;
7681
+ }
7682
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7683
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7684
+ if (!nameMatch) {
7685
+ return 1;
7686
+ }
7687
+ const tagName = nameMatch[1];
7688
+ let depth = 0;
7689
+ const tagPattern = new RegExp(`<(/?)${escapeRegExpSource2(tagName)}(\\s[^>]*)?>`, "g");
7690
+ tagPattern.lastIndex = start;
7691
+ let match;
7692
+ while ((match = tagPattern.exec(buffer)) !== null) {
7693
+ const isSelfClosing = match[0].endsWith("/>");
7694
+ const isClosing = match[1] === "/";
7695
+ if (isSelfClosing) {
7696
+ if (depth === 0) {
7697
+ return match.index + match[0].length - start;
7698
+ }
7699
+ } else if (isClosing) {
7700
+ depth--;
7701
+ if (depth === 0) {
7702
+ return match.index + match[0].length - start;
7069
7703
  }
7070
- const add = [];
7071
- tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7072
- if (fromB === 0 && toB === inserted.length) {
7704
+ } else {
7705
+ depth++;
7706
+ }
7707
+ }
7708
+ return 0;
7709
+ };
7710
+ var linkLength = (buffer, start, bracketAt) => {
7711
+ const bracketClose = buffer.indexOf("]", bracketAt + 1);
7712
+ if (bracketClose === -1) {
7713
+ return 0;
7714
+ }
7715
+ if (bracketClose + 1 >= buffer.length) {
7716
+ return 0;
7717
+ }
7718
+ if (buffer[bracketClose + 1] !== "(") {
7719
+ return 1;
7720
+ }
7721
+ const parenClose = buffer.indexOf(")", bracketClose + 2);
7722
+ if (parenClose === -1) {
7723
+ return 0;
7724
+ }
7725
+ return parenClose + 1 - start;
7726
+ };
7727
+
7728
+ // src/extensions/tags/xml-block-decoration.ts
7729
+ import { xmlLanguage as xmlLanguage2 } from "@codemirror/lang-xml";
7730
+ import { Decoration as Decoration16, ViewPlugin as ViewPlugin22 } from "@codemirror/view";
7731
+ var xmlBlockDecoration = ({ tag, lineClass, contentClass, hideTags }) => {
7732
+ const lineDecoration = lineClass ? Decoration16.line({
7733
+ class: lineClass
7734
+ }) : void 0;
7735
+ const contentDecoration = contentClass ? Decoration16.mark({
7736
+ class: contentClass
7737
+ }) : void 0;
7738
+ const hideDecoration = hideTags ? Decoration16.replace({}) : void 0;
7739
+ const buildDecorations5 = (view) => {
7740
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7741
+ if (!text.includes(`<${tag}`)) {
7742
+ return Decoration16.none;
7743
+ }
7744
+ const tree = xmlLanguage2.parser.parse(text);
7745
+ const ranges = [];
7746
+ tree.iterate({
7747
+ enter: (node) => {
7748
+ if (node.type.name !== "Element") {
7073
7749
  return;
7074
7750
  }
7075
- if (toA === tr.startState.doc.length && inserted.length > 0) {
7076
- add.push(Decoration14.mark({
7077
- class: "cm-fade-in"
7078
- }).range(fromB, toB));
7751
+ const openTag = node.node.getChild("OpenTag");
7752
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7753
+ const tagNameNode = openTag?.getChild("TagName");
7754
+ if (!openTag || !tagNameNode) {
7755
+ return;
7079
7756
  }
7080
- });
7081
- return next.update({
7082
- add
7083
- });
7084
- },
7085
- provide: (f) => EditorView28.decorations.from(f)
7086
- });
7087
- const timerPlugin = ViewPlugin18.fromClass(class {
7088
- view;
7089
- // Map a simple key "from-to" to timer id.
7090
- _timers = /* @__PURE__ */ new Map();
7757
+ if (text.slice(tagNameNode.from, tagNameNode.to) !== tag) {
7758
+ return;
7759
+ }
7760
+ const contentFrom = openTag.to;
7761
+ const contentTo = closeTag?.from ?? node.node.to;
7762
+ if (hideDecoration) {
7763
+ ranges.push(hideDecoration.range(openTag.from, openTag.to));
7764
+ if (closeTag) {
7765
+ ranges.push(hideDecoration.range(closeTag.from, closeTag.to));
7766
+ }
7767
+ }
7768
+ if (contentDecoration && contentFrom < contentTo) {
7769
+ ranges.push(contentDecoration.range(contentFrom, contentTo));
7770
+ }
7771
+ if (lineDecoration && contentFrom <= view.state.doc.length) {
7772
+ let pos = contentFrom;
7773
+ while (pos <= contentTo && pos <= view.state.doc.length) {
7774
+ const line = view.state.doc.lineAt(pos);
7775
+ ranges.push(lineDecoration.range(line.from));
7776
+ if (line.to >= contentTo) {
7777
+ break;
7778
+ }
7779
+ pos = line.to + 1;
7780
+ }
7781
+ }
7782
+ }
7783
+ });
7784
+ return Decoration16.set(ranges, true);
7785
+ };
7786
+ return ViewPlugin22.fromClass(class {
7787
+ decorations;
7091
7788
  constructor(view) {
7092
- this.view = view;
7789
+ this.decorations = buildDecorations5(view);
7093
7790
  }
7094
7791
  update(update2) {
7095
- if (!update2.docChanged) {
7096
- return;
7792
+ if (update2.docChanged) {
7793
+ this.decorations = buildDecorations5(update2.view);
7097
7794
  }
7098
- update2.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7099
- if (toA !== update2.startState.doc.length || inserted.length === 0) {
7795
+ }
7796
+ }, {
7797
+ decorations: (instance) => instance.decorations
7798
+ });
7799
+ };
7800
+
7801
+ // src/extensions/tags/xml-formatting.ts
7802
+ import { xmlLanguage as xmlLanguage3 } from "@codemirror/lang-xml";
7803
+ import { Decoration as Decoration17, EditorView as EditorView31, ViewPlugin as ViewPlugin23 } from "@codemirror/view";
7804
+ var XML_TAG_NODES = /* @__PURE__ */ new Set([
7805
+ "OpenTag",
7806
+ "CloseTag",
7807
+ "SelfClosingTag",
7808
+ "MismatchedCloseTag"
7809
+ ]);
7810
+ var xmlElementMark = Decoration17.mark({
7811
+ class: "cm-xml-element"
7812
+ });
7813
+ var xmlTagMark = Decoration17.mark({
7814
+ class: "cm-xml-tag"
7815
+ });
7816
+ var xmlContentMark = Decoration17.mark({
7817
+ class: "cm-xml-content"
7818
+ });
7819
+ var xmlFormatting = ({ skip } = {}) => {
7820
+ const skipSet = skip && skip.length > 0 ? new Set(skip) : void 0;
7821
+ const buildDecorations5 = (view) => {
7822
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7823
+ if (!text.includes("<")) {
7824
+ return Decoration17.none;
7825
+ }
7826
+ const tagNameAt = (node) => text.slice(node.from, node.to);
7827
+ const tree = xmlLanguage3.parser.parse(text);
7828
+ const ranges = [];
7829
+ tree.iterate({
7830
+ enter: (node) => {
7831
+ const name = node.type.name;
7832
+ if (name === "SelfClosingTag" && node.from < node.to) {
7833
+ if (skipSet) {
7834
+ const tagNameNode = node.node.getChild("TagName");
7835
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7836
+ return false;
7837
+ }
7838
+ }
7839
+ ranges.push(xmlElementMark.range(node.from, node.to));
7840
+ ranges.push(xmlTagMark.range(node.from, node.to));
7100
7841
  return;
7101
7842
  }
7102
- const key = `${fromB}-${toB}`;
7103
- if (this._timers.has(key)) {
7104
- clearTimeout(this._timers.get(key));
7843
+ if (XML_TAG_NODES.has(name) && node.from < node.to) {
7844
+ ranges.push(xmlTagMark.range(node.from, node.to));
7845
+ return;
7846
+ }
7847
+ if (name === "Element" && node.from < node.to) {
7848
+ const openTag = node.node.getChild("OpenTag");
7849
+ if (openTag && skipSet) {
7850
+ const tagNameNode = openTag.getChild("TagName");
7851
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7852
+ return false;
7853
+ }
7854
+ }
7855
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7856
+ ranges.push(xmlElementMark.range(node.from, node.to));
7857
+ if (openTag && closeTag && openTag.to < closeTag.from) {
7858
+ ranges.push(xmlContentMark.range(openTag.to, closeTag.from));
7859
+ }
7105
7860
  }
7106
- const totalDelay = FADE_IN_DURATION + removalDelay;
7107
- const id = setTimeout(() => {
7108
- this.view.dispatch({
7109
- effects: removeDecoration.of({
7110
- from: fromB,
7111
- to: toB
7112
- })
7113
- });
7114
- this._timers.delete(key);
7115
- }, totalDelay);
7116
- this._timers.set(key, id);
7117
- });
7118
- }
7119
- destroy() {
7120
- for (const id of this._timers.values()) {
7121
- clearTimeout(id);
7122
7861
  }
7123
- this._timers.clear();
7124
- }
7125
- });
7862
+ });
7863
+ return Decoration17.set(ranges, true);
7864
+ };
7126
7865
  return [
7127
- fadeField,
7128
- timerPlugin,
7129
- EditorView28.theme({
7130
- ".cm-line > span": {
7131
- opacity: "0.8"
7132
- },
7133
- ".cm-fade-in": {
7134
- animation: "fade-in 3s ease-out forwards"
7135
- },
7136
- "@keyframes fade-in": {
7137
- "0%": {
7138
- opacity: "0"
7139
- },
7140
- "80%": {
7141
- opacity: "1"
7142
- },
7143
- "100%": {
7144
- opacity: "0.8"
7866
+ ViewPlugin23.fromClass(class {
7867
+ decorations;
7868
+ constructor(view) {
7869
+ this.decorations = buildDecorations5(view);
7870
+ }
7871
+ update(update2) {
7872
+ if (update2.docChanged) {
7873
+ this.decorations = buildDecorations5(update2.view);
7145
7874
  }
7146
7875
  }
7876
+ }, {
7877
+ decorations: (instance) => instance.decorations
7878
+ }),
7879
+ EditorView31.baseTheme({
7880
+ ".cm-xml-element": {
7881
+ backgroundColor: "var(--color-current-surface)",
7882
+ borderRadius: "0.25rem",
7883
+ padding: "0.25rem"
7884
+ },
7885
+ ".cm-xml-tag": {
7886
+ color: "var(--color-blue-500)",
7887
+ fontFamily: "var(--font-mono)"
7888
+ },
7889
+ ".cm-xml-content": {}
7147
7890
  })
7148
7891
  ];
7149
7892
  };
7150
7893
 
7151
7894
  // src/extensions/tags/xml-tags.ts
7152
7895
  import { syntaxTree as syntaxTree11 } from "@codemirror/language";
7153
- import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect9, StateField as StateField13 } from "@codemirror/state";
7154
- import { Decoration as Decoration15, EditorView as EditorView29, ViewPlugin as ViewPlugin19, WidgetType as WidgetType10, keymap as keymap13 } from "@codemirror/view";
7896
+ import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect12, StateField as StateField14 } from "@codemirror/state";
7897
+ import { Decoration as Decoration18, EditorView as EditorView32, ViewPlugin as ViewPlugin24, WidgetType as WidgetType10, keymap as keymap14 } from "@codemirror/view";
7155
7898
  import { invariant as invariant7 } from "@dxos/invariant";
7156
7899
  import { log as log11 } from "@dxos/log";
7900
+ import { Domino as Domino4 } from "@dxos/ui";
7157
7901
 
7158
7902
  // src/extensions/tags/xml-util.ts
7159
7903
  import { invariant as invariant6 } from "@dxos/invariant";
7160
7904
  var __dxlog_file16 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-util.ts";
7161
7905
  var nodeToJson = (state, node) => {
7162
- invariant6(node.type.name === "Element", "Node is not an Element", {
7163
- F: __dxlog_file16,
7164
- L: 18,
7165
- S: void 0,
7166
- A: [
7167
- "node.type.name === 'Element'",
7168
- "'Node is not an Element'"
7169
- ]
7170
- });
7906
+ invariant6(node.type.name === "Element", "Node is not an Element", { "~LogMeta": "~LogMeta", F: __dxlog_file16, L: 8, S: void 0, A: ["node.type.name === 'Element'", "'Node is not an Element'"] });
7171
7907
  const openTag = node.node.getChild("OpenTag") || node.node.getChild("SelfClosingTag");
7172
7908
  if (openTag) {
7173
7909
  const tagName = openTag.getChild("TagName");
@@ -7199,13 +7935,23 @@ var nodeToJson = (state, node) => {
7199
7935
  if (node.type.name === "Element" && openTag.type.name !== "SelfClosingTag") {
7200
7936
  const children = [];
7201
7937
  let child = node.node.firstChild;
7938
+ const appendText = (raw) => {
7939
+ if (raw.length === 0) {
7940
+ return;
7941
+ }
7942
+ const last = children[children.length - 1];
7943
+ if (typeof last === "string") {
7944
+ children[children.length - 1] = last + raw;
7945
+ } else {
7946
+ children.push(raw);
7947
+ }
7948
+ };
7202
7949
  while (child) {
7203
7950
  if (child.type.name !== "OpenTag" && child.type.name !== "CloseTag") {
7204
7951
  if (child.type.name === "Text") {
7205
- const text = state.doc.sliceString(child.from, child.to).trim();
7206
- if (text) {
7207
- children.push(text);
7208
- }
7952
+ appendText(state.doc.sliceString(child.from, child.to));
7953
+ } else if (child.type.name === "EntityReference" || child.type.name === "CharacterReference") {
7954
+ appendText(decodeXmlEntity(state.doc.sliceString(child.from, child.to)));
7209
7955
  } else if (child.type.name === "Element") {
7210
7956
  const data = nodeToJson(state, child);
7211
7957
  if (data) {
@@ -7215,26 +7961,63 @@ var nodeToJson = (state, node) => {
7215
7961
  }
7216
7962
  child = child.nextSibling;
7217
7963
  }
7964
+ if (children.length > 0 && typeof children[0] === "string") {
7965
+ children[0] = children[0].trimStart();
7966
+ }
7218
7967
  if (children.length > 0) {
7219
- tag.children = children;
7968
+ const lastIndex = children.length - 1;
7969
+ const last = children[lastIndex];
7970
+ if (typeof last === "string") {
7971
+ children[lastIndex] = last.trimEnd();
7972
+ }
7973
+ }
7974
+ const trimmed = children.filter((value) => typeof value !== "string" || value.length > 0);
7975
+ if (trimmed.length > 0) {
7976
+ tag.children = trimmed;
7220
7977
  }
7221
7978
  }
7222
7979
  return tag;
7223
7980
  }
7224
7981
  };
7982
+ var XML_NAMED_ENTITIES = {
7983
+ "&lt;": "<",
7984
+ "&gt;": ">",
7985
+ "&amp;": "&",
7986
+ "&quot;": '"',
7987
+ "&apos;": "'"
7988
+ };
7989
+ var decodeXmlEntity = (raw) => {
7990
+ const named = XML_NAMED_ENTITIES[raw];
7991
+ if (named !== void 0) {
7992
+ return named;
7993
+ }
7994
+ const numeric = /^&#(x?)([0-9a-fA-F]+);$/.exec(raw);
7995
+ if (numeric) {
7996
+ const code = parseInt(numeric[2], numeric[1] ? 16 : 10);
7997
+ if (Number.isFinite(code)) {
7998
+ try {
7999
+ return String.fromCodePoint(code);
8000
+ } catch {
8001
+ }
8002
+ }
8003
+ }
8004
+ return raw;
8005
+ };
7225
8006
 
7226
8007
  // src/extensions/tags/xml-tags.ts
7227
8008
  var __dxlog_file17 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-tags.ts";
7228
- var navigatePreviousEffect = StateEffect9.define();
7229
- var navigateNextEffect = StateEffect9.define();
8009
+ var navigatePreviousEffect = StateEffect12.define();
8010
+ var navigateNextEffect = StateEffect12.define();
7230
8011
  var getXmlTextChild = (children) => {
7231
8012
  const child = children?.[0];
7232
8013
  return typeof child === "string" ? child : null;
7233
8014
  };
7234
- var xmlTagContextEffect = StateEffect9.define();
7235
- var xmlTagResetEffect = StateEffect9.define();
7236
- var xmlTagUpdateEffect = StateEffect9.define();
7237
- var widgetContextStateField = StateField13.define({
8015
+ var xmlWidgetId = (explicit, fallback) => typeof explicit === "string" && explicit.length > 0 ? explicit : fallback;
8016
+ var escapeRegExpSource3 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8017
+ var xmlTagContextEffect = StateEffect12.define();
8018
+ var xmlTagResetEffect = StateEffect12.define();
8019
+ var xmlTagUpdateEffect = StateEffect12.define();
8020
+ var widgetContextStateField = StateField14.define({
7238
8021
  create: () => void 0,
7239
8022
  update: (value, tr) => {
7240
8023
  for (const effect of tr.effects) {
@@ -7245,7 +8028,7 @@ var widgetContextStateField = StateField13.define({
7245
8028
  return value;
7246
8029
  }
7247
8030
  });
7248
- var widgetStateMapStateField = StateField13.define({
8031
+ var widgetStateMapStateField = StateField14.define({
7249
8032
  create: () => ({}),
7250
8033
  update: (map, tr) => {
7251
8034
  for (const effect of tr.effects) {
@@ -7257,12 +8040,7 @@ var widgetStateMapStateField = StateField13.define({
7257
8040
  log11("widget updated", {
7258
8041
  id,
7259
8042
  value
7260
- }, {
7261
- F: __dxlog_file17,
7262
- L: 153,
7263
- S: void 0,
7264
- C: (f, a) => f(...a)
7265
- });
8043
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 59, S: void 0 });
7266
8044
  const state = typeof value === "function" ? value(map[id]) : value;
7267
8045
  return {
7268
8046
  ...map,
@@ -7292,12 +8070,7 @@ var createWidgetMap = (setWidgets) => {
7292
8070
  log11("widget mounted", {
7293
8071
  id: state.id,
7294
8072
  tag: state.props._tag
7295
- }, {
7296
- F: __dxlog_file17,
7297
- L: 206,
7298
- S: void 0,
7299
- C: (f, a) => f(...a)
7300
- });
8073
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 101, S: void 0 });
7301
8074
  widgets.set(state.id, state);
7302
8075
  setWidgets?.([
7303
8076
  ...widgets.values()
@@ -7308,12 +8081,7 @@ var createWidgetMap = (setWidgets) => {
7308
8081
  log11("widget unmounted", {
7309
8082
  id,
7310
8083
  tag: state?.props._tag
7311
- }, {
7312
- F: __dxlog_file17,
7313
- L: 212,
7314
- S: void 0,
7315
- C: (f, a) => f(...a)
7316
- });
8084
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 112, S: void 0 });
7317
8085
  widgets.delete(id);
7318
8086
  setWidgets?.([
7319
8087
  ...widgets.values()
@@ -7322,7 +8090,7 @@ var createWidgetMap = (setWidgets) => {
7322
8090
  };
7323
8091
  return notifier;
7324
8092
  };
7325
- var keyHandlers = keymap13.of([
8093
+ var keyHandlers = keymap14.of([
7326
8094
  {
7327
8095
  key: "Mod-ArrowUp",
7328
8096
  run: (view) => {
@@ -7343,7 +8111,7 @@ var keyHandlers = keymap13.of([
7343
8111
  }
7344
8112
  ]);
7345
8113
  var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7346
- return EditorView29.updateListener.of((update2) => {
8114
+ return EditorView32.updateListener.of((update2) => {
7347
8115
  update2.transactions.forEach((transaction) => {
7348
8116
  for (const effect of transaction.effects) {
7349
8117
  if (effect.is(navigatePreviousEffect)) {
@@ -7428,7 +8196,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7428
8196
  });
7429
8197
  });
7430
8198
  };
7431
- var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin19.fromClass(class {
8199
+ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin24.fromClass(class {
7432
8200
  update(update2) {
7433
8201
  const widgetStateMap = update2.state.field(widgetStateMapStateField);
7434
8202
  const { decorations: decorations2 } = update2.state.field(widgetDecorationsField);
@@ -7455,14 +8223,14 @@ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin1
7455
8223
  }
7456
8224
  }
7457
8225
  });
7458
- var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.define({
8226
+ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.define({
7459
8227
  create: (state) => {
7460
8228
  return buildDecorations4(state, {
7461
8229
  from: 0,
7462
8230
  to: state.doc.length
7463
8231
  }, registry, notifier);
7464
8232
  },
7465
- update: ({ from, decorations: decorations2 }, tr) => {
8233
+ update: ({ from, streamingFrom, decorations: decorations2 }, tr) => {
7466
8234
  for (const effect of tr.effects) {
7467
8235
  if (effect.is(xmlTagResetEffect)) {
7468
8236
  if (tr.docChanged) {
@@ -7473,7 +8241,7 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.def
7473
8241
  }
7474
8242
  return {
7475
8243
  from: 0,
7476
- decorations: Decoration15.none
8244
+ decorations: Decoration18.none
7477
8245
  };
7478
8246
  }
7479
8247
  }
@@ -7484,24 +8252,23 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.def
7484
8252
  log11("document reset", {
7485
8253
  from,
7486
8254
  to: state.doc.length
7487
- }, {
7488
- F: __dxlog_file17,
7489
- L: 374,
7490
- S: void 0,
7491
- C: (f, a) => f(...a)
7492
- });
8255
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 298, S: void 0 });
7493
8256
  return buildDecorations4(state, {
7494
8257
  from: 0,
7495
8258
  to: state.doc.length
7496
8259
  }, registry, notifier);
7497
8260
  } else {
8261
+ const rebuildFrom = streamingFrom ?? from;
7498
8262
  const result = buildDecorations4(state, {
7499
- from,
8263
+ from: rebuildFrom,
7500
8264
  to: state.doc.length
7501
8265
  }, registry, notifier);
7502
8266
  return {
7503
8267
  from: result.from,
8268
+ streamingFrom: result.streamingFrom,
7504
8269
  decorations: decorations2.update({
8270
+ // Remove old streaming decorations — they are rebuilt each tick.
8271
+ filter: (_f, _t, deco) => !deco.spec.streaming,
7505
8272
  add: decorationSetToArray(result.decorations)
7506
8273
  })
7507
8274
  };
@@ -7509,12 +8276,13 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.def
7509
8276
  }
7510
8277
  return {
7511
8278
  from,
8279
+ streamingFrom,
7512
8280
  decorations: decorations2
7513
8281
  };
7514
8282
  },
7515
8283
  provide: (field) => [
7516
- EditorView29.decorations.from(field, (v) => v.decorations),
7517
- EditorView29.atomicRanges.of((view) => view.state.field(field).decorations || Decoration15.none)
8284
+ EditorView32.decorations.from(field, (v) => v.decorations),
8285
+ EditorView32.atomicRanges.of((view) => view.state.field(field).decorations || Decoration18.none)
7518
8286
  ]
7519
8287
  });
7520
8288
  var buildDecorations4 = (state, range, registry, notifier) => {
@@ -7525,10 +8293,11 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7525
8293
  if (!tree || tree.type.name === "Program" && tree.length === 0) {
7526
8294
  return {
7527
8295
  from: range.from,
7528
- decorations: Decoration15.none
8296
+ decorations: Decoration18.none
7529
8297
  };
7530
8298
  }
7531
8299
  let last = range.from;
8300
+ let streamingFrom;
7532
8301
  tree.iterate({
7533
8302
  from: range.from,
7534
8303
  to: range.to,
@@ -7541,21 +8310,26 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7541
8310
  if (args) {
7542
8311
  const def = registry[args._tag];
7543
8312
  if (def) {
8313
+ if (def.streaming && !node.node.getChild("CloseTag")) {
8314
+ return false;
8315
+ }
7544
8316
  const { block, factory, Component } = def;
7545
- const widgetState = args.id ? widgetStateMap[args.id] : void 0;
7546
8317
  const nodeRange = {
7547
8318
  from: node.node.from,
7548
8319
  to: node.node.to
7549
8320
  };
8321
+ const widgetId = xmlWidgetId(args.id, def.streaming ? `cm-xml-${nodeRange.from}` : `cm-xml-${nodeRange.from}-${nodeRange.to}`);
8322
+ const widgetState = widgetStateMap[widgetId];
7550
8323
  const props = {
7551
- context,
8324
+ id: widgetId,
7552
8325
  range: nodeRange,
8326
+ context,
7553
8327
  ...args,
7554
8328
  ...widgetState
7555
8329
  };
7556
- const widget = factory ? factory(props) : Component ? args.id && new PlaceholderWidget2(args.id, Component, props, notifier) : void 0;
8330
+ const widget = factory ? factory(props) ?? void 0 : Component ? new PlaceholderWidget2(widgetId, Component, props, notifier) : void 0;
7557
8331
  if (widget) {
7558
- builder.add(nodeRange.from, nodeRange.to, Decoration15.replace({
8332
+ builder.add(nodeRange.from, nodeRange.to, Decoration18.replace({
7559
8333
  widget,
7560
8334
  block,
7561
8335
  atomic: true,
@@ -7567,20 +8341,72 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7567
8341
  }
7568
8342
  }
7569
8343
  } catch (err) {
7570
- log11.catch(err, void 0, {
7571
- F: __dxlog_file17,
7572
- L: 459,
7573
- S: void 0,
7574
- C: (f, a) => f(...a)
7575
- });
8344
+ log11.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 401, S: void 0 });
7576
8345
  }
7577
8346
  return false;
7578
8347
  }
7579
8348
  }
7580
8349
  }
7581
8350
  });
8351
+ const streamingTagNames = Object.entries(registry).filter(([, def]) => def.streaming).map(([name]) => name).sort((a, b) => b.length - a.length);
8352
+ if (streamingTagNames.length > 0) {
8353
+ const tailText = state.sliceDoc(range.from, range.to);
8354
+ const streamingPattern = streamingTagNames.map(escapeRegExpSource3).join("|");
8355
+ const tagPattern = new RegExp(`<(${streamingPattern})(\\s[^>]*)?>`, "g");
8356
+ let match;
8357
+ while ((match = tagPattern.exec(tailText)) !== null) {
8358
+ const tagName = match[1];
8359
+ const closeTag = `</${tagName}>`;
8360
+ const afterOpen = match.index + match[0].length;
8361
+ if (tailText.indexOf(closeTag, afterOpen) === -1) {
8362
+ const absoluteFrom = range.from + match.index;
8363
+ const contentFrom = range.from + afterOpen;
8364
+ const innerText = state.sliceDoc(contentFrom, range.to).trim();
8365
+ const def = registry[tagName];
8366
+ const props = {
8367
+ _tag: tagName,
8368
+ context,
8369
+ range: {
8370
+ from: absoluteFrom,
8371
+ to: range.to
8372
+ },
8373
+ children: innerText ? [
8374
+ innerText
8375
+ ] : void 0
8376
+ };
8377
+ const attrPattern = /(\w+)="([^"]*)"/g;
8378
+ let attrMatch;
8379
+ while ((attrMatch = attrPattern.exec(match[0])) !== null) {
8380
+ props[attrMatch[1]] = attrMatch[2];
8381
+ }
8382
+ const widgetId = xmlWidgetId(props.id, `cm-xml-${absoluteFrom}`);
8383
+ const widgetState = widgetStateMap[widgetId];
8384
+ const mergedProps = {
8385
+ ...props,
8386
+ id: widgetId,
8387
+ ...widgetState
8388
+ };
8389
+ const widget = def.factory ? def.factory(mergedProps) ?? void 0 : def.Component ? new PlaceholderWidget2(widgetId, def.Component, mergedProps, notifier, true) : void 0;
8390
+ if (widget) {
8391
+ builder.add(absoluteFrom, range.to, Decoration18.replace({
8392
+ widget,
8393
+ block: def.block,
8394
+ atomic: true,
8395
+ inclusive: true,
8396
+ tag: tagName,
8397
+ streaming: true,
8398
+ contentFrom
8399
+ }));
8400
+ streamingFrom = absoluteFrom;
8401
+ last = absoluteFrom;
8402
+ }
8403
+ break;
8404
+ }
8405
+ }
8406
+ }
7582
8407
  return {
7583
8408
  from: last,
8409
+ streamingFrom,
7584
8410
  decorations: builder.finish()
7585
8411
  };
7586
8412
  };
@@ -7589,108 +8415,62 @@ var PlaceholderWidget2 = class extends WidgetType10 {
7589
8415
  Component;
7590
8416
  props;
7591
8417
  notifier;
7592
- _root = null;
7593
- constructor(id, Component, props, notifier) {
7594
- super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier;
7595
- invariant7(id, void 0, {
7596
- F: __dxlog_file17,
7597
- L: 485,
7598
- S: this,
7599
- A: [
7600
- "id",
7601
- ""
7602
- ]
7603
- });
8418
+ streaming;
8419
+ #root = null;
8420
+ #view;
8421
+ constructor(id, Component, props, notifier, streaming) {
8422
+ super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier, this.streaming = streaming;
8423
+ invariant7(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 488, S: this, A: ["id", ""] });
7604
8424
  }
7605
8425
  get root() {
7606
- return this._root;
8426
+ return this.#root;
7607
8427
  }
7608
8428
  eq(other) {
8429
+ if (this.streaming) {
8430
+ return false;
8431
+ }
7609
8432
  return this.id === other.id;
7610
8433
  }
7611
8434
  ignoreEvent() {
7612
8435
  return true;
7613
8436
  }
7614
- toDOM(_view) {
7615
- this._root = document.createElement("span");
8437
+ toDOM(view) {
8438
+ this.#view = view;
8439
+ this.#root = Domino4.of("div").classNames("min-h-[24px]").root;
8440
+ const props = Object.assign({}, this.props, {
8441
+ view
8442
+ });
8443
+ this.notifier.mounted({
8444
+ id: this.id,
8445
+ root: this.#root,
8446
+ props,
8447
+ Component: this.Component
8448
+ });
8449
+ return this.#root;
8450
+ }
8451
+ updateDOM(dom) {
8452
+ this.#root = dom;
8453
+ const props = Object.assign({}, this.props, {
8454
+ view: this.#view
8455
+ });
7616
8456
  this.notifier.mounted({
7617
8457
  id: this.id,
7618
- root: this._root,
7619
- props: this.props,
8458
+ root: this.#root,
8459
+ props,
7620
8460
  Component: this.Component
7621
8461
  });
7622
- return this._root;
8462
+ return true;
7623
8463
  }
7624
8464
  destroy(_dom) {
7625
8465
  this.notifier.unmounted(this.id);
7626
- this._root = null;
8466
+ this.#root = null;
8467
+ this.#view = void 0;
7627
8468
  }
7628
8469
  };
7629
-
7630
- // src/extensions/typewriter.ts
7631
- import { keymap as keymap14 } from "@codemirror/view";
7632
- var defaultItems = [
7633
- "hello world!",
7634
- "this is a test.",
7635
- "this is [DXOS](https://dxos.org)"
7636
- ];
7637
- var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
7638
- let t;
7639
- let idx = 0;
7640
- return [
7641
- keymap14.of([
7642
- {
7643
- // Reset.
7644
- key: "alt-meta-'",
7645
- run: () => {
7646
- clearTimeout(t);
7647
- idx = 0;
7648
- return true;
7649
- }
7650
- },
7651
- {
7652
- // Next prompt.
7653
- // TODO(burdon): Press 1-9 to select prompt?
7654
- key: "Shift-Meta-'",
7655
- run: (view) => {
7656
- clearTimeout(t);
7657
- const text = items[idx++];
7658
- if (idx === items?.length) {
7659
- idx = 0;
7660
- }
7661
- let i = 0;
7662
- const insert = (d = 0) => {
7663
- t = setTimeout(() => {
7664
- const pos = view.state.selection.main.head;
7665
- view.dispatch({
7666
- changes: {
7667
- from: pos,
7668
- insert: text[i++]
7669
- },
7670
- selection: {
7671
- anchor: pos + 1
7672
- }
7673
- });
7674
- if (i < text.length) {
7675
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
7676
- }
7677
- }, d);
7678
- };
7679
- insert();
7680
- return true;
7681
- }
7682
- }
7683
- ])
7684
- ];
7685
- };
7686
8470
  export {
7687
8471
  Cursor,
7688
- EditorInputMode,
7689
- EditorInputModes,
7690
- EditorState3 as EditorState,
7691
- EditorView30 as EditorView,
7692
- EditorViewMode,
7693
- EditorViewModes,
8472
+ EditorState4 as EditorState,
8473
+ EditorView33 as EditorView,
7694
8474
  Inline,
7695
8475
  InputModeExtensions,
7696
8476
  List,
@@ -7707,6 +8487,7 @@ export {
7707
8487
  addStyle,
7708
8488
  annotations,
7709
8489
  autoScroll,
8490
+ autoScrollEffect,
7710
8491
  autocomplete,
7711
8492
  automerge,
7712
8493
  awareness,
@@ -7720,6 +8501,7 @@ export {
7720
8501
  commentClickedEffect,
7721
8502
  comments,
7722
8503
  commentsState,
8504
+ compactSlots,
7723
8505
  convertTreeToJson,
7724
8506
  createBasicExtensions,
7725
8507
  createComment,
@@ -7743,12 +8525,12 @@ export {
7743
8525
  defaultThemeSlots,
7744
8526
  deleteItem,
7745
8527
  documentId,
8528
+ documentSlots,
7746
8529
  dropFile,
8530
+ editorClassNames,
7747
8531
  editorInputMode,
7748
- editorSlots,
7749
- editorWidth,
7750
- editorWithToolbarLayout,
7751
8532
  extendedMarkdown,
8533
+ fader,
7752
8534
  filterChars,
7753
8535
  flattenRect,
7754
8536
  focus,
@@ -7784,6 +8566,7 @@ export {
7784
8566
  markdownTagsExtensions,
7785
8567
  matchCompletion,
7786
8568
  mention,
8569
+ mobileSlots,
7787
8570
  modalStateEffect,
7788
8571
  modalStateField,
7789
8572
  moveItemDown,
@@ -7803,6 +8586,7 @@ export {
7803
8586
  removeList,
7804
8587
  removeStyle,
7805
8588
  replacer,
8589
+ scrollPastEnd,
7806
8590
  scrollThreadIntoView,
7807
8591
  scrollToLine,
7808
8592
  scroller,
@@ -7815,9 +8599,8 @@ export {
7815
8599
  setSelection,
7816
8600
  setStyle,
7817
8601
  singleValueFacet,
7818
- stackItemContentEditorClassNames,
8602
+ snippets2 as snippets,
7819
8603
  staticCompletion,
7820
- streamer,
7821
8604
  submit,
7822
8605
  tabbable,
7823
8606
  table,
@@ -7836,7 +8619,12 @@ export {
7836
8619
  treeFacet,
7837
8620
  typeahead,
7838
8621
  typewriter,
8622
+ typewriterBypass,
8623
+ typewriterDrainingEffect,
7839
8624
  wrapWithCatch,
8625
+ xmlBlockDecoration,
8626
+ xmlElementLength,
8627
+ xmlFormatting,
7840
8628
  xmlTagContextEffect,
7841
8629
  xmlTagResetEffect,
7842
8630
  xmlTagUpdateEffect,