@dxos/ui-editor 0.8.4-main.ef1bc66f44 → 0.8.4-main.fcc0d83b33

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 (171) hide show
  1. package/dist/lib/browser/index.mjs +1846 -1002
  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 +1843 -998
  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 +18 -0
  15. package/dist/types/src/extensions/auto-scroll.d.ts.map +1 -0
  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.map +1 -1
  19. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -1
  20. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  25. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  26. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
  27. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  28. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  29. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  30. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  31. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  32. package/dist/types/src/extensions/factories.d.ts +4 -3
  33. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  34. package/dist/types/src/extensions/factories.test.d.ts +2 -0
  35. package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
  36. package/dist/types/src/extensions/focus.d.ts +1 -1
  37. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  38. package/dist/types/src/extensions/index.d.ts +4 -3
  39. package/dist/types/src/extensions/index.d.ts.map +1 -1
  40. package/dist/types/src/extensions/json.d.ts.map +1 -1
  41. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  42. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -0
  44. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  45. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  46. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  51. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
  53. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  54. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  55. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -1
  56. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
  57. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -1
  58. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
  59. package/dist/types/src/extensions/preview/preview.d.ts +2 -0
  60. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  61. package/dist/types/src/extensions/replacer.d.ts.map +1 -1
  62. package/dist/types/src/extensions/scroll-past-end.d.ts +3 -0
  63. package/dist/types/src/extensions/scroll-past-end.d.ts.map +1 -0
  64. package/dist/types/src/extensions/scroller.d.ts +68 -0
  65. package/dist/types/src/extensions/scroller.d.ts.map +1 -0
  66. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  67. package/dist/types/src/extensions/snippets.d.ts +10 -0
  68. package/dist/types/src/extensions/snippets.d.ts.map +1 -0
  69. package/dist/types/src/extensions/submit.d.ts.map +1 -1
  70. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -1
  71. package/dist/types/src/extensions/tags/fader.d.ts +12 -0
  72. package/dist/types/src/extensions/tags/fader.d.ts.map +1 -0
  73. package/dist/types/src/extensions/tags/index.d.ts +4 -1
  74. package/dist/types/src/extensions/tags/index.d.ts.map +1 -1
  75. package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
  76. package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
  77. package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
  78. package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
  79. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
  80. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
  81. package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
  82. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
  83. package/dist/types/src/extensions/tags/xml-tags.d.ts +28 -8
  84. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  85. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -1
  86. package/dist/types/src/index.d.ts +0 -1
  87. package/dist/types/src/index.d.ts.map +1 -1
  88. package/dist/types/src/styles/index.d.ts +0 -2
  89. package/dist/types/src/styles/index.d.ts.map +1 -1
  90. package/dist/types/src/styles/theme.d.ts +15 -0
  91. package/dist/types/src/styles/theme.d.ts.map +1 -1
  92. package/dist/types/src/types/types.d.ts +4 -4
  93. package/dist/types/src/types/types.d.ts.map +1 -1
  94. package/dist/types/src/util/cursor.d.ts +1 -1
  95. package/dist/types/src/util/cursor.d.ts.map +1 -1
  96. package/dist/types/src/util/debug.d.ts.map +1 -1
  97. package/dist/types/src/util/decorations.d.ts.map +1 -1
  98. package/dist/types/src/util/dom.d.ts.map +1 -1
  99. package/dist/types/src/util/facet.d.ts.map +1 -1
  100. package/dist/types/src/util/util.d.ts.map +1 -1
  101. package/dist/types/tsconfig.tsbuildinfo +1 -1
  102. package/package.json +31 -34
  103. package/src/defaults.ts +33 -20
  104. package/src/extensions/annotations.ts +1 -1
  105. package/src/extensions/auto-scroll.ts +232 -0
  106. package/src/extensions/automerge/automerge.test.tsx +30 -10
  107. package/src/extensions/automerge/automerge.ts +5 -7
  108. package/src/extensions/blocks.ts +5 -5
  109. package/src/extensions/comments.ts +5 -6
  110. package/src/extensions/dnd.ts +2 -2
  111. package/src/extensions/factories.test.ts +88 -0
  112. package/src/extensions/factories.ts +32 -15
  113. package/src/extensions/folding.ts +5 -22
  114. package/src/extensions/index.ts +4 -3
  115. package/src/extensions/markdown/action.ts +0 -1
  116. package/src/extensions/markdown/bundle.ts +23 -9
  117. package/src/extensions/markdown/decorate.ts +15 -12
  118. package/src/extensions/markdown/highlight.ts +15 -7
  119. package/src/extensions/markdown/link.ts +27 -33
  120. package/src/extensions/markdown/parser.test.ts +0 -1
  121. package/src/extensions/markdown/styles.ts +42 -9
  122. package/src/extensions/markdown/table.ts +24 -2
  123. package/src/extensions/outliner/outliner.test.ts +0 -1
  124. package/src/extensions/outliner/outliner.ts +3 -4
  125. package/src/extensions/outliner/tree.test.ts +0 -1
  126. package/src/extensions/preview/preview.ts +55 -8
  127. package/src/extensions/scroll-past-end.ts +32 -0
  128. package/src/extensions/scroller.ts +256 -0
  129. package/src/extensions/selection.ts +1 -1
  130. package/src/extensions/snippets.ts +67 -0
  131. package/src/extensions/tags/extended-markdown.test.ts +120 -2
  132. package/src/extensions/tags/extended-markdown.ts +80 -1
  133. package/src/extensions/tags/fader.ts +195 -0
  134. package/src/extensions/tags/index.ts +4 -1
  135. package/src/extensions/tags/testing/text.md +36 -0
  136. package/src/extensions/tags/testing/text.txt +35 -0
  137. package/src/extensions/tags/typewriter.test.ts +65 -0
  138. package/src/extensions/tags/typewriter.ts +594 -0
  139. package/src/extensions/tags/xml-block-decoration.ts +123 -0
  140. package/src/extensions/tags/xml-formatting.ts +125 -0
  141. package/src/extensions/tags/xml-tags.ts +186 -35
  142. package/src/extensions/tags/xml-util.test.ts +199 -24
  143. package/src/extensions/tags/xml-util.ts +62 -5
  144. package/src/index.ts +0 -1
  145. package/src/styles/index.ts +0 -2
  146. package/src/styles/theme.ts +124 -33
  147. package/src/types/types.ts +10 -2
  148. package/src/typings.d.ts +8 -0
  149. package/src/util/cursor.ts +1 -2
  150. package/dist/lib/browser/chunk-HL3YF6WC.mjs +0 -22
  151. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +0 -7
  152. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs +0 -24
  153. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +0 -7
  154. package/dist/types/src/extensions/autoscroll.d.ts +0 -20
  155. package/dist/types/src/extensions/autoscroll.d.ts.map +0 -1
  156. package/dist/types/src/extensions/scrolling.d.ts +0 -78
  157. package/dist/types/src/extensions/scrolling.d.ts.map +0 -1
  158. package/dist/types/src/extensions/tags/streamer.d.ts +0 -12
  159. package/dist/types/src/extensions/tags/streamer.d.ts.map +0 -1
  160. package/dist/types/src/extensions/typewriter.d.ts +0 -10
  161. package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
  162. package/dist/types/src/styles/markdown.d.ts +0 -8
  163. package/dist/types/src/styles/markdown.d.ts.map +0 -1
  164. package/dist/types/src/styles/tokens.d.ts +0 -3
  165. package/dist/types/src/styles/tokens.d.ts.map +0 -1
  166. package/src/extensions/autoscroll.ts +0 -165
  167. package/src/extensions/scrolling.ts +0 -189
  168. package/src/extensions/tags/streamer.ts +0 -243
  169. package/src/extensions/typewriter.ts +0 -68
  170. package/src/styles/markdown.ts +0 -26
  171. package/src/styles/tokens.ts +0 -17
@@ -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 = "!mli-auto is-full max-is-[min(50rem,100%-4rem)]";
17
- var editorSlots = {
18
- scroll: {
19
- className: "pbs-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("p-0.5 dx-focus-ring-inset attention-surface data-[toolbar=disabled]:pbs-2", role === "section" ? "[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-bs-24" : "min-bs-0");
27
40
 
28
41
  // src/extensions/annotations.ts
29
42
  import { RangeSetBuilder } from "@codemirror/state";
@@ -58,7 +71,7 @@ var annotations = ({ match } = {}) => {
58
71
  ".cm-annotation": {
59
72
  textDecoration: "underline",
60
73
  textDecorationStyle: "wavy",
61
- textDecorationColor: "var(--dx-errorText)"
74
+ textDecorationColor: "var(--color-error-text)"
62
75
  }
63
76
  })
64
77
  ];
@@ -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
 
@@ -501,228 +504,336 @@ var typeahead = ({ onComplete } = {}) => {
501
504
  ];
502
505
  };
503
506
 
504
- // src/extensions/autoscroll.ts
507
+ // src/extensions/auto-scroll.ts
505
508
  import { StateEffect as StateEffect2 } from "@codemirror/state";
506
509
  import { EditorView as EditorView5, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
507
- import { debounce } from "@dxos/async";
510
+ import { addEventListener, combine, throttle } from "@dxos/async";
508
511
  import { Domino } from "@dxos/ui";
512
+ import { getSize } from "@dxos/ui-theme";
509
513
 
510
- // src/extensions/scrolling.ts
514
+ // src/extensions/scroller.ts
511
515
  import { StateEffect } from "@codemirror/state";
512
516
  import { EditorView as EditorView4, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
513
- var scrollToLineEffect = StateEffect.define();
514
- var smoothScroll = ({ offset = 0, position = "start" } = {}) => {
515
- const scrollPlugin = ViewPlugin5.fromClass(class SmoothScrollPlugin {
517
+ import { log as log2 } from "@dxos/log";
518
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/scroller.ts";
519
+ var scrollerLineEffect = StateEffect.define();
520
+ var scrollerCrawlEffect = StateEffect.define();
521
+ var scrollToLine = (view, options) => {
522
+ view.dispatch({
523
+ effects: scrollerLineEffect.of(options)
524
+ });
525
+ };
526
+ var scroller = ({ overScroll = 0 } = {}) => {
527
+ const scrollPlugin = ViewPlugin5.fromClass(class ScrollerPlugin {
516
528
  view;
529
+ crawler;
517
530
  constructor(view) {
518
531
  this.view = view;
532
+ this.crawler = createCrawler(this.view);
519
533
  }
520
534
  // No-op.
521
535
  destroy() {
536
+ this.crawler.cancel();
522
537
  }
523
- /**
524
- * Perform smooth scroll to the specified line.
525
- */
526
- scrollToLine(lineNumber, options) {
527
- const { offset: animOffset = 0, position: animPosition, behavior } = options;
528
- const doc = this.view.state.doc;
529
- const scroller = this.view.scrollDOM;
530
- const targetLine = Math.max(0, lineNumber - 1);
531
- if (behavior === "instant") {
532
- requestAnimationFrame(() => {
533
- this.view.dispatch({
534
- selection: {
535
- anchor: doc.line(targetLine + 1).from
536
- },
537
- scrollIntoView: true
538
- });
539
- });
540
- return;
541
- }
542
- if (targetLine >= doc.lines) {
543
- const targetScrollTop2 = scroller.scrollHeight - scroller.clientHeight + (animOffset || 0);
544
- this.animateScroll(scroller, targetScrollTop2);
545
- return;
546
- }
547
- const lineStart = doc.line(targetLine + 1).from;
548
- const coords = this.view.coordsAtPos(lineStart);
549
- if (!coords) {
550
- return;
551
- }
552
- const currentScrollTop = scroller.scrollTop;
553
- const scrollerRect = scroller.getBoundingClientRect();
554
- const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
555
- let targetScrollTop;
556
- if (animPosition === "end") {
557
- targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + animOffset;
538
+ cancel() {
539
+ this.crawler.cancel();
540
+ }
541
+ crawl(start = false) {
542
+ if (start) {
543
+ this.crawler.scroll();
558
544
  } else {
559
- targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + animOffset;
545
+ this.crawler.cancel();
560
546
  }
561
- const clampedScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
562
- this.animateScroll(scroller, clampedScrollTop);
563
547
  }
564
- /**
565
- * Animate scroll using browser's built-in smooth scrolling.
566
- */
567
- animateScroll(element, targetScrollTop) {
568
- if (Math.abs(targetScrollTop - element.scrollTop) < 1) {
569
- return;
548
+ scroll({ line, offset = 0, position, behavior = "instant" }) {
549
+ const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
550
+ const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
551
+ const doc = this.view.state.doc;
552
+ let targetScrollTop = scrollHeight - clientHeight + offset;
553
+ if (line >= 0 && line <= doc.lines - 1) {
554
+ const lineStart = doc.line(line + 1).from;
555
+ const coords = this.view.coordsAtPos(lineStart);
556
+ if (coords) {
557
+ const currentScrollTop = scrollTop;
558
+ const maxScrollTop = scrollHeight - clientHeight;
559
+ if (position === "end") {
560
+ targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
561
+ } else {
562
+ targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
563
+ }
564
+ targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
565
+ }
570
566
  }
571
- element.scrollTo({
572
- top: targetScrollTop,
573
- behavior: "smooth"
567
+ requestAnimationFrame(() => {
568
+ this.view.scrollDOM.scrollTo({
569
+ top: targetScrollTop
570
+ });
574
571
  });
575
572
  }
576
573
  });
577
574
  return [
578
575
  scrollPlugin,
579
- // Update listener to handle scroll effects.
576
+ // Listen for effect.s
580
577
  EditorView4.updateListener.of((update2) => {
581
578
  update2.transactions.forEach((transaction) => {
582
- for (const effect of transaction.effects) {
583
- if (effect.is(scrollToLineEffect)) {
584
- const { line, options = {} } = effect.value;
585
- const plugin = update2.view.plugin(scrollPlugin);
586
- if (plugin) {
587
- plugin.scrollToLine(line, {
588
- offset,
589
- position,
590
- ...options
591
- });
579
+ try {
580
+ const plugin = update2.view.plugin(scrollPlugin);
581
+ if (plugin) {
582
+ for (const effect of transaction.effects) {
583
+ if (effect.is(scrollerCrawlEffect)) {
584
+ plugin.crawl(effect.value);
585
+ } else if (effect.is(scrollerLineEffect)) {
586
+ plugin.scroll(effect.value);
587
+ }
592
588
  }
593
589
  }
590
+ } catch (err) {
591
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 91, S: void 0 });
594
592
  }
595
593
  });
594
+ }),
595
+ // Styles.
596
+ EditorView4.theme({
597
+ ".cm-scroller": {
598
+ overflowY: "scroll",
599
+ // Browser scroll-anchoring: when widgets above the viewport resize (e.g. tool blocks
600
+ // expanding their TogglePanel), the browser picks a stable element near the viewport
601
+ // top and adjusts `scrollTop` so the user's view doesn't jump. Auto-scroll's pinning
602
+ // logic still has the final word when pinned (forces scrollTop to scrollHeight).
603
+ overflowAnchor: "auto"
604
+ },
605
+ ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
606
+ display: "none"
607
+ },
608
+ ".cm-scroller::-webkit-scrollbar-thumb": {
609
+ background: "transparent",
610
+ transition: "background 0.15s"
611
+ },
612
+ "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
613
+ background: "var(--color-scrollbar-thumb)"
614
+ },
615
+ // Spacer below the last text line. Implemented as a real block pseudo-element
616
+ // (rather than `padding-bottom` on `.cm-content`) so it materializes in the
617
+ // scroller's `scrollHeight` regardless of how `padding` is reset by the base
618
+ // theme or downstream classes — this is what gives auto-scroll its head-room
619
+ // so the last line stays `overScroll` px above the viewport bottom.
620
+ ".cm-content::after": {
621
+ content: '""',
622
+ display: "block",
623
+ height: `${overScroll}px`
624
+ },
625
+ ".cm-scroll-button": {
626
+ position: "absolute",
627
+ bottom: "0.5rem",
628
+ right: "1rem"
629
+ }
596
630
  })
597
631
  ];
598
632
  };
599
- var scrollToLine = (view, line, options) => {
600
- view.dispatch({
601
- effects: scrollToLineEffect.of({
602
- line,
603
- options
604
- })
605
- });
606
- };
633
+ function createCrawler(view, omega = 5, snapThreshold = 5, snapVelocity = 50) {
634
+ const el = view.scrollDOM;
635
+ let currentTop = 0;
636
+ let velocity = 0;
637
+ let rafId = null;
638
+ let lastTime = 0;
639
+ function frame(now) {
640
+ const dt = lastTime === 0 ? 1 / 60 : Math.min(0.1, (now - lastTime) / 1e3);
641
+ lastTime = now;
642
+ const targetTop = el.scrollHeight - el.clientHeight;
643
+ const delta = targetTop - currentTop;
644
+ if (Math.abs(delta) < snapThreshold && Math.abs(velocity) < snapVelocity) {
645
+ el.scrollTop = targetTop;
646
+ currentTop = targetTop;
647
+ velocity = 0;
648
+ rafId = null;
649
+ lastTime = 0;
650
+ return;
651
+ }
652
+ const accel = omega * omega * delta - 2 * omega * velocity;
653
+ velocity += accel * dt;
654
+ currentTop += velocity * dt;
655
+ el.scrollTop = currentTop;
656
+ rafId = requestAnimationFrame(frame);
657
+ }
658
+ return {
659
+ scroll: () => {
660
+ if (rafId === null) {
661
+ currentTop = el.scrollTop;
662
+ lastTime = 0;
663
+ rafId = requestAnimationFrame(frame);
664
+ }
665
+ },
666
+ cancel: () => {
667
+ if (rafId !== null) {
668
+ cancelAnimationFrame(rafId);
669
+ velocity = 0;
670
+ lastTime = 0;
671
+ rafId = null;
672
+ }
673
+ }
674
+ };
675
+ }
607
676
 
608
- // src/extensions/autoscroll.ts
609
- var scrollToBottomEffect = StateEffect2.define();
610
- var autoScroll = ({ autoScroll: autoScroll2 = true, threshold = 100, throttleDelay = 1e3, onAutoScroll } = {}) => {
677
+ // src/extensions/auto-scroll.ts
678
+ var autoScrollEffect = StateEffect2.define();
679
+ var autoScroll = ({ scrollOnResize = true } = {}) => {
611
680
  let buttonContainer;
612
- let hideTimeout;
613
- let lastScrollTop = 0;
614
681
  let isPinned = true;
615
- const setPinned = (pin) => {
616
- isPinned = pin;
617
- buttonContainer?.classList.toggle("opacity-0", pin);
618
- };
619
- const hideScrollbar = (view) => {
620
- view.scrollDOM.classList.add("cm-hide-scrollbar");
621
- clearTimeout(hideTimeout);
622
- hideTimeout = setTimeout(() => {
623
- view.scrollDOM.classList.remove("cm-hide-scrollbar");
624
- }, 1e3);
625
- };
626
- const scrollToBottom = (view, behavior) => {
627
- setPinned(true);
628
- hideScrollbar(view);
629
- const line = view.state.doc.lineAt(view.state.doc.length);
630
- view.dispatch({
631
- selection: {
632
- anchor: line.to,
633
- head: line.to
634
- },
635
- effects: scrollToLineEffect.of({
636
- line: line.number,
637
- options: {
638
- position: "end",
639
- offset: threshold,
640
- behavior
641
- }
642
- })
643
- });
682
+ let jumpPending = false;
683
+ let enabled = true;
684
+ let firstUpdate = true;
685
+ const setPinned = (pinned) => {
686
+ buttonContainer?.classList.toggle("opacity-0", pinned);
687
+ isPinned = pinned;
644
688
  };
645
- const checkDistance = debounce((view) => {
646
- const scrollerRect = view.scrollDOM.getBoundingClientRect();
647
- const coords = view.coordsAtPos(view.state.doc.length);
648
- const distanceFromBottom = coords ? coords.bottom - scrollerRect.bottom : 0;
649
- setPinned(distanceFromBottom < 0);
650
- }, 1e3);
651
- const triggerUpdate = debounce((view) => scrollToBottom(view), throttleDelay);
652
689
  return [
653
- // Update listener for logging when scrolling is needed.
654
- EditorView5.updateListener.of(({ view, transactions, heightChanged }) => {
655
- transactions.forEach((transaction) => {
656
- for (const effect of transaction.effects) {
657
- if (effect.is(scrollToBottomEffect)) {
658
- scrollToBottom(view, effect.value);
690
+ // Update listener for scrolling when content changes.
691
+ EditorView5.updateListener.of((update2) => {
692
+ const { view, heightChanged, state, startState } = update2;
693
+ for (const tr of update2.transactions) {
694
+ for (const effect of tr.effects) {
695
+ if (effect.is(autoScrollEffect)) {
696
+ enabled = effect.value;
697
+ if (enabled) {
698
+ setPinned(true);
699
+ view.dispatch({
700
+ effects: scrollerCrawlEffect.of(true)
701
+ });
702
+ } else {
703
+ view.dispatch({
704
+ effects: scrollerCrawlEffect.of(false)
705
+ });
706
+ }
659
707
  }
660
708
  }
661
- });
662
- if (heightChanged && isPinned) {
663
- const coords = view.coordsAtPos(view.state.doc.length);
664
- const scrollerRect = view.scrollDOM.getBoundingClientRect();
665
- const distanceFromBottom = coords ? scrollerRect.bottom - coords.bottom : 0;
666
- if (autoScroll2 && distanceFromBottom < threshold) {
667
- const shouldScroll = onAutoScroll?.({
668
- view,
669
- distanceFromBottom
670
- }) ?? true;
671
- if (shouldScroll) {
672
- triggerUpdate(view);
709
+ }
710
+ if (!enabled) {
711
+ return;
712
+ }
713
+ if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
714
+ firstUpdate = false;
715
+ jumpPending = true;
716
+ requestAnimationFrame(() => {
717
+ view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
718
+ jumpPending = false;
719
+ });
720
+ return;
721
+ }
722
+ firstUpdate = false;
723
+ if (jumpPending) {
724
+ return;
725
+ }
726
+ if (heightChanged) {
727
+ if (isPinned) {
728
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
729
+ const delta = scrollHeight - scrollTop - clientHeight;
730
+ if (delta > 0) {
731
+ setPinned(true);
732
+ view.dispatch({
733
+ effects: scrollerCrawlEffect.of(true)
734
+ });
735
+ } else if (delta < -1) {
736
+ setPinned(false);
737
+ }
738
+ } else {
739
+ if (state.doc.length === 0) {
740
+ setPinned(true);
673
741
  }
674
- } else if (distanceFromBottom < 0) {
675
- setPinned(false);
676
742
  }
677
743
  }
678
744
  }),
679
- // Detect user scroll.
680
- EditorView5.domEventHandlers({
681
- scroll: (event, view) => {
682
- const currentScrollTop = view.scrollDOM.scrollTop;
683
- const scrollingUp = currentScrollTop < lastScrollTop;
684
- lastScrollTop = currentScrollTop;
685
- if (scrollingUp) {
686
- setPinned(false);
687
- } else {
688
- checkDistance(view);
689
- }
745
+ // Re-pin and jump to bottom when the scroll container itself resizes (e.g. sidebar toggle,
746
+ // window resize). Doc-driven height changes are handled by the updateListener above; this
747
+ // observer covers the case where the viewport changes while the doc length is unchanged.
748
+ scrollOnResize ? ViewPlugin6.fromClass(class {
749
+ observer;
750
+ firstObservation = true;
751
+ destroyed = false;
752
+ constructor(view) {
753
+ const onResize = throttle(() => {
754
+ if (this.destroyed || !enabled) {
755
+ return;
756
+ }
757
+ setPinned(true);
758
+ requestAnimationFrame(() => {
759
+ if (this.destroyed) {
760
+ return;
761
+ }
762
+ view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
763
+ view.dispatch({
764
+ effects: scrollerCrawlEffect.of(true)
765
+ });
766
+ });
767
+ }, 100);
768
+ this.observer = new ResizeObserver(() => {
769
+ if (this.firstObservation) {
770
+ this.firstObservation = false;
771
+ return;
772
+ }
773
+ onResize();
774
+ });
775
+ this.observer.observe(view.scrollDOM);
776
+ }
777
+ destroy() {
778
+ this.destroyed = true;
779
+ this.observer.disconnect();
780
+ }
781
+ }) : [],
782
+ // Detect user scroll and unpin (or re-pin if scrolled to the bottom).
783
+ ViewPlugin6.fromClass(class {
784
+ cleanup;
785
+ constructor(view) {
786
+ this.cleanup = createUserScrollDetector(view.scrollDOM, throttle(() => {
787
+ requestAnimationFrame(() => {
788
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
789
+ const delta = scrollHeight - scrollTop - clientHeight;
790
+ const pinned = delta === 0;
791
+ setPinned(pinned);
792
+ if (!pinned) {
793
+ view.dispatch({
794
+ effects: scrollerCrawlEffect.of(false)
795
+ });
796
+ }
797
+ });
798
+ }, 500));
799
+ }
800
+ destroy() {
801
+ this.cleanup();
690
802
  }
691
803
  }),
692
804
  // Scroll button.
693
805
  ViewPlugin6.fromClass(class {
694
806
  constructor(view) {
695
- const icon = Domino.of("dx-icon").attributes({
807
+ const icon = Domino.of("dx-icon").classNames(getSize(4)).attributes({
696
808
  icon: "ph--arrow-down--regular"
697
809
  });
698
- const button = Domino.of("button").classNames("dx-button bg-accentSurface").attributes({
810
+ const button = Domino.of("button").classNames("dx-button bg-accent-surface").attributes({
699
811
  "data-density": "fine"
700
- }).children(icon).on("click", () => {
701
- scrollToBottom(view);
812
+ }).append(icon).on("click", () => {
813
+ setPinned(true);
814
+ view.dispatch({
815
+ effects: scrollerLineEffect.of({
816
+ line: -1,
817
+ position: "end",
818
+ behavior: "smooth"
819
+ })
820
+ });
702
821
  });
703
- buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").children(button).root;
822
+ buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").append(button).root;
704
823
  view.scrollDOM.parentElement.appendChild(buttonContainer);
705
824
  }
706
- }),
707
- // Styles.
708
- EditorView5.theme({
709
- ".cm-scroller": {
710
- scrollbarWidth: "thin"
711
- },
712
- ".cm-scroller.cm-hide-scrollbar": {
713
- scrollbarWidth: "none"
714
- },
715
- ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
716
- display: "none"
717
- },
718
- ".cm-scroll-button": {
719
- position: "absolute",
720
- bottom: "0.5rem",
721
- right: "1rem"
722
- }
723
825
  })
724
826
  ];
725
827
  };
828
+ function createUserScrollDetector(element, onUserScroll) {
829
+ return combine(addEventListener(element, "wheel", () => onUserScroll(), {
830
+ passive: true
831
+ }), addEventListener(element, "pointerdown", (event) => {
832
+ if (event.clientX > element.getBoundingClientRect().right - (element.offsetWidth - element.clientWidth)) {
833
+ onUserScroll();
834
+ }
835
+ }));
836
+ }
726
837
 
727
838
  // src/extensions/automerge/automerge.ts
728
839
  import { next as A3 } from "@automerge/automerge";
@@ -736,32 +847,22 @@ var initialSync = Transaction.userEvent.of("initial.sync");
736
847
 
737
848
  // src/extensions/automerge/cursor.ts
738
849
  import { fromCursor, toCursor } from "@dxos/echo-db";
739
- import { log as log2 } from "@dxos/log";
740
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/cursor.ts";
850
+ import { log as log3 } from "@dxos/log";
851
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/cursor.ts";
741
852
  var cursorConverter = (accessor) => ({
742
853
  toCursor: (pos, assoc) => {
743
854
  try {
744
855
  return toCursor(accessor, pos, assoc);
745
856
  } catch (err) {
746
- log2.catch(err, void 0, {
747
- F: __dxlog_file2,
748
- L: 15,
749
- S: void 0,
750
- C: (f, a) => f(...a)
751
- });
857
+ log3.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 11, S: void 0 });
752
858
  return "";
753
859
  }
754
860
  },
755
- fromCursor: (cursor2) => {
861
+ fromCursor: (cursor) => {
756
862
  try {
757
- return fromCursor(accessor, cursor2);
863
+ return fromCursor(accessor, cursor);
758
864
  } catch (err) {
759
- log2.catch(err, void 0, {
760
- F: __dxlog_file2,
761
- L: 24,
762
- S: void 0,
763
- C: (f, a) => f(...a)
764
- });
865
+ log3.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 19, S: void 0 });
765
866
  return 0;
766
867
  }
767
868
  }
@@ -782,7 +883,7 @@ var isReconcile = (tr) => {
782
883
 
783
884
  // src/extensions/automerge/sync.ts
784
885
  import { next as A2 } from "@automerge/automerge";
785
- import { log as log3 } from "@dxos/log";
886
+ import { log as log4 } from "@dxos/log";
786
887
 
787
888
  // src/extensions/automerge/update-automerge.ts
788
889
  import { next as A } from "@automerge/automerge";
@@ -923,7 +1024,7 @@ var charPath = (textPath, candidatePath) => {
923
1024
  };
924
1025
 
925
1026
  // src/extensions/automerge/sync.ts
926
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/sync.ts";
1027
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/sync.ts";
927
1028
  var Syncer = class {
928
1029
  _handle;
929
1030
  _state;
@@ -946,12 +1047,7 @@ var Syncer = class {
946
1047
  this._pending = false;
947
1048
  }
948
1049
  onEditorChange(view) {
949
- log3("onEditorChange", void 0, {
950
- F: __dxlog_file3,
951
- L: 45,
952
- S: this,
953
- C: (f, a) => f(...a)
954
- });
1050
+ log4("onEditorChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 35, S: this });
955
1051
  const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
956
1052
  const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
957
1053
  if (newHeads) {
@@ -962,12 +1058,7 @@ var Syncer = class {
962
1058
  }
963
1059
  }
964
1060
  onAutomergeChange(view) {
965
- log3("onAutomergeChange", void 0, {
966
- F: __dxlog_file3,
967
- L: 60,
968
- S: this,
969
- C: (f, a) => f(...a)
970
- });
1061
+ log4("onAutomergeChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 47, S: this });
971
1062
  const oldHeads = getLastHeads(view.state, this._state);
972
1063
  const newHeads = A2.getHeads(this._handle.doc());
973
1064
  const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.doc(), oldHeads, newHeads);
@@ -1029,6 +1120,17 @@ var automerge = (accessor) => {
1029
1120
  const value = DocAccessor.getValue(accessor);
1030
1121
  const current = this._view.state.doc.toString();
1031
1122
  if (value !== current) {
1123
+ this._view.dispatch({
1124
+ changes: {
1125
+ from: 0,
1126
+ to: this._view.state.doc.length,
1127
+ insert: value
1128
+ },
1129
+ annotations: [
1130
+ initialSync,
1131
+ reconcileAnnotation.of(true)
1132
+ ]
1133
+ });
1032
1134
  }
1033
1135
  });
1034
1136
  }
@@ -1056,7 +1158,7 @@ import { Annotation as Annotation2, RangeSet } from "@codemirror/state";
1056
1158
  import { Decoration as Decoration5, EditorView as EditorView7, ViewPlugin as ViewPlugin8, WidgetType as WidgetType3 } from "@codemirror/view";
1057
1159
  import { Event } from "@dxos/async";
1058
1160
  import { Context } from "@dxos/context";
1059
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness.ts";
1161
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness.ts";
1060
1162
  var dummyProvider = {
1061
1163
  remoteStateChange: new Event(),
1062
1164
  open: () => {
@@ -1079,10 +1181,7 @@ var awareness = (provider = dummyProvider) => {
1079
1181
  ];
1080
1182
  };
1081
1183
  var RemoteSelectionsDecorator = class {
1082
- _ctx = new Context(void 0, {
1083
- F: __dxlog_file4,
1084
- L: 80
1085
- });
1184
+ _ctx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 33 });
1086
1185
  _cursorConverter;
1087
1186
  _provider;
1088
1187
  _lastAnchor;
@@ -1293,8 +1392,8 @@ var styles = EditorView7.theme({
1293
1392
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
1294
1393
  import { Context as Context2 } from "@dxos/context";
1295
1394
  import { invariant } from "@dxos/invariant";
1296
- import { log as log4 } from "@dxos/log";
1297
- var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness-provider.ts";
1395
+ import { log as log5 } from "@dxos/log";
1396
+ var __dxlog_file6 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness-provider.ts";
1298
1397
  var DEBOUNCE_INTERVAL = 100;
1299
1398
  var SpaceAwarenessProvider = class {
1300
1399
  _remoteStates = /* @__PURE__ */ new Map();
@@ -1313,10 +1412,7 @@ var SpaceAwarenessProvider = class {
1313
1412
  this._info = info;
1314
1413
  }
1315
1414
  open() {
1316
- this._ctx = new Context2(void 0, {
1317
- F: __dxlog_file5,
1318
- L: 57
1319
- });
1415
+ this._ctx = new Context2(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 28 });
1320
1416
  this._postTask = new DeferredTask(this._ctx, async () => {
1321
1417
  if (this._localState) {
1322
1418
  await this._messenger.postMessage(this._channel, {
@@ -1341,14 +1437,9 @@ var SpaceAwarenessProvider = class {
1341
1437
  void this._messenger.postMessage(this._channel, {
1342
1438
  kind: "query"
1343
1439
  }).catch((err) => {
1344
- log4.debug("failed to query awareness", {
1440
+ log5.debug("failed to query awareness", {
1345
1441
  err
1346
- }, {
1347
- F: __dxlog_file5,
1348
- L: 91,
1349
- S: this,
1350
- C: (f, a) => f(...a)
1351
- });
1442
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 57, S: this });
1352
1443
  });
1353
1444
  }
1354
1445
  close() {
@@ -1360,15 +1451,7 @@ var SpaceAwarenessProvider = class {
1360
1451
  return Array.from(this._remoteStates.values());
1361
1452
  }
1362
1453
  update(position) {
1363
- invariant(this._postTask, void 0, {
1364
- F: __dxlog_file5,
1365
- L: 106,
1366
- S: this,
1367
- A: [
1368
- "this._postTask",
1369
- ""
1370
- ]
1371
- });
1454
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 71, S: this, A: ["this._postTask", ""] });
1372
1455
  this._localState = {
1373
1456
  peerId: this._peerId,
1374
1457
  position,
@@ -1377,27 +1460,11 @@ var SpaceAwarenessProvider = class {
1377
1460
  this._postTask.schedule();
1378
1461
  }
1379
1462
  _handleQueryMessage() {
1380
- invariant(this._postTask, void 0, {
1381
- F: __dxlog_file5,
1382
- L: 117,
1383
- S: this,
1384
- A: [
1385
- "this._postTask",
1386
- ""
1387
- ]
1388
- });
1463
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 80, S: this, A: ["this._postTask", ""] });
1389
1464
  this._postTask.schedule();
1390
1465
  }
1391
1466
  _handlePostMessage(message) {
1392
- invariant(message.kind === "post", void 0, {
1393
- F: __dxlog_file5,
1394
- L: 122,
1395
- S: this,
1396
- A: [
1397
- "message.kind === 'post'",
1398
- ""
1399
- ]
1400
- });
1467
+ invariant(message.kind === "post", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 84, S: this, A: ["message.kind === 'post'", ""] });
1401
1468
  this._remoteStates.set(message.state.peerId, message.state);
1402
1469
  this.remoteStateChange.emit();
1403
1470
  }
@@ -1406,9 +1473,9 @@ var SpaceAwarenessProvider = class {
1406
1473
  // src/extensions/blast.ts
1407
1474
  import { EditorView as EditorView8, keymap as keymap3 } from "@codemirror/view";
1408
1475
  import defaultsDeep from "lodash.defaultsdeep";
1409
- import { throttle } from "@dxos/async";
1476
+ import { throttle as throttle2 } from "@dxos/async";
1410
1477
  import { invariant as invariant2 } from "@dxos/invariant";
1411
- var __dxlog_file6 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/blast.ts";
1478
+ var __dxlog_file7 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/blast.ts";
1412
1479
  var defaultOptions = {
1413
1480
  effect: 2,
1414
1481
  maxParticles: 200,
@@ -1526,15 +1593,7 @@ var Blaster = class {
1526
1593
  return this._node;
1527
1594
  }
1528
1595
  initialize() {
1529
- invariant2(!this._canvas && !this._ctx, void 0, {
1530
- F: __dxlog_file6,
1531
- L: 142,
1532
- S: this,
1533
- A: [
1534
- "!this._canvas && !this._ctx",
1535
- ""
1536
- ]
1537
- });
1596
+ invariant2(!this._canvas && !this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 134, S: this, A: ["!this._canvas && !this._ctx", ""] });
1538
1597
  this._canvas = document.createElement("canvas");
1539
1598
  this._canvas.id = "code-blast-canvas";
1540
1599
  this._canvas.style.position = "absolute";
@@ -1563,15 +1622,7 @@ var Blaster = class {
1563
1622
  }
1564
1623
  }
1565
1624
  start() {
1566
- invariant2(this._canvas && this._ctx, void 0, {
1567
- F: __dxlog_file6,
1568
- L: 181,
1569
- S: this,
1570
- A: [
1571
- "this._canvas && this._ctx",
1572
- ""
1573
- ]
1574
- });
1625
+ invariant2(this._canvas && this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 166, S: this, A: ["this._canvas && this._ctx", ""] });
1575
1626
  this._running = true;
1576
1627
  this.loop();
1577
1628
  }
@@ -1598,11 +1649,11 @@ var Blaster = class {
1598
1649
  this.drawParticles();
1599
1650
  requestAnimationFrame(this.loop.bind(this));
1600
1651
  }
1601
- shake = throttle(({ time }) => {
1652
+ shake = throttle2(({ time }) => {
1602
1653
  this._shakeTime = this._shakeTimeMax || time;
1603
1654
  this._shakeTimeMax = time;
1604
1655
  }, 100);
1605
- spawn = throttle(({ element, point }) => {
1656
+ spawn = throttle2(({ element, point }) => {
1606
1657
  const color = getRGBComponents(element, this._options.color);
1607
1658
  const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
1608
1659
  const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
@@ -1776,11 +1827,11 @@ var blocks = () => [
1776
1827
  ".cm-line.block-line": {
1777
1828
  paddingLeft: "0.75rem",
1778
1829
  paddingRight: "0.75rem",
1779
- borderLeft: "1px solid var(--dx-subduedSeparator)",
1780
- borderRight: "1px solid var(--dx-subduedSeparator)"
1830
+ borderLeft: "1px solid var(--color-subdued-separator)",
1831
+ borderRight: "1px solid var(--color-subdued-separator)"
1781
1832
  },
1782
1833
  ".cm-line.block-single": {
1783
- border: "1px solid var(--dx-subduedSeparator)",
1834
+ border: "1px solid var(--color-subdued-separator)",
1784
1835
  borderRadius: "6px",
1785
1836
  paddingTop: "0.5rem",
1786
1837
  paddingBottom: "0.5rem",
@@ -1788,7 +1839,7 @@ var blocks = () => [
1788
1839
  marginBottom: "0.5rem"
1789
1840
  },
1790
1841
  ".cm-line.block-first": {
1791
- borderTop: "1px solid var(--dx-subduedSeparator)",
1842
+ borderTop: "1px solid var(--color-subdued-separator)",
1792
1843
  borderTopLeftRadius: "6px",
1793
1844
  borderTopRightRadius: "6px",
1794
1845
  paddingTop: "0.5rem",
@@ -1796,7 +1847,7 @@ var blocks = () => [
1796
1847
  },
1797
1848
  ".cm-line.block-middle": {},
1798
1849
  ".cm-line.block-last": {
1799
- borderBottom: "1px solid var(--dx-subduedSeparator)",
1850
+ borderBottom: "1px solid var(--color-subdued-separator)",
1800
1851
  borderBottomLeftRadius: "6px",
1801
1852
  borderBottomRightRadius: "6px",
1802
1853
  paddingBottom: "0.5rem",
@@ -1808,8 +1859,8 @@ var blocks = () => [
1808
1859
  // src/extensions/bookmarks.ts
1809
1860
  import { Prec as Prec3, StateEffect as StateEffect4, StateField as StateField2 } from "@codemirror/state";
1810
1861
  import { keymap as keymap4 } from "@codemirror/view";
1811
- import { log as log5 } from "@dxos/log";
1812
- var __dxlog_file7 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/bookmarks.ts";
1862
+ import { log as log6 } from "@dxos/log";
1863
+ var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/bookmarks.ts";
1813
1864
  var addBookmark = StateEffect4.define();
1814
1865
  var removeBookmark = StateEffect4.define();
1815
1866
  var clearBookmarks = StateEffect4.define();
@@ -1821,12 +1872,7 @@ var bookmarks = () => {
1821
1872
  key: "Mod-ArrowUp",
1822
1873
  run: (view) => {
1823
1874
  const bookmarks2 = view.state.field(bookmarksField);
1824
- log5("up", bookmarks2, {
1825
- F: __dxlog_file7,
1826
- L: 29,
1827
- S: void 0,
1828
- C: (f, a) => f(...a)
1829
- });
1875
+ log6("up", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 18, S: void 0 });
1830
1876
  return true;
1831
1877
  }
1832
1878
  },
@@ -1834,12 +1880,7 @@ var bookmarks = () => {
1834
1880
  key: "Mod-ArrowDown",
1835
1881
  run: (view) => {
1836
1882
  const bookmarks2 = view.state.field(bookmarksField);
1837
- log5("down", bookmarks2, {
1838
- F: __dxlog_file7,
1839
- L: 37,
1840
- S: void 0,
1841
- C: (f, a) => f(...a)
1842
- });
1883
+ log6("down", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 26, S: void 0 });
1843
1884
  return true;
1844
1885
  }
1845
1886
  }
@@ -1879,19 +1920,19 @@ import { invertedEffects } from "@codemirror/commands";
1879
1920
  import { StateEffect as StateEffect5, StateField as StateField3 } from "@codemirror/state";
1880
1921
  import { Decoration as Decoration7, EditorView as EditorView11, ViewPlugin as ViewPlugin10, hoverTooltip, keymap as keymap6 } from "@codemirror/view";
1881
1922
  import sortBy from "lodash.sortby";
1882
- import { debounce as debounce3 } from "@dxos/async";
1883
- import { log as log6 } from "@dxos/log";
1923
+ import { debounce as debounce2 } from "@dxos/async";
1924
+ import { log as log7 } from "@dxos/log";
1884
1925
  import { isNonNullable } from "@dxos/util";
1885
1926
 
1886
1927
  // src/extensions/selection.ts
1887
1928
  import { Transaction as Transaction3 } from "@codemirror/state";
1888
1929
  import { EditorView as EditorView10, keymap as keymap5 } from "@codemirror/view";
1889
- import { debounce as debounce2 } from "@dxos/async";
1930
+ import { debounce } from "@dxos/async";
1890
1931
  import { invariant as invariant3 } from "@dxos/invariant";
1891
1932
  import { isTruthy } from "@dxos/util";
1892
- var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/selection.ts";
1933
+ var __dxlog_file9 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/selection.ts";
1893
1934
  var documentId = singleValueFacet();
1894
- var stateRestoreAnnotation = "dxos.org/cm/state-restore";
1935
+ var stateRestoreAnnotation = "org.dxos.cm.state-restore";
1895
1936
  var createEditorStateTransaction = ({ scrollTo, selection }) => {
1896
1937
  return {
1897
1938
  selection,
@@ -1904,33 +1945,17 @@ var createEditorStateTransaction = ({ scrollTo, selection }) => {
1904
1945
  };
1905
1946
  var createEditorStateStore = (keyPrefix) => ({
1906
1947
  getState: (id) => {
1907
- invariant3(id, void 0, {
1908
- F: __dxlog_file8,
1909
- L: 47,
1910
- S: void 0,
1911
- A: [
1912
- "id",
1913
- ""
1914
- ]
1915
- });
1948
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 26, S: void 0, A: ["id", ""] });
1916
1949
  const state = localStorage.getItem(`${keyPrefix}/${id}`);
1917
1950
  return state ? JSON.parse(state) : void 0;
1918
1951
  },
1919
1952
  setState: (id, state) => {
1920
- invariant3(id, void 0, {
1921
- F: __dxlog_file8,
1922
- L: 53,
1923
- S: void 0,
1924
- A: [
1925
- "id",
1926
- ""
1927
- ]
1928
- });
1953
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 31, S: void 0, A: ["id", ""] });
1929
1954
  localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
1930
1955
  }
1931
1956
  });
1932
1957
  var selectionState = ({ getState, setState } = {}) => {
1933
- const setStateDebounced = debounce2(setState, 1e3);
1958
+ const setStateDebounced = debounce(setState, 1e3);
1934
1959
  return [
1935
1960
  // TODO(burdon): Track scrolling (currently only updates when cursor moves).
1936
1961
  // EditorView.domEventHandlers({
@@ -1977,7 +2002,7 @@ var selectionState = ({ getState, setState } = {}) => {
1977
2002
  };
1978
2003
 
1979
2004
  // src/extensions/comments.ts
1980
- var __dxlog_file9 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
2005
+ var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
1981
2006
  var setComments = StateEffect5.define();
1982
2007
  var setSelection = StateEffect5.define();
1983
2008
  var setCommentState = StateEffect5.define();
@@ -2022,14 +2047,14 @@ var commentsState = StateField3.define({
2022
2047
  var styles2 = EditorView11.theme({
2023
2048
  ".cm-comment, .cm-comment-current": {
2024
2049
  padding: "3px 0",
2025
- color: "var(--dx-cmCommentText)",
2026
- backgroundColor: "var(--dx-cmCommentSurface)"
2050
+ color: "var(--color-cm-comment-text)",
2051
+ backgroundColor: "var(--color-cm-comment-surface)"
2027
2052
  },
2028
2053
  ".cm-comment > span, .cm-comment-current > span": {
2029
2054
  boxDecorationBreak: "clone",
2030
- boxShadow: "0 0 1px 3px var(--dx-cmCommentSurface)",
2031
- backgroundColor: "var(--dx-cmCommentSurface)",
2032
- color: "var(--dx-cmCommentText)",
2055
+ boxShadow: "0 0 1px 3px var(--color-cm-comment-surface)",
2056
+ backgroundColor: "var(--color-cm-comment-surface)",
2057
+ color: "var(--color-cm-comment-text)",
2033
2058
  cursor: "pointer"
2034
2059
  }
2035
2060
  });
@@ -2047,12 +2072,7 @@ var commentsDecorations = EditorView11.decorations.compute([
2047
2072
  const decorations2 = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
2048
2073
  const range = comment.range;
2049
2074
  if (!range) {
2050
- log6.warn("Invalid range:", range, {
2051
- F: __dxlog_file9,
2052
- L: 140,
2053
- S: void 0,
2054
- C: (f, a) => f(...a)
2055
- });
2075
+ log7.warn("Invalid range:", range, { "~LogMeta": "~LogMeta", F: __dxlog_file10, L: 93, S: void 0 });
2056
2076
  return void 0;
2057
2077
  } else if (range.from === range.to) {
2058
2078
  return void 0;
@@ -2165,10 +2185,10 @@ var trackPastedComments = (onUpdate) => {
2165
2185
  const { comments: comments2 } = update2.startState.field(commentsState);
2166
2186
  const exists = comments2.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
2167
2187
  if (!exists) {
2168
- const cursor2 = Cursor.getCursorFromRange(update2.state, comment);
2188
+ const cursor = Cursor.getCursorFromRange(update2.state, comment);
2169
2189
  onUpdate({
2170
2190
  id: comment.id,
2171
- cursor: cursor2
2191
+ cursor
2172
2192
  });
2173
2193
  }
2174
2194
  }
@@ -2197,13 +2217,13 @@ var createComment = (view) => {
2197
2217
  }
2198
2218
  });
2199
2219
  }
2200
- const cursor2 = Cursor.getCursorFromRange(view.state, {
2220
+ const cursor = Cursor.getCursorFromRange(view.state, {
2201
2221
  from,
2202
2222
  to
2203
2223
  });
2204
- if (cursor2) {
2224
+ if (cursor) {
2205
2225
  options.onCreate?.({
2206
- cursor: cursor2,
2226
+ cursor,
2207
2227
  from,
2208
2228
  location: view.coordsAtPos(from)
2209
2229
  });
@@ -2214,7 +2234,7 @@ var createComment = (view) => {
2214
2234
  var optionsFacet = singleValueFacet();
2215
2235
  var comments = (options = {}) => {
2216
2236
  const { key: shortcut = "meta-'" } = options;
2217
- const handleSelect = debounce3((state) => options.onSelect?.(state), 200);
2237
+ const handleSelect = debounce2((state) => options.onSelect?.(state), 200);
2218
2238
  return [
2219
2239
  optionsFacet.of(options),
2220
2240
  options.id ? documentId.of(options.id) : void 0,
@@ -2392,9 +2412,9 @@ var createExternalCommentSync = (id, subscribe, getComments) => ViewPlugin10.fro
2392
2412
  // src/extensions/debug.ts
2393
2413
  import { syntaxTree } from "@codemirror/language";
2394
2414
  import { StateField as StateField4 } from "@codemirror/state";
2395
- var debugNodeLogger = (log11 = console.log) => {
2415
+ var debugNodeLogger = (log12 = console.log) => {
2396
2416
  const logTokens = (state) => syntaxTree(state).iterate({
2397
- enter: (node) => log11(node.type)
2417
+ enter: (node) => log12(node.type)
2398
2418
  });
2399
2419
  return StateField4.define({
2400
2420
  create: (state) => logTokens(state),
@@ -2429,8 +2449,8 @@ var dropFile = (options = {}) => {
2429
2449
  };
2430
2450
  var styles3 = EditorView12.theme({
2431
2451
  ".cm-dropCursor": {
2432
- borderLeft: "2px solid var(--dx-accentText)",
2433
- color: "var(--dx-accentText)",
2452
+ borderLeft: "2px solid var(--color-accent-text)",
2453
+ color: "var(--color-accent-text)",
2434
2454
  padding: "0 4px"
2435
2455
  },
2436
2456
  ".cm-dropCursor:after": {
@@ -2444,47 +2464,66 @@ import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap }
2444
2464
  import { HighlightStyle, bracketMatching, syntaxHighlighting } from "@codemirror/language";
2445
2465
  import { searchKeymap } from "@codemirror/search";
2446
2466
  import { EditorState } from "@codemirror/state";
2447
- import { EditorView as EditorView15, ViewPlugin as ViewPlugin11, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap7, lineNumbers, placeholder as placeholder2, scrollPastEnd } from "@codemirror/view";
2467
+ import { EditorView as EditorView16, ViewPlugin as ViewPlugin12, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap7, lineNumbers, placeholder as placeholder2 } from "@codemirror/view";
2448
2468
  import { vscodeDarkStyle, vscodeLightStyle } from "@uiw/codemirror-theme-vscode";
2449
2469
  import defaultsDeep2 from "lodash.defaultsdeep";
2450
2470
  import { generateName } from "@dxos/display-name";
2451
- import { log as log7 } from "@dxos/log";
2471
+ import { log as log8 } from "@dxos/log";
2452
2472
  import { hexToHue, isTruthy as isTruthy2 } from "@dxos/util";
2453
2473
 
2454
- // src/styles/markdown.ts
2474
+ // src/styles/theme.ts
2475
+ import { EditorView as EditorView13 } from "@codemirror/view";
2455
2476
  import { mx as mx3 } from "@dxos/ui-theme";
2456
2477
  var headings = {
2457
- 1: "text-4xl",
2458
- 2: "text-3xl",
2459
- 3: "text-2xl",
2460
- 4: "text-xl",
2461
- 5: "text-lg",
2462
- 6: ""
2478
+ 1: {
2479
+ className: "text-3xl",
2480
+ fontSize: "var(--text-3xl)",
2481
+ lineHeight: "var(--text-4xl--line-height)"
2482
+ },
2483
+ 2: {
2484
+ className: "text-2xl",
2485
+ fontSize: "var(--text-2xl)",
2486
+ lineHeight: "var(--text-3xl--line-height)"
2487
+ },
2488
+ 3: {
2489
+ className: "text-xl",
2490
+ fontSize: "var(--text-xl)",
2491
+ lineHeight: "var(--text-2xl--line-height)"
2492
+ },
2493
+ 4: {
2494
+ className: "text-lg",
2495
+ fontSize: "var(--text-lg)",
2496
+ lineHeight: "var(--text-xl--line-height)"
2497
+ },
2498
+ 5: {
2499
+ className: "text-base",
2500
+ fontSize: "var(--text-base)",
2501
+ lineHeight: "var(--text-lg--line-height)"
2502
+ },
2503
+ 6: {
2504
+ className: "text-base",
2505
+ fontSize: "var(--text-base)",
2506
+ lineHeight: "var(--text-base--line-height)"
2507
+ }
2463
2508
  };
2509
+ var fontBody = '"Inter Variable", ui-sans-serif, system-ui, sans-serif';
2510
+ var fontMono = '"JetBrains Mono Variable", ui-monospace, "Cascadia Code", "Source Code Pro", monospace';
2464
2511
  var markdownTheme = {
2465
- code: "font-mono !no-underline text-neutral-700 dark:text-neutral-300",
2466
- codeMark: "font-mono text-primary-500",
2467
- mark: "opacity-50",
2468
- heading: (level) => {
2469
- return mx3(headings[level], "dark:text-neutral-400");
2470
- }
2512
+ code: "font-mono! cm-code-inline",
2513
+ codeMark: "font-mono! cm-code-mark",
2514
+ mark: "font-mono!",
2515
+ heading: (level) => ({
2516
+ className: mx3(headings[level].className, "font-light text-(--color-cm-heading-number)"),
2517
+ color: "var(--color-cm-heading) !important",
2518
+ lineHeight: headings[level].lineHeight,
2519
+ fontSize: headings[level].fontSize,
2520
+ fontWeight: "100 !important"
2521
+ })
2471
2522
  };
2472
-
2473
- // src/styles/theme.ts
2474
- import { EditorView as EditorView13 } from "@codemirror/view";
2475
-
2476
- // src/styles/tokens.ts
2477
- import { tokens } from "@dxos/ui-theme";
2478
- import { get } from "@dxos/util";
2479
- var getToken = (path, defaultValue) => {
2480
- const value = get(tokens, path, defaultValue);
2481
- return value?.toString() ?? "";
2482
- };
2483
- var fontBody = getToken("fontFamily.body");
2484
- var fontMono = getToken("fontFamily.mono");
2485
-
2486
- // src/styles/theme.ts
2487
2523
  var baseTheme = EditorView13.baseTheme({
2524
+ /**
2525
+ * Outer frame.
2526
+ */
2488
2527
  "&": {},
2489
2528
  "&.cm-focused": {
2490
2529
  outline: "none"
@@ -2493,7 +2532,27 @@ var baseTheme = EditorView13.baseTheme({
2493
2532
  * Scroller
2494
2533
  */
2495
2534
  ".cm-scroller": {
2496
- overflowY: "auto"
2535
+ // Browser scroll-anchoring: see comment in `scroller.ts`. `auto` lets the browser pin a
2536
+ // stable element near the viewport top so widget resizes (e.g. tool-block TogglePanel
2537
+ // open/close) don't jump the user's view.
2538
+ overflowAnchor: "auto"
2539
+ },
2540
+ ".cm-scroller::-webkit-scrollbar": {
2541
+ width: "var(--scrollbar-size,8px)",
2542
+ height: "var(--scrollbar-size,8px)"
2543
+ },
2544
+ ".cm-scroller::-webkit-scrollbar-corner": {
2545
+ background: "transparent"
2546
+ },
2547
+ ".cm-scroller::-webkit-scrollbar-track": {
2548
+ background: "transparent"
2549
+ },
2550
+ ".cm-scroller::-webkit-scrollbar-thumb": {
2551
+ background: "transparent",
2552
+ transition: "background 0.15s"
2553
+ },
2554
+ "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
2555
+ background: "var(--color-scrollbar-thumb)"
2497
2556
  },
2498
2557
  /**
2499
2558
  * Content
@@ -2501,7 +2560,6 @@ var baseTheme = EditorView13.baseTheme({
2501
2560
  */
2502
2561
  ".cm-content": {
2503
2562
  padding: "unset",
2504
- lineHeight: "24px",
2505
2563
  color: "unset"
2506
2564
  },
2507
2565
  /**
@@ -2515,8 +2573,8 @@ var baseTheme = EditorView13.baseTheme({
2515
2573
  ".cm-gutter": {},
2516
2574
  ".cm-gutter.cm-lineNumbers": {
2517
2575
  paddingRight: "4px",
2518
- borderRight: "1px solid var(--dx-subduedSeparator)",
2519
- color: "var(--dx-subduedText)"
2576
+ borderRight: "1px solid var(--color-subdued-separator)",
2577
+ color: "var(--color-subdued)"
2520
2578
  },
2521
2579
  ".cm-gutter.cm-lineNumbers .cm-gutterElement": {
2522
2580
  minWidth: "40px"
@@ -2532,29 +2590,36 @@ var baseTheme = EditorView13.baseTheme({
2532
2590
  * Line.
2533
2591
  */
2534
2592
  ".cm-line": {
2535
- lineHeight: "24px",
2593
+ lineHeight: 1.5,
2536
2594
  paddingInline: 0
2537
2595
  },
2596
+ /**
2597
+ * Force all inline children to inherit line-height to prevent monospace font metrics
2598
+ * (JetBrains Mono ascent/descent) from inflating the line box beyond 24px.
2599
+ */
2600
+ ".cm-line *": {
2601
+ lineHeight: "inherit"
2602
+ },
2538
2603
  ".cm-activeLine": {
2539
- background: "var(--dx-cmActiveLine)"
2604
+ background: "var(--color-cm-active-line)"
2540
2605
  },
2541
2606
  /**
2542
2607
  * Cursor (layer).
2543
2608
  */
2544
2609
  ".cm-cursor, .cm-dropCursor": {
2545
- borderLeft: "2px solid var(--dx-cmCursor)"
2610
+ borderLeft: "2px solid var(--color-cm-cursor)"
2546
2611
  },
2547
2612
  ".cm-placeholder": {
2548
- color: "var(--dx-placeholder)"
2613
+ color: "var(--color-placeholder)"
2549
2614
  },
2550
2615
  /**
2551
2616
  * Selection (layer).
2552
2617
  */
2553
2618
  ".cm-selectionBackground": {
2554
- background: "var(--dx-cmSelection)"
2619
+ background: "var(--color-cm-selection)"
2555
2620
  },
2556
2621
  "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2557
- background: "var(--dx-cmFocusedSelection)"
2622
+ background: "var(--color-cm-focused-selection)"
2558
2623
  },
2559
2624
  /**
2560
2625
  * Search.
@@ -2564,8 +2629,8 @@ var baseTheme = EditorView13.baseTheme({
2564
2629
  margin: "0 -3px",
2565
2630
  padding: "3px",
2566
2631
  borderRadius: "3px",
2567
- background: "var(--dx-cmHighlightSurface)",
2568
- color: "var(--dx-cmHighlight)"
2632
+ background: "var(--color-cm-highlight-surface)",
2633
+ color: "var(--color-cm-highlight)"
2569
2634
  },
2570
2635
  ".cm-searchMatch-selected": {
2571
2636
  textDecoration: "underline"
@@ -2576,20 +2641,29 @@ var baseTheme = EditorView13.baseTheme({
2576
2641
  ".cm-link": {
2577
2642
  textDecorationLine: "underline",
2578
2643
  textDecorationThickness: "1px",
2579
- textDecorationColor: "var(--dx-separator)",
2644
+ textDecorationColor: "var(--color-separator)",
2580
2645
  textUnderlineOffset: "2px",
2581
2646
  borderRadius: ".125rem"
2582
2647
  },
2583
2648
  ".cm-link > span": {
2584
- color: "var(--dx-accentText)"
2649
+ color: "var(--color-accent-text)"
2650
+ },
2651
+ ".cm-link > span:hover": {
2652
+ color: "var(--color-accent-text-hover)"
2585
2653
  },
2586
2654
  /**
2587
2655
  * Tooltip.
2588
2656
  */
2589
2657
  ".cm-tooltip": {
2590
- background: "var(--dx-baseSurface)"
2658
+ background: "var(--color-modal-surface)"
2591
2659
  },
2592
2660
  ".cm-tooltip-below": {},
2661
+ ".cm-tooltip-hover": {
2662
+ background: "var(--color-modal-surface)",
2663
+ border: "1px solid var(--color-separator)",
2664
+ borderRadius: "4px",
2665
+ overflow: "hidden"
2666
+ },
2593
2667
  /**
2594
2668
  * Autocomplete.
2595
2669
  * https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
@@ -2597,7 +2671,7 @@ var baseTheme = EditorView13.baseTheme({
2597
2671
  ".cm-tooltip.cm-tooltip-autocomplete": {
2598
2672
  marginTop: "6px",
2599
2673
  marginLeft: "-10px",
2600
- border: "2px solid var(--dx-separator)",
2674
+ border: "2px solid var(--color-separator)",
2601
2675
  borderRadius: "4px"
2602
2676
  },
2603
2677
  ".cm-tooltip.cm-tooltip-autocomplete > ul": {
@@ -2607,12 +2681,12 @@ var baseTheme = EditorView13.baseTheme({
2607
2681
  padding: "4px"
2608
2682
  },
2609
2683
  ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {
2610
- background: "var(--dx-activeSurface)",
2611
- color: "var(--dx-activeSurfaceText)"
2684
+ background: "var(--color-active-surface)",
2685
+ color: "var(--color-base-surface-text)"
2612
2686
  },
2613
2687
  ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
2614
2688
  paddingLeft: "4px !important",
2615
- color: "var(--dx-hoverSurfaceText)"
2689
+ color: "var(--color-base-surface-text)"
2616
2690
  },
2617
2691
  /**
2618
2692
  * Completion info.
@@ -2621,17 +2695,17 @@ var baseTheme = EditorView13.baseTheme({
2621
2695
  width: "360px !important",
2622
2696
  margin: "-10px 1px 0 1px",
2623
2697
  padding: "8px !important",
2624
- borderColor: "var(--dx-separator)"
2698
+ borderColor: "var(--color-separator)"
2625
2699
  },
2626
2700
  ".cm-completionIcon": {
2627
2701
  display: "none"
2628
2702
  },
2629
2703
  ".cm-completionLabel": {
2630
- color: "var(--dx-description)",
2704
+ color: "var(--color-description)",
2631
2705
  padding: "0 4px"
2632
2706
  },
2633
2707
  ".cm-completionMatchedText": {
2634
- color: "var(--dx-baseText)",
2708
+ color: "var(--color-base-surface-text)",
2635
2709
  textDecoration: "none !important"
2636
2710
  },
2637
2711
  /**
@@ -2655,7 +2729,7 @@ var baseTheme = EditorView13.baseTheme({
2655
2729
  backgroundColor: "var(--surface-bg)"
2656
2730
  },
2657
2731
  ".cm-panel input, .cm-panel button, .cm-panel label": {
2658
- color: "var(--dx-subdued)",
2732
+ color: "var(--color-subdued)",
2659
2733
  fontSize: "14px",
2660
2734
  all: "unset",
2661
2735
  margin: "3px !important",
@@ -2663,10 +2737,10 @@ var baseTheme = EditorView13.baseTheme({
2663
2737
  outline: "1px solid transparent"
2664
2738
  },
2665
2739
  ".cm-panel input, .cm-panel button": {
2666
- backgroundColor: "var(--dx-inputSurface)"
2740
+ backgroundColor: "var(--color-input-surface)"
2667
2741
  },
2668
2742
  ".cm-panel input:focus, .cm-panel button:focus": {
2669
- outline: "1px solid var(--dx-neutralFocusIndicator)"
2743
+ outline: "1px solid var(--color-neutral-focus-indicator)"
2670
2744
  },
2671
2745
  ".cm-panel label": {
2672
2746
  display: "inline-flex",
@@ -2679,33 +2753,33 @@ var baseTheme = EditorView13.baseTheme({
2679
2753
  height: "8px",
2680
2754
  marginRight: "6px !important",
2681
2755
  padding: "2px !important",
2682
- color: "var(--dx-neutralFocusIndicator)"
2756
+ color: "var(--color-neutral-focus-indicator)"
2683
2757
  },
2684
2758
  ".cm-panel button": {
2685
2759
  "&:hover": {
2686
- backgroundColor: "var(--dx-accentSurfaceHover) !important"
2760
+ // TODO(burdon): Replace with layer and @apply bg-accent-surface-hover
2761
+ backgroundColor: "var(--color-accent-surface-hover) !important"
2687
2762
  },
2688
2763
  "&:active": {
2689
- backgroundColor: "var(--dx-accentSurfaceHover)"
2764
+ backgroundColor: "var(--color-accent-surface-hover)"
2690
2765
  }
2691
2766
  },
2692
2767
  ".cm-panel.cm-search": {
2693
2768
  padding: "4px",
2694
- borderTop: "1px solid var(--dx-separator)"
2769
+ borderTop: "1px solid var(--color-separator)"
2695
2770
  }
2696
2771
  });
2697
2772
  var editorGutter = EditorView13.theme({
2698
2773
  ".cm-gutters": {
2699
2774
  // NOTE: Non-transparent background required to cover content if scrolling horizontally.
2700
- background: "var(--dx-baseSurface) !important",
2775
+ background: "var(--color-base-surface) !important",
2701
2776
  paddingRight: "1rem"
2702
2777
  }
2703
2778
  });
2704
2779
  var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2705
- // Set metrics on the scroller (this is often what CM uses for layout).
2780
+ // Main content.
2706
2781
  ".cm-scroller": {
2707
- fontFamily: monospace ? fontMono : fontBody,
2708
- fontSize: "16px"
2782
+ fontFamily: monospace ? fontMono : fontBody
2709
2783
  },
2710
2784
  // Maintain defaults for UI components.
2711
2785
  ".cm-content, .cm-gutters, .cm-panel": {
@@ -2745,9 +2819,32 @@ var focus = [
2745
2819
  })
2746
2820
  ];
2747
2821
 
2822
+ // src/extensions/scroll-past-end.ts
2823
+ import { EditorView as EditorView15, ViewPlugin as ViewPlugin11 } from "@codemirror/view";
2824
+ var scrollPastEndPlugin = ViewPlugin11.fromClass(class {
2825
+ height = 1e3;
2826
+ attrs = {
2827
+ style: "padding-bottom: 1000px"
2828
+ };
2829
+ update({ view }) {
2830
+ const lastLineBlock = view.lineBlockAt(view.state.doc.length);
2831
+ const height = view.dom.clientHeight - lastLineBlock.height - view.documentPadding.top - 0.5;
2832
+ if (height >= 0 && height !== this.height) {
2833
+ this.height = height;
2834
+ this.attrs = {
2835
+ style: `padding-bottom: ${height}px`
2836
+ };
2837
+ }
2838
+ }
2839
+ });
2840
+ var scrollPastEnd = () => [
2841
+ scrollPastEndPlugin,
2842
+ EditorView15.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?.attrs ?? null)
2843
+ ];
2844
+
2748
2845
  // src/extensions/factories.ts
2749
- var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/factories.ts";
2750
- var tabbable = EditorView15.contentAttributes.of({
2846
+ var __dxlog_file11 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/factories.ts";
2847
+ var tabbable = EditorView16.contentAttributes.of({
2751
2848
  tabindex: "0"
2752
2849
  });
2753
2850
  var filterChars = (chars) => {
@@ -2800,13 +2897,8 @@ var createBasicExtensions = (propsProp) => {
2800
2897
  const props = defaultsDeep2({}, propsProp, defaultBasicOptions);
2801
2898
  return [
2802
2899
  // NOTE: Doesn't catch errors in keymap functions.
2803
- EditorView15.exceptionSink.of((err) => {
2804
- log7.catch(err, void 0, {
2805
- F: __dxlog_file10,
2806
- L: 132,
2807
- S: void 0,
2808
- C: (f, a) => f(...a)
2809
- });
2900
+ EditorView16.exceptionSink.of((err) => {
2901
+ log8.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file11, L: 79, S: void 0 });
2810
2902
  }),
2811
2903
  props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
2812
2904
  props.bracketMatching && bracketMatching(),
@@ -2815,7 +2907,7 @@ var createBasicExtensions = (propsProp) => {
2815
2907
  props.drawSelection && drawSelection({
2816
2908
  cursorBlinkRate: 1200
2817
2909
  }),
2818
- props.editable !== void 0 && EditorView15.editable.of(props.editable),
2910
+ props.editable !== void 0 && EditorView16.editable.of(props.editable),
2819
2911
  props.focus && focus,
2820
2912
  props.highlightActiveLine && highlightActiveLine(),
2821
2913
  props.history && history(),
@@ -2823,9 +2915,16 @@ var createBasicExtensions = (propsProp) => {
2823
2915
  lineNumbers(),
2824
2916
  editorGutter
2825
2917
  ],
2826
- props.lineWrapping && EditorView15.lineWrapping,
2918
+ props.lineWrapping && EditorView16.lineWrapping,
2827
2919
  props.placeholder && placeholder2(props.placeholder),
2828
2920
  props.readOnly !== void 0 && EditorState.readOnly.of(props.readOnly),
2921
+ // `EditorState.readOnly` is advisory — CodeMirror doesn't auto-reject doc-changing
2922
+ // transactions. Some extensions (e.g. `@codemirror/lang-markdown`'s Enter handler that
2923
+ // continues a list) dispatch programmatic edits regardless. Drop user-initiated edits
2924
+ // (`input` / `delete` keymap dispatches plus `undo` / `redo` from the history extension)
2925
+ // but pass programmatic dispatches — streaming `MarkdownStream` and similar consumers
2926
+ // depend on being able to populate the doc themselves.
2927
+ props.readOnly && EditorState.transactionFilter.of((tr) => tr.docChanged && (tr.isUserEvent("input") || tr.isUserEvent("delete") || tr.isUserEvent("undo") || tr.isUserEvent("redo")) ? [] : tr),
2829
2928
  props.scrollPastEnd && scrollPastEnd(),
2830
2929
  props.tabbable && tabbable,
2831
2930
  props.tabSize && EditorState.tabSize.of(props.tabSize),
@@ -2855,12 +2954,12 @@ var createBasicExtensions = (propsProp) => {
2855
2954
  };
2856
2955
  var grow = {
2857
2956
  editor: {
2858
- className: "bs-full is-full"
2957
+ className: "h-full w-full"
2859
2958
  }
2860
2959
  };
2861
2960
  var fullWidth = {
2862
2961
  editor: {
2863
- className: "is-full"
2962
+ className: "w-full"
2864
2963
  }
2865
2964
  };
2866
2965
  var defaultThemeSlots = grow;
@@ -2868,24 +2967,29 @@ var defaultStyles = {
2868
2967
  dark: vscodeDarkStyle,
2869
2968
  light: vscodeLightStyle
2870
2969
  };
2871
- var createThemeExtensions = ({ monospace, themeMode, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp } = {}) => {
2970
+ var createThemeExtensions = ({ monospace, scrollbarThin, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp, themeMode } = {}) => {
2872
2971
  const slots = defaultsDeep2({}, slotsProp, defaultThemeSlots);
2873
2972
  return [
2874
2973
  baseTheme,
2875
- EditorView15.darkTheme.of(themeMode === "dark"),
2974
+ EditorView16.darkTheme.of(themeMode === "dark"),
2876
2975
  createFontTheme({
2877
2976
  monospace
2878
2977
  }),
2879
2978
  syntaxHighlightingProp && syntaxHighlighting(HighlightStyle.define(themeMode === "dark" ? defaultStyles.dark : defaultStyles.light)),
2880
- slots.editor?.className && EditorView15.editorAttributes.of({
2979
+ slots.editor?.className && EditorView16.editorAttributes.of({
2881
2980
  class: slots.editor.className
2882
2981
  }),
2883
- slots.content?.className && EditorView15.contentAttributes.of({
2982
+ slots.content?.className && EditorView16.contentAttributes.of({
2884
2983
  class: slots.content.className
2885
2984
  }),
2886
- slots.scroll?.className && ViewPlugin11.fromClass(class {
2985
+ (slots.scroller?.className || scrollbarThin) && ViewPlugin12.fromClass(class {
2887
2986
  constructor(view) {
2888
- view.scrollDOM.classList.add(...slots.scroll.className.split(/\s+/));
2987
+ if (slots.scroller?.className) {
2988
+ view.scrollDOM.classList.add(...slots.scroller.className.split(/\s+/));
2989
+ }
2990
+ if (scrollbarThin) {
2991
+ view.scrollDOM.style.setProperty("--scrollbar-size", "4px");
2992
+ }
2889
2993
  }
2890
2994
  })
2891
2995
  ].filter(isTruthy2);
@@ -2903,8 +3007,8 @@ var createDataExtensions = ({ id, text, messenger, identity }) => {
2903
3007
  channel: `awareness.${id}`,
2904
3008
  peerId: identity.identityKey.toHex(),
2905
3009
  info: {
2906
- darkColor: `var(--dx-${hue}Cursor)`,
2907
- lightColor: `var(--dx-${hue}Cursor)`,
3010
+ darkColor: `var(--color-${hue}-border)`,
3011
+ lightColor: `var(--color-${hue}-border)`,
2908
3012
  displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex())
2909
3013
  }
2910
3014
  })));
@@ -2914,7 +3018,7 @@ var createDataExtensions = ({ id, text, messenger, identity }) => {
2914
3018
 
2915
3019
  // src/extensions/folding.ts
2916
3020
  import { codeFolding, foldGutter } from "@codemirror/language";
2917
- import { EditorView as EditorView16 } from "@codemirror/view";
3021
+ import { EditorView as EditorView17 } from "@codemirror/view";
2918
3022
  import { Domino as Domino2, mx as mx4 } from "@dxos/ui";
2919
3023
  var folding = () => {
2920
3024
  return [
@@ -2922,13 +3026,14 @@ var folding = () => {
2922
3026
  placeholderDOM: () => Domino2.of("span").root
2923
3027
  }),
2924
3028
  foldGutter({
3029
+ // NOTE: We can't animate since the element is remounted on state change.
2925
3030
  markerDOM: (open) => {
2926
- return Domino2.of("div").classNames("flex bs-full justify-center items-center").children(Domino2.of("svg", Domino2.SVG).classNames(mx4("is-4 bs-4 cursor-pointer", open && "rotate-90")).children(Domino2.of("use", Domino2.SVG).attributes({
3031
+ 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({
2927
3032
  href: Domino2.icon("ph--caret-right--regular")
2928
3033
  }))).root;
2929
3034
  }
2930
3035
  }),
2931
- EditorView16.theme({
3036
+ EditorView17.theme({
2932
3037
  ".cm-foldGutter": {
2933
3038
  opacity: 0.3,
2934
3039
  transition: "opacity 0.3s",
@@ -2942,7 +3047,7 @@ var folding = () => {
2942
3047
  };
2943
3048
 
2944
3049
  // src/extensions/hashtag.ts
2945
- import { Decoration as Decoration8, EditorView as EditorView17, MatchDecorator, ViewPlugin as ViewPlugin12, WidgetType as WidgetType4 } from "@codemirror/view";
3050
+ import { Decoration as Decoration8, EditorView as EditorView18, MatchDecorator, ViewPlugin as ViewPlugin13, WidgetType as WidgetType4 } from "@codemirror/view";
2946
3051
  import { getHashStyles, mx as mx5 } from "@dxos/ui-theme";
2947
3052
  var TagWidget = class extends WidgetType4 {
2948
3053
  _text;
@@ -2963,7 +3068,7 @@ var tagMatcher = new MatchDecorator({
2963
3068
  })
2964
3069
  });
2965
3070
  var hashtag = () => [
2966
- ViewPlugin12.fromClass(class {
3071
+ ViewPlugin13.fromClass(class {
2967
3072
  tags;
2968
3073
  constructor(view) {
2969
3074
  this.tags = tagMatcher.createDeco(view);
@@ -2973,11 +3078,11 @@ var hashtag = () => [
2973
3078
  }
2974
3079
  }, {
2975
3080
  decorations: (instance) => instance.tags,
2976
- provide: (plugin) => EditorView17.atomicRanges.of((view) => {
3081
+ provide: (plugin) => EditorView18.atomicRanges.of((view) => {
2977
3082
  return view.plugin(plugin)?.tags || Decoration8.none;
2978
3083
  })
2979
3084
  }),
2980
- EditorView17.theme({
3085
+ EditorView18.theme({
2981
3086
  ".cm-tag": {
2982
3087
  borderRadius: "4px",
2983
3088
  marginRight: "6px",
@@ -3032,18 +3137,18 @@ var schemaLinter = (validate) => (view) => {
3032
3137
  };
3033
3138
 
3034
3139
  // src/extensions/listener.ts
3035
- import { EditorView as EditorView18 } from "@codemirror/view";
3140
+ import { EditorView as EditorView19 } from "@codemirror/view";
3036
3141
  import { isNonNullable as isNonNullable2 } from "@dxos/util";
3037
3142
  var listener = ({ onFocus, onChange }) => {
3038
3143
  return [
3039
- onFocus && EditorView18.focusChangeEffect.of((state, focusing) => {
3144
+ onFocus && EditorView19.focusChangeEffect.of((state, focusing) => {
3040
3145
  onFocus({
3041
3146
  id: state.facet(documentId),
3042
3147
  focusing
3043
3148
  });
3044
3149
  return null;
3045
3150
  }),
3046
- onChange && EditorView18.updateListener.of(({ state, docChanged }) => {
3151
+ onChange && EditorView19.updateListener.of(({ state, docChanged }) => {
3047
3152
  if (docChanged) {
3048
3153
  onChange({
3049
3154
  id: state.facet(documentId),
@@ -3058,7 +3163,7 @@ var listener = ({ onFocus, onChange }) => {
3058
3163
  import { snippet } from "@codemirror/autocomplete";
3059
3164
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
3060
3165
  import { EditorSelection as EditorSelection2 } from "@codemirror/state";
3061
- import { EditorView as EditorView19, keymap as keymap8 } from "@codemirror/view";
3166
+ import { EditorView as EditorView20, keymap as keymap8 } from "@codemirror/view";
3062
3167
  import { debounceAndThrottle } from "@dxos/async";
3063
3168
  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;
3064
3169
  var Inline = /* @__PURE__ */ (function(Inline2) {
@@ -4147,7 +4252,7 @@ var getFormatting = (state) => {
4147
4252
  };
4148
4253
  };
4149
4254
  var formattingListener = (onStateChange, delay = 100) => {
4150
- return EditorView19.updateListener.of(debounceAndThrottle((update2) => {
4255
+ return EditorView20.updateListener.of(debounceAndThrottle((update2) => {
4151
4256
  if (update2.docChanged || update2.selectionSet) {
4152
4257
  onStateChange(getFormatting(update2.state));
4153
4258
  }
@@ -4208,8 +4313,7 @@ import { completionKeymap } from "@codemirror/autocomplete";
4208
4313
  import { defaultKeymap as defaultKeymap2, indentWithTab as indentWithTab2 } from "@codemirror/commands";
4209
4314
  import { jsonLanguage } from "@codemirror/lang-json";
4210
4315
  import { markdown, markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
4211
- import { xml } from "@codemirror/lang-xml";
4212
- import { LanguageDescription, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4316
+ import { foldNodeProp, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4213
4317
  import { languages } from "@codemirror/language-data";
4214
4318
  import { keymap as keymap9 } from "@codemirror/view";
4215
4319
  import { isTruthy as isTruthy3 } from "@dxos/util";
@@ -4330,30 +4434,38 @@ var markdownHighlightStyle = (_options = {}) => {
4330
4434
  ],
4331
4435
  class: "font-mono"
4332
4436
  },
4333
- // Headings.
4437
+ // Headings — use CSS properties only (no class:) so CodeMirror generates scoped CSS via
4438
+ // StyleModule that overrides vscodeDarkStyle's t.heading rule. When class: is present,
4439
+ // HighlightStyle silently ignores all other CSS properties (they're mutually exclusive).
4440
+ // Font sizes use Tailwind v4 CSS variables so nothing is hardcoded.
4441
+ {
4442
+ tag: tags.heading,
4443
+ color: "var(--color-cm-heading) !important",
4444
+ fontWeight: "300"
4445
+ },
4334
4446
  {
4335
4447
  tag: tags.heading1,
4336
- class: markdownTheme.heading(1)
4448
+ ...markdownTheme.heading(1)
4337
4449
  },
4338
4450
  {
4339
4451
  tag: tags.heading2,
4340
- class: markdownTheme.heading(2)
4452
+ ...markdownTheme.heading(2)
4341
4453
  },
4342
4454
  {
4343
4455
  tag: tags.heading3,
4344
- class: markdownTheme.heading(3)
4456
+ ...markdownTheme.heading(3)
4345
4457
  },
4346
4458
  {
4347
4459
  tag: tags.heading4,
4348
- class: markdownTheme.heading(4)
4460
+ ...markdownTheme.heading(4)
4349
4461
  },
4350
4462
  {
4351
4463
  tag: tags.heading5,
4352
- class: markdownTheme.heading(5)
4464
+ ...markdownTheme.heading(5)
4353
4465
  },
4354
4466
  {
4355
4467
  tag: tags.heading6,
4356
- class: markdownTheme.heading(6)
4468
+ ...markdownTheme.heading(6)
4357
4469
  },
4358
4470
  // Emphasis.
4359
4471
  {
@@ -4408,15 +4520,23 @@ var createMarkdownExtensions = (options = {}) => {
4408
4520
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
4409
4521
  base: markdownLanguage2,
4410
4522
  // Languages for syntax highlighting fenced code blocks.
4523
+ // Caller-supplied languages are checked first so they can override defaults.
4411
4524
  defaultCodeLanguage: jsonLanguage,
4412
- codeLanguages: languages,
4525
+ codeLanguages: [
4526
+ ...options.codeLanguages ?? [],
4527
+ ...languages
4528
+ ],
4413
4529
  // Don't complete HTML tags.
4414
4530
  completeHTMLTags: false,
4415
4531
  // Parser extensions.
4416
4532
  extensions: [
4417
4533
  // GFM provided by default.
4418
4534
  markdownTagsExtensions,
4419
- ...options.extensions ?? defaultExtensions()
4535
+ ...options.extensions ?? defaultExtensions(),
4536
+ // Disable folding for fenced code blocks by overriding foldNodeProp.
4537
+ // Note: returning null from foldService does not prevent syntaxFolding fallback,
4538
+ // so we must override the node prop directly on the FencedCode node type.
4539
+ noFencedCodeFolding
4420
4540
  ]
4421
4541
  }),
4422
4542
  // Custom styles.
@@ -4431,18 +4551,13 @@ var createMarkdownExtensions = (options = {}) => {
4431
4551
  ].filter(isTruthy3))
4432
4552
  ];
4433
4553
  };
4434
- var xmlLanguageDesc = LanguageDescription.of({
4435
- name: "xml",
4436
- alias: [
4437
- "html",
4438
- "xhtml"
4439
- ],
4440
- extensions: [
4441
- "xml",
4442
- "xhtml"
4443
- ],
4444
- load: async () => xml()
4445
- });
4554
+ var noFencedCodeFolding = {
4555
+ props: [
4556
+ foldNodeProp.add({
4557
+ FencedCode: () => null
4558
+ })
4559
+ ]
4560
+ };
4446
4561
  var defaultExtensions = () => [
4447
4562
  noSetExtHeading,
4448
4563
  noHtml
@@ -4462,19 +4577,19 @@ var debugTree = (cb) => StateField6.define({
4462
4577
  update: (value, tr) => cb(convertTreeToJson(tr.state))
4463
4578
  });
4464
4579
  var convertTreeToJson = (state) => {
4465
- const treeToJson = (cursor2) => {
4580
+ const treeToJson = (cursor) => {
4466
4581
  const node = {
4467
- type: cursor2.type.name,
4468
- from: cursor2.from,
4469
- to: cursor2.to,
4470
- text: state.doc.slice(cursor2.from, cursor2.to).toString(),
4582
+ type: cursor.type.name,
4583
+ from: cursor.from,
4584
+ to: cursor.to,
4585
+ text: state.doc.slice(cursor.from, cursor.to).toString(),
4471
4586
  children: []
4472
4587
  };
4473
- if (cursor2.firstChild()) {
4588
+ if (cursor.firstChild()) {
4474
4589
  do {
4475
- node.children.push(treeToJson(cursor2));
4476
- } while (cursor2.nextSibling());
4477
- cursor2.parent();
4590
+ node.children.push(treeToJson(cursor));
4591
+ } while (cursor.nextSibling());
4592
+ cursor.parent();
4478
4593
  }
4479
4594
  return node;
4480
4595
  };
@@ -4484,16 +4599,15 @@ var convertTreeToJson = (state) => {
4484
4599
  // src/extensions/markdown/decorate.ts
4485
4600
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
4486
4601
  import { Prec as Prec4, RangeSetBuilder as RangeSetBuilder5, StateEffect as StateEffect7 } from "@codemirror/state";
4487
- import { Decoration as Decoration11, EditorView as EditorView23, ViewPlugin as ViewPlugin14, WidgetType as WidgetType7 } from "@codemirror/view";
4602
+ import { Decoration as Decoration11, EditorView as EditorView24, ViewPlugin as ViewPlugin15, WidgetType as WidgetType7 } from "@codemirror/view";
4488
4603
  import { invariant as invariant4 } from "@dxos/invariant";
4489
- import { mx as mx6 } from "@dxos/ui-theme";
4490
4604
 
4491
4605
  // src/extensions/markdown/changes.ts
4492
4606
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
4493
4607
  import { Transaction as Transaction4 } from "@codemirror/state";
4494
- import { ViewPlugin as ViewPlugin13 } from "@codemirror/view";
4608
+ import { ViewPlugin as ViewPlugin14 } from "@codemirror/view";
4495
4609
  var adjustChanges = () => {
4496
- return ViewPlugin13.fromClass(class {
4610
+ return ViewPlugin14.fromClass(class {
4497
4611
  update(update2) {
4498
4612
  const tree = syntaxTree4(update2.state);
4499
4613
  const adjustments = [];
@@ -4635,7 +4749,7 @@ var getValidUrl = (str) => {
4635
4749
  // src/extensions/markdown/image.ts
4636
4750
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
4637
4751
  import { StateField as StateField7 } from "@codemirror/state";
4638
- import { Decoration as Decoration9, EditorView as EditorView20, WidgetType as WidgetType5 } from "@codemirror/view";
4752
+ import { Decoration as Decoration9, EditorView as EditorView21, WidgetType as WidgetType5 } from "@codemirror/view";
4639
4753
  var image = (_options = {}) => {
4640
4754
  return [
4641
4755
  StateField7.define({
@@ -4646,10 +4760,10 @@ var image = (_options = {}) => {
4646
4760
  if (!tr.docChanged && !tr.selection) {
4647
4761
  return value;
4648
4762
  }
4649
- const cursor2 = tr.state.selection.main.head;
4763
+ const cursor = tr.state.selection.main.head;
4650
4764
  const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
4651
- let from = Math.min(cursor2, oldCursor);
4652
- let to = Math.max(cursor2, oldCursor);
4765
+ let from = Math.min(cursor, oldCursor);
4766
+ let to = Math.max(cursor, oldCursor);
4653
4767
  tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
4654
4768
  from = Math.min(from, fromB);
4655
4769
  to = Math.max(to, toB);
@@ -4663,19 +4777,19 @@ var image = (_options = {}) => {
4663
4777
  add: buildDecorations(tr.state, from, to)
4664
4778
  });
4665
4779
  },
4666
- provide: (field) => EditorView20.decorations.from(field)
4780
+ provide: (field) => EditorView21.decorations.from(field)
4667
4781
  })
4668
4782
  ];
4669
4783
  };
4670
4784
  var buildDecorations = (state, from, to) => {
4671
4785
  const decorations2 = [];
4672
- const cursor2 = state.selection.main.head;
4786
+ const cursor = state.selection.main.head;
4673
4787
  syntaxTree5(state).iterate({
4674
4788
  enter: (node) => {
4675
4789
  if (node.name === "Image") {
4676
4790
  const urlNode = node.node.getChild("URL");
4677
4791
  if (urlNode) {
4678
- const hide2 = state.readOnly || cursor2 < node.from || cursor2 > node.to || !state.field(focusField);
4792
+ const hide2 = state.readOnly || cursor < node.from || cursor > node.to || !state.field(focusField);
4679
4793
  const url = state.sliceDoc(urlNode.from, urlNode.to);
4680
4794
  if (url.match(/^https?:\/\//) === null && url.match(/^file?:\/\//) === null) {
4681
4795
  return;
@@ -4723,10 +4837,10 @@ var ImageWidget = class extends WidgetType5 {
4723
4837
  };
4724
4838
 
4725
4839
  // src/extensions/markdown/styles.ts
4726
- import { EditorView as EditorView21 } from "@codemirror/view";
4840
+ import { EditorView as EditorView22 } from "@codemirror/view";
4727
4841
  var bulletListIndentationWidth = 24;
4728
4842
  var orderedListIndentationWidth = 36;
4729
- var formattingStyles = EditorView21.theme({
4843
+ var formattingStyles = EditorView22.theme({
4730
4844
  /**
4731
4845
  * Horizontal rule.
4732
4846
  */
@@ -4735,7 +4849,7 @@ var formattingStyles = EditorView21.theme({
4735
4849
  width: "100%",
4736
4850
  height: "0",
4737
4851
  verticalAlign: "middle",
4738
- borderTop: "1px solid var(--dx-cmSeparator)",
4852
+ borderTop: "1px solid var(--color-cm-separator)",
4739
4853
  opacity: 0.5
4740
4854
  },
4741
4855
  /**
@@ -4758,19 +4872,44 @@ var formattingStyles = EditorView21.theme({
4758
4872
  * Blockquote.
4759
4873
  */
4760
4874
  "& .cm-blockquote": {
4761
- background: "var(--dx-cmCodeblock)",
4762
- borderLeft: "2px solid var(--dx-cmSeparator)",
4875
+ background: "var(--color-cm-codeblock)",
4876
+ borderLeft: "2px solid var(--color-cm-separator)",
4763
4877
  paddingLeft: "1rem",
4764
- margin: "0"
4878
+ margin: 0
4765
4879
  },
4766
4880
  /**
4767
4881
  * Code and codeblocks.
4768
4882
  */
4883
+ "& code": {
4884
+ fontFamily: fontMono,
4885
+ color: "var(--color-cm-code)",
4886
+ whiteSpace: "nowrap"
4887
+ },
4769
4888
  "& .cm-code": {
4770
- fontFamily: fontMono
4889
+ fontFamily: fontMono,
4890
+ color: "var(--color-cm-code)"
4891
+ },
4892
+ // Inline code spans (triggered by backticks) use `cm-code-inline` + `font-mono`.
4893
+ // Different monospace font metrics can slightly overflow the fixed CodeMirror line box,
4894
+ // so constrain them to the target 24px height.
4895
+ "& .cm-code-inline": {
4896
+ fontFamily: fontMono,
4897
+ height: "24px",
4898
+ // display: 'inline-flex',
4899
+ alignItems: "center",
4900
+ overflow: "hidden",
4901
+ whiteSpace: "nowrap",
4902
+ color: "var(--color-cm-code-inline)"
4903
+ },
4904
+ "& .cm-code-mark": {
4905
+ fontFamily: fontMono,
4906
+ height: "24px",
4907
+ display: "inline-flex",
4908
+ alignItems: "center",
4909
+ overflow: "hidden"
4771
4910
  },
4772
4911
  "& .cm-codeblock-line": {
4773
- background: "var(--dx-cmCodeblock)",
4912
+ background: "var(--color-cm-codeblock)",
4774
4913
  paddingInline: "1rem !important"
4775
4914
  },
4776
4915
  "& .cm-codeblock-start": {
@@ -4799,16 +4938,24 @@ var formattingStyles = EditorView21.theme({
4799
4938
  */
4800
4939
  ".cm-table *": {
4801
4940
  fontFamily: fontMono,
4941
+ lineHeight: 1.5,
4802
4942
  textDecoration: "none !important"
4803
4943
  },
4804
4944
  ".cm-table-head": {
4805
4945
  padding: "2px 16px 2px 0px",
4946
+ overflowWrap: "break-word",
4947
+ whiteSpace: "pre-wrap",
4948
+ wordBreak: "keep-all",
4806
4949
  textAlign: "left",
4807
- borderBottom: "1px solid var(--dx-cmSeparator)",
4808
- color: "var(--dx-subdued)"
4950
+ color: "var(--color-subdued)",
4951
+ borderBottom: "1px solid var(--color-cm-separator)"
4809
4952
  },
4810
4953
  ".cm-table-cell": {
4811
- padding: "2px 16px 2px 0px"
4954
+ padding: "2px 16px 2px 0px",
4955
+ overflowWrap: "break-word",
4956
+ whiteSpace: "pre-wrap",
4957
+ wordBreak: "keep-all",
4958
+ verticalAlign: "top"
4812
4959
  },
4813
4960
  /**
4814
4961
  * Image.
@@ -4824,12 +4971,12 @@ var formattingStyles = EditorView21.theme({
4824
4971
  },
4825
4972
  ".cm-image-with-loader": {
4826
4973
  display: "block",
4827
- opacity: "0",
4974
+ opacity: 0,
4828
4975
  transitionDuration: "350ms",
4829
4976
  transitionProperty: "opacity"
4830
4977
  },
4831
4978
  ".cm-image-with-loader.cm-loaded-image": {
4832
- opacity: "1"
4979
+ opacity: 1
4833
4980
  },
4834
4981
  ".cm-image-wrapper": {
4835
4982
  "grid-template-columns": "1fr",
@@ -4848,17 +4995,17 @@ var formattingStyles = EditorView21.theme({
4848
4995
  // src/extensions/markdown/table.ts
4849
4996
  import { syntaxTree as syntaxTree6 } from "@codemirror/language";
4850
4997
  import { RangeSetBuilder as RangeSetBuilder4, StateField as StateField8 } from "@codemirror/state";
4851
- import { Decoration as Decoration10, EditorView as EditorView22, WidgetType as WidgetType6 } from "@codemirror/view";
4998
+ import { Decoration as Decoration10, EditorView as EditorView23, WidgetType as WidgetType6 } from "@codemirror/view";
4852
4999
  var table = (options = {}) => {
4853
5000
  return StateField8.define({
4854
5001
  create: (state) => update(state, options),
4855
5002
  update: (_, tr) => update(tr.state, options),
4856
- provide: (field) => EditorView22.decorations.from(field)
5003
+ provide: (field) => EditorView23.decorations.from(field)
4857
5004
  });
4858
5005
  };
4859
5006
  var update = (state, _options) => {
4860
5007
  const builder = new RangeSetBuilder4();
4861
- const cursor2 = state.selection.main.head;
5008
+ const cursor = state.selection.main.head;
4862
5009
  const tables = [];
4863
5010
  const getTable = () => tables[tables.length - 1];
4864
5011
  const getRow = () => {
@@ -4896,7 +5043,7 @@ var update = (state, _options) => {
4896
5043
  }
4897
5044
  });
4898
5045
  tables.forEach((table2) => {
4899
- const replace = state.readOnly || cursor2 < table2.from || cursor2 > table2.to;
5046
+ const replace = state.readOnly || cursor < table2.from || cursor > table2.to;
4900
5047
  if (replace) {
4901
5048
  builder.add(table2.from, table2.to, Decoration10.replace({
4902
5049
  block: true,
@@ -4910,9 +5057,29 @@ var update = (state, _options) => {
4910
5057
  });
4911
5058
  return builder.finish();
4912
5059
  };
4913
- var TableWidget = class extends WidgetType6 {
4914
- _table;
4915
- constructor(_table) {
5060
+ var renderCellContent = (el, text) => {
5061
+ const parts = text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*|__[^_\n]+__|\*[^*\n]+\*|_[^_\n]+_)/);
5062
+ for (const part of parts) {
5063
+ if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
5064
+ const code = document.createElement("code");
5065
+ code.textContent = part.slice(1, -1);
5066
+ el.appendChild(code);
5067
+ } else if (part.startsWith("**") && part.endsWith("**") || part.startsWith("__") && part.endsWith("__")) {
5068
+ const strong = document.createElement("strong");
5069
+ strong.textContent = part.slice(2, -2);
5070
+ el.appendChild(strong);
5071
+ } else if (part.startsWith("*") && part.endsWith("*") || part.startsWith("_") && part.endsWith("_")) {
5072
+ const em = document.createElement("em");
5073
+ em.textContent = part.slice(1, -1);
5074
+ el.appendChild(em);
5075
+ } else {
5076
+ el.appendChild(document.createTextNode(part));
5077
+ }
5078
+ }
5079
+ };
5080
+ var TableWidget = class extends WidgetType6 {
5081
+ _table;
5082
+ constructor(_table) {
4916
5083
  super(), this._table = _table;
4917
5084
  }
4918
5085
  eq(other) {
@@ -4929,7 +5096,7 @@ var TableWidget = class extends WidgetType6 {
4929
5096
  this._table.header?.forEach((cell) => {
4930
5097
  const th = document.createElement("th");
4931
5098
  th.setAttribute("class", "cm-table-head");
4932
- tr.appendChild(th).textContent = cell;
5099
+ renderCellContent(tr.appendChild(th), cell);
4933
5100
  });
4934
5101
  const body = table2.appendChild(document.createElement("tbody"));
4935
5102
  this._table.rows?.forEach((row) => {
@@ -4937,7 +5104,7 @@ var TableWidget = class extends WidgetType6 {
4937
5104
  row.forEach((cell) => {
4938
5105
  const td = document.createElement("td");
4939
5106
  td.setAttribute("class", "cm-table-cell");
4940
- tr2.appendChild(td).textContent = cell;
5107
+ renderCellContent(tr2.appendChild(td), cell);
4941
5108
  });
4942
5109
  });
4943
5110
  return div;
@@ -4945,7 +5112,7 @@ var TableWidget = class extends WidgetType6 {
4945
5112
  };
4946
5113
 
4947
5114
  // src/extensions/markdown/decorate.ts
4948
- var __dxlog_file11 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/markdown/decorate.ts";
5115
+ var __dxlog_file12 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/markdown/decorate.ts";
4949
5116
  var Unicode = {
4950
5117
  emDash: "\u2014",
4951
5118
  bullet: "\u2022",
@@ -4968,7 +5135,6 @@ var LinkButton = class extends WidgetType7 {
4968
5135
  eq(other) {
4969
5136
  return this.url === other.url;
4970
5137
  }
4971
- // TODO(burdon): Create icon and link directly without react?
4972
5138
  toDOM(view) {
4973
5139
  const el = document.createElement("span");
4974
5140
  this.render(el, {
@@ -5045,10 +5211,10 @@ var fencedCodeLine = Decoration11.line({
5045
5211
  class: "cm-code cm-codeblock-line"
5046
5212
  });
5047
5213
  var fencedCodeLineFirst = Decoration11.line({
5048
- class: mx6("cm-code cm-codeblock-line", "cm-codeblock-start")
5214
+ class: "cm-code cm-codeblock-line cm-codeblock-start"
5049
5215
  });
5050
5216
  var fencedCodeLineLast = Decoration11.line({
5051
- class: mx6("cm-code cm-codeblock-line", "cm-codeblock-end")
5217
+ class: "cm-code cm-codeblock-line cm-codeblock-end"
5052
5218
  });
5053
5219
  var commentBlockLine = fencedCodeLine;
5054
5220
  var commentBlockLineFirst = fencedCodeLineFirst;
@@ -5080,15 +5246,7 @@ var buildDecorations2 = (view, options, focus2) => {
5080
5246
  const { state } = view;
5081
5247
  const headerLevels = [];
5082
5248
  const getHeaderLevels = (node, level) => {
5083
- invariant4(level > 0, void 0, {
5084
- F: __dxlog_file11,
5085
- L: 180,
5086
- S: void 0,
5087
- A: [
5088
- "level > 0",
5089
- ""
5090
- ]
5091
- });
5249
+ invariant4(level > 0, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 160, S: void 0, A: ["level > 0", ""] });
5092
5250
  if (level > headerLevels.length) {
5093
5251
  const len = headerLevels.length;
5094
5252
  headerLevels.length = level;
@@ -5119,15 +5277,7 @@ var buildDecorations2 = (view, options, focus2) => {
5119
5277
  listLevels.pop();
5120
5278
  };
5121
5279
  const getCurrentListLevel = () => {
5122
- invariant4(listLevels.length, void 0, {
5123
- F: __dxlog_file11,
5124
- L: 202,
5125
- S: void 0,
5126
- A: [
5127
- "listLevels.length",
5128
- ""
5129
- ]
5130
- });
5280
+ invariant4(listLevels.length, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 192, S: void 0, A: ["listLevels.length", ""] });
5131
5281
  return listLevels[listLevels.length - 1];
5132
5282
  };
5133
5283
  const enterNode = (node) => {
@@ -5165,13 +5315,13 @@ var buildDecorations2 = (view, options, focus2) => {
5165
5315
  deco: hide
5166
5316
  });
5167
5317
  } else {
5168
- const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + " ";
5318
+ const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + "). ";
5169
5319
  if (num.length) {
5170
5320
  atomicDecoRanges.push({
5171
5321
  from: mark.from,
5172
5322
  to: mark.from + len,
5173
5323
  deco: Decoration11.replace({
5174
- widget: new TextWidget(num, markdownTheme.heading(level))
5324
+ widget: new TextWidget(num, markdownTheme.heading(level).className)
5175
5325
  })
5176
5326
  });
5177
5327
  }
@@ -5348,11 +5498,11 @@ var buildDecorations2 = (view, options, focus2) => {
5348
5498
  }
5349
5499
  decoRanges.push({
5350
5500
  from: marks[0].to,
5351
- to: marks[1].from,
5501
+ to: !editing && options.renderLinkButton ? node.to : marks[1].from,
5352
5502
  deco: Decoration11.mark({
5353
5503
  tagName: "a",
5354
5504
  attributes: {
5355
- class: "cm-link",
5505
+ class: options.renderLinkButton ? "cm-link cm-link-with-button" : "cm-link",
5356
5506
  href: url,
5357
5507
  rel: "noreferrer",
5358
5508
  target: "_blank"
@@ -5430,8 +5580,11 @@ var buildDecorations2 = (view, options, focus2) => {
5430
5580
  deco.add(from, to, d);
5431
5581
  }
5432
5582
  const atomicDeco = new RangeSetBuilder5();
5433
- for (const { from, to, deco: d } of atomicDecoRanges) {
5434
- atomicDeco.add(from, to, d);
5583
+ for (const { from, to, deco: deco2 } of atomicDecoRanges) {
5584
+ if (from < to && state.doc.lineAt(from).number !== state.doc.lineAt(to).number) {
5585
+ continue;
5586
+ }
5587
+ atomicDeco.add(from, to, deco2);
5435
5588
  }
5436
5589
  return {
5437
5590
  deco: deco.finish(),
@@ -5441,7 +5594,7 @@ var buildDecorations2 = (view, options, focus2) => {
5441
5594
  var forceUpdate = StateEffect7.define();
5442
5595
  var decorateMarkdown = (options = {}) => {
5443
5596
  return [
5444
- ViewPlugin14.fromClass(class {
5597
+ ViewPlugin15.fromClass(class {
5445
5598
  deco;
5446
5599
  atomicDeco;
5447
5600
  pendingUpdate;
@@ -5476,9 +5629,9 @@ var decorateMarkdown = (options = {}) => {
5476
5629
  }
5477
5630
  }, {
5478
5631
  provide: (plugin) => [
5479
- Prec4.low(EditorView23.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5480
- EditorView23.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5481
- EditorView23.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5632
+ Prec4.low(EditorView24.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5633
+ EditorView24.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5634
+ EditorView24.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5482
5635
  ]
5483
5636
  }),
5484
5637
  image(),
@@ -5510,8 +5663,7 @@ var linkTooltip = (renderTooltip) => {
5510
5663
  return {
5511
5664
  pos: link.from,
5512
5665
  end: link.to,
5513
- // NOTE: Forcing above causes the tooltip to flicker.
5514
- // above: true,
5666
+ above: true,
5515
5667
  create: () => {
5516
5668
  const el = document.createElement("div");
5517
5669
  el.className = tooltipContent({});
@@ -5527,16 +5679,13 @@ var linkTooltip = (renderTooltip) => {
5527
5679
  };
5528
5680
  }
5529
5681
  };
5530
- }, {
5531
- // NOTE: 0 = default of 300ms.
5532
- hoverTime: 1
5533
5682
  });
5534
5683
  };
5535
5684
 
5536
5685
  // src/extensions/mention.ts
5537
5686
  import { autocompletion } from "@codemirror/autocomplete";
5538
- import { log as log8 } from "@dxos/log";
5539
- var __dxlog_file12 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/mention.ts";
5687
+ import { log as log9 } from "@dxos/log";
5688
+ var __dxlog_file13 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/mention.ts";
5540
5689
  var mention = ({ debug, onSearch }) => {
5541
5690
  return autocompletion({
5542
5691
  // TODO(burdon): Not working.
@@ -5548,14 +5697,9 @@ var mention = ({ debug, onSearch }) => {
5548
5697
  icons: false,
5549
5698
  override: [
5550
5699
  (context) => {
5551
- log8.info("completion context", {
5700
+ log9.info("completion context", {
5552
5701
  context
5553
- }, {
5554
- F: __dxlog_file12,
5555
- L: 27,
5556
- S: void 0,
5557
- C: (f, a) => f(...a)
5558
- });
5702
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file13, L: 18, S: void 0 });
5559
5703
  const match = context.matchBefore(/@(\w+)?/);
5560
5704
  if (!match || match.from === match.to && !context.explicit) {
5561
5705
  return null;
@@ -5634,7 +5778,7 @@ import { syntaxTree as syntaxTree9 } from "@codemirror/language";
5634
5778
  import { StateField as StateField10 } from "@codemirror/state";
5635
5779
  import { Facet as Facet2 } from "@codemirror/state";
5636
5780
  import { invariant as invariant5 } from "@dxos/invariant";
5637
- var __dxlog_file13 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/tree.ts";
5781
+ var __dxlog_file14 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/tree.ts";
5638
5782
  var itemToJSON = ({ type, index, level, lineRange, contentRange, children }) => {
5639
5783
  return {
5640
5784
  type,
@@ -5788,15 +5932,7 @@ var outlinerTree = (_options = {}) => {
5788
5932
  break;
5789
5933
  }
5790
5934
  case "BulletList": {
5791
- invariant5(current, void 0, {
5792
- F: __dxlog_file13,
5793
- L: 219,
5794
- S: void 0,
5795
- A: [
5796
- "current",
5797
- ""
5798
- ]
5799
- });
5935
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 169, S: void 0, A: ["current", ""] });
5800
5936
  parent = current;
5801
5937
  if (current) {
5802
5938
  current.lineRange.to = current.node.from;
@@ -5805,15 +5941,7 @@ var outlinerTree = (_options = {}) => {
5805
5941
  break;
5806
5942
  }
5807
5943
  case "ListItem": {
5808
- invariant5(parent, void 0, {
5809
- F: __dxlog_file13,
5810
- L: 228,
5811
- S: void 0,
5812
- A: [
5813
- "parent",
5814
- ""
5815
- ]
5816
- });
5944
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 179, S: void 0, A: ["parent", ""] });
5817
5945
  const nextSibling = node.node.nextSibling ?? node.node.parent?.nextSibling;
5818
5946
  const docRange = {
5819
5947
  from: state.doc.lineAt(node.from).from,
@@ -5847,42 +5975,18 @@ var outlinerTree = (_options = {}) => {
5847
5975
  break;
5848
5976
  }
5849
5977
  case "ListMark": {
5850
- invariant5(current, void 0, {
5851
- F: __dxlog_file13,
5852
- L: 272,
5853
- S: void 0,
5854
- A: [
5855
- "current",
5856
- ""
5857
- ]
5858
- });
5978
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 219, S: void 0, A: ["current", ""] });
5859
5979
  current.type = "bullet";
5860
5980
  current.contentRange.from = node.from + "- ".length;
5861
5981
  break;
5862
5982
  }
5863
5983
  case "Task": {
5864
- invariant5(current, void 0, {
5865
- F: __dxlog_file13,
5866
- L: 278,
5867
- S: void 0,
5868
- A: [
5869
- "current",
5870
- ""
5871
- ]
5872
- });
5984
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 226, S: void 0, A: ["current", ""] });
5873
5985
  current.type = "task";
5874
5986
  break;
5875
5987
  }
5876
5988
  case "TaskMarker": {
5877
- invariant5(current, void 0, {
5878
- F: __dxlog_file13,
5879
- L: 283,
5880
- S: void 0,
5881
- A: [
5882
- "current",
5883
- ""
5884
- ]
5885
- });
5989
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 232, S: void 0, A: ["current", ""] });
5886
5990
  current.contentRange.from = node.from + "[ ] ".length;
5887
5991
  break;
5888
5992
  }
@@ -5890,29 +5994,13 @@ var outlinerTree = (_options = {}) => {
5890
5994
  },
5891
5995
  leave: (node) => {
5892
5996
  if (node.name === "BulletList") {
5893
- invariant5(parent, void 0, {
5894
- F: __dxlog_file13,
5895
- L: 291,
5896
- S: void 0,
5897
- A: [
5898
- "parent",
5899
- ""
5900
- ]
5901
- });
5997
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 240, S: void 0, A: ["parent", ""] });
5902
5998
  prevSiblings[level--] = void 0;
5903
5999
  parent = parent.parent;
5904
6000
  }
5905
6001
  }
5906
6002
  });
5907
- invariant5(tree, void 0, {
5908
- F: __dxlog_file13,
5909
- L: 298,
5910
- S: void 0,
5911
- A: [
5912
- "tree",
5913
- ""
5914
- ]
5915
- });
6003
+ invariant5(tree, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 246, S: void 0, A: ["tree", ""] });
5916
6004
  return tree;
5917
6005
  };
5918
6006
  return [
@@ -6197,17 +6285,17 @@ var commands = () => keymap11.of([
6197
6285
 
6198
6286
  // src/extensions/outliner/outliner.ts
6199
6287
  import { Prec as Prec5 } from "@codemirror/state";
6200
- import { Decoration as Decoration12, EditorView as EditorView25, ViewPlugin as ViewPlugin17 } from "@codemirror/view";
6201
- import { mx as mx7 } from "@dxos/ui-theme";
6288
+ import { Decoration as Decoration12, EditorView as EditorView26, ViewPlugin as ViewPlugin18 } from "@codemirror/view";
6289
+ import { mx as mx6 } from "@dxos/ui-theme";
6202
6290
 
6203
6291
  // src/extensions/outliner/editor.ts
6204
6292
  import { EditorSelection as EditorSelection4, EditorState as EditorState2 } from "@codemirror/state";
6205
- import { ViewPlugin as ViewPlugin15 } from "@codemirror/view";
6206
- import { log as log9 } from "@dxos/log";
6207
- var __dxlog_file14 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/editor.ts";
6293
+ import { ViewPlugin as ViewPlugin16 } from "@codemirror/view";
6294
+ import { log as log10 } from "@dxos/log";
6295
+ var __dxlog_file15 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/editor.ts";
6208
6296
  var LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
6209
6297
  var initialize = () => {
6210
- return ViewPlugin15.fromClass(class {
6298
+ return ViewPlugin16.fromClass(class {
6211
6299
  constructor(view) {
6212
6300
  const first = view.state.doc.lineAt(0);
6213
6301
  const text = view.state.sliceDoc(first.from, first.to);
@@ -6336,7 +6424,7 @@ var editor = () => [
6336
6424
  cancel = true;
6337
6425
  return;
6338
6426
  }
6339
- log9("change", {
6427
+ log10("change", {
6340
6428
  item,
6341
6429
  line: {
6342
6430
  from: line.from,
@@ -6354,35 +6442,20 @@ var editor = () => [
6354
6442
  text: insert.toString(),
6355
6443
  length: insert.length
6356
6444
  }
6357
- }, {
6358
- F: __dxlog_file14,
6359
- L: 164,
6360
- S: void 0,
6361
- C: (f, a) => f(...a)
6362
- });
6445
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 174, S: void 0 });
6363
6446
  }
6364
6447
  });
6365
6448
  if (changes.length > 0) {
6366
- log9("modified,", {
6449
+ log10("modified,", {
6367
6450
  changes
6368
- }, {
6369
- F: __dxlog_file14,
6370
- L: 175,
6371
- S: void 0,
6372
- C: (f, a) => f(...a)
6373
- });
6451
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 196, S: void 0 });
6374
6452
  return [
6375
6453
  {
6376
6454
  changes
6377
6455
  }
6378
6456
  ];
6379
6457
  } else if (cancel) {
6380
- log9("cancel", void 0, {
6381
- F: __dxlog_file14,
6382
- L: 178,
6383
- S: void 0,
6384
- C: (f, a) => f(...a)
6385
- });
6458
+ log10("cancel", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 205, S: void 0 });
6386
6459
  return [];
6387
6460
  }
6388
6461
  return tr;
@@ -6390,10 +6463,10 @@ var editor = () => [
6390
6463
  ];
6391
6464
 
6392
6465
  // src/extensions/outliner/menu.ts
6393
- import { EditorView as EditorView24, ViewPlugin as ViewPlugin16 } from "@codemirror/view";
6394
- import { addEventListener } from "@dxos/async";
6466
+ import { EditorView as EditorView25, ViewPlugin as ViewPlugin17 } from "@codemirror/view";
6467
+ import { addEventListener as addEventListener2 } from "@dxos/async";
6395
6468
  var menu = (options = {}) => [
6396
- ViewPlugin16.fromClass(class {
6469
+ ViewPlugin17.fromClass(class {
6397
6470
  view;
6398
6471
  tag;
6399
6472
  rafId;
@@ -6413,7 +6486,7 @@ var menu = (options = {}) => [
6413
6486
  }
6414
6487
  container.appendChild(this.tag);
6415
6488
  const handler = () => this.scheduleUpdate();
6416
- this.cleanup = addEventListener(container, "scroll", handler);
6489
+ this.cleanup = addEventListener2(container, "scroll", handler);
6417
6490
  this.scheduleUpdate();
6418
6491
  }
6419
6492
  destroy() {
@@ -6455,7 +6528,7 @@ var menu = (options = {}) => [
6455
6528
  this.rafId = requestAnimationFrame(this.updateButtonPosition.bind(this));
6456
6529
  }
6457
6530
  }),
6458
- EditorView24.theme({
6531
+ EditorView25.theme({
6459
6532
  ".cm-popover-trigger": {
6460
6533
  position: "fixed",
6461
6534
  padding: "0",
@@ -6491,12 +6564,12 @@ var outliner = (_options = {}) => [
6491
6564
  listPaddingLeft: 8
6492
6565
  }),
6493
6566
  // Researve space for menu.
6494
- EditorView25.contentAttributes.of({
6495
- class: "is-full !mr-[3rem]"
6567
+ EditorView26.contentAttributes.of({
6568
+ class: "w-full !mr-[3rem]"
6496
6569
  })
6497
6570
  ];
6498
6571
  var decorations = () => [
6499
- ViewPlugin17.fromClass(class {
6572
+ ViewPlugin18.fromClass(class {
6500
6573
  decorations = Decoration12.none;
6501
6574
  constructor(view) {
6502
6575
  this.updateDecorations(view.state, view);
@@ -6521,7 +6594,7 @@ var decorations = () => [
6521
6594
  const lineTo = doc.lineAt(item.contentRange.to);
6522
6595
  const isSelected = selection.includes(item.index) || item === current;
6523
6596
  decorations2.push(Decoration12.line({
6524
- 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"))
6597
+ 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"))
6525
6598
  }).range(line.from, line.from));
6526
6599
  }
6527
6600
  }
@@ -6531,7 +6604,7 @@ var decorations = () => [
6531
6604
  decorations: (v) => v.decorations
6532
6605
  }),
6533
6606
  // Theme.
6534
- EditorView25.theme(Object.assign({
6607
+ EditorView26.theme(Object.assign({
6535
6608
  ".cm-list-item": {
6536
6609
  borderLeftWidth: "1px",
6537
6610
  borderRightWidth: "1px",
@@ -6556,38 +6629,67 @@ var decorations = () => [
6556
6629
  marginBottom: "2px"
6557
6630
  },
6558
6631
  ".cm-list-item-focused": {
6559
- borderColor: "var(--dx-neutralFocusIndicator)"
6632
+ borderColor: "var(--color-neutral-focus-indicator)"
6560
6633
  },
6561
6634
  "&:focus-within .cm-list-item-selected": {
6562
- borderColor: "var(--dx-separator)"
6635
+ borderColor: "var(--color-separator)"
6563
6636
  }
6564
6637
  }))
6565
6638
  ];
6566
6639
 
6567
6640
  // src/extensions/preview/preview.ts
6568
6641
  import { syntaxTree as syntaxTree10 } from "@codemirror/language";
6569
- import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField11 } from "@codemirror/state";
6570
- import { Decoration as Decoration13, EditorView as EditorView26, WidgetType as WidgetType8 } from "@codemirror/view";
6642
+ import { RangeSetBuilder as RangeSetBuilder6, StateEffect as StateEffect9, StateField as StateField11 } from "@codemirror/state";
6643
+ import { Decoration as Decoration13, EditorView as EditorView27, ViewPlugin as ViewPlugin19, WidgetType as WidgetType8 } from "@codemirror/view";
6644
+ import { DXN, Entity } from "@dxos/echo";
6645
+ var labelResolvedEffect = StateEffect9.define();
6571
6646
  var preview = (options = {}) => {
6647
+ const viewRef = {
6648
+ current: void 0
6649
+ };
6572
6650
  return [
6573
6651
  // NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
6574
6652
  // "Block decorations may not be specified via plugins".
6575
6653
  StateField11.define({
6576
- create: (state) => buildDecorations3(state, options),
6654
+ create: (state) => buildDecorations3(state, options, viewRef),
6577
6655
  update: (decorations2, tr) => {
6578
- if (tr.docChanged) {
6579
- return buildDecorations3(tr.state, options);
6656
+ if (tr.docChanged || tr.effects.some((effect) => effect.is(labelResolvedEffect))) {
6657
+ return buildDecorations3(tr.state, options, viewRef);
6580
6658
  }
6581
6659
  return decorations2.map(tr.changes);
6582
6660
  },
6583
6661
  provide: (field) => [
6584
- EditorView26.decorations.from(field),
6585
- EditorView26.atomicRanges.of((view) => view.state.field(field))
6662
+ EditorView27.decorations.from(field),
6663
+ EditorView27.atomicRanges.of((view) => view.state.field(field))
6586
6664
  ]
6665
+ }),
6666
+ ViewPlugin19.define((view) => {
6667
+ viewRef.current = view;
6668
+ return {
6669
+ destroy() {
6670
+ viewRef.current = void 0;
6671
+ }
6672
+ };
6587
6673
  })
6588
6674
  ];
6589
6675
  };
6590
- var buildDecorations3 = (state, options) => {
6676
+ var resolveLabel = (db, dxnStr, viewRef) => {
6677
+ const dxn = DXN.tryParse(dxnStr);
6678
+ if (!dxn) {
6679
+ return;
6680
+ }
6681
+ const ref = db.makeRef(dxn);
6682
+ const target = ref.target;
6683
+ if (target) {
6684
+ return Entity.getLabel(target);
6685
+ }
6686
+ void ref.tryLoad().then(() => {
6687
+ viewRef.current?.dispatch({
6688
+ effects: labelResolvedEffect.of(void 0)
6689
+ });
6690
+ });
6691
+ };
6692
+ var buildDecorations3 = (state, options, viewRef) => {
6591
6693
  const builder = new RangeSetBuilder6();
6592
6694
  syntaxTree10(state).iterate({
6593
6695
  enter: (node) => {
@@ -6599,8 +6701,13 @@ var buildDecorations3 = (state, options) => {
6599
6701
  case "Link": {
6600
6702
  const link = getLinkRef(state, node.node);
6601
6703
  if (link) {
6704
+ const resolved = options.db ? resolveLabel(options.db, link.dxn, viewRef) : void 0;
6705
+ const displayLink = resolved ? {
6706
+ ...link,
6707
+ label: resolved
6708
+ } : link;
6602
6709
  builder.add(node.from, node.to, Decoration13.replace({
6603
- widget: new PreviewInlineWidget(options, link),
6710
+ widget: new PreviewInlineWidget(options, displayLink),
6604
6711
  side: 1
6605
6712
  }));
6606
6713
  }
@@ -6676,7 +6783,7 @@ var PreviewBlockWidget = class extends WidgetType8 {
6676
6783
  }
6677
6784
  toDOM(_view) {
6678
6785
  const root = document.createElement("div");
6679
- root.classList.add("cm-preview-block", "density-fine");
6786
+ root.classList.add("cm-preview-block", "dx-density-fine");
6680
6787
  this._options.addBlockContainer?.({
6681
6788
  link: this._link,
6682
6789
  el: root
@@ -6692,7 +6799,7 @@ var PreviewBlockWidget = class extends WidgetType8 {
6692
6799
  };
6693
6800
 
6694
6801
  // src/extensions/replacer.ts
6695
- import { EditorView as EditorView27 } from "@codemirror/view";
6802
+ import { EditorView as EditorView28 } from "@codemirror/view";
6696
6803
  var defaultReplacements = [
6697
6804
  {
6698
6805
  input: "--",
@@ -6755,7 +6862,7 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
6755
6862
  const sortedReplacements = [
6756
6863
  ...replacements
6757
6864
  ].sort((a, b) => b.input.length - a.input.length);
6758
- return EditorView27.inputHandler.of((view, from, to, insert) => {
6865
+ return EditorView28.inputHandler.of((view, from, to, insert) => {
6759
6866
  if (insert.length !== 1) {
6760
6867
  return false;
6761
6868
  }
@@ -6789,12 +6896,69 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
6789
6896
  });
6790
6897
  };
6791
6898
 
6899
+ // src/extensions/snippets.ts
6900
+ import { keymap as keymap12 } from "@codemirror/view";
6901
+ var defaultItems = [
6902
+ "hello world!",
6903
+ "this is a test.",
6904
+ "this is [DXOS](https://dxos.org)"
6905
+ ];
6906
+ var snippets2 = ({ delay = 75, items = defaultItems } = {}) => {
6907
+ let timer;
6908
+ let index = 0;
6909
+ return [
6910
+ keymap12.of([
6911
+ {
6912
+ // Reset.
6913
+ key: "alt-meta-'",
6914
+ run: () => {
6915
+ clearTimeout(timer);
6916
+ index = 0;
6917
+ return true;
6918
+ }
6919
+ },
6920
+ {
6921
+ // Next snippet.
6922
+ // TODO(burdon): Press 1-9 to select snippet?
6923
+ key: "Shift-Meta-'",
6924
+ run: (view) => {
6925
+ clearTimeout(timer);
6926
+ const text = items[index++];
6927
+ if (index === items?.length) {
6928
+ index = 0;
6929
+ }
6930
+ let offset = 0;
6931
+ const insert = (delayMs = 0) => {
6932
+ timer = setTimeout(() => {
6933
+ const pos = view.state.selection.main.head;
6934
+ view.dispatch({
6935
+ changes: {
6936
+ from: pos,
6937
+ insert: text[offset++]
6938
+ },
6939
+ selection: {
6940
+ anchor: pos + 1
6941
+ }
6942
+ });
6943
+ if (offset < text.length) {
6944
+ insert(Math.random() * delay * (text[offset] === " " ? 2 : 1));
6945
+ }
6946
+ }, delayMs);
6947
+ };
6948
+ insert();
6949
+ return true;
6950
+ }
6951
+ }
6952
+ ])
6953
+ ];
6954
+ };
6955
+
6792
6956
  // src/extensions/submit.ts
6793
6957
  import { Prec as Prec6 } from "@codemirror/state";
6794
- import { keymap as keymap12 } from "@codemirror/view";
6958
+ import { keymap as keymap13 } from "@codemirror/view";
6795
6959
  var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
6796
6960
  return [
6797
- Prec6.highest(keymap12.of([
6961
+ Prec6.highest(keymap13.of([
6798
6962
  {
6799
6963
  key: "Enter",
6800
6964
  preventDefault: true,
@@ -6839,6 +7003,7 @@ var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
6839
7003
  // src/extensions/tags/extended-markdown.ts
6840
7004
  import { xmlLanguage } from "@codemirror/lang-xml";
6841
7005
  import { parseMixed } from "@lezer/common";
7006
+ var escapeRegExpSource = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6842
7007
  var extendedMarkdown = ({ registry } = {}) => {
6843
7008
  return [
6844
7009
  createMarkdownExtensions({
@@ -6850,13 +7015,65 @@ var extendedMarkdown = ({ registry } = {}) => {
6850
7015
  {
6851
7016
  name: "SetextHeading",
6852
7017
  parse: () => false
6853
- }
7018
+ },
7019
+ // Custom XML block parser that keeps registered tags as a single HTMLBlock
7020
+ // even when their content contains blank lines.
7021
+ ...xmlBlockParsers(registry)
6854
7022
  ]
6855
7023
  }
6856
7024
  ]
6857
7025
  })
6858
7026
  ];
6859
7027
  };
7028
+ var xmlBlockParsers = (registry) => {
7029
+ const customTags = Object.keys(registry ?? {});
7030
+ if (customTags.length === 0) {
7031
+ return [];
7032
+ }
7033
+ const tagPattern = customTags.map(escapeRegExpSource).join("|");
7034
+ const selfClosePattern = new RegExp(`^\\s*<(${tagPattern})(\\s[^>]*)?\\/>\\s*$`);
7035
+ const openPattern = new RegExp(`^\\s*<(${tagPattern})(\\s[^>]*)?\\/?>`);
7036
+ return [
7037
+ {
7038
+ name: "XMLBlock",
7039
+ before: "HTMLBlock",
7040
+ parse: (cx, line) => {
7041
+ const match = openPattern.exec(line.text);
7042
+ if (!match) {
7043
+ return false;
7044
+ }
7045
+ if (selfClosePattern.test(line.text)) {
7046
+ const end2 = cx.lineStart + line.text.length;
7047
+ cx.addElement(cx.elt("HTMLBlock", cx.lineStart, end2));
7048
+ cx.nextLine();
7049
+ return true;
7050
+ }
7051
+ if (match[0].trimEnd().endsWith("/>")) {
7052
+ return false;
7053
+ }
7054
+ const tagName = match[1];
7055
+ const closeTag = `</${tagName}>`;
7056
+ const start = cx.lineStart;
7057
+ if (line.text.includes(closeTag)) {
7058
+ cx.addElement(cx.elt("HTMLBlock", start, start + line.text.length));
7059
+ cx.nextLine();
7060
+ return true;
7061
+ }
7062
+ let end = cx.lineStart + line.text.length;
7063
+ while (cx.nextLine()) {
7064
+ end = cx.lineStart + line.text.length;
7065
+ if (line.text.includes(closeTag)) {
7066
+ cx.addElement(cx.elt("HTMLBlock", start, end));
7067
+ cx.nextLine();
7068
+ return true;
7069
+ }
7070
+ }
7071
+ cx.addElement(cx.elt("HTMLBlock", start, end));
7072
+ return true;
7073
+ }
7074
+ }
7075
+ ];
7076
+ };
6860
7077
  var mixedParser = (registry) => {
6861
7078
  const customTags = Object.keys(registry ?? {});
6862
7079
  const tagPattern = new RegExp(`<(${customTags.join("|")})`);
@@ -6890,219 +7107,793 @@ var mixedParser = (registry) => {
6890
7107
  });
6891
7108
  };
6892
7109
 
6893
- // src/extensions/tags/streamer.ts
6894
- import { StateEffect as StateEffect9, StateField as StateField12 } from "@codemirror/state";
6895
- import { Decoration as Decoration14, EditorView as EditorView28, ViewPlugin as ViewPlugin18, WidgetType as WidgetType9 } from "@codemirror/view";
6896
- import { Domino as Domino3 } from "@dxos/ui";
6897
- import { isTruthy as isTruthy4 } from "@dxos/util";
6898
- var BLINK_RATE = 2e3;
6899
- var streamer = (options = {}) => {
7110
+ // src/extensions/tags/fader.ts
7111
+ import { StateEffect as StateEffect10, StateField as StateField12 } from "@codemirror/state";
7112
+ import { Decoration as Decoration14, EditorView as EditorView29, ViewPlugin as ViewPlugin20 } from "@codemirror/view";
7113
+ var DEFAULT_REMOVAL_DELAY = 5e3;
7114
+ var DEFAULT_COALESCE_WINDOW = 100;
7115
+ var CLEANUP_INTERVAL = 1e3;
7116
+ var fader = (options = {}) => {
7117
+ const removalDelay = DEFAULT_REMOVAL_DELAY;
7118
+ const coalesceWindow = options.coalesce ?? DEFAULT_COALESCE_WINDOW;
7119
+ let lastCount = -1;
7120
+ const log12 = (expiries) => {
7121
+ if (expiries.length !== lastCount) {
7122
+ lastCount = expiries.length;
7123
+ }
7124
+ };
7125
+ const dequeue = StateEffect10.define();
7126
+ const fadeField = StateField12.define({
7127
+ create: () => ({
7128
+ decorations: Decoration14.none,
7129
+ expiries: [],
7130
+ batchStart: 0
7131
+ }),
7132
+ update: ({ decorations: decorations2, expiries, batchStart }, tr) => {
7133
+ for (const effect of tr.effects) {
7134
+ if (effect.is(dequeue)) {
7135
+ const now2 = effect.value;
7136
+ let removeCount = 0;
7137
+ while (removeCount < expiries.length && expiries[removeCount] <= now2) {
7138
+ removeCount++;
7139
+ }
7140
+ if (removeCount > 0) {
7141
+ expiries = expiries.slice(removeCount);
7142
+ let skipped = 0;
7143
+ decorations2 = decorations2.update({
7144
+ filter: () => {
7145
+ if (skipped < removeCount) {
7146
+ skipped++;
7147
+ return false;
7148
+ }
7149
+ return true;
7150
+ }
7151
+ });
7152
+ }
7153
+ }
7154
+ }
7155
+ if (!tr.docChanged) {
7156
+ log12(expiries);
7157
+ return {
7158
+ decorations: decorations2,
7159
+ expiries,
7160
+ batchStart
7161
+ };
7162
+ }
7163
+ let isReset = tr.state.doc.length === 0;
7164
+ if (!isReset && tr.startState.doc.length > 0) {
7165
+ tr.changes.iterChanges((fromA, toA) => {
7166
+ if (fromA === 0 && toA === tr.startState.doc.length) {
7167
+ isReset = true;
7168
+ }
7169
+ });
7170
+ }
7171
+ if (isReset) {
7172
+ log12([]);
7173
+ return {
7174
+ decorations: Decoration14.none,
7175
+ expiries: [],
7176
+ batchStart: 0
7177
+ };
7178
+ }
7179
+ const now = Date.now();
7180
+ const add = [];
7181
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7182
+ if (toA === tr.startState.doc.length && inserted.length > 0) {
7183
+ add.push({
7184
+ from: fromB,
7185
+ to: toB
7186
+ });
7187
+ }
7188
+ });
7189
+ if (add.length > 0) {
7190
+ const canCoalesce = expiries.length > 0 && batchStart > 0 && now - batchStart < coalesceWindow;
7191
+ if (canCoalesce) {
7192
+ let lastFrom = -1;
7193
+ let lastTo = -1;
7194
+ decorations2.between(0, tr.state.doc.length, (from, to) => {
7195
+ lastFrom = from;
7196
+ lastTo = to;
7197
+ });
7198
+ if (lastFrom >= 0) {
7199
+ decorations2 = decorations2.update({
7200
+ filter: (from, to) => !(from === lastFrom && to === lastTo)
7201
+ });
7202
+ const mergedFrom = Math.min(lastFrom, add[0].from);
7203
+ const mergedTo = add[add.length - 1].to;
7204
+ decorations2 = decorations2.update({
7205
+ add: [
7206
+ Decoration14.mark({
7207
+ class: "cm-fader"
7208
+ }).range(mergedFrom, mergedTo)
7209
+ ]
7210
+ });
7211
+ expiries = [
7212
+ ...expiries.slice(0, -1),
7213
+ now + removalDelay
7214
+ ];
7215
+ }
7216
+ } else {
7217
+ batchStart = now;
7218
+ expiries = [
7219
+ ...expiries,
7220
+ now + removalDelay
7221
+ ];
7222
+ decorations2 = decorations2.update({
7223
+ add: add.map(({ from, to }) => Decoration14.mark({
7224
+ class: "cm-fader"
7225
+ }).range(from, to))
7226
+ });
7227
+ }
7228
+ }
7229
+ log12(expiries);
7230
+ return {
7231
+ decorations: decorations2,
7232
+ expiries,
7233
+ batchStart
7234
+ };
7235
+ },
7236
+ provide: (f) => EditorView29.decorations.from(f, (value) => value.decorations)
7237
+ });
7238
+ const cleanup = ViewPlugin20.fromClass(class {
7239
+ view;
7240
+ #timer;
7241
+ constructor(view) {
7242
+ this.view = view;
7243
+ this.#schedule();
7244
+ }
7245
+ update() {
7246
+ this.#schedule();
7247
+ }
7248
+ #schedule() {
7249
+ const { expiries } = this.view.state.field(fadeField);
7250
+ if (expiries.length === 0) {
7251
+ clearTimeout(this.#timer);
7252
+ this.#timer = void 0;
7253
+ return;
7254
+ }
7255
+ if (this.#timer !== void 0) {
7256
+ return;
7257
+ }
7258
+ const delay = Math.max(CLEANUP_INTERVAL, expiries[0] - Date.now());
7259
+ this.#timer = setTimeout(() => {
7260
+ this.#timer = void 0;
7261
+ this.view.dispatch({
7262
+ effects: dequeue.of(Date.now())
7263
+ });
7264
+ }, delay);
7265
+ }
7266
+ destroy() {
7267
+ clearTimeout(this.#timer);
7268
+ }
7269
+ });
6900
7270
  return [
6901
- options.cursor && cursor(),
6902
- options.fadeIn && fadeIn(typeof options.fadeIn === "object" ? options.fadeIn : {})
6903
- ].filter(isTruthy4);
6904
- };
6905
- var cursor = () => {
6906
- const hideCursor = StateEffect9.define();
6907
- const showCursor = StateField12.define({
6908
- create: () => true,
7271
+ fadeField,
7272
+ cleanup,
7273
+ EditorView29.theme({
7274
+ ".cm-fader": {
7275
+ animation: "fader 1s ease-out forwards"
7276
+ },
7277
+ "@keyframes fader": {
7278
+ "0%": {
7279
+ textShadow: "0 0 16px rgba(100, 200, 255, 1), 0 0 32px rgba(100, 200, 255, 0.6)"
7280
+ },
7281
+ "100%": {}
7282
+ }
7283
+ })
7284
+ ];
7285
+ };
7286
+
7287
+ // src/extensions/tags/typewriter.ts
7288
+ import { Annotation as Annotation3, ChangeSet as ChangeSet2, EditorState as EditorState3, StateEffect as StateEffect11, StateField as StateField13 } from "@codemirror/state";
7289
+ import { Decoration as Decoration15, EditorView as EditorView30, ViewPlugin as ViewPlugin21, WidgetType as WidgetType9 } from "@codemirror/view";
7290
+ import { Domino as Domino3 } from "@dxos/ui";
7291
+ var typewriterBypass = Annotation3.define();
7292
+ var typewriterDrainingEffect = StateEffect11.define();
7293
+ var CURSOR_LINGER = 3e3;
7294
+ var FRAME_BUDGET_MS = 4;
7295
+ var CHARS_PER_FRAME = 5;
7296
+ var FLUSH_THRESHOLD = 2e3;
7297
+ var COMPACT_HEAD_THRESHOLD = 4096;
7298
+ var typewriter = (options = {}) => {
7299
+ const streamingTags = options.streamingTags ?? /* @__PURE__ */ new Set();
7300
+ const flushThreshold = options.flushThreshold ?? FLUSH_THRESHOLD;
7301
+ const frameBudgetMs = options.frameBudgetMs ?? FRAME_BUDGET_MS;
7302
+ const charsPerFrame = options.charsPerFrame ?? CHARS_PER_FRAME;
7303
+ const suppressAppend = StateEffect11.define();
7304
+ const insertChunk = StateEffect11.define();
7305
+ const bufferField = StateField13.define({
7306
+ create: () => ({
7307
+ text: "",
7308
+ head: 0,
7309
+ insertAt: 0
7310
+ }),
6909
7311
  update: (value, tr) => {
7312
+ let { text, head, insertAt } = value;
6910
7313
  for (const effect of tr.effects) {
6911
- if (effect.is(hideCursor)) {
6912
- return false;
7314
+ if (effect.is(suppressAppend)) {
7315
+ if (text.length === head) {
7316
+ insertAt = effect.value.from;
7317
+ }
7318
+ text += effect.value.text;
7319
+ }
7320
+ if (effect.is(insertChunk)) {
7321
+ head += effect.value.text.length;
7322
+ insertAt = effect.value.from + effect.value.text.length;
7323
+ if (head >= COMPACT_HEAD_THRESHOLD || head > 0 && head * 2 >= text.length) {
7324
+ text = text.slice(head);
7325
+ head = 0;
7326
+ }
6913
7327
  }
6914
7328
  }
6915
7329
  if (tr.docChanged) {
6916
- return true;
7330
+ let isReset = tr.state.doc.length === 0;
7331
+ if (!isReset && tr.startState.doc.length > 0) {
7332
+ tr.changes.iterChanges((fromA, toA) => {
7333
+ if (fromA === 0 && toA === tr.startState.doc.length) {
7334
+ isReset = true;
7335
+ }
7336
+ });
7337
+ }
7338
+ if (isReset) {
7339
+ return {
7340
+ text: "",
7341
+ head: 0,
7342
+ insertAt: 0
7343
+ };
7344
+ }
7345
+ if (!tr.effects.some((effect) => effect.is(insertChunk))) {
7346
+ insertAt = tr.changes.mapPos(Math.min(insertAt, tr.startState.doc.length));
7347
+ }
6917
7348
  }
6918
- return value;
7349
+ return {
7350
+ text,
7351
+ head,
7352
+ insertAt
7353
+ };
6919
7354
  }
6920
7355
  });
6921
- const timerPlugin = ViewPlugin18.fromClass(class {
7356
+ const filter = EditorState3.transactionFilter.of((tr) => {
7357
+ if (!tr.docChanged) {
7358
+ return tr;
7359
+ }
7360
+ if (tr.annotation(typewriterBypass) || tr.effects.some((effect) => effect.is(insertChunk))) {
7361
+ return tr;
7362
+ }
7363
+ let appendedText = "";
7364
+ let appendFrom = -1;
7365
+ let isAppendOnly = true;
7366
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
7367
+ if (toA === tr.startState.doc.length && fromA === toA && inserted.length > 0) {
7368
+ appendedText += inserted.sliceString(0);
7369
+ if (appendFrom === -1) {
7370
+ appendFrom = fromA;
7371
+ }
7372
+ } else {
7373
+ isAppendOnly = false;
7374
+ }
7375
+ });
7376
+ if (!isAppendOnly || appendedText.length === 0) {
7377
+ return tr;
7378
+ }
7379
+ return {
7380
+ changes: ChangeSet2.empty(tr.startState.doc.length),
7381
+ effects: suppressAppend.of({
7382
+ from: appendFrom,
7383
+ text: appendedText
7384
+ })
7385
+ };
7386
+ });
7387
+ const drainPlugin = ViewPlugin21.fromClass(class {
6922
7388
  view;
6923
- timer;
7389
+ _raf;
7390
+ _activeStreamTag = null;
6924
7391
  constructor(view) {
6925
7392
  this.view = view;
6926
7393
  }
6927
7394
  update(update2) {
6928
- if (update2.docChanged) {
6929
- clearTimeout(this.timer);
6930
- this.timer = setTimeout(() => {
6931
- this.view.dispatch({
6932
- effects: hideCursor.of(null)
6933
- });
6934
- }, BLINK_RATE);
7395
+ const { text, head } = update2.state.field(bufferField);
7396
+ const pending = text.length - head;
7397
+ if (pending === 0) {
7398
+ this._activeStreamTag = null;
7399
+ }
7400
+ if (pending > 0 && this._raf === void 0) {
7401
+ this._start();
6935
7402
  }
6936
7403
  }
7404
+ _start() {
7405
+ queueMicrotask(() => {
7406
+ this.view.dispatch({
7407
+ effects: typewriterDrainingEffect.of(true),
7408
+ annotations: typewriterBypass.of(true)
7409
+ });
7410
+ });
7411
+ this._raf = requestAnimationFrame(this._tick);
7412
+ }
7413
+ _tick = () => {
7414
+ const { text, head, insertAt } = this.view.state.field(bufferField);
7415
+ const pending = text.length - head;
7416
+ if (pending === 0) {
7417
+ this.view.dispatch({
7418
+ effects: typewriterDrainingEffect.of(false),
7419
+ annotations: typewriterBypass.of(true)
7420
+ });
7421
+ this._raf = void 0;
7422
+ return;
7423
+ }
7424
+ if (pending > flushThreshold) {
7425
+ const chunk = text.slice(head);
7426
+ this._activeStreamTag = null;
7427
+ this.view.dispatch({
7428
+ changes: {
7429
+ from: insertAt,
7430
+ insert: chunk
7431
+ },
7432
+ effects: insertChunk.of({
7433
+ from: insertAt,
7434
+ text: chunk
7435
+ })
7436
+ });
7437
+ this._raf = requestAnimationFrame(this._tick);
7438
+ return;
7439
+ }
7440
+ const startTime = performance.now();
7441
+ let pos = head;
7442
+ let activeTag = this._activeStreamTag;
7443
+ let charsEmitted = 0;
7444
+ while (pos < text.length && performance.now() - startTime < frameBudgetMs) {
7445
+ const result = flushable(text, pos, streamingTags, activeTag);
7446
+ if (result.count === 0) {
7447
+ break;
7448
+ }
7449
+ if (charsEmitted > 0 && charsEmitted + result.count > charsPerFrame) {
7450
+ break;
7451
+ }
7452
+ if (result.enterTag) {
7453
+ activeTag = result.enterTag;
7454
+ }
7455
+ if (result.exitTag) {
7456
+ activeTag = null;
7457
+ }
7458
+ pos += result.count;
7459
+ charsEmitted += result.count;
7460
+ }
7461
+ const totalCount = pos - head;
7462
+ if (totalCount > 0) {
7463
+ const chunk = text.slice(head, head + totalCount);
7464
+ this._activeStreamTag = activeTag;
7465
+ this.view.dispatch({
7466
+ changes: {
7467
+ from: insertAt,
7468
+ insert: chunk
7469
+ },
7470
+ effects: insertChunk.of({
7471
+ from: insertAt,
7472
+ text: chunk
7473
+ })
7474
+ });
7475
+ }
7476
+ this._raf = requestAnimationFrame(this._tick);
7477
+ };
6937
7478
  destroy() {
6938
- clearTimeout(this.timer);
7479
+ if (this._raf !== void 0) {
7480
+ cancelAnimationFrame(this._raf);
7481
+ }
6939
7482
  }
6940
7483
  });
6941
- const cursorDecoration = StateField12.define({
6942
- create: () => Decoration14.none,
6943
- update: (_decorations, tr) => {
6944
- const show = tr.state.field(showCursor);
6945
- if (!show) {
6946
- return Decoration14.none;
7484
+ return [
7485
+ bufferField,
7486
+ filter,
7487
+ drainPlugin,
7488
+ options.cursor && typewriterCursor(bufferField)
7489
+ ].filter(Boolean);
7490
+ };
7491
+ var typewriterCursor = (bufferField) => {
7492
+ const hideCursor = StateEffect11.define();
7493
+ const visibilityField = StateField13.define({
7494
+ create: () => ({
7495
+ visible: false,
7496
+ insertAt: 0,
7497
+ lastNonWsAt: 0
7498
+ }),
7499
+ update: (value, tr) => {
7500
+ const { text, head, insertAt } = tr.state.field(bufferField);
7501
+ const pending = text.length - head;
7502
+ if (pending > 0) {
7503
+ let lastNonWsAt = tr.changes.mapPos(Math.min(value.lastNonWsAt, tr.startState.doc.length));
7504
+ if (tr.docChanged) {
7505
+ tr.changes.iterChanges((_fromA, _toA, _fromB, _toB, inserted) => {
7506
+ const chunk = inserted.sliceString(0);
7507
+ if (chunk.trim().length > 0) {
7508
+ lastNonWsAt = _fromB + chunk.length;
7509
+ }
7510
+ });
7511
+ }
7512
+ return {
7513
+ visible: true,
7514
+ insertAt,
7515
+ lastNonWsAt
7516
+ };
7517
+ }
7518
+ for (const effect of tr.effects) {
7519
+ if (effect.is(hideCursor)) {
7520
+ return {
7521
+ ...value,
7522
+ visible: false
7523
+ };
7524
+ }
6947
7525
  }
6948
- const endPos = tr.state.doc.length;
6949
- return Decoration14.set([
6950
- Decoration14.widget({
7526
+ return value;
7527
+ }
7528
+ });
7529
+ const decorationField = StateField13.define({
7530
+ create: () => Decoration15.none,
7531
+ update: (_decorations, tr) => {
7532
+ const { visible, insertAt, lastNonWsAt } = tr.state.field(visibilityField);
7533
+ if (!visible) {
7534
+ return Decoration15.none;
7535
+ }
7536
+ const { text, head } = tr.state.field(bufferField);
7537
+ const cursorAt = text.length > head ? insertAt : lastNonWsAt;
7538
+ const pos = Math.min(cursorAt, tr.state.doc.length);
7539
+ return Decoration15.set([
7540
+ Decoration15.widget({
6951
7541
  widget: new CursorWidget(),
6952
7542
  side: 1
6953
- }).range(endPos)
7543
+ }).range(pos)
6954
7544
  ]);
6955
7545
  },
6956
- provide: (f) => EditorView28.decorations.from(f)
7546
+ provide: (field) => EditorView30.decorations.from(field)
7547
+ });
7548
+ const timerPlugin = ViewPlugin21.fromClass(class {
7549
+ view;
7550
+ _timer;
7551
+ constructor(view) {
7552
+ this.view = view;
7553
+ }
7554
+ update(update2) {
7555
+ const { text, head } = update2.state.field(bufferField);
7556
+ const { visible } = update2.state.field(visibilityField);
7557
+ const pending = text.length - head;
7558
+ if (pending > 0) {
7559
+ clearTimeout(this._timer);
7560
+ this._timer = void 0;
7561
+ } else if (visible && this._timer === void 0) {
7562
+ this._timer = setTimeout(() => {
7563
+ this.view.dispatch({
7564
+ effects: hideCursor.of(null)
7565
+ });
7566
+ this._timer = void 0;
7567
+ }, CURSOR_LINGER);
7568
+ }
7569
+ }
7570
+ destroy() {
7571
+ clearTimeout(this._timer);
7572
+ }
6957
7573
  });
6958
7574
  return [
6959
- showCursor,
6960
- timerPlugin,
6961
- cursorDecoration
7575
+ visibilityField,
7576
+ decorationField,
7577
+ timerPlugin
6962
7578
  ];
6963
7579
  };
6964
- var CursorWidget = class extends WidgetType9 {
7580
+ var CursorWidget = class _CursorWidget extends WidgetType9 {
7581
+ // All instances are interchangeable — let CM reuse the existing DOM across drips so
7582
+ // the blink animation isn't restarted on every transaction.
7583
+ eq(other) {
7584
+ return other instanceof _CursorWidget;
7585
+ }
6965
7586
  toDOM() {
6966
- const inner = Domino3.of("span").text("\u258F").style({
6967
- animation: "blink 2s infinite"
7587
+ const inner = Domino3.of("span").text("\u2217").style({
7588
+ animation: "blink 1s infinite",
7589
+ animationDelay: "250ms"
6968
7590
  });
6969
7591
  return Domino3.of("span").style({
6970
7592
  opacity: "0.8"
6971
- }).children(inner).root;
7593
+ }).append(inner).root;
6972
7594
  }
6973
7595
  };
6974
- var fadeIn = (options = {}) => {
6975
- const FADE_IN_DURATION = 1e3;
6976
- const DEFAULT_REMOVAL_DELAY = 5e3;
6977
- const removalDelay = options.removalDelay ?? DEFAULT_REMOVAL_DELAY;
6978
- const removeDecoration = StateEffect9.define();
6979
- const fadeField = StateField12.define({
6980
- create: () => Decoration14.none,
6981
- update: (decorations2, tr) => {
6982
- let next = decorations2;
6983
- for (const effect of tr.effects) {
6984
- if (effect.is(removeDecoration)) {
6985
- const target = effect.value;
6986
- next = next.update({
6987
- filter: (from, to) => !(from === target.from && to === target.to)
6988
- });
6989
- }
6990
- }
6991
- if (!tr.docChanged) {
6992
- return next;
7596
+ var OPENING_TAG_NAME = /^<([a-zA-Z][\w-]*)/;
7597
+ var TAG_NAME_PROBE = 64;
7598
+ var escapeRegExpSource2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7599
+ var flushable = (buffer, start, streamingTags, activeStreamTag) => {
7600
+ if (start >= buffer.length) {
7601
+ return {
7602
+ count: 0
7603
+ };
7604
+ }
7605
+ if (activeStreamTag) {
7606
+ const closeTag = `</${activeStreamTag}>`;
7607
+ if (buffer.startsWith(closeTag, start)) {
7608
+ return {
7609
+ count: closeTag.length,
7610
+ exitTag: true
7611
+ };
7612
+ }
7613
+ if (buffer[start] === "<") {
7614
+ return {
7615
+ count: xmlElementLength(buffer, start)
7616
+ };
7617
+ }
7618
+ return {
7619
+ count: 1
7620
+ };
7621
+ }
7622
+ const ch = buffer[start];
7623
+ if (ch === "<") {
7624
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7625
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7626
+ if (nameMatch && streamingTags.has(nameMatch[1])) {
7627
+ const close = buffer.indexOf(">", start);
7628
+ if (close === -1) {
7629
+ return {
7630
+ count: 0
7631
+ };
6993
7632
  }
6994
- let isReset = tr.state.doc.length === 0;
6995
- if (!isReset) {
6996
- tr.changes.iterChanges((fromA, toA) => {
6997
- if (fromA === 0 && toA === tr.startState.doc.length) {
6998
- isReset = true;
6999
- }
7000
- });
7633
+ if (buffer[close - 1] === "/") {
7634
+ return {
7635
+ count: close + 1 - start
7636
+ };
7001
7637
  }
7002
- if (isReset) {
7003
- return Decoration14.none;
7638
+ return {
7639
+ count: close + 1 - start,
7640
+ enterTag: nameMatch[1]
7641
+ };
7642
+ }
7643
+ return {
7644
+ count: xmlElementLength(buffer, start)
7645
+ };
7646
+ }
7647
+ if (ch === "!" && buffer.length > start + 1 && buffer[start + 1] === "[") {
7648
+ return {
7649
+ count: linkLength(buffer, start, start + 1)
7650
+ };
7651
+ }
7652
+ if (ch === "[") {
7653
+ return {
7654
+ count: linkLength(buffer, start, start)
7655
+ };
7656
+ }
7657
+ return {
7658
+ count: 1
7659
+ };
7660
+ };
7661
+ var xmlElementLength = (buffer, start = 0) => {
7662
+ const close = buffer.indexOf(">", start);
7663
+ if (close === -1) {
7664
+ return 0;
7665
+ }
7666
+ if (buffer[close - 1] === "/") {
7667
+ return close + 1 - start;
7668
+ }
7669
+ if (buffer[start + 1] === "/") {
7670
+ return close + 1 - start;
7671
+ }
7672
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7673
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7674
+ if (!nameMatch) {
7675
+ return 1;
7676
+ }
7677
+ const tagName = nameMatch[1];
7678
+ let depth = 0;
7679
+ const tagPattern = new RegExp(`<(/?)${escapeRegExpSource2(tagName)}(\\s[^>]*)?>`, "g");
7680
+ tagPattern.lastIndex = start;
7681
+ let match;
7682
+ while ((match = tagPattern.exec(buffer)) !== null) {
7683
+ const isSelfClosing = match[0].endsWith("/>");
7684
+ const isClosing = match[1] === "/";
7685
+ if (isSelfClosing) {
7686
+ if (depth === 0) {
7687
+ return match.index + match[0].length - start;
7688
+ }
7689
+ } else if (isClosing) {
7690
+ depth--;
7691
+ if (depth === 0) {
7692
+ return match.index + match[0].length - start;
7004
7693
  }
7005
- const add = [];
7006
- tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7007
- if (fromB === 0 && toB === inserted.length) {
7694
+ } else {
7695
+ depth++;
7696
+ }
7697
+ }
7698
+ return 0;
7699
+ };
7700
+ var linkLength = (buffer, start, bracketAt) => {
7701
+ const bracketClose = buffer.indexOf("]", bracketAt + 1);
7702
+ if (bracketClose === -1) {
7703
+ return 0;
7704
+ }
7705
+ if (bracketClose + 1 >= buffer.length) {
7706
+ return 0;
7707
+ }
7708
+ if (buffer[bracketClose + 1] !== "(") {
7709
+ return 1;
7710
+ }
7711
+ const parenClose = buffer.indexOf(")", bracketClose + 2);
7712
+ if (parenClose === -1) {
7713
+ return 0;
7714
+ }
7715
+ return parenClose + 1 - start;
7716
+ };
7717
+
7718
+ // src/extensions/tags/xml-block-decoration.ts
7719
+ import { xmlLanguage as xmlLanguage2 } from "@codemirror/lang-xml";
7720
+ import { Decoration as Decoration16, ViewPlugin as ViewPlugin22 } from "@codemirror/view";
7721
+ var xmlBlockDecoration = ({ tag, lineClass, contentClass, hideTags }) => {
7722
+ const lineDecoration = lineClass ? Decoration16.line({
7723
+ class: lineClass
7724
+ }) : void 0;
7725
+ const contentDecoration = contentClass ? Decoration16.mark({
7726
+ class: contentClass
7727
+ }) : void 0;
7728
+ const hideDecoration = hideTags ? Decoration16.replace({}) : void 0;
7729
+ const buildDecorations5 = (view) => {
7730
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7731
+ if (!text.includes(`<${tag}`)) {
7732
+ return Decoration16.none;
7733
+ }
7734
+ const tree = xmlLanguage2.parser.parse(text);
7735
+ const ranges = [];
7736
+ tree.iterate({
7737
+ enter: (node) => {
7738
+ if (node.type.name !== "Element") {
7008
7739
  return;
7009
7740
  }
7010
- if (toA === tr.startState.doc.length && inserted.length > 0) {
7011
- add.push(Decoration14.mark({
7012
- class: "cm-fade-in"
7013
- }).range(fromB, toB));
7741
+ const openTag = node.node.getChild("OpenTag");
7742
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7743
+ const tagNameNode = openTag?.getChild("TagName");
7744
+ if (!openTag || !tagNameNode) {
7745
+ return;
7014
7746
  }
7015
- });
7016
- return next.update({
7017
- add
7018
- });
7019
- },
7020
- provide: (f) => EditorView28.decorations.from(f)
7021
- });
7022
- const timerPlugin = ViewPlugin18.fromClass(class {
7023
- view;
7024
- // Map a simple key "from-to" to timer id.
7025
- _timers = /* @__PURE__ */ new Map();
7747
+ if (text.slice(tagNameNode.from, tagNameNode.to) !== tag) {
7748
+ return;
7749
+ }
7750
+ const contentFrom = openTag.to;
7751
+ const contentTo = closeTag?.from ?? node.node.to;
7752
+ if (hideDecoration) {
7753
+ ranges.push(hideDecoration.range(openTag.from, openTag.to));
7754
+ if (closeTag) {
7755
+ ranges.push(hideDecoration.range(closeTag.from, closeTag.to));
7756
+ }
7757
+ }
7758
+ if (contentDecoration && contentFrom < contentTo) {
7759
+ ranges.push(contentDecoration.range(contentFrom, contentTo));
7760
+ }
7761
+ if (lineDecoration && contentFrom <= view.state.doc.length) {
7762
+ let pos = contentFrom;
7763
+ while (pos <= contentTo && pos <= view.state.doc.length) {
7764
+ const line = view.state.doc.lineAt(pos);
7765
+ ranges.push(lineDecoration.range(line.from));
7766
+ if (line.to >= contentTo) {
7767
+ break;
7768
+ }
7769
+ pos = line.to + 1;
7770
+ }
7771
+ }
7772
+ }
7773
+ });
7774
+ return Decoration16.set(ranges, true);
7775
+ };
7776
+ return ViewPlugin22.fromClass(class {
7777
+ decorations;
7026
7778
  constructor(view) {
7027
- this.view = view;
7779
+ this.decorations = buildDecorations5(view);
7028
7780
  }
7029
7781
  update(update2) {
7030
- if (!update2.docChanged) {
7031
- return;
7782
+ if (update2.docChanged) {
7783
+ this.decorations = buildDecorations5(update2.view);
7032
7784
  }
7033
- update2.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7034
- if (toA !== update2.startState.doc.length || inserted.length === 0) {
7785
+ }
7786
+ }, {
7787
+ decorations: (instance) => instance.decorations
7788
+ });
7789
+ };
7790
+
7791
+ // src/extensions/tags/xml-formatting.ts
7792
+ import { xmlLanguage as xmlLanguage3 } from "@codemirror/lang-xml";
7793
+ import { Decoration as Decoration17, EditorView as EditorView31, ViewPlugin as ViewPlugin23 } from "@codemirror/view";
7794
+ var XML_TAG_NODES = /* @__PURE__ */ new Set([
7795
+ "OpenTag",
7796
+ "CloseTag",
7797
+ "SelfClosingTag",
7798
+ "MismatchedCloseTag"
7799
+ ]);
7800
+ var xmlElementMark = Decoration17.mark({
7801
+ class: "cm-xml-element"
7802
+ });
7803
+ var xmlTagMark = Decoration17.mark({
7804
+ class: "cm-xml-tag"
7805
+ });
7806
+ var xmlContentMark = Decoration17.mark({
7807
+ class: "cm-xml-content"
7808
+ });
7809
+ var xmlFormatting = ({ skip } = {}) => {
7810
+ const skipSet = skip && skip.length > 0 ? new Set(skip) : void 0;
7811
+ const buildDecorations5 = (view) => {
7812
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7813
+ if (!text.includes("<")) {
7814
+ return Decoration17.none;
7815
+ }
7816
+ const tagNameAt = (node) => text.slice(node.from, node.to);
7817
+ const tree = xmlLanguage3.parser.parse(text);
7818
+ const ranges = [];
7819
+ tree.iterate({
7820
+ enter: (node) => {
7821
+ const name = node.type.name;
7822
+ if (name === "SelfClosingTag" && node.from < node.to) {
7823
+ if (skipSet) {
7824
+ const tagNameNode = node.node.getChild("TagName");
7825
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7826
+ return false;
7827
+ }
7828
+ }
7829
+ ranges.push(xmlElementMark.range(node.from, node.to));
7830
+ ranges.push(xmlTagMark.range(node.from, node.to));
7831
+ return;
7832
+ }
7833
+ if (XML_TAG_NODES.has(name) && node.from < node.to) {
7834
+ ranges.push(xmlTagMark.range(node.from, node.to));
7035
7835
  return;
7036
7836
  }
7037
- const key = `${fromB}-${toB}`;
7038
- if (this._timers.has(key)) {
7039
- clearTimeout(this._timers.get(key));
7837
+ if (name === "Element" && node.from < node.to) {
7838
+ const openTag = node.node.getChild("OpenTag");
7839
+ if (openTag && skipSet) {
7840
+ const tagNameNode = openTag.getChild("TagName");
7841
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7842
+ return false;
7843
+ }
7844
+ }
7845
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7846
+ ranges.push(xmlElementMark.range(node.from, node.to));
7847
+ if (openTag && closeTag && openTag.to < closeTag.from) {
7848
+ ranges.push(xmlContentMark.range(openTag.to, closeTag.from));
7849
+ }
7040
7850
  }
7041
- const totalDelay = FADE_IN_DURATION + removalDelay;
7042
- const id = setTimeout(() => {
7043
- this.view.dispatch({
7044
- effects: removeDecoration.of({
7045
- from: fromB,
7046
- to: toB
7047
- })
7048
- });
7049
- this._timers.delete(key);
7050
- }, totalDelay);
7051
- this._timers.set(key, id);
7052
- });
7053
- }
7054
- destroy() {
7055
- for (const id of this._timers.values()) {
7056
- clearTimeout(id);
7057
7851
  }
7058
- this._timers.clear();
7059
- }
7060
- });
7852
+ });
7853
+ return Decoration17.set(ranges, true);
7854
+ };
7061
7855
  return [
7062
- fadeField,
7063
- timerPlugin,
7064
- EditorView28.theme({
7065
- ".cm-line > span": {
7066
- opacity: "0.8"
7067
- },
7068
- ".cm-fade-in": {
7069
- animation: "fade-in 3s ease-out forwards"
7070
- },
7071
- "@keyframes fade-in": {
7072
- "0%": {
7073
- opacity: "0"
7074
- },
7075
- "80%": {
7076
- opacity: "1"
7077
- },
7078
- "100%": {
7079
- opacity: "0.8"
7856
+ ViewPlugin23.fromClass(class {
7857
+ decorations;
7858
+ constructor(view) {
7859
+ this.decorations = buildDecorations5(view);
7860
+ }
7861
+ update(update2) {
7862
+ if (update2.docChanged) {
7863
+ this.decorations = buildDecorations5(update2.view);
7080
7864
  }
7081
7865
  }
7866
+ }, {
7867
+ decorations: (instance) => instance.decorations
7868
+ }),
7869
+ EditorView31.baseTheme({
7870
+ ".cm-xml-element": {
7871
+ backgroundColor: "var(--color-active-surface)",
7872
+ borderRadius: "0.25rem",
7873
+ padding: "0.25rem"
7874
+ },
7875
+ ".cm-xml-tag": {
7876
+ color: "var(--color-blue-500)",
7877
+ fontFamily: "var(--font-mono)"
7878
+ },
7879
+ ".cm-xml-content": {}
7082
7880
  })
7083
7881
  ];
7084
7882
  };
7085
7883
 
7086
7884
  // src/extensions/tags/xml-tags.ts
7087
7885
  import { syntaxTree as syntaxTree11 } from "@codemirror/language";
7088
- import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect10, StateField as StateField13 } from "@codemirror/state";
7089
- import { Decoration as Decoration15, EditorView as EditorView29, ViewPlugin as ViewPlugin19, WidgetType as WidgetType10, keymap as keymap13 } from "@codemirror/view";
7886
+ import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect12, StateField as StateField14 } from "@codemirror/state";
7887
+ import { Decoration as Decoration18, EditorView as EditorView32, ViewPlugin as ViewPlugin24, WidgetType as WidgetType10, keymap as keymap14 } from "@codemirror/view";
7090
7888
  import { invariant as invariant7 } from "@dxos/invariant";
7091
- import { log as log10 } from "@dxos/log";
7889
+ import { log as log11 } from "@dxos/log";
7890
+ import { Domino as Domino4 } from "@dxos/ui";
7092
7891
 
7093
7892
  // src/extensions/tags/xml-util.ts
7094
7893
  import { invariant as invariant6 } from "@dxos/invariant";
7095
- var __dxlog_file15 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-util.ts";
7894
+ var __dxlog_file16 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-util.ts";
7096
7895
  var nodeToJson = (state, node) => {
7097
- invariant6(node.type.name === "Element", "Node is not an Element", {
7098
- F: __dxlog_file15,
7099
- L: 18,
7100
- S: void 0,
7101
- A: [
7102
- "node.type.name === 'Element'",
7103
- "'Node is not an Element'"
7104
- ]
7105
- });
7896
+ 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'"] });
7106
7897
  const openTag = node.node.getChild("OpenTag") || node.node.getChild("SelfClosingTag");
7107
7898
  if (openTag) {
7108
7899
  const tagName = openTag.getChild("TagName");
@@ -7134,13 +7925,23 @@ var nodeToJson = (state, node) => {
7134
7925
  if (node.type.name === "Element" && openTag.type.name !== "SelfClosingTag") {
7135
7926
  const children = [];
7136
7927
  let child = node.node.firstChild;
7928
+ const appendText = (raw) => {
7929
+ if (raw.length === 0) {
7930
+ return;
7931
+ }
7932
+ const last = children[children.length - 1];
7933
+ if (typeof last === "string") {
7934
+ children[children.length - 1] = last + raw;
7935
+ } else {
7936
+ children.push(raw);
7937
+ }
7938
+ };
7137
7939
  while (child) {
7138
7940
  if (child.type.name !== "OpenTag" && child.type.name !== "CloseTag") {
7139
7941
  if (child.type.name === "Text") {
7140
- const text = state.doc.sliceString(child.from, child.to).trim();
7141
- if (text) {
7142
- children.push(text);
7143
- }
7942
+ appendText(state.doc.sliceString(child.from, child.to));
7943
+ } else if (child.type.name === "EntityReference" || child.type.name === "CharacterReference") {
7944
+ appendText(decodeXmlEntity(state.doc.sliceString(child.from, child.to)));
7144
7945
  } else if (child.type.name === "Element") {
7145
7946
  const data = nodeToJson(state, child);
7146
7947
  if (data) {
@@ -7150,26 +7951,63 @@ var nodeToJson = (state, node) => {
7150
7951
  }
7151
7952
  child = child.nextSibling;
7152
7953
  }
7954
+ if (children.length > 0 && typeof children[0] === "string") {
7955
+ children[0] = children[0].trimStart();
7956
+ }
7153
7957
  if (children.length > 0) {
7154
- tag.children = children;
7958
+ const lastIndex = children.length - 1;
7959
+ const last = children[lastIndex];
7960
+ if (typeof last === "string") {
7961
+ children[lastIndex] = last.trimEnd();
7962
+ }
7963
+ }
7964
+ const trimmed = children.filter((value) => typeof value !== "string" || value.length > 0);
7965
+ if (trimmed.length > 0) {
7966
+ tag.children = trimmed;
7155
7967
  }
7156
7968
  }
7157
7969
  return tag;
7158
7970
  }
7159
7971
  };
7972
+ var XML_NAMED_ENTITIES = {
7973
+ "&lt;": "<",
7974
+ "&gt;": ">",
7975
+ "&amp;": "&",
7976
+ "&quot;": '"',
7977
+ "&apos;": "'"
7978
+ };
7979
+ var decodeXmlEntity = (raw) => {
7980
+ const named = XML_NAMED_ENTITIES[raw];
7981
+ if (named !== void 0) {
7982
+ return named;
7983
+ }
7984
+ const numeric = /^&#(x?)([0-9a-fA-F]+);$/.exec(raw);
7985
+ if (numeric) {
7986
+ const code = parseInt(numeric[2], numeric[1] ? 16 : 10);
7987
+ if (Number.isFinite(code)) {
7988
+ try {
7989
+ return String.fromCodePoint(code);
7990
+ } catch {
7991
+ }
7992
+ }
7993
+ }
7994
+ return raw;
7995
+ };
7160
7996
 
7161
7997
  // src/extensions/tags/xml-tags.ts
7162
- var __dxlog_file16 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-tags.ts";
7163
- var navigatePreviousEffect = StateEffect10.define();
7164
- var navigateNextEffect = StateEffect10.define();
7998
+ var __dxlog_file17 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-tags.ts";
7999
+ var navigatePreviousEffect = StateEffect12.define();
8000
+ var navigateNextEffect = StateEffect12.define();
7165
8001
  var getXmlTextChild = (children) => {
7166
8002
  const child = children?.[0];
7167
8003
  return typeof child === "string" ? child : null;
7168
8004
  };
7169
- var xmlTagContextEffect = StateEffect10.define();
7170
- var xmlTagResetEffect = StateEffect10.define();
7171
- var xmlTagUpdateEffect = StateEffect10.define();
7172
- var widgetContextStateField = StateField13.define({
8005
+ var xmlWidgetId = (explicit, fallback) => typeof explicit === "string" && explicit.length > 0 ? explicit : fallback;
8006
+ var escapeRegExpSource3 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8007
+ var xmlTagContextEffect = StateEffect12.define();
8008
+ var xmlTagResetEffect = StateEffect12.define();
8009
+ var xmlTagUpdateEffect = StateEffect12.define();
8010
+ var widgetContextStateField = StateField14.define({
7173
8011
  create: () => void 0,
7174
8012
  update: (value, tr) => {
7175
8013
  for (const effect of tr.effects) {
@@ -7180,7 +8018,7 @@ var widgetContextStateField = StateField13.define({
7180
8018
  return value;
7181
8019
  }
7182
8020
  });
7183
- var widgetStateMapStateField = StateField13.define({
8021
+ var widgetStateMapStateField = StateField14.define({
7184
8022
  create: () => ({}),
7185
8023
  update: (map, tr) => {
7186
8024
  for (const effect of tr.effects) {
@@ -7189,15 +8027,10 @@ var widgetStateMapStateField = StateField13.define({
7189
8027
  }
7190
8028
  if (effect.is(xmlTagUpdateEffect)) {
7191
8029
  const { id, value } = effect.value;
7192
- log10("widget updated", {
8030
+ log11("widget updated", {
7193
8031
  id,
7194
8032
  value
7195
- }, {
7196
- F: __dxlog_file16,
7197
- L: 153,
7198
- S: void 0,
7199
- C: (f, a) => f(...a)
7200
- });
8033
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 59, S: void 0 });
7201
8034
  const state = typeof value === "function" ? value(map[id]) : value;
7202
8035
  return {
7203
8036
  ...map,
@@ -7224,15 +8057,10 @@ var createWidgetMap = (setWidgets) => {
7224
8057
  const widgets = /* @__PURE__ */ new Map();
7225
8058
  const notifier = {
7226
8059
  mounted: (state) => {
7227
- log10("widget mounted", {
8060
+ log11("widget mounted", {
7228
8061
  id: state.id,
7229
8062
  tag: state.props._tag
7230
- }, {
7231
- F: __dxlog_file16,
7232
- L: 206,
7233
- S: void 0,
7234
- C: (f, a) => f(...a)
7235
- });
8063
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 101, S: void 0 });
7236
8064
  widgets.set(state.id, state);
7237
8065
  setWidgets?.([
7238
8066
  ...widgets.values()
@@ -7240,15 +8068,10 @@ var createWidgetMap = (setWidgets) => {
7240
8068
  },
7241
8069
  unmounted: (id) => {
7242
8070
  const state = widgets.get(id);
7243
- log10("widget unmounted", {
8071
+ log11("widget unmounted", {
7244
8072
  id,
7245
8073
  tag: state?.props._tag
7246
- }, {
7247
- F: __dxlog_file16,
7248
- L: 212,
7249
- S: void 0,
7250
- C: (f, a) => f(...a)
7251
- });
8074
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 112, S: void 0 });
7252
8075
  widgets.delete(id);
7253
8076
  setWidgets?.([
7254
8077
  ...widgets.values()
@@ -7257,7 +8080,7 @@ var createWidgetMap = (setWidgets) => {
7257
8080
  };
7258
8081
  return notifier;
7259
8082
  };
7260
- var keyHandlers = keymap13.of([
8083
+ var keyHandlers = keymap14.of([
7261
8084
  {
7262
8085
  key: "Mod-ArrowUp",
7263
8086
  run: (view) => {
@@ -7278,7 +8101,7 @@ var keyHandlers = keymap13.of([
7278
8101
  }
7279
8102
  ]);
7280
8103
  var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7281
- return EditorView29.updateListener.of((update2) => {
8104
+ return EditorView32.updateListener.of((update2) => {
7282
8105
  update2.transactions.forEach((transaction) => {
7283
8106
  for (const effect of transaction.effects) {
7284
8107
  if (effect.is(navigatePreviousEffect)) {
@@ -7306,11 +8129,9 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7306
8129
  anchor: line.from,
7307
8130
  head: line.from
7308
8131
  },
7309
- effects: scrollToLineEffect.of({
7310
- line: line.number,
7311
- options: {
7312
- offset: -16
7313
- }
8132
+ effects: scrollerLineEffect.of({
8133
+ line: line.number - 1,
8134
+ offset: -16
7314
8135
  })
7315
8136
  });
7316
8137
  continue;
@@ -7341,11 +8162,9 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7341
8162
  anchor: line.to,
7342
8163
  head: line.to
7343
8164
  },
7344
- effects: scrollToLineEffect.of({
7345
- line: line.number,
7346
- options: {
7347
- offset: -16
7348
- }
8165
+ effects: scrollerLineEffect.of({
8166
+ line: line.number - 1,
8167
+ offset: -16
7349
8168
  })
7350
8169
  });
7351
8170
  } else {
@@ -7355,11 +8174,9 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7355
8174
  anchor: line.to,
7356
8175
  head: line.to
7357
8176
  },
7358
- effects: scrollToLineEffect.of({
7359
- line: line.number,
7360
- options: {
7361
- position: "end"
7362
- }
8177
+ effects: scrollerLineEffect.of({
8178
+ line: line.number - 1,
8179
+ position: "end"
7363
8180
  })
7364
8181
  });
7365
8182
  }
@@ -7369,7 +8186,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7369
8186
  });
7370
8187
  });
7371
8188
  };
7372
- var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin19.fromClass(class {
8189
+ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin24.fromClass(class {
7373
8190
  update(update2) {
7374
8191
  const widgetStateMap = update2.state.field(widgetStateMapStateField);
7375
8192
  const { decorations: decorations2 } = update2.state.field(widgetDecorationsField);
@@ -7396,19 +8213,25 @@ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin1
7396
8213
  }
7397
8214
  }
7398
8215
  });
7399
- var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.define({
8216
+ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.define({
7400
8217
  create: (state) => {
7401
8218
  return buildDecorations4(state, {
7402
8219
  from: 0,
7403
8220
  to: state.doc.length
7404
8221
  }, registry, notifier);
7405
8222
  },
7406
- update: ({ from, decorations: decorations2 }, tr) => {
8223
+ update: ({ from, streamingFrom, decorations: decorations2 }, tr) => {
7407
8224
  for (const effect of tr.effects) {
7408
8225
  if (effect.is(xmlTagResetEffect)) {
8226
+ if (tr.docChanged) {
8227
+ return buildDecorations4(tr.state, {
8228
+ from: 0,
8229
+ to: tr.state.doc.length
8230
+ }, registry, notifier);
8231
+ }
7409
8232
  return {
7410
8233
  from: 0,
7411
- decorations: Decoration15.none
8234
+ decorations: Decoration18.none
7412
8235
  };
7413
8236
  }
7414
8237
  }
@@ -7416,27 +8239,26 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.def
7416
8239
  const { state } = tr;
7417
8240
  const reset = tr.changes.touchesRange(0, from);
7418
8241
  if (reset) {
7419
- log10("document reset", {
8242
+ log11("document reset", {
7420
8243
  from,
7421
8244
  to: state.doc.length
7422
- }, {
7423
- F: __dxlog_file16,
7424
- L: 371,
7425
- S: void 0,
7426
- C: (f, a) => f(...a)
7427
- });
8245
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 298, S: void 0 });
7428
8246
  return buildDecorations4(state, {
7429
8247
  from: 0,
7430
8248
  to: state.doc.length
7431
8249
  }, registry, notifier);
7432
8250
  } else {
8251
+ const rebuildFrom = streamingFrom ?? from;
7433
8252
  const result = buildDecorations4(state, {
7434
- from,
8253
+ from: rebuildFrom,
7435
8254
  to: state.doc.length
7436
8255
  }, registry, notifier);
7437
8256
  return {
7438
8257
  from: result.from,
8258
+ streamingFrom: result.streamingFrom,
7439
8259
  decorations: decorations2.update({
8260
+ // Remove old streaming decorations — they are rebuilt each tick.
8261
+ filter: (_f, _t, deco) => !deco.spec.streaming,
7440
8262
  add: decorationSetToArray(result.decorations)
7441
8263
  })
7442
8264
  };
@@ -7444,12 +8266,13 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.def
7444
8266
  }
7445
8267
  return {
7446
8268
  from,
8269
+ streamingFrom,
7447
8270
  decorations: decorations2
7448
8271
  };
7449
8272
  },
7450
8273
  provide: (field) => [
7451
- EditorView29.decorations.from(field, (v) => v.decorations),
7452
- EditorView29.atomicRanges.of((view) => view.state.field(field).decorations || Decoration15.none)
8274
+ EditorView32.decorations.from(field, (v) => v.decorations),
8275
+ EditorView32.atomicRanges.of((view) => view.state.field(field).decorations || Decoration18.none)
7453
8276
  ]
7454
8277
  });
7455
8278
  var buildDecorations4 = (state, range, registry, notifier) => {
@@ -7460,10 +8283,11 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7460
8283
  if (!tree || tree.type.name === "Program" && tree.length === 0) {
7461
8284
  return {
7462
8285
  from: range.from,
7463
- decorations: Decoration15.none
8286
+ decorations: Decoration18.none
7464
8287
  };
7465
8288
  }
7466
8289
  let last = range.from;
8290
+ let streamingFrom;
7467
8291
  tree.iterate({
7468
8292
  from: range.from,
7469
8293
  to: range.to,
@@ -7476,21 +8300,26 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7476
8300
  if (args) {
7477
8301
  const def = registry[args._tag];
7478
8302
  if (def) {
8303
+ if (def.streaming && !node.node.getChild("CloseTag")) {
8304
+ return false;
8305
+ }
7479
8306
  const { block, factory, Component } = def;
7480
- const widgetState = args.id ? widgetStateMap[args.id] : void 0;
7481
8307
  const nodeRange = {
7482
8308
  from: node.node.from,
7483
8309
  to: node.node.to
7484
8310
  };
8311
+ const widgetId = xmlWidgetId(args.id, def.streaming ? `cm-xml-${nodeRange.from}` : `cm-xml-${nodeRange.from}-${nodeRange.to}`);
8312
+ const widgetState = widgetStateMap[widgetId];
7485
8313
  const props = {
7486
- context,
8314
+ id: widgetId,
7487
8315
  range: nodeRange,
8316
+ context,
7488
8317
  ...args,
7489
8318
  ...widgetState
7490
8319
  };
7491
- const widget = factory ? factory(props) : Component ? args.id && new PlaceholderWidget2(args.id, Component, props, notifier) : void 0;
8320
+ const widget = factory ? factory(props) ?? void 0 : Component ? new PlaceholderWidget2(widgetId, Component, props, notifier) : void 0;
7492
8321
  if (widget) {
7493
- builder.add(nodeRange.from, nodeRange.to, Decoration15.replace({
8322
+ builder.add(nodeRange.from, nodeRange.to, Decoration18.replace({
7494
8323
  widget,
7495
8324
  block,
7496
8325
  atomic: true,
@@ -7502,20 +8331,72 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7502
8331
  }
7503
8332
  }
7504
8333
  } catch (err) {
7505
- log10.catch(err, void 0, {
7506
- F: __dxlog_file16,
7507
- L: 456,
7508
- S: void 0,
7509
- C: (f, a) => f(...a)
7510
- });
8334
+ log11.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 401, S: void 0 });
7511
8335
  }
7512
8336
  return false;
7513
8337
  }
7514
8338
  }
7515
8339
  }
7516
8340
  });
8341
+ const streamingTagNames = Object.entries(registry).filter(([, def]) => def.streaming).map(([name]) => name).sort((a, b) => b.length - a.length);
8342
+ if (streamingTagNames.length > 0) {
8343
+ const tailText = state.sliceDoc(range.from, range.to);
8344
+ const streamingPattern = streamingTagNames.map(escapeRegExpSource3).join("|");
8345
+ const tagPattern = new RegExp(`<(${streamingPattern})(\\s[^>]*)?>`, "g");
8346
+ let match;
8347
+ while ((match = tagPattern.exec(tailText)) !== null) {
8348
+ const tagName = match[1];
8349
+ const closeTag = `</${tagName}>`;
8350
+ const afterOpen = match.index + match[0].length;
8351
+ if (tailText.indexOf(closeTag, afterOpen) === -1) {
8352
+ const absoluteFrom = range.from + match.index;
8353
+ const contentFrom = range.from + afterOpen;
8354
+ const innerText = state.sliceDoc(contentFrom, range.to).trim();
8355
+ const def = registry[tagName];
8356
+ const props = {
8357
+ _tag: tagName,
8358
+ context,
8359
+ range: {
8360
+ from: absoluteFrom,
8361
+ to: range.to
8362
+ },
8363
+ children: innerText ? [
8364
+ innerText
8365
+ ] : void 0
8366
+ };
8367
+ const attrPattern = /(\w+)="([^"]*)"/g;
8368
+ let attrMatch;
8369
+ while ((attrMatch = attrPattern.exec(match[0])) !== null) {
8370
+ props[attrMatch[1]] = attrMatch[2];
8371
+ }
8372
+ const widgetId = xmlWidgetId(props.id, `cm-xml-${absoluteFrom}`);
8373
+ const widgetState = widgetStateMap[widgetId];
8374
+ const mergedProps = {
8375
+ ...props,
8376
+ id: widgetId,
8377
+ ...widgetState
8378
+ };
8379
+ const widget = def.factory ? def.factory(mergedProps) ?? void 0 : def.Component ? new PlaceholderWidget2(widgetId, def.Component, mergedProps, notifier, true) : void 0;
8380
+ if (widget) {
8381
+ builder.add(absoluteFrom, range.to, Decoration18.replace({
8382
+ widget,
8383
+ block: def.block,
8384
+ atomic: true,
8385
+ inclusive: true,
8386
+ tag: tagName,
8387
+ streaming: true,
8388
+ contentFrom
8389
+ }));
8390
+ streamingFrom = absoluteFrom;
8391
+ last = absoluteFrom;
8392
+ }
8393
+ break;
8394
+ }
8395
+ }
8396
+ }
7517
8397
  return {
7518
8398
  from: last,
8399
+ streamingFrom,
7519
8400
  decorations: builder.finish()
7520
8401
  };
7521
8402
  };
@@ -7524,108 +8405,62 @@ var PlaceholderWidget2 = class extends WidgetType10 {
7524
8405
  Component;
7525
8406
  props;
7526
8407
  notifier;
7527
- _root = null;
7528
- constructor(id, Component, props, notifier) {
7529
- super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier;
7530
- invariant7(id, void 0, {
7531
- F: __dxlog_file16,
7532
- L: 482,
7533
- S: this,
7534
- A: [
7535
- "id",
7536
- ""
7537
- ]
7538
- });
8408
+ streaming;
8409
+ #root = null;
8410
+ #view;
8411
+ constructor(id, Component, props, notifier, streaming) {
8412
+ super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier, this.streaming = streaming;
8413
+ invariant7(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 488, S: this, A: ["id", ""] });
7539
8414
  }
7540
8415
  get root() {
7541
- return this._root;
8416
+ return this.#root;
7542
8417
  }
7543
8418
  eq(other) {
8419
+ if (this.streaming) {
8420
+ return false;
8421
+ }
7544
8422
  return this.id === other.id;
7545
8423
  }
7546
8424
  ignoreEvent() {
7547
8425
  return true;
7548
8426
  }
7549
- toDOM(_view) {
7550
- this._root = document.createElement("span");
8427
+ toDOM(view) {
8428
+ this.#view = view;
8429
+ this.#root = Domino4.of("div").classNames("min-h-[24px]").root;
8430
+ const props = Object.assign({}, this.props, {
8431
+ view
8432
+ });
7551
8433
  this.notifier.mounted({
7552
8434
  id: this.id,
7553
- root: this._root,
7554
- props: this.props,
8435
+ root: this.#root,
8436
+ props,
7555
8437
  Component: this.Component
7556
8438
  });
7557
- return this._root;
8439
+ return this.#root;
8440
+ }
8441
+ updateDOM(dom) {
8442
+ this.#root = dom;
8443
+ const props = Object.assign({}, this.props, {
8444
+ view: this.#view
8445
+ });
8446
+ this.notifier.mounted({
8447
+ id: this.id,
8448
+ root: this.#root,
8449
+ props,
8450
+ Component: this.Component
8451
+ });
8452
+ return true;
7558
8453
  }
7559
8454
  destroy(_dom) {
7560
8455
  this.notifier.unmounted(this.id);
7561
- this._root = null;
8456
+ this.#root = null;
8457
+ this.#view = void 0;
7562
8458
  }
7563
8459
  };
7564
-
7565
- // src/extensions/typewriter.ts
7566
- import { keymap as keymap14 } from "@codemirror/view";
7567
- var defaultItems = [
7568
- "hello world!",
7569
- "this is a test.",
7570
- "this is [DXOS](https://dxos.org)"
7571
- ];
7572
- var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
7573
- let t;
7574
- let idx = 0;
7575
- return [
7576
- keymap14.of([
7577
- {
7578
- // Reset.
7579
- key: "alt-meta-'",
7580
- run: () => {
7581
- clearTimeout(t);
7582
- idx = 0;
7583
- return true;
7584
- }
7585
- },
7586
- {
7587
- // Next prompt.
7588
- // TODO(burdon): Press 1-9 to select prompt?
7589
- key: "Shift-Meta-'",
7590
- run: (view) => {
7591
- clearTimeout(t);
7592
- const text = items[idx++];
7593
- if (idx === items?.length) {
7594
- idx = 0;
7595
- }
7596
- let i = 0;
7597
- const insert = (d = 0) => {
7598
- t = setTimeout(() => {
7599
- const pos = view.state.selection.main.head;
7600
- view.dispatch({
7601
- changes: {
7602
- from: pos,
7603
- insert: text[i++]
7604
- },
7605
- selection: {
7606
- anchor: pos + 1
7607
- }
7608
- });
7609
- if (i < text.length) {
7610
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
7611
- }
7612
- }, d);
7613
- };
7614
- insert();
7615
- return true;
7616
- }
7617
- }
7618
- ])
7619
- ];
7620
- };
7621
8460
  export {
7622
8461
  Cursor,
7623
- EditorInputMode,
7624
- EditorInputModes,
7625
- EditorState3 as EditorState,
7626
- EditorView30 as EditorView,
7627
- EditorViewMode,
7628
- EditorViewModes,
8462
+ EditorState4 as EditorState,
8463
+ EditorView33 as EditorView,
7629
8464
  Inline,
7630
8465
  InputModeExtensions,
7631
8466
  List,
@@ -7642,6 +8477,7 @@ export {
7642
8477
  addStyle,
7643
8478
  annotations,
7644
8479
  autoScroll,
8480
+ autoScrollEffect,
7645
8481
  autocomplete,
7646
8482
  automerge,
7647
8483
  awareness,
@@ -7655,9 +8491,11 @@ export {
7655
8491
  commentClickedEffect,
7656
8492
  comments,
7657
8493
  commentsState,
8494
+ compactSlots,
7658
8495
  convertTreeToJson,
7659
8496
  createBasicExtensions,
7660
8497
  createComment,
8498
+ createCrawler,
7661
8499
  createDataExtensions,
7662
8500
  createEditorStateStore,
7663
8501
  createEditorStateTransaction,
@@ -7677,12 +8515,12 @@ export {
7677
8515
  defaultThemeSlots,
7678
8516
  deleteItem,
7679
8517
  documentId,
8518
+ documentSlots,
7680
8519
  dropFile,
8520
+ editorClassNames,
7681
8521
  editorInputMode,
7682
- editorSlots,
7683
- editorWidth,
7684
- editorWithToolbarLayout,
7685
8522
  extendedMarkdown,
8523
+ fader,
7686
8524
  filterChars,
7687
8525
  flattenRect,
7688
8526
  focus,
@@ -7718,6 +8556,7 @@ export {
7718
8556
  markdownTagsExtensions,
7719
8557
  matchCompletion,
7720
8558
  mention,
8559
+ mobileSlots,
7721
8560
  modalStateEffect,
7722
8561
  modalStateField,
7723
8562
  moveItemDown,
@@ -7737,10 +8576,12 @@ export {
7737
8576
  removeList,
7738
8577
  removeStyle,
7739
8578
  replacer,
8579
+ scrollPastEnd,
7740
8580
  scrollThreadIntoView,
7741
- scrollToBottomEffect,
7742
8581
  scrollToLine,
7743
- scrollToLineEffect,
8582
+ scroller,
8583
+ scrollerCrawlEffect,
8584
+ scrollerLineEffect,
7744
8585
  selectionState,
7745
8586
  setBlockquote,
7746
8587
  setComments,
@@ -7748,10 +8589,8 @@ export {
7748
8589
  setSelection,
7749
8590
  setStyle,
7750
8591
  singleValueFacet,
7751
- smoothScroll,
7752
- stackItemContentEditorClassNames,
8592
+ snippets2 as snippets,
7753
8593
  staticCompletion,
7754
- streamer,
7755
8594
  submit,
7756
8595
  tabbable,
7757
8596
  table,
@@ -7770,7 +8609,12 @@ export {
7770
8609
  treeFacet,
7771
8610
  typeahead,
7772
8611
  typewriter,
8612
+ typewriterBypass,
8613
+ typewriterDrainingEffect,
7773
8614
  wrapWithCatch,
8615
+ xmlBlockDecoration,
8616
+ xmlElementLength,
8617
+ xmlFormatting,
7774
8618
  xmlTagContextEffect,
7775
8619
  xmlTagResetEffect,
7776
8620
  xmlTagUpdateEffect,