@dxos/ui-editor 0.8.4-main.3eb6e50203 → 0.8.4-main.3fbcb4aa9b

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 (173) hide show
  1. package/dist/lib/browser/index.mjs +1871 -1018
  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 +1869 -1015
  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 +5 -2
  19. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -1
  20. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  25. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  26. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  27. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
  28. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  29. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  30. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  31. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  32. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  33. package/dist/types/src/extensions/factories.d.ts +4 -3
  34. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  35. package/dist/types/src/extensions/factories.test.d.ts +2 -0
  36. package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
  37. package/dist/types/src/extensions/focus.d.ts +1 -1
  38. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  39. package/dist/types/src/extensions/index.d.ts +4 -3
  40. package/dist/types/src/extensions/index.d.ts.map +1 -1
  41. package/dist/types/src/extensions/json.d.ts.map +1 -1
  42. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -1
  44. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -0
  45. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  46. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  51. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  53. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
  54. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  55. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  56. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -1
  57. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
  58. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -1
  59. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
  60. package/dist/types/src/extensions/preview/preview.d.ts +2 -0
  61. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  62. package/dist/types/src/extensions/replacer.d.ts.map +1 -1
  63. package/dist/types/src/extensions/scroll-past-end.d.ts +3 -0
  64. package/dist/types/src/extensions/scroll-past-end.d.ts.map +1 -0
  65. package/dist/types/src/extensions/scroller.d.ts +68 -0
  66. package/dist/types/src/extensions/scroller.d.ts.map +1 -0
  67. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  68. package/dist/types/src/extensions/snippets.d.ts +10 -0
  69. package/dist/types/src/extensions/snippets.d.ts.map +1 -0
  70. package/dist/types/src/extensions/submit.d.ts.map +1 -1
  71. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -1
  72. package/dist/types/src/extensions/tags/fader.d.ts +12 -0
  73. package/dist/types/src/extensions/tags/fader.d.ts.map +1 -0
  74. package/dist/types/src/extensions/tags/index.d.ts +4 -1
  75. package/dist/types/src/extensions/tags/index.d.ts.map +1 -1
  76. package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
  77. package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
  78. package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
  79. package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
  80. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
  81. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
  82. package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
  83. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
  84. package/dist/types/src/extensions/tags/xml-tags.d.ts +28 -8
  85. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  86. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -1
  87. package/dist/types/src/index.d.ts +0 -1
  88. package/dist/types/src/index.d.ts.map +1 -1
  89. package/dist/types/src/styles/index.d.ts +0 -2
  90. package/dist/types/src/styles/index.d.ts.map +1 -1
  91. package/dist/types/src/styles/theme.d.ts +15 -0
  92. package/dist/types/src/styles/theme.d.ts.map +1 -1
  93. package/dist/types/src/types/types.d.ts +4 -4
  94. package/dist/types/src/types/types.d.ts.map +1 -1
  95. package/dist/types/src/util/cursor.d.ts +1 -1
  96. package/dist/types/src/util/cursor.d.ts.map +1 -1
  97. package/dist/types/src/util/debug.d.ts.map +1 -1
  98. package/dist/types/src/util/decorations.d.ts.map +1 -1
  99. package/dist/types/src/util/dom.d.ts.map +1 -1
  100. package/dist/types/src/util/facet.d.ts.map +1 -1
  101. package/dist/types/src/util/util.d.ts.map +1 -1
  102. package/dist/types/tsconfig.tsbuildinfo +1 -1
  103. package/package.json +36 -39
  104. package/src/defaults.ts +33 -20
  105. package/src/extensions/annotations.ts +1 -1
  106. package/src/extensions/auto-scroll.ts +234 -0
  107. package/src/extensions/autocomplete/placeholder.ts +37 -18
  108. package/src/extensions/automerge/automerge.test.tsx +37 -11
  109. package/src/extensions/automerge/automerge.ts +5 -7
  110. package/src/extensions/blocks.ts +5 -5
  111. package/src/extensions/comments.ts +5 -6
  112. package/src/extensions/dnd.ts +2 -2
  113. package/src/extensions/factories.test.ts +88 -0
  114. package/src/extensions/factories.ts +32 -15
  115. package/src/extensions/folding.ts +5 -22
  116. package/src/extensions/index.ts +4 -3
  117. package/src/extensions/markdown/action.ts +0 -1
  118. package/src/extensions/markdown/bundle.ts +23 -9
  119. package/src/extensions/markdown/decorate.ts +15 -12
  120. package/src/extensions/markdown/highlight.ts +15 -7
  121. package/src/extensions/markdown/link.ts +27 -33
  122. package/src/extensions/markdown/parser.test.ts +0 -1
  123. package/src/extensions/markdown/styles.ts +42 -9
  124. package/src/extensions/markdown/table.ts +24 -2
  125. package/src/extensions/outliner/outliner.test.ts +0 -1
  126. package/src/extensions/outliner/outliner.ts +3 -4
  127. package/src/extensions/outliner/tree.test.ts +0 -1
  128. package/src/extensions/preview/preview.ts +55 -8
  129. package/src/extensions/scroll-past-end.ts +32 -0
  130. package/src/extensions/scroller.ts +256 -0
  131. package/src/extensions/selection.ts +1 -1
  132. package/src/extensions/snippets.ts +67 -0
  133. package/src/extensions/tags/extended-markdown.test.ts +120 -2
  134. package/src/extensions/tags/extended-markdown.ts +80 -1
  135. package/src/extensions/tags/fader.ts +195 -0
  136. package/src/extensions/tags/index.ts +4 -1
  137. package/src/extensions/tags/testing/text.md +36 -0
  138. package/src/extensions/tags/testing/text.txt +35 -0
  139. package/src/extensions/tags/typewriter.test.ts +65 -0
  140. package/src/extensions/tags/typewriter.ts +594 -0
  141. package/src/extensions/tags/xml-block-decoration.ts +123 -0
  142. package/src/extensions/tags/xml-formatting.ts +125 -0
  143. package/src/extensions/tags/xml-tags.ts +186 -35
  144. package/src/extensions/tags/xml-util.test.ts +199 -24
  145. package/src/extensions/tags/xml-util.ts +62 -5
  146. package/src/index.ts +0 -1
  147. package/src/styles/index.ts +0 -2
  148. package/src/styles/theme.ts +124 -34
  149. package/src/types/types.ts +10 -2
  150. package/src/typings.d.ts +8 -0
  151. package/src/util/cursor.ts +1 -2
  152. package/dist/lib/browser/chunk-HL3YF6WC.mjs +0 -22
  153. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +0 -7
  154. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs +0 -24
  155. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +0 -7
  156. package/dist/types/src/extensions/autoscroll.d.ts +0 -20
  157. package/dist/types/src/extensions/autoscroll.d.ts.map +0 -1
  158. package/dist/types/src/extensions/scrolling.d.ts +0 -78
  159. package/dist/types/src/extensions/scrolling.d.ts.map +0 -1
  160. package/dist/types/src/extensions/tags/streamer.d.ts +0 -12
  161. package/dist/types/src/extensions/tags/streamer.d.ts.map +0 -1
  162. package/dist/types/src/extensions/typewriter.d.ts +0 -10
  163. package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
  164. package/dist/types/src/styles/markdown.d.ts +0 -8
  165. package/dist/types/src/styles/markdown.d.ts.map +0 -1
  166. package/dist/types/src/styles/tokens.d.ts +0 -3
  167. package/dist/types/src/styles/tokens.d.ts.map +0 -1
  168. package/src/extensions/autoscroll.ts +0 -165
  169. package/src/extensions/scrolling.ts +0 -189
  170. package/src/extensions/tags/streamer.ts +0 -243
  171. package/src/extensions/typewriter.ts +0 -68
  172. package/src/styles/markdown.ts +0 -26
  173. 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
 
@@ -360,30 +363,37 @@ var insertAtLineStart = (view, from, insert) => {
360
363
  };
361
364
 
362
365
  // src/extensions/autocomplete/placeholder.ts
363
- var placeholder = ({ content, delay = 3e3 }) => {
366
+ var placeholder = ({ content, delay = 3e3, focusOnly = false }) => {
364
367
  const plugin = ViewPlugin3.fromClass(class {
365
368
  _timeout;
366
369
  _decorations = Decoration3.none;
367
370
  update(update2) {
371
+ if (!update2.docChanged && !update2.selectionSet && !update2.focusChanged) {
372
+ return;
373
+ }
368
374
  if (this._timeout) {
369
375
  window.clearTimeout(this._timeout);
370
376
  this._timeout = void 0;
371
377
  }
378
+ this._decorations = Decoration3.none;
379
+ if (focusOnly && !update2.view.hasFocus) {
380
+ return;
381
+ }
372
382
  const activeLine = update2.view.state.doc.lineAt(update2.view.state.selection.main.head);
373
- const isEmpty = activeLine.text.trim() === "";
374
- if (isEmpty) {
375
- const lineStart = activeLine.from;
376
- this._timeout = setTimeout(() => {
377
- this._decorations = Decoration3.set([
378
- Decoration3.widget({
379
- widget: new PlaceholderWidget(content),
380
- side: 1
381
- }).range(lineStart)
382
- ]);
383
- update2.view.update([]);
384
- }, delay);
383
+ if (activeLine.text.trim() !== "") {
384
+ return;
385
385
  }
386
- this._decorations = Decoration3.none;
386
+ const lineStart = activeLine.from;
387
+ const view = update2.view;
388
+ this._timeout = setTimeout(() => {
389
+ this._decorations = Decoration3.set([
390
+ Decoration3.widget({
391
+ widget: new PlaceholderWidget(content),
392
+ side: 1
393
+ }).range(lineStart)
394
+ ]);
395
+ view.update([]);
396
+ }, delay);
387
397
  }
388
398
  destroy() {
389
399
  if (this._timeout) {
@@ -501,228 +511,339 @@ var typeahead = ({ onComplete } = {}) => {
501
511
  ];
502
512
  };
503
513
 
504
- // src/extensions/autoscroll.ts
514
+ // src/extensions/auto-scroll.ts
505
515
  import { StateEffect as StateEffect2 } from "@codemirror/state";
506
516
  import { EditorView as EditorView5, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
507
- import { debounce } from "@dxos/async";
517
+ import { addEventListener, combine, throttle } from "@dxos/async";
508
518
  import { Domino } from "@dxos/ui";
519
+ import { getSize } from "@dxos/ui-theme";
509
520
 
510
- // src/extensions/scrolling.ts
521
+ // src/extensions/scroller.ts
511
522
  import { StateEffect } from "@codemirror/state";
512
523
  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 {
524
+ import { log as log2 } from "@dxos/log";
525
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/scroller.ts";
526
+ var scrollerLineEffect = StateEffect.define();
527
+ var scrollerCrawlEffect = StateEffect.define();
528
+ var scrollToLine = (view, options) => {
529
+ view.dispatch({
530
+ effects: scrollerLineEffect.of(options)
531
+ });
532
+ };
533
+ var scroller = ({ overScroll = 0 } = {}) => {
534
+ const scrollPlugin = ViewPlugin5.fromClass(class ScrollerPlugin {
516
535
  view;
536
+ crawler;
517
537
  constructor(view) {
518
538
  this.view = view;
539
+ this.crawler = createCrawler(this.view);
519
540
  }
520
541
  // No-op.
521
542
  destroy() {
543
+ this.crawler.cancel();
522
544
  }
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;
545
+ cancel() {
546
+ this.crawler.cancel();
547
+ }
548
+ crawl(start = false) {
549
+ if (start) {
550
+ this.crawler.scroll();
558
551
  } else {
559
- targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + animOffset;
552
+ this.crawler.cancel();
560
553
  }
561
- const clampedScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
562
- this.animateScroll(scroller, clampedScrollTop);
563
554
  }
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;
555
+ scroll({ line, offset = 0, position, behavior = "instant" }) {
556
+ const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
557
+ const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
558
+ const doc = this.view.state.doc;
559
+ let targetScrollTop = scrollHeight - clientHeight + offset;
560
+ if (line >= 0 && line <= doc.lines - 1) {
561
+ const lineStart = doc.line(line + 1).from;
562
+ const coords = this.view.coordsAtPos(lineStart);
563
+ if (coords) {
564
+ const currentScrollTop = scrollTop;
565
+ const maxScrollTop = scrollHeight - clientHeight;
566
+ if (position === "end") {
567
+ targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
568
+ } else {
569
+ targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
570
+ }
571
+ targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
572
+ }
570
573
  }
571
- element.scrollTo({
572
- top: targetScrollTop,
573
- behavior: "smooth"
574
+ requestAnimationFrame(() => {
575
+ this.view.scrollDOM.scrollTo({
576
+ top: targetScrollTop
577
+ });
574
578
  });
575
579
  }
576
580
  });
577
581
  return [
578
582
  scrollPlugin,
579
- // Update listener to handle scroll effects.
583
+ // Listen for effect.
580
584
  EditorView4.updateListener.of((update2) => {
581
585
  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
- });
586
+ try {
587
+ const plugin = update2.view.plugin(scrollPlugin);
588
+ if (plugin) {
589
+ for (const effect of transaction.effects) {
590
+ if (effect.is(scrollerCrawlEffect)) {
591
+ plugin.crawl(effect.value);
592
+ } else if (effect.is(scrollerLineEffect)) {
593
+ plugin.scroll(effect.value);
594
+ }
592
595
  }
593
596
  }
597
+ } catch (err) {
598
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 91, S: void 0 });
594
599
  }
595
600
  });
601
+ }),
602
+ // Styles.
603
+ EditorView4.theme({
604
+ ".cm-scroller": {
605
+ overflowY: "scroll",
606
+ // Browser scroll-anchoring: when widgets above the viewport resize (e.g. tool blocks
607
+ // expanding their TogglePanel), the browser picks a stable element near the viewport
608
+ // top and adjusts `scrollTop` so the user's view doesn't jump. Auto-scroll's pinning
609
+ // logic still has the final word when pinned (forces scrollTop to scrollHeight).
610
+ overflowAnchor: "auto"
611
+ },
612
+ ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
613
+ display: "none"
614
+ },
615
+ ".cm-scroller::-webkit-scrollbar-thumb": {
616
+ background: "transparent",
617
+ transition: "background 0.15s"
618
+ },
619
+ "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
620
+ background: "var(--color-scrollbar-thumb)"
621
+ },
622
+ // Spacer below the last text line. Implemented as a real block pseudo-element
623
+ // (rather than `padding-bottom` on `.cm-content`) so it materializes in the
624
+ // scroller's `scrollHeight` regardless of how `padding` is reset by the base
625
+ // theme or downstream classes — this is what gives auto-scroll its head-room
626
+ // so the last line stays `overScroll` px above the viewport bottom.
627
+ ".cm-content::after": {
628
+ content: '""',
629
+ display: "block",
630
+ height: `${overScroll}px`
631
+ },
632
+ ".cm-scroll-button": {
633
+ position: "absolute",
634
+ bottom: "0.5rem",
635
+ right: "1rem"
636
+ }
596
637
  })
597
638
  ];
598
639
  };
599
- var scrollToLine = (view, line, options) => {
600
- view.dispatch({
601
- effects: scrollToLineEffect.of({
602
- line,
603
- options
604
- })
605
- });
606
- };
640
+ function createCrawler(view, omega = 5, snapThreshold = 5, snapVelocity = 50) {
641
+ const el = view.scrollDOM;
642
+ let currentTop = 0;
643
+ let velocity = 0;
644
+ let rafId = null;
645
+ let lastTime = 0;
646
+ function frame(now) {
647
+ const dt = lastTime === 0 ? 1 / 60 : Math.min(0.1, (now - lastTime) / 1e3);
648
+ lastTime = now;
649
+ const targetTop = el.scrollHeight - el.clientHeight;
650
+ const delta = targetTop - currentTop;
651
+ if (Math.abs(delta) < snapThreshold && Math.abs(velocity) < snapVelocity) {
652
+ el.scrollTop = targetTop;
653
+ currentTop = targetTop;
654
+ velocity = 0;
655
+ rafId = null;
656
+ lastTime = 0;
657
+ return;
658
+ }
659
+ const accel = omega * omega * delta - 2 * omega * velocity;
660
+ velocity += accel * dt;
661
+ currentTop += velocity * dt;
662
+ el.scrollTop = currentTop;
663
+ rafId = requestAnimationFrame(frame);
664
+ }
665
+ return {
666
+ scroll: () => {
667
+ if (rafId === null) {
668
+ currentTop = el.scrollTop;
669
+ lastTime = 0;
670
+ rafId = requestAnimationFrame(frame);
671
+ }
672
+ },
673
+ cancel: () => {
674
+ if (rafId !== null) {
675
+ cancelAnimationFrame(rafId);
676
+ velocity = 0;
677
+ lastTime = 0;
678
+ rafId = null;
679
+ }
680
+ }
681
+ };
682
+ }
607
683
 
608
- // src/extensions/autoscroll.ts
609
- var scrollToBottomEffect = StateEffect2.define();
610
- var autoScroll = ({ autoScroll: autoScroll2 = true, threshold = 100, throttleDelay = 1e3, onAutoScroll } = {}) => {
684
+ // src/extensions/auto-scroll.ts
685
+ var autoScrollEffect = StateEffect2.define();
686
+ var autoScroll = ({ scrollOnResize = true } = {}) => {
611
687
  let buttonContainer;
612
- let hideTimeout;
613
- let lastScrollTop = 0;
614
688
  let isPinned = true;
615
- const setPinned = (pin) => {
616
- isPinned = pin;
617
- buttonContainer?.classList.toggle("opacity-0", pin);
689
+ let jumpPending = false;
690
+ let enabled = true;
691
+ let firstUpdate = true;
692
+ const setPinned = (pinned) => {
693
+ buttonContainer?.classList.toggle("opacity-0", pinned);
694
+ isPinned = pinned;
618
695
  };
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
- });
644
- };
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
696
  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);
697
+ // Update listener for scrolling when content changes.
698
+ EditorView5.updateListener.of((update2) => {
699
+ const { view, heightChanged, state, startState } = update2;
700
+ for (const tr of update2.transactions) {
701
+ for (const effect of tr.effects) {
702
+ if (effect.is(autoScrollEffect)) {
703
+ enabled = effect.value;
704
+ if (enabled) {
705
+ setPinned(true);
706
+ view.dispatch({
707
+ effects: scrollerCrawlEffect.of(true)
708
+ });
709
+ } else {
710
+ view.dispatch({
711
+ effects: scrollerCrawlEffect.of(false)
712
+ });
713
+ }
659
714
  }
660
715
  }
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);
716
+ }
717
+ if (!enabled) {
718
+ return;
719
+ }
720
+ if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
721
+ firstUpdate = false;
722
+ jumpPending = true;
723
+ requestAnimationFrame(() => {
724
+ view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
725
+ jumpPending = false;
726
+ });
727
+ return;
728
+ }
729
+ firstUpdate = false;
730
+ if (jumpPending) {
731
+ return;
732
+ }
733
+ if (heightChanged) {
734
+ if (isPinned) {
735
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
736
+ const delta = scrollHeight - scrollTop - clientHeight;
737
+ if (delta > 0) {
738
+ setPinned(true);
739
+ view.dispatch({
740
+ effects: scrollerCrawlEffect.of(true)
741
+ });
742
+ } else if (delta < -1) {
743
+ setPinned(false);
744
+ }
745
+ } else {
746
+ if (state.doc.length === 0) {
747
+ setPinned(true);
673
748
  }
674
- } else if (distanceFromBottom < 0) {
675
- setPinned(false);
676
749
  }
677
750
  }
678
751
  }),
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
- }
752
+ // Re-pin and jump to bottom when the scroll container itself resizes (e.g. sidebar toggle,
753
+ // window resize). Doc-driven height changes are handled by the updateListener above; this
754
+ // observer covers the case where the viewport changes while the doc length is unchanged.
755
+ scrollOnResize ? ViewPlugin6.fromClass(class {
756
+ observer;
757
+ firstObservation = true;
758
+ destroyed = false;
759
+ constructor(view) {
760
+ const onResize = throttle(() => {
761
+ if (this.destroyed || !enabled) {
762
+ return;
763
+ }
764
+ setPinned(true);
765
+ requestAnimationFrame(() => {
766
+ if (this.destroyed) {
767
+ return;
768
+ }
769
+ view.scrollDOM.scrollTo({
770
+ top: view.scrollDOM.scrollHeight,
771
+ behavior: "instant"
772
+ });
773
+ view.dispatch({
774
+ effects: scrollerCrawlEffect.of(false)
775
+ });
776
+ });
777
+ }, 50);
778
+ this.observer = new ResizeObserver(() => {
779
+ if (this.firstObservation) {
780
+ this.firstObservation = false;
781
+ return;
782
+ }
783
+ onResize();
784
+ });
785
+ this.observer.observe(view.scrollDOM);
786
+ }
787
+ destroy() {
788
+ this.destroyed = true;
789
+ this.observer.disconnect();
790
+ }
791
+ }) : [],
792
+ // Detect user scroll and unpin (or re-pin if scrolled to the bottom).
793
+ ViewPlugin6.fromClass(class {
794
+ cleanup;
795
+ constructor(view) {
796
+ this.cleanup = createUserScrollDetector(view.scrollDOM, throttle(() => {
797
+ requestAnimationFrame(() => {
798
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
799
+ const delta = scrollHeight - scrollTop - clientHeight;
800
+ const pinned = delta === 0;
801
+ setPinned(pinned);
802
+ if (!pinned) {
803
+ view.dispatch({
804
+ effects: scrollerCrawlEffect.of(false)
805
+ });
806
+ }
807
+ });
808
+ }, 500));
809
+ }
810
+ destroy() {
811
+ this.cleanup();
690
812
  }
691
813
  }),
692
814
  // Scroll button.
693
815
  ViewPlugin6.fromClass(class {
694
816
  constructor(view) {
695
- const icon = Domino.of("dx-icon").attributes({
817
+ const icon = Domino.of("dx-icon").classNames(getSize(4)).attributes({
696
818
  icon: "ph--arrow-down--regular"
697
819
  });
698
- const button = Domino.of("button").classNames("dx-button bg-accentSurface").attributes({
820
+ const button = Domino.of("button").classNames("dx-button bg-accent-surface").attributes({
699
821
  "data-density": "fine"
700
- }).children(icon).on("click", () => {
701
- scrollToBottom(view);
822
+ }).append(icon).on("click", () => {
823
+ setPinned(true);
824
+ view.dispatch({
825
+ effects: scrollerLineEffect.of({
826
+ line: -1,
827
+ position: "end",
828
+ behavior: "smooth"
829
+ })
830
+ });
702
831
  });
703
- buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").children(button).root;
832
+ buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").append(button).root;
704
833
  view.scrollDOM.parentElement.appendChild(buttonContainer);
705
834
  }
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
835
  })
724
836
  ];
725
837
  };
838
+ function createUserScrollDetector(element, onUserScroll) {
839
+ return combine(addEventListener(element, "wheel", () => onUserScroll(), {
840
+ passive: true
841
+ }), addEventListener(element, "pointerdown", (event) => {
842
+ if (event.clientX > element.getBoundingClientRect().right - (element.offsetWidth - element.clientWidth)) {
843
+ onUserScroll();
844
+ }
845
+ }));
846
+ }
726
847
 
727
848
  // src/extensions/automerge/automerge.ts
728
849
  import { next as A3 } from "@automerge/automerge";
@@ -736,32 +857,22 @@ var initialSync = Transaction.userEvent.of("initial.sync");
736
857
 
737
858
  // src/extensions/automerge/cursor.ts
738
859
  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";
860
+ import { log as log3 } from "@dxos/log";
861
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/cursor.ts";
741
862
  var cursorConverter = (accessor) => ({
742
863
  toCursor: (pos, assoc) => {
743
864
  try {
744
865
  return toCursor(accessor, pos, assoc);
745
866
  } 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
- });
867
+ log3.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 11, S: void 0 });
752
868
  return "";
753
869
  }
754
870
  },
755
- fromCursor: (cursor2) => {
871
+ fromCursor: (cursor) => {
756
872
  try {
757
- return fromCursor(accessor, cursor2);
873
+ return fromCursor(accessor, cursor);
758
874
  } 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
- });
875
+ log3.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 19, S: void 0 });
765
876
  return 0;
766
877
  }
767
878
  }
@@ -782,7 +893,7 @@ var isReconcile = (tr) => {
782
893
 
783
894
  // src/extensions/automerge/sync.ts
784
895
  import { next as A2 } from "@automerge/automerge";
785
- import { log as log3 } from "@dxos/log";
896
+ import { log as log4 } from "@dxos/log";
786
897
 
787
898
  // src/extensions/automerge/update-automerge.ts
788
899
  import { next as A } from "@automerge/automerge";
@@ -923,7 +1034,7 @@ var charPath = (textPath, candidatePath) => {
923
1034
  };
924
1035
 
925
1036
  // src/extensions/automerge/sync.ts
926
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/sync.ts";
1037
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/sync.ts";
927
1038
  var Syncer = class {
928
1039
  _handle;
929
1040
  _state;
@@ -946,12 +1057,7 @@ var Syncer = class {
946
1057
  this._pending = false;
947
1058
  }
948
1059
  onEditorChange(view) {
949
- log3("onEditorChange", void 0, {
950
- F: __dxlog_file3,
951
- L: 45,
952
- S: this,
953
- C: (f, a) => f(...a)
954
- });
1060
+ log4("onEditorChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 35, S: this });
955
1061
  const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
956
1062
  const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
957
1063
  if (newHeads) {
@@ -962,12 +1068,7 @@ var Syncer = class {
962
1068
  }
963
1069
  }
964
1070
  onAutomergeChange(view) {
965
- log3("onAutomergeChange", void 0, {
966
- F: __dxlog_file3,
967
- L: 60,
968
- S: this,
969
- C: (f, a) => f(...a)
970
- });
1071
+ log4("onAutomergeChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 47, S: this });
971
1072
  const oldHeads = getLastHeads(view.state, this._state);
972
1073
  const newHeads = A2.getHeads(this._handle.doc());
973
1074
  const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.doc(), oldHeads, newHeads);
@@ -1029,6 +1130,17 @@ var automerge = (accessor) => {
1029
1130
  const value = DocAccessor.getValue(accessor);
1030
1131
  const current = this._view.state.doc.toString();
1031
1132
  if (value !== current) {
1133
+ this._view.dispatch({
1134
+ changes: {
1135
+ from: 0,
1136
+ to: this._view.state.doc.length,
1137
+ insert: value
1138
+ },
1139
+ annotations: [
1140
+ initialSync,
1141
+ reconcileAnnotation.of(true)
1142
+ ]
1143
+ });
1032
1144
  }
1033
1145
  });
1034
1146
  }
@@ -1056,7 +1168,7 @@ import { Annotation as Annotation2, RangeSet } from "@codemirror/state";
1056
1168
  import { Decoration as Decoration5, EditorView as EditorView7, ViewPlugin as ViewPlugin8, WidgetType as WidgetType3 } from "@codemirror/view";
1057
1169
  import { Event } from "@dxos/async";
1058
1170
  import { Context } from "@dxos/context";
1059
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness.ts";
1171
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness.ts";
1060
1172
  var dummyProvider = {
1061
1173
  remoteStateChange: new Event(),
1062
1174
  open: () => {
@@ -1079,10 +1191,7 @@ var awareness = (provider = dummyProvider) => {
1079
1191
  ];
1080
1192
  };
1081
1193
  var RemoteSelectionsDecorator = class {
1082
- _ctx = new Context(void 0, {
1083
- F: __dxlog_file4,
1084
- L: 80
1085
- });
1194
+ _ctx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 33 });
1086
1195
  _cursorConverter;
1087
1196
  _provider;
1088
1197
  _lastAnchor;
@@ -1293,8 +1402,8 @@ var styles = EditorView7.theme({
1293
1402
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
1294
1403
  import { Context as Context2 } from "@dxos/context";
1295
1404
  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";
1405
+ import { log as log5 } from "@dxos/log";
1406
+ var __dxlog_file6 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness-provider.ts";
1298
1407
  var DEBOUNCE_INTERVAL = 100;
1299
1408
  var SpaceAwarenessProvider = class {
1300
1409
  _remoteStates = /* @__PURE__ */ new Map();
@@ -1313,10 +1422,7 @@ var SpaceAwarenessProvider = class {
1313
1422
  this._info = info;
1314
1423
  }
1315
1424
  open() {
1316
- this._ctx = new Context2(void 0, {
1317
- F: __dxlog_file5,
1318
- L: 57
1319
- });
1425
+ this._ctx = new Context2(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 28 });
1320
1426
  this._postTask = new DeferredTask(this._ctx, async () => {
1321
1427
  if (this._localState) {
1322
1428
  await this._messenger.postMessage(this._channel, {
@@ -1341,14 +1447,9 @@ var SpaceAwarenessProvider = class {
1341
1447
  void this._messenger.postMessage(this._channel, {
1342
1448
  kind: "query"
1343
1449
  }).catch((err) => {
1344
- log4.debug("failed to query awareness", {
1450
+ log5.debug("failed to query awareness", {
1345
1451
  err
1346
- }, {
1347
- F: __dxlog_file5,
1348
- L: 91,
1349
- S: this,
1350
- C: (f, a) => f(...a)
1351
- });
1452
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 57, S: this });
1352
1453
  });
1353
1454
  }
1354
1455
  close() {
@@ -1360,15 +1461,7 @@ var SpaceAwarenessProvider = class {
1360
1461
  return Array.from(this._remoteStates.values());
1361
1462
  }
1362
1463
  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
- });
1464
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 71, S: this, A: ["this._postTask", ""] });
1372
1465
  this._localState = {
1373
1466
  peerId: this._peerId,
1374
1467
  position,
@@ -1377,27 +1470,11 @@ var SpaceAwarenessProvider = class {
1377
1470
  this._postTask.schedule();
1378
1471
  }
1379
1472
  _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
- });
1473
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 80, S: this, A: ["this._postTask", ""] });
1389
1474
  this._postTask.schedule();
1390
1475
  }
1391
1476
  _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
- });
1477
+ invariant(message.kind === "post", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 84, S: this, A: ["message.kind === 'post'", ""] });
1401
1478
  this._remoteStates.set(message.state.peerId, message.state);
1402
1479
  this.remoteStateChange.emit();
1403
1480
  }
@@ -1406,9 +1483,9 @@ var SpaceAwarenessProvider = class {
1406
1483
  // src/extensions/blast.ts
1407
1484
  import { EditorView as EditorView8, keymap as keymap3 } from "@codemirror/view";
1408
1485
  import defaultsDeep from "lodash.defaultsdeep";
1409
- import { throttle } from "@dxos/async";
1486
+ import { throttle as throttle2 } from "@dxos/async";
1410
1487
  import { invariant as invariant2 } from "@dxos/invariant";
1411
- var __dxlog_file6 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/blast.ts";
1488
+ var __dxlog_file7 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/blast.ts";
1412
1489
  var defaultOptions = {
1413
1490
  effect: 2,
1414
1491
  maxParticles: 200,
@@ -1526,15 +1603,7 @@ var Blaster = class {
1526
1603
  return this._node;
1527
1604
  }
1528
1605
  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
- });
1606
+ invariant2(!this._canvas && !this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 134, S: this, A: ["!this._canvas && !this._ctx", ""] });
1538
1607
  this._canvas = document.createElement("canvas");
1539
1608
  this._canvas.id = "code-blast-canvas";
1540
1609
  this._canvas.style.position = "absolute";
@@ -1563,15 +1632,7 @@ var Blaster = class {
1563
1632
  }
1564
1633
  }
1565
1634
  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
- });
1635
+ invariant2(this._canvas && this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 166, S: this, A: ["this._canvas && this._ctx", ""] });
1575
1636
  this._running = true;
1576
1637
  this.loop();
1577
1638
  }
@@ -1598,11 +1659,11 @@ var Blaster = class {
1598
1659
  this.drawParticles();
1599
1660
  requestAnimationFrame(this.loop.bind(this));
1600
1661
  }
1601
- shake = throttle(({ time }) => {
1662
+ shake = throttle2(({ time }) => {
1602
1663
  this._shakeTime = this._shakeTimeMax || time;
1603
1664
  this._shakeTimeMax = time;
1604
1665
  }, 100);
1605
- spawn = throttle(({ element, point }) => {
1666
+ spawn = throttle2(({ element, point }) => {
1606
1667
  const color = getRGBComponents(element, this._options.color);
1607
1668
  const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
1608
1669
  const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
@@ -1776,11 +1837,11 @@ var blocks = () => [
1776
1837
  ".cm-line.block-line": {
1777
1838
  paddingLeft: "0.75rem",
1778
1839
  paddingRight: "0.75rem",
1779
- borderLeft: "1px solid var(--dx-subduedSeparator)",
1780
- borderRight: "1px solid var(--dx-subduedSeparator)"
1840
+ borderLeft: "1px solid var(--color-subdued-separator)",
1841
+ borderRight: "1px solid var(--color-subdued-separator)"
1781
1842
  },
1782
1843
  ".cm-line.block-single": {
1783
- border: "1px solid var(--dx-subduedSeparator)",
1844
+ border: "1px solid var(--color-subdued-separator)",
1784
1845
  borderRadius: "6px",
1785
1846
  paddingTop: "0.5rem",
1786
1847
  paddingBottom: "0.5rem",
@@ -1788,7 +1849,7 @@ var blocks = () => [
1788
1849
  marginBottom: "0.5rem"
1789
1850
  },
1790
1851
  ".cm-line.block-first": {
1791
- borderTop: "1px solid var(--dx-subduedSeparator)",
1852
+ borderTop: "1px solid var(--color-subdued-separator)",
1792
1853
  borderTopLeftRadius: "6px",
1793
1854
  borderTopRightRadius: "6px",
1794
1855
  paddingTop: "0.5rem",
@@ -1796,7 +1857,7 @@ var blocks = () => [
1796
1857
  },
1797
1858
  ".cm-line.block-middle": {},
1798
1859
  ".cm-line.block-last": {
1799
- borderBottom: "1px solid var(--dx-subduedSeparator)",
1860
+ borderBottom: "1px solid var(--color-subdued-separator)",
1800
1861
  borderBottomLeftRadius: "6px",
1801
1862
  borderBottomRightRadius: "6px",
1802
1863
  paddingBottom: "0.5rem",
@@ -1808,8 +1869,8 @@ var blocks = () => [
1808
1869
  // src/extensions/bookmarks.ts
1809
1870
  import { Prec as Prec3, StateEffect as StateEffect4, StateField as StateField2 } from "@codemirror/state";
1810
1871
  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";
1872
+ import { log as log6 } from "@dxos/log";
1873
+ var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/bookmarks.ts";
1813
1874
  var addBookmark = StateEffect4.define();
1814
1875
  var removeBookmark = StateEffect4.define();
1815
1876
  var clearBookmarks = StateEffect4.define();
@@ -1821,12 +1882,7 @@ var bookmarks = () => {
1821
1882
  key: "Mod-ArrowUp",
1822
1883
  run: (view) => {
1823
1884
  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
- });
1885
+ log6("up", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 18, S: void 0 });
1830
1886
  return true;
1831
1887
  }
1832
1888
  },
@@ -1834,12 +1890,7 @@ var bookmarks = () => {
1834
1890
  key: "Mod-ArrowDown",
1835
1891
  run: (view) => {
1836
1892
  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
- });
1893
+ log6("down", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 26, S: void 0 });
1843
1894
  return true;
1844
1895
  }
1845
1896
  }
@@ -1879,19 +1930,19 @@ import { invertedEffects } from "@codemirror/commands";
1879
1930
  import { StateEffect as StateEffect5, StateField as StateField3 } from "@codemirror/state";
1880
1931
  import { Decoration as Decoration7, EditorView as EditorView11, ViewPlugin as ViewPlugin10, hoverTooltip, keymap as keymap6 } from "@codemirror/view";
1881
1932
  import sortBy from "lodash.sortby";
1882
- import { debounce as debounce3 } from "@dxos/async";
1883
- import { log as log6 } from "@dxos/log";
1933
+ import { debounce as debounce2 } from "@dxos/async";
1934
+ import { log as log7 } from "@dxos/log";
1884
1935
  import { isNonNullable } from "@dxos/util";
1885
1936
 
1886
1937
  // src/extensions/selection.ts
1887
1938
  import { Transaction as Transaction3 } from "@codemirror/state";
1888
1939
  import { EditorView as EditorView10, keymap as keymap5 } from "@codemirror/view";
1889
- import { debounce as debounce2 } from "@dxos/async";
1940
+ import { debounce } from "@dxos/async";
1890
1941
  import { invariant as invariant3 } from "@dxos/invariant";
1891
1942
  import { isTruthy } from "@dxos/util";
1892
- var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/selection.ts";
1943
+ var __dxlog_file9 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/selection.ts";
1893
1944
  var documentId = singleValueFacet();
1894
- var stateRestoreAnnotation = "dxos.org/cm/state-restore";
1945
+ var stateRestoreAnnotation = "org.dxos.cm.state-restore";
1895
1946
  var createEditorStateTransaction = ({ scrollTo, selection }) => {
1896
1947
  return {
1897
1948
  selection,
@@ -1904,33 +1955,17 @@ var createEditorStateTransaction = ({ scrollTo, selection }) => {
1904
1955
  };
1905
1956
  var createEditorStateStore = (keyPrefix) => ({
1906
1957
  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
- });
1958
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 26, S: void 0, A: ["id", ""] });
1916
1959
  const state = localStorage.getItem(`${keyPrefix}/${id}`);
1917
1960
  return state ? JSON.parse(state) : void 0;
1918
1961
  },
1919
1962
  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
- });
1963
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 31, S: void 0, A: ["id", ""] });
1929
1964
  localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
1930
1965
  }
1931
1966
  });
1932
1967
  var selectionState = ({ getState, setState } = {}) => {
1933
- const setStateDebounced = debounce2(setState, 1e3);
1968
+ const setStateDebounced = debounce(setState, 1e3);
1934
1969
  return [
1935
1970
  // TODO(burdon): Track scrolling (currently only updates when cursor moves).
1936
1971
  // EditorView.domEventHandlers({
@@ -1977,7 +2012,7 @@ var selectionState = ({ getState, setState } = {}) => {
1977
2012
  };
1978
2013
 
1979
2014
  // src/extensions/comments.ts
1980
- var __dxlog_file9 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
2015
+ var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
1981
2016
  var setComments = StateEffect5.define();
1982
2017
  var setSelection = StateEffect5.define();
1983
2018
  var setCommentState = StateEffect5.define();
@@ -2022,14 +2057,14 @@ var commentsState = StateField3.define({
2022
2057
  var styles2 = EditorView11.theme({
2023
2058
  ".cm-comment, .cm-comment-current": {
2024
2059
  padding: "3px 0",
2025
- color: "var(--dx-cmCommentText)",
2026
- backgroundColor: "var(--dx-cmCommentSurface)"
2060
+ color: "var(--color-cm-comment-text)",
2061
+ backgroundColor: "var(--color-cm-comment-surface)"
2027
2062
  },
2028
2063
  ".cm-comment > span, .cm-comment-current > span": {
2029
2064
  boxDecorationBreak: "clone",
2030
- boxShadow: "0 0 1px 3px var(--dx-cmCommentSurface)",
2031
- backgroundColor: "var(--dx-cmCommentSurface)",
2032
- color: "var(--dx-cmCommentText)",
2065
+ boxShadow: "0 0 1px 3px var(--color-cm-comment-surface)",
2066
+ backgroundColor: "var(--color-cm-comment-surface)",
2067
+ color: "var(--color-cm-comment-text)",
2033
2068
  cursor: "pointer"
2034
2069
  }
2035
2070
  });
@@ -2047,12 +2082,7 @@ var commentsDecorations = EditorView11.decorations.compute([
2047
2082
  const decorations2 = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
2048
2083
  const range = comment.range;
2049
2084
  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
- });
2085
+ log7.warn("Invalid range:", range, { "~LogMeta": "~LogMeta", F: __dxlog_file10, L: 93, S: void 0 });
2056
2086
  return void 0;
2057
2087
  } else if (range.from === range.to) {
2058
2088
  return void 0;
@@ -2165,10 +2195,10 @@ var trackPastedComments = (onUpdate) => {
2165
2195
  const { comments: comments2 } = update2.startState.field(commentsState);
2166
2196
  const exists = comments2.some((c) => c.comment.id === comment.id && c.range.from < c.range.to);
2167
2197
  if (!exists) {
2168
- const cursor2 = Cursor.getCursorFromRange(update2.state, comment);
2198
+ const cursor = Cursor.getCursorFromRange(update2.state, comment);
2169
2199
  onUpdate({
2170
2200
  id: comment.id,
2171
- cursor: cursor2
2201
+ cursor
2172
2202
  });
2173
2203
  }
2174
2204
  }
@@ -2197,13 +2227,13 @@ var createComment = (view) => {
2197
2227
  }
2198
2228
  });
2199
2229
  }
2200
- const cursor2 = Cursor.getCursorFromRange(view.state, {
2230
+ const cursor = Cursor.getCursorFromRange(view.state, {
2201
2231
  from,
2202
2232
  to
2203
2233
  });
2204
- if (cursor2) {
2234
+ if (cursor) {
2205
2235
  options.onCreate?.({
2206
- cursor: cursor2,
2236
+ cursor,
2207
2237
  from,
2208
2238
  location: view.coordsAtPos(from)
2209
2239
  });
@@ -2214,7 +2244,7 @@ var createComment = (view) => {
2214
2244
  var optionsFacet = singleValueFacet();
2215
2245
  var comments = (options = {}) => {
2216
2246
  const { key: shortcut = "meta-'" } = options;
2217
- const handleSelect = debounce3((state) => options.onSelect?.(state), 200);
2247
+ const handleSelect = debounce2((state) => options.onSelect?.(state), 200);
2218
2248
  return [
2219
2249
  optionsFacet.of(options),
2220
2250
  options.id ? documentId.of(options.id) : void 0,
@@ -2392,9 +2422,9 @@ var createExternalCommentSync = (id, subscribe, getComments) => ViewPlugin10.fro
2392
2422
  // src/extensions/debug.ts
2393
2423
  import { syntaxTree } from "@codemirror/language";
2394
2424
  import { StateField as StateField4 } from "@codemirror/state";
2395
- var debugNodeLogger = (log11 = console.log) => {
2425
+ var debugNodeLogger = (log12 = console.log) => {
2396
2426
  const logTokens = (state) => syntaxTree(state).iterate({
2397
- enter: (node) => log11(node.type)
2427
+ enter: (node) => log12(node.type)
2398
2428
  });
2399
2429
  return StateField4.define({
2400
2430
  create: (state) => logTokens(state),
@@ -2429,8 +2459,8 @@ var dropFile = (options = {}) => {
2429
2459
  };
2430
2460
  var styles3 = EditorView12.theme({
2431
2461
  ".cm-dropCursor": {
2432
- borderLeft: "2px solid var(--dx-accentText)",
2433
- color: "var(--dx-accentText)",
2462
+ borderLeft: "2px solid var(--color-accent-text)",
2463
+ color: "var(--color-accent-text)",
2434
2464
  padding: "0 4px"
2435
2465
  },
2436
2466
  ".cm-dropCursor:after": {
@@ -2444,47 +2474,66 @@ import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap }
2444
2474
  import { HighlightStyle, bracketMatching, syntaxHighlighting } from "@codemirror/language";
2445
2475
  import { searchKeymap } from "@codemirror/search";
2446
2476
  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";
2477
+ import { EditorView as EditorView16, ViewPlugin as ViewPlugin12, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap7, lineNumbers, placeholder as placeholder2 } from "@codemirror/view";
2448
2478
  import { vscodeDarkStyle, vscodeLightStyle } from "@uiw/codemirror-theme-vscode";
2449
2479
  import defaultsDeep2 from "lodash.defaultsdeep";
2450
2480
  import { generateName } from "@dxos/display-name";
2451
- import { log as log7 } from "@dxos/log";
2481
+ import { log as log8 } from "@dxos/log";
2452
2482
  import { hexToHue, isTruthy as isTruthy2 } from "@dxos/util";
2453
2483
 
2454
- // src/styles/markdown.ts
2484
+ // src/styles/theme.ts
2485
+ import { EditorView as EditorView13 } from "@codemirror/view";
2455
2486
  import { mx as mx3 } from "@dxos/ui-theme";
2456
2487
  var headings = {
2457
- 1: "text-4xl",
2458
- 2: "text-3xl",
2459
- 3: "text-2xl",
2460
- 4: "text-xl",
2461
- 5: "text-lg",
2462
- 6: ""
2488
+ 1: {
2489
+ className: "text-3xl",
2490
+ fontSize: "var(--text-3xl)",
2491
+ lineHeight: "var(--text-4xl--line-height)"
2492
+ },
2493
+ 2: {
2494
+ className: "text-2xl",
2495
+ fontSize: "var(--text-2xl)",
2496
+ lineHeight: "var(--text-3xl--line-height)"
2497
+ },
2498
+ 3: {
2499
+ className: "text-xl",
2500
+ fontSize: "var(--text-xl)",
2501
+ lineHeight: "var(--text-2xl--line-height)"
2502
+ },
2503
+ 4: {
2504
+ className: "text-lg",
2505
+ fontSize: "var(--text-lg)",
2506
+ lineHeight: "var(--text-xl--line-height)"
2507
+ },
2508
+ 5: {
2509
+ className: "text-base",
2510
+ fontSize: "var(--text-base)",
2511
+ lineHeight: "var(--text-lg--line-height)"
2512
+ },
2513
+ 6: {
2514
+ className: "text-base",
2515
+ fontSize: "var(--text-base)",
2516
+ lineHeight: "var(--text-base--line-height)"
2517
+ }
2463
2518
  };
2519
+ var fontBody = '"Inter Variable", ui-sans-serif, system-ui, sans-serif';
2520
+ var fontMono = '"JetBrains Mono Variable", ui-monospace, "Cascadia Code", "Source Code Pro", monospace';
2464
2521
  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
- }
2522
+ code: "font-mono! cm-code-inline",
2523
+ codeMark: "font-mono! cm-code-mark",
2524
+ mark: "font-mono!",
2525
+ heading: (level) => ({
2526
+ className: mx3(headings[level].className, "font-light text-(--color-cm-heading-number)"),
2527
+ color: "var(--color-cm-heading) !important",
2528
+ lineHeight: headings[level].lineHeight,
2529
+ fontSize: headings[level].fontSize,
2530
+ fontWeight: "100 !important"
2531
+ })
2471
2532
  };
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
2533
  var baseTheme = EditorView13.baseTheme({
2534
+ /**
2535
+ * Outer frame.
2536
+ */
2488
2537
  "&": {},
2489
2538
  "&.cm-focused": {
2490
2539
  outline: "none"
@@ -2493,7 +2542,27 @@ var baseTheme = EditorView13.baseTheme({
2493
2542
  * Scroller
2494
2543
  */
2495
2544
  ".cm-scroller": {
2496
- overflowY: "auto"
2545
+ // Browser scroll-anchoring: see comment in `scroller.ts`. `auto` lets the browser pin a
2546
+ // stable element near the viewport top so widget resizes (e.g. tool-block TogglePanel
2547
+ // open/close) don't jump the user's view.
2548
+ overflowAnchor: "auto"
2549
+ },
2550
+ ".cm-scroller::-webkit-scrollbar": {
2551
+ width: "var(--scrollbar-size,8px)",
2552
+ height: "var(--scrollbar-size,8px)"
2553
+ },
2554
+ ".cm-scroller::-webkit-scrollbar-corner": {
2555
+ background: "transparent"
2556
+ },
2557
+ ".cm-scroller::-webkit-scrollbar-track": {
2558
+ background: "transparent"
2559
+ },
2560
+ ".cm-scroller::-webkit-scrollbar-thumb": {
2561
+ background: "transparent",
2562
+ transition: "background 0.15s"
2563
+ },
2564
+ "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
2565
+ background: "var(--color-scrollbar-thumb)"
2497
2566
  },
2498
2567
  /**
2499
2568
  * Content
@@ -2501,7 +2570,6 @@ var baseTheme = EditorView13.baseTheme({
2501
2570
  */
2502
2571
  ".cm-content": {
2503
2572
  padding: "unset",
2504
- lineHeight: "24px",
2505
2573
  color: "unset"
2506
2574
  },
2507
2575
  /**
@@ -2515,8 +2583,8 @@ var baseTheme = EditorView13.baseTheme({
2515
2583
  ".cm-gutter": {},
2516
2584
  ".cm-gutter.cm-lineNumbers": {
2517
2585
  paddingRight: "4px",
2518
- borderRight: "1px solid var(--dx-subduedSeparator)",
2519
- color: "var(--dx-subduedText)"
2586
+ borderRight: "1px solid var(--color-subdued-separator)",
2587
+ color: "var(--color-subdued)"
2520
2588
  },
2521
2589
  ".cm-gutter.cm-lineNumbers .cm-gutterElement": {
2522
2590
  minWidth: "40px"
@@ -2532,29 +2600,36 @@ var baseTheme = EditorView13.baseTheme({
2532
2600
  * Line.
2533
2601
  */
2534
2602
  ".cm-line": {
2535
- lineHeight: "24px",
2603
+ lineHeight: 1.5,
2536
2604
  paddingInline: 0
2537
2605
  },
2606
+ /**
2607
+ * Force all inline children to inherit line-height to prevent monospace font metrics
2608
+ * (JetBrains Mono ascent/descent) from inflating the line box beyond 24px.
2609
+ */
2610
+ ".cm-line *": {
2611
+ lineHeight: "inherit"
2612
+ },
2538
2613
  ".cm-activeLine": {
2539
- background: "var(--dx-cmActiveLine)"
2614
+ background: "var(--color-cm-active-line)"
2540
2615
  },
2541
2616
  /**
2542
2617
  * Cursor (layer).
2543
2618
  */
2544
2619
  ".cm-cursor, .cm-dropCursor": {
2545
- borderLeft: "2px solid var(--dx-cmCursor)"
2620
+ borderLeft: "2px solid var(--color-cm-cursor)"
2546
2621
  },
2547
2622
  ".cm-placeholder": {
2548
- color: "var(--dx-placeholder)"
2623
+ color: "var(--color-placeholder)"
2549
2624
  },
2550
2625
  /**
2551
2626
  * Selection (layer).
2552
2627
  */
2553
2628
  ".cm-selectionBackground": {
2554
- background: "var(--dx-cmSelection)"
2629
+ background: "var(--color-cm-selection)"
2555
2630
  },
2556
2631
  "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
2557
- background: "var(--dx-cmFocusedSelection)"
2632
+ background: "var(--color-cm-focused-selection)"
2558
2633
  },
2559
2634
  /**
2560
2635
  * Search.
@@ -2564,8 +2639,8 @@ var baseTheme = EditorView13.baseTheme({
2564
2639
  margin: "0 -3px",
2565
2640
  padding: "3px",
2566
2641
  borderRadius: "3px",
2567
- background: "var(--dx-cmHighlightSurface)",
2568
- color: "var(--dx-cmHighlight)"
2642
+ background: "var(--color-cm-highlight-surface)",
2643
+ color: "var(--color-cm-highlight)"
2569
2644
  },
2570
2645
  ".cm-searchMatch-selected": {
2571
2646
  textDecoration: "underline"
@@ -2576,20 +2651,29 @@ var baseTheme = EditorView13.baseTheme({
2576
2651
  ".cm-link": {
2577
2652
  textDecorationLine: "underline",
2578
2653
  textDecorationThickness: "1px",
2579
- textDecorationColor: "var(--dx-separator)",
2654
+ textDecorationColor: "var(--color-separator)",
2580
2655
  textUnderlineOffset: "2px",
2581
2656
  borderRadius: ".125rem"
2582
2657
  },
2583
2658
  ".cm-link > span": {
2584
- color: "var(--dx-accentText)"
2659
+ color: "var(--color-accent-text)"
2660
+ },
2661
+ ".cm-link > span:hover": {
2662
+ color: "var(--color-accent-text-hover)"
2585
2663
  },
2586
2664
  /**
2587
2665
  * Tooltip.
2588
2666
  */
2589
2667
  ".cm-tooltip": {
2590
- background: "var(--dx-baseSurface)"
2668
+ background: "var(--color-modal-surface)"
2591
2669
  },
2592
2670
  ".cm-tooltip-below": {},
2671
+ ".cm-tooltip-hover": {
2672
+ background: "var(--color-modal-surface)",
2673
+ border: "1px solid var(--color-separator)",
2674
+ borderRadius: "4px",
2675
+ overflow: "hidden"
2676
+ },
2593
2677
  /**
2594
2678
  * Autocomplete.
2595
2679
  * https://github.com/codemirror/autocomplete/blob/main/src/completion.ts
@@ -2597,7 +2681,7 @@ var baseTheme = EditorView13.baseTheme({
2597
2681
  ".cm-tooltip.cm-tooltip-autocomplete": {
2598
2682
  marginTop: "6px",
2599
2683
  marginLeft: "-10px",
2600
- border: "2px solid var(--dx-separator)",
2684
+ border: "2px solid var(--color-separator)",
2601
2685
  borderRadius: "4px"
2602
2686
  },
2603
2687
  ".cm-tooltip.cm-tooltip-autocomplete > ul": {
@@ -2607,12 +2691,12 @@ var baseTheme = EditorView13.baseTheme({
2607
2691
  padding: "4px"
2608
2692
  },
2609
2693
  ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {
2610
- background: "var(--dx-activeSurface)",
2611
- color: "var(--dx-activeSurfaceText)"
2694
+ background: "var(--color-active-surface)",
2695
+ color: "var(--color-base-surface-text)"
2612
2696
  },
2613
2697
  ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
2614
2698
  paddingLeft: "4px !important",
2615
- color: "var(--dx-hoverSurfaceText)"
2699
+ color: "var(--color-base-surface-text)"
2616
2700
  },
2617
2701
  /**
2618
2702
  * Completion info.
@@ -2621,17 +2705,17 @@ var baseTheme = EditorView13.baseTheme({
2621
2705
  width: "360px !important",
2622
2706
  margin: "-10px 1px 0 1px",
2623
2707
  padding: "8px !important",
2624
- borderColor: "var(--dx-separator)"
2708
+ borderColor: "var(--color-separator)"
2625
2709
  },
2626
2710
  ".cm-completionIcon": {
2627
2711
  display: "none"
2628
2712
  },
2629
2713
  ".cm-completionLabel": {
2630
- color: "var(--dx-description)",
2714
+ color: "var(--color-description)",
2631
2715
  padding: "0 4px"
2632
2716
  },
2633
2717
  ".cm-completionMatchedText": {
2634
- color: "var(--dx-baseText)",
2718
+ color: "var(--color-base-surface-text)",
2635
2719
  textDecoration: "none !important"
2636
2720
  },
2637
2721
  /**
@@ -2655,7 +2739,7 @@ var baseTheme = EditorView13.baseTheme({
2655
2739
  backgroundColor: "var(--surface-bg)"
2656
2740
  },
2657
2741
  ".cm-panel input, .cm-panel button, .cm-panel label": {
2658
- color: "var(--dx-subdued)",
2742
+ color: "var(--color-subdued)",
2659
2743
  fontSize: "14px",
2660
2744
  all: "unset",
2661
2745
  margin: "3px !important",
@@ -2663,10 +2747,10 @@ var baseTheme = EditorView13.baseTheme({
2663
2747
  outline: "1px solid transparent"
2664
2748
  },
2665
2749
  ".cm-panel input, .cm-panel button": {
2666
- backgroundColor: "var(--dx-inputSurface)"
2750
+ backgroundColor: "var(--color-input-surface)"
2667
2751
  },
2668
2752
  ".cm-panel input:focus, .cm-panel button:focus": {
2669
- outline: "1px solid var(--dx-neutralFocusIndicator)"
2753
+ outline: "1px solid var(--color-neutral-focus-indicator)"
2670
2754
  },
2671
2755
  ".cm-panel label": {
2672
2756
  display: "inline-flex",
@@ -2679,34 +2763,33 @@ var baseTheme = EditorView13.baseTheme({
2679
2763
  height: "8px",
2680
2764
  marginRight: "6px !important",
2681
2765
  padding: "2px !important",
2682
- color: "var(--dx-neutralFocusIndicator)"
2766
+ color: "var(--color-neutral-focus-indicator)"
2683
2767
  },
2684
2768
  ".cm-panel button": {
2685
2769
  "&:hover": {
2686
- // TODO(burdon): Replace with layer and @apply bg-accentSurfaceHover
2687
- backgroundColor: "var(--dx-accentSurfaceHover) !important"
2770
+ // TODO(burdon): Replace with layer and @apply bg-accent-surface-hover
2771
+ backgroundColor: "var(--color-accent-surface-hover) !important"
2688
2772
  },
2689
2773
  "&:active": {
2690
- backgroundColor: "var(--dx-accentSurfaceHover)"
2774
+ backgroundColor: "var(--color-accent-surface-hover)"
2691
2775
  }
2692
2776
  },
2693
2777
  ".cm-panel.cm-search": {
2694
2778
  padding: "4px",
2695
- borderTop: "1px solid var(--dx-separator)"
2779
+ borderTop: "1px solid var(--color-separator)"
2696
2780
  }
2697
2781
  });
2698
2782
  var editorGutter = EditorView13.theme({
2699
2783
  ".cm-gutters": {
2700
2784
  // NOTE: Non-transparent background required to cover content if scrolling horizontally.
2701
- background: "var(--dx-baseSurface) !important",
2785
+ background: "var(--color-base-surface) !important",
2702
2786
  paddingRight: "1rem"
2703
2787
  }
2704
2788
  });
2705
2789
  var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2706
- // Set metrics on the scroller (this is often what CM uses for layout).
2790
+ // Main content.
2707
2791
  ".cm-scroller": {
2708
- fontFamily: monospace ? fontMono : fontBody,
2709
- fontSize: "16px"
2792
+ fontFamily: monospace ? fontMono : fontBody
2710
2793
  },
2711
2794
  // Maintain defaults for UI components.
2712
2795
  ".cm-content, .cm-gutters, .cm-panel": {
@@ -2746,9 +2829,32 @@ var focus = [
2746
2829
  })
2747
2830
  ];
2748
2831
 
2832
+ // src/extensions/scroll-past-end.ts
2833
+ import { EditorView as EditorView15, ViewPlugin as ViewPlugin11 } from "@codemirror/view";
2834
+ var scrollPastEndPlugin = ViewPlugin11.fromClass(class {
2835
+ height = 1e3;
2836
+ attrs = {
2837
+ style: "padding-bottom: 1000px"
2838
+ };
2839
+ update({ view }) {
2840
+ const lastLineBlock = view.lineBlockAt(view.state.doc.length);
2841
+ const height = view.dom.clientHeight - lastLineBlock.height - view.documentPadding.top - 0.5;
2842
+ if (height >= 0 && height !== this.height) {
2843
+ this.height = height;
2844
+ this.attrs = {
2845
+ style: `padding-bottom: ${height}px`
2846
+ };
2847
+ }
2848
+ }
2849
+ });
2850
+ var scrollPastEnd = () => [
2851
+ scrollPastEndPlugin,
2852
+ EditorView15.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?.attrs ?? null)
2853
+ ];
2854
+
2749
2855
  // src/extensions/factories.ts
2750
- var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/factories.ts";
2751
- var tabbable = EditorView15.contentAttributes.of({
2856
+ var __dxlog_file11 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/factories.ts";
2857
+ var tabbable = EditorView16.contentAttributes.of({
2752
2858
  tabindex: "0"
2753
2859
  });
2754
2860
  var filterChars = (chars) => {
@@ -2801,13 +2907,8 @@ var createBasicExtensions = (propsProp) => {
2801
2907
  const props = defaultsDeep2({}, propsProp, defaultBasicOptions);
2802
2908
  return [
2803
2909
  // NOTE: Doesn't catch errors in keymap functions.
2804
- EditorView15.exceptionSink.of((err) => {
2805
- log7.catch(err, void 0, {
2806
- F: __dxlog_file10,
2807
- L: 132,
2808
- S: void 0,
2809
- C: (f, a) => f(...a)
2810
- });
2910
+ EditorView16.exceptionSink.of((err) => {
2911
+ log8.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file11, L: 79, S: void 0 });
2811
2912
  }),
2812
2913
  props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
2813
2914
  props.bracketMatching && bracketMatching(),
@@ -2816,7 +2917,7 @@ var createBasicExtensions = (propsProp) => {
2816
2917
  props.drawSelection && drawSelection({
2817
2918
  cursorBlinkRate: 1200
2818
2919
  }),
2819
- props.editable !== void 0 && EditorView15.editable.of(props.editable),
2920
+ props.editable !== void 0 && EditorView16.editable.of(props.editable),
2820
2921
  props.focus && focus,
2821
2922
  props.highlightActiveLine && highlightActiveLine(),
2822
2923
  props.history && history(),
@@ -2824,9 +2925,16 @@ var createBasicExtensions = (propsProp) => {
2824
2925
  lineNumbers(),
2825
2926
  editorGutter
2826
2927
  ],
2827
- props.lineWrapping && EditorView15.lineWrapping,
2928
+ props.lineWrapping && EditorView16.lineWrapping,
2828
2929
  props.placeholder && placeholder2(props.placeholder),
2829
2930
  props.readOnly !== void 0 && EditorState.readOnly.of(props.readOnly),
2931
+ // `EditorState.readOnly` is advisory — CodeMirror doesn't auto-reject doc-changing
2932
+ // transactions. Some extensions (e.g. `@codemirror/lang-markdown`'s Enter handler that
2933
+ // continues a list) dispatch programmatic edits regardless. Drop user-initiated edits
2934
+ // (`input` / `delete` keymap dispatches plus `undo` / `redo` from the history extension)
2935
+ // but pass programmatic dispatches — streaming `MarkdownStream` and similar consumers
2936
+ // depend on being able to populate the doc themselves.
2937
+ props.readOnly && EditorState.transactionFilter.of((tr) => tr.docChanged && (tr.isUserEvent("input") || tr.isUserEvent("delete") || tr.isUserEvent("undo") || tr.isUserEvent("redo")) ? [] : tr),
2830
2938
  props.scrollPastEnd && scrollPastEnd(),
2831
2939
  props.tabbable && tabbable,
2832
2940
  props.tabSize && EditorState.tabSize.of(props.tabSize),
@@ -2856,12 +2964,12 @@ var createBasicExtensions = (propsProp) => {
2856
2964
  };
2857
2965
  var grow = {
2858
2966
  editor: {
2859
- className: "bs-full is-full"
2967
+ className: "h-full w-full"
2860
2968
  }
2861
2969
  };
2862
2970
  var fullWidth = {
2863
2971
  editor: {
2864
- className: "is-full"
2972
+ className: "w-full"
2865
2973
  }
2866
2974
  };
2867
2975
  var defaultThemeSlots = grow;
@@ -2869,24 +2977,29 @@ var defaultStyles = {
2869
2977
  dark: vscodeDarkStyle,
2870
2978
  light: vscodeLightStyle
2871
2979
  };
2872
- var createThemeExtensions = ({ monospace, themeMode, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp } = {}) => {
2980
+ var createThemeExtensions = ({ monospace, scrollbarThin, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp, themeMode } = {}) => {
2873
2981
  const slots = defaultsDeep2({}, slotsProp, defaultThemeSlots);
2874
2982
  return [
2875
2983
  baseTheme,
2876
- EditorView15.darkTheme.of(themeMode === "dark"),
2984
+ EditorView16.darkTheme.of(themeMode === "dark"),
2877
2985
  createFontTheme({
2878
2986
  monospace
2879
2987
  }),
2880
2988
  syntaxHighlightingProp && syntaxHighlighting(HighlightStyle.define(themeMode === "dark" ? defaultStyles.dark : defaultStyles.light)),
2881
- slots.editor?.className && EditorView15.editorAttributes.of({
2989
+ slots.editor?.className && EditorView16.editorAttributes.of({
2882
2990
  class: slots.editor.className
2883
2991
  }),
2884
- slots.content?.className && EditorView15.contentAttributes.of({
2992
+ slots.content?.className && EditorView16.contentAttributes.of({
2885
2993
  class: slots.content.className
2886
2994
  }),
2887
- slots.scroll?.className && ViewPlugin11.fromClass(class {
2995
+ (slots.scroller?.className || scrollbarThin) && ViewPlugin12.fromClass(class {
2888
2996
  constructor(view) {
2889
- view.scrollDOM.classList.add(...slots.scroll.className.split(/\s+/));
2997
+ if (slots.scroller?.className) {
2998
+ view.scrollDOM.classList.add(...slots.scroller.className.split(/\s+/));
2999
+ }
3000
+ if (scrollbarThin) {
3001
+ view.scrollDOM.style.setProperty("--scrollbar-size", "4px");
3002
+ }
2890
3003
  }
2891
3004
  })
2892
3005
  ].filter(isTruthy2);
@@ -2904,8 +3017,8 @@ var createDataExtensions = ({ id, text, messenger, identity }) => {
2904
3017
  channel: `awareness.${id}`,
2905
3018
  peerId: identity.identityKey.toHex(),
2906
3019
  info: {
2907
- darkColor: `var(--dx-${hue}Cursor)`,
2908
- lightColor: `var(--dx-${hue}Cursor)`,
3020
+ darkColor: `var(--color-${hue}-border)`,
3021
+ lightColor: `var(--color-${hue}-border)`,
2909
3022
  displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex())
2910
3023
  }
2911
3024
  })));
@@ -2915,7 +3028,7 @@ var createDataExtensions = ({ id, text, messenger, identity }) => {
2915
3028
 
2916
3029
  // src/extensions/folding.ts
2917
3030
  import { codeFolding, foldGutter } from "@codemirror/language";
2918
- import { EditorView as EditorView16 } from "@codemirror/view";
3031
+ import { EditorView as EditorView17 } from "@codemirror/view";
2919
3032
  import { Domino as Domino2, mx as mx4 } from "@dxos/ui";
2920
3033
  var folding = () => {
2921
3034
  return [
@@ -2923,13 +3036,14 @@ var folding = () => {
2923
3036
  placeholderDOM: () => Domino2.of("span").root
2924
3037
  }),
2925
3038
  foldGutter({
3039
+ // NOTE: We can't animate since the element is remounted on state change.
2926
3040
  markerDOM: (open) => {
2927
- 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({
3041
+ return Domino2.of("div").classNames("flex h-full justify-center items-center").append(Domino2.of("svg", Domino2.SVG).classNames(mx4("w-4 h-4 cursor-pointer", open && "rotate-90")).append(Domino2.of("use", Domino2.SVG).attributes({
2928
3042
  href: Domino2.icon("ph--caret-right--regular")
2929
3043
  }))).root;
2930
3044
  }
2931
3045
  }),
2932
- EditorView16.theme({
3046
+ EditorView17.theme({
2933
3047
  ".cm-foldGutter": {
2934
3048
  opacity: 0.3,
2935
3049
  transition: "opacity 0.3s",
@@ -2943,7 +3057,7 @@ var folding = () => {
2943
3057
  };
2944
3058
 
2945
3059
  // src/extensions/hashtag.ts
2946
- import { Decoration as Decoration8, EditorView as EditorView17, MatchDecorator, ViewPlugin as ViewPlugin12, WidgetType as WidgetType4 } from "@codemirror/view";
3060
+ import { Decoration as Decoration8, EditorView as EditorView18, MatchDecorator, ViewPlugin as ViewPlugin13, WidgetType as WidgetType4 } from "@codemirror/view";
2947
3061
  import { getHashStyles, mx as mx5 } from "@dxos/ui-theme";
2948
3062
  var TagWidget = class extends WidgetType4 {
2949
3063
  _text;
@@ -2964,7 +3078,7 @@ var tagMatcher = new MatchDecorator({
2964
3078
  })
2965
3079
  });
2966
3080
  var hashtag = () => [
2967
- ViewPlugin12.fromClass(class {
3081
+ ViewPlugin13.fromClass(class {
2968
3082
  tags;
2969
3083
  constructor(view) {
2970
3084
  this.tags = tagMatcher.createDeco(view);
@@ -2974,11 +3088,11 @@ var hashtag = () => [
2974
3088
  }
2975
3089
  }, {
2976
3090
  decorations: (instance) => instance.tags,
2977
- provide: (plugin) => EditorView17.atomicRanges.of((view) => {
3091
+ provide: (plugin) => EditorView18.atomicRanges.of((view) => {
2978
3092
  return view.plugin(plugin)?.tags || Decoration8.none;
2979
3093
  })
2980
3094
  }),
2981
- EditorView17.theme({
3095
+ EditorView18.theme({
2982
3096
  ".cm-tag": {
2983
3097
  borderRadius: "4px",
2984
3098
  marginRight: "6px",
@@ -3033,18 +3147,18 @@ var schemaLinter = (validate) => (view) => {
3033
3147
  };
3034
3148
 
3035
3149
  // src/extensions/listener.ts
3036
- import { EditorView as EditorView18 } from "@codemirror/view";
3150
+ import { EditorView as EditorView19 } from "@codemirror/view";
3037
3151
  import { isNonNullable as isNonNullable2 } from "@dxos/util";
3038
3152
  var listener = ({ onFocus, onChange }) => {
3039
3153
  return [
3040
- onFocus && EditorView18.focusChangeEffect.of((state, focusing) => {
3154
+ onFocus && EditorView19.focusChangeEffect.of((state, focusing) => {
3041
3155
  onFocus({
3042
3156
  id: state.facet(documentId),
3043
3157
  focusing
3044
3158
  });
3045
3159
  return null;
3046
3160
  }),
3047
- onChange && EditorView18.updateListener.of(({ state, docChanged }) => {
3161
+ onChange && EditorView19.updateListener.of(({ state, docChanged }) => {
3048
3162
  if (docChanged) {
3049
3163
  onChange({
3050
3164
  id: state.facet(documentId),
@@ -3059,7 +3173,7 @@ var listener = ({ onFocus, onChange }) => {
3059
3173
  import { snippet } from "@codemirror/autocomplete";
3060
3174
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
3061
3175
  import { EditorSelection as EditorSelection2 } from "@codemirror/state";
3062
- import { EditorView as EditorView19, keymap as keymap8 } from "@codemirror/view";
3176
+ import { EditorView as EditorView20, keymap as keymap8 } from "@codemirror/view";
3063
3177
  import { debounceAndThrottle } from "@dxos/async";
3064
3178
  var formattingEquals = (a, b) => a.blockType === b.blockType && a.strong === b.strong && a.emphasis === b.emphasis && a.strikethrough === b.strikethrough && a.code === b.code && a.link === b.link && a.listStyle === b.listStyle && a.blockQuote === b.blockQuote;
3065
3179
  var Inline = /* @__PURE__ */ (function(Inline2) {
@@ -4148,7 +4262,7 @@ var getFormatting = (state) => {
4148
4262
  };
4149
4263
  };
4150
4264
  var formattingListener = (onStateChange, delay = 100) => {
4151
- return EditorView19.updateListener.of(debounceAndThrottle((update2) => {
4265
+ return EditorView20.updateListener.of(debounceAndThrottle((update2) => {
4152
4266
  if (update2.docChanged || update2.selectionSet) {
4153
4267
  onStateChange(getFormatting(update2.state));
4154
4268
  }
@@ -4209,8 +4323,7 @@ import { completionKeymap } from "@codemirror/autocomplete";
4209
4323
  import { defaultKeymap as defaultKeymap2, indentWithTab as indentWithTab2 } from "@codemirror/commands";
4210
4324
  import { jsonLanguage } from "@codemirror/lang-json";
4211
4325
  import { markdown, markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
4212
- import { xml } from "@codemirror/lang-xml";
4213
- import { LanguageDescription, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4326
+ import { foldNodeProp, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4214
4327
  import { languages } from "@codemirror/language-data";
4215
4328
  import { keymap as keymap9 } from "@codemirror/view";
4216
4329
  import { isTruthy as isTruthy3 } from "@dxos/util";
@@ -4331,30 +4444,38 @@ var markdownHighlightStyle = (_options = {}) => {
4331
4444
  ],
4332
4445
  class: "font-mono"
4333
4446
  },
4334
- // Headings.
4447
+ // Headings — use CSS properties only (no class:) so CodeMirror generates scoped CSS via
4448
+ // StyleModule that overrides vscodeDarkStyle's t.heading rule. When class: is present,
4449
+ // HighlightStyle silently ignores all other CSS properties (they're mutually exclusive).
4450
+ // Font sizes use Tailwind v4 CSS variables so nothing is hardcoded.
4451
+ {
4452
+ tag: tags.heading,
4453
+ color: "var(--color-cm-heading) !important",
4454
+ fontWeight: "300"
4455
+ },
4335
4456
  {
4336
4457
  tag: tags.heading1,
4337
- class: markdownTheme.heading(1)
4458
+ ...markdownTheme.heading(1)
4338
4459
  },
4339
4460
  {
4340
4461
  tag: tags.heading2,
4341
- class: markdownTheme.heading(2)
4462
+ ...markdownTheme.heading(2)
4342
4463
  },
4343
4464
  {
4344
4465
  tag: tags.heading3,
4345
- class: markdownTheme.heading(3)
4466
+ ...markdownTheme.heading(3)
4346
4467
  },
4347
4468
  {
4348
4469
  tag: tags.heading4,
4349
- class: markdownTheme.heading(4)
4470
+ ...markdownTheme.heading(4)
4350
4471
  },
4351
4472
  {
4352
4473
  tag: tags.heading5,
4353
- class: markdownTheme.heading(5)
4474
+ ...markdownTheme.heading(5)
4354
4475
  },
4355
4476
  {
4356
4477
  tag: tags.heading6,
4357
- class: markdownTheme.heading(6)
4478
+ ...markdownTheme.heading(6)
4358
4479
  },
4359
4480
  // Emphasis.
4360
4481
  {
@@ -4409,15 +4530,23 @@ var createMarkdownExtensions = (options = {}) => {
4409
4530
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
4410
4531
  base: markdownLanguage2,
4411
4532
  // Languages for syntax highlighting fenced code blocks.
4533
+ // Caller-supplied languages are checked first so they can override defaults.
4412
4534
  defaultCodeLanguage: jsonLanguage,
4413
- codeLanguages: languages,
4535
+ codeLanguages: [
4536
+ ...options.codeLanguages ?? [],
4537
+ ...languages
4538
+ ],
4414
4539
  // Don't complete HTML tags.
4415
4540
  completeHTMLTags: false,
4416
4541
  // Parser extensions.
4417
4542
  extensions: [
4418
4543
  // GFM provided by default.
4419
4544
  markdownTagsExtensions,
4420
- ...options.extensions ?? defaultExtensions()
4545
+ ...options.extensions ?? defaultExtensions(),
4546
+ // Disable folding for fenced code blocks by overriding foldNodeProp.
4547
+ // Note: returning null from foldService does not prevent syntaxFolding fallback,
4548
+ // so we must override the node prop directly on the FencedCode node type.
4549
+ noFencedCodeFolding
4421
4550
  ]
4422
4551
  }),
4423
4552
  // Custom styles.
@@ -4432,18 +4561,13 @@ var createMarkdownExtensions = (options = {}) => {
4432
4561
  ].filter(isTruthy3))
4433
4562
  ];
4434
4563
  };
4435
- var xmlLanguageDesc = LanguageDescription.of({
4436
- name: "xml",
4437
- alias: [
4438
- "html",
4439
- "xhtml"
4440
- ],
4441
- extensions: [
4442
- "xml",
4443
- "xhtml"
4444
- ],
4445
- load: async () => xml()
4446
- });
4564
+ var noFencedCodeFolding = {
4565
+ props: [
4566
+ foldNodeProp.add({
4567
+ FencedCode: () => null
4568
+ })
4569
+ ]
4570
+ };
4447
4571
  var defaultExtensions = () => [
4448
4572
  noSetExtHeading,
4449
4573
  noHtml
@@ -4463,19 +4587,19 @@ var debugTree = (cb) => StateField6.define({
4463
4587
  update: (value, tr) => cb(convertTreeToJson(tr.state))
4464
4588
  });
4465
4589
  var convertTreeToJson = (state) => {
4466
- const treeToJson = (cursor2) => {
4590
+ const treeToJson = (cursor) => {
4467
4591
  const node = {
4468
- type: cursor2.type.name,
4469
- from: cursor2.from,
4470
- to: cursor2.to,
4471
- text: state.doc.slice(cursor2.from, cursor2.to).toString(),
4592
+ type: cursor.type.name,
4593
+ from: cursor.from,
4594
+ to: cursor.to,
4595
+ text: state.doc.slice(cursor.from, cursor.to).toString(),
4472
4596
  children: []
4473
4597
  };
4474
- if (cursor2.firstChild()) {
4598
+ if (cursor.firstChild()) {
4475
4599
  do {
4476
- node.children.push(treeToJson(cursor2));
4477
- } while (cursor2.nextSibling());
4478
- cursor2.parent();
4600
+ node.children.push(treeToJson(cursor));
4601
+ } while (cursor.nextSibling());
4602
+ cursor.parent();
4479
4603
  }
4480
4604
  return node;
4481
4605
  };
@@ -4485,16 +4609,15 @@ var convertTreeToJson = (state) => {
4485
4609
  // src/extensions/markdown/decorate.ts
4486
4610
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
4487
4611
  import { Prec as Prec4, RangeSetBuilder as RangeSetBuilder5, StateEffect as StateEffect7 } from "@codemirror/state";
4488
- import { Decoration as Decoration11, EditorView as EditorView23, ViewPlugin as ViewPlugin14, WidgetType as WidgetType7 } from "@codemirror/view";
4612
+ import { Decoration as Decoration11, EditorView as EditorView24, ViewPlugin as ViewPlugin15, WidgetType as WidgetType7 } from "@codemirror/view";
4489
4613
  import { invariant as invariant4 } from "@dxos/invariant";
4490
- import { mx as mx6 } from "@dxos/ui-theme";
4491
4614
 
4492
4615
  // src/extensions/markdown/changes.ts
4493
4616
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
4494
4617
  import { Transaction as Transaction4 } from "@codemirror/state";
4495
- import { ViewPlugin as ViewPlugin13 } from "@codemirror/view";
4618
+ import { ViewPlugin as ViewPlugin14 } from "@codemirror/view";
4496
4619
  var adjustChanges = () => {
4497
- return ViewPlugin13.fromClass(class {
4620
+ return ViewPlugin14.fromClass(class {
4498
4621
  update(update2) {
4499
4622
  const tree = syntaxTree4(update2.state);
4500
4623
  const adjustments = [];
@@ -4636,7 +4759,7 @@ var getValidUrl = (str) => {
4636
4759
  // src/extensions/markdown/image.ts
4637
4760
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
4638
4761
  import { StateField as StateField7 } from "@codemirror/state";
4639
- import { Decoration as Decoration9, EditorView as EditorView20, WidgetType as WidgetType5 } from "@codemirror/view";
4762
+ import { Decoration as Decoration9, EditorView as EditorView21, WidgetType as WidgetType5 } from "@codemirror/view";
4640
4763
  var image = (_options = {}) => {
4641
4764
  return [
4642
4765
  StateField7.define({
@@ -4647,10 +4770,10 @@ var image = (_options = {}) => {
4647
4770
  if (!tr.docChanged && !tr.selection) {
4648
4771
  return value;
4649
4772
  }
4650
- const cursor2 = tr.state.selection.main.head;
4773
+ const cursor = tr.state.selection.main.head;
4651
4774
  const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
4652
- let from = Math.min(cursor2, oldCursor);
4653
- let to = Math.max(cursor2, oldCursor);
4775
+ let from = Math.min(cursor, oldCursor);
4776
+ let to = Math.max(cursor, oldCursor);
4654
4777
  tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
4655
4778
  from = Math.min(from, fromB);
4656
4779
  to = Math.max(to, toB);
@@ -4664,19 +4787,19 @@ var image = (_options = {}) => {
4664
4787
  add: buildDecorations(tr.state, from, to)
4665
4788
  });
4666
4789
  },
4667
- provide: (field) => EditorView20.decorations.from(field)
4790
+ provide: (field) => EditorView21.decorations.from(field)
4668
4791
  })
4669
4792
  ];
4670
4793
  };
4671
4794
  var buildDecorations = (state, from, to) => {
4672
4795
  const decorations2 = [];
4673
- const cursor2 = state.selection.main.head;
4796
+ const cursor = state.selection.main.head;
4674
4797
  syntaxTree5(state).iterate({
4675
4798
  enter: (node) => {
4676
4799
  if (node.name === "Image") {
4677
4800
  const urlNode = node.node.getChild("URL");
4678
4801
  if (urlNode) {
4679
- const hide2 = state.readOnly || cursor2 < node.from || cursor2 > node.to || !state.field(focusField);
4802
+ const hide2 = state.readOnly || cursor < node.from || cursor > node.to || !state.field(focusField);
4680
4803
  const url = state.sliceDoc(urlNode.from, urlNode.to);
4681
4804
  if (url.match(/^https?:\/\//) === null && url.match(/^file?:\/\//) === null) {
4682
4805
  return;
@@ -4724,10 +4847,10 @@ var ImageWidget = class extends WidgetType5 {
4724
4847
  };
4725
4848
 
4726
4849
  // src/extensions/markdown/styles.ts
4727
- import { EditorView as EditorView21 } from "@codemirror/view";
4850
+ import { EditorView as EditorView22 } from "@codemirror/view";
4728
4851
  var bulletListIndentationWidth = 24;
4729
4852
  var orderedListIndentationWidth = 36;
4730
- var formattingStyles = EditorView21.theme({
4853
+ var formattingStyles = EditorView22.theme({
4731
4854
  /**
4732
4855
  * Horizontal rule.
4733
4856
  */
@@ -4736,7 +4859,7 @@ var formattingStyles = EditorView21.theme({
4736
4859
  width: "100%",
4737
4860
  height: "0",
4738
4861
  verticalAlign: "middle",
4739
- borderTop: "1px solid var(--dx-cmSeparator)",
4862
+ borderTop: "1px solid var(--color-cm-separator)",
4740
4863
  opacity: 0.5
4741
4864
  },
4742
4865
  /**
@@ -4759,22 +4882,47 @@ var formattingStyles = EditorView21.theme({
4759
4882
  * Blockquote.
4760
4883
  */
4761
4884
  "& .cm-blockquote": {
4762
- background: "var(--dx-cmCodeblock)",
4763
- borderLeft: "2px solid var(--dx-cmSeparator)",
4885
+ background: "var(--color-cm-codeblock)",
4886
+ borderLeft: "2px solid var(--color-cm-separator)",
4764
4887
  paddingLeft: "1rem",
4765
- margin: "0"
4888
+ margin: 0
4766
4889
  },
4767
4890
  /**
4768
4891
  * Code and codeblocks.
4769
4892
  */
4770
- "& .cm-code": {
4771
- fontFamily: fontMono
4893
+ "& code": {
4894
+ fontFamily: fontMono,
4895
+ color: "var(--color-cm-code)",
4896
+ whiteSpace: "nowrap"
4772
4897
  },
4773
- "& .cm-codeblock-line": {
4774
- background: "var(--dx-cmCodeblock)",
4775
- paddingInline: "1rem !important"
4898
+ "& .cm-code": {
4899
+ fontFamily: fontMono,
4900
+ color: "var(--color-cm-code)"
4776
4901
  },
4777
- "& .cm-codeblock-start": {
4902
+ // Inline code spans (triggered by backticks) use `cm-code-inline` + `font-mono`.
4903
+ // Different monospace font metrics can slightly overflow the fixed CodeMirror line box,
4904
+ // so constrain them to the target 24px height.
4905
+ "& .cm-code-inline": {
4906
+ fontFamily: fontMono,
4907
+ height: "24px",
4908
+ // display: 'inline-flex',
4909
+ alignItems: "center",
4910
+ overflow: "hidden",
4911
+ whiteSpace: "nowrap",
4912
+ color: "var(--color-cm-code-inline)"
4913
+ },
4914
+ "& .cm-code-mark": {
4915
+ fontFamily: fontMono,
4916
+ height: "24px",
4917
+ display: "inline-flex",
4918
+ alignItems: "center",
4919
+ overflow: "hidden"
4920
+ },
4921
+ "& .cm-codeblock-line": {
4922
+ background: "var(--color-cm-codeblock)",
4923
+ paddingInline: "1rem !important"
4924
+ },
4925
+ "& .cm-codeblock-start": {
4778
4926
  borderTopLeftRadius: ".25rem",
4779
4927
  borderTopRightRadius: ".25rem"
4780
4928
  },
@@ -4800,16 +4948,24 @@ var formattingStyles = EditorView21.theme({
4800
4948
  */
4801
4949
  ".cm-table *": {
4802
4950
  fontFamily: fontMono,
4951
+ lineHeight: 1.5,
4803
4952
  textDecoration: "none !important"
4804
4953
  },
4805
4954
  ".cm-table-head": {
4806
4955
  padding: "2px 16px 2px 0px",
4956
+ overflowWrap: "break-word",
4957
+ whiteSpace: "pre-wrap",
4958
+ wordBreak: "keep-all",
4807
4959
  textAlign: "left",
4808
- borderBottom: "1px solid var(--dx-cmSeparator)",
4809
- color: "var(--dx-subdued)"
4960
+ color: "var(--color-subdued)",
4961
+ borderBottom: "1px solid var(--color-cm-separator)"
4810
4962
  },
4811
4963
  ".cm-table-cell": {
4812
- padding: "2px 16px 2px 0px"
4964
+ padding: "2px 16px 2px 0px",
4965
+ overflowWrap: "break-word",
4966
+ whiteSpace: "pre-wrap",
4967
+ wordBreak: "keep-all",
4968
+ verticalAlign: "top"
4813
4969
  },
4814
4970
  /**
4815
4971
  * Image.
@@ -4825,12 +4981,12 @@ var formattingStyles = EditorView21.theme({
4825
4981
  },
4826
4982
  ".cm-image-with-loader": {
4827
4983
  display: "block",
4828
- opacity: "0",
4984
+ opacity: 0,
4829
4985
  transitionDuration: "350ms",
4830
4986
  transitionProperty: "opacity"
4831
4987
  },
4832
4988
  ".cm-image-with-loader.cm-loaded-image": {
4833
- opacity: "1"
4989
+ opacity: 1
4834
4990
  },
4835
4991
  ".cm-image-wrapper": {
4836
4992
  "grid-template-columns": "1fr",
@@ -4849,17 +5005,17 @@ var formattingStyles = EditorView21.theme({
4849
5005
  // src/extensions/markdown/table.ts
4850
5006
  import { syntaxTree as syntaxTree6 } from "@codemirror/language";
4851
5007
  import { RangeSetBuilder as RangeSetBuilder4, StateField as StateField8 } from "@codemirror/state";
4852
- import { Decoration as Decoration10, EditorView as EditorView22, WidgetType as WidgetType6 } from "@codemirror/view";
5008
+ import { Decoration as Decoration10, EditorView as EditorView23, WidgetType as WidgetType6 } from "@codemirror/view";
4853
5009
  var table = (options = {}) => {
4854
5010
  return StateField8.define({
4855
5011
  create: (state) => update(state, options),
4856
5012
  update: (_, tr) => update(tr.state, options),
4857
- provide: (field) => EditorView22.decorations.from(field)
5013
+ provide: (field) => EditorView23.decorations.from(field)
4858
5014
  });
4859
5015
  };
4860
5016
  var update = (state, _options) => {
4861
5017
  const builder = new RangeSetBuilder4();
4862
- const cursor2 = state.selection.main.head;
5018
+ const cursor = state.selection.main.head;
4863
5019
  const tables = [];
4864
5020
  const getTable = () => tables[tables.length - 1];
4865
5021
  const getRow = () => {
@@ -4897,7 +5053,7 @@ var update = (state, _options) => {
4897
5053
  }
4898
5054
  });
4899
5055
  tables.forEach((table2) => {
4900
- const replace = state.readOnly || cursor2 < table2.from || cursor2 > table2.to;
5056
+ const replace = state.readOnly || cursor < table2.from || cursor > table2.to;
4901
5057
  if (replace) {
4902
5058
  builder.add(table2.from, table2.to, Decoration10.replace({
4903
5059
  block: true,
@@ -4911,6 +5067,26 @@ var update = (state, _options) => {
4911
5067
  });
4912
5068
  return builder.finish();
4913
5069
  };
5070
+ var renderCellContent = (el, text) => {
5071
+ const parts = text.split(/(`[^`\n]+`|\*\*[^*\n]+\*\*|__[^_\n]+__|\*[^*\n]+\*|_[^_\n]+_)/);
5072
+ for (const part of parts) {
5073
+ if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
5074
+ const code = document.createElement("code");
5075
+ code.textContent = part.slice(1, -1);
5076
+ el.appendChild(code);
5077
+ } else if (part.startsWith("**") && part.endsWith("**") || part.startsWith("__") && part.endsWith("__")) {
5078
+ const strong = document.createElement("strong");
5079
+ strong.textContent = part.slice(2, -2);
5080
+ el.appendChild(strong);
5081
+ } else if (part.startsWith("*") && part.endsWith("*") || part.startsWith("_") && part.endsWith("_")) {
5082
+ const em = document.createElement("em");
5083
+ em.textContent = part.slice(1, -1);
5084
+ el.appendChild(em);
5085
+ } else {
5086
+ el.appendChild(document.createTextNode(part));
5087
+ }
5088
+ }
5089
+ };
4914
5090
  var TableWidget = class extends WidgetType6 {
4915
5091
  _table;
4916
5092
  constructor(_table) {
@@ -4930,7 +5106,7 @@ var TableWidget = class extends WidgetType6 {
4930
5106
  this._table.header?.forEach((cell) => {
4931
5107
  const th = document.createElement("th");
4932
5108
  th.setAttribute("class", "cm-table-head");
4933
- tr.appendChild(th).textContent = cell;
5109
+ renderCellContent(tr.appendChild(th), cell);
4934
5110
  });
4935
5111
  const body = table2.appendChild(document.createElement("tbody"));
4936
5112
  this._table.rows?.forEach((row) => {
@@ -4938,7 +5114,7 @@ var TableWidget = class extends WidgetType6 {
4938
5114
  row.forEach((cell) => {
4939
5115
  const td = document.createElement("td");
4940
5116
  td.setAttribute("class", "cm-table-cell");
4941
- tr2.appendChild(td).textContent = cell;
5117
+ renderCellContent(tr2.appendChild(td), cell);
4942
5118
  });
4943
5119
  });
4944
5120
  return div;
@@ -4946,7 +5122,7 @@ var TableWidget = class extends WidgetType6 {
4946
5122
  };
4947
5123
 
4948
5124
  // src/extensions/markdown/decorate.ts
4949
- var __dxlog_file11 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/markdown/decorate.ts";
5125
+ var __dxlog_file12 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/markdown/decorate.ts";
4950
5126
  var Unicode = {
4951
5127
  emDash: "\u2014",
4952
5128
  bullet: "\u2022",
@@ -4969,7 +5145,6 @@ var LinkButton = class extends WidgetType7 {
4969
5145
  eq(other) {
4970
5146
  return this.url === other.url;
4971
5147
  }
4972
- // TODO(burdon): Create icon and link directly without react?
4973
5148
  toDOM(view) {
4974
5149
  const el = document.createElement("span");
4975
5150
  this.render(el, {
@@ -5046,10 +5221,10 @@ var fencedCodeLine = Decoration11.line({
5046
5221
  class: "cm-code cm-codeblock-line"
5047
5222
  });
5048
5223
  var fencedCodeLineFirst = Decoration11.line({
5049
- class: mx6("cm-code cm-codeblock-line", "cm-codeblock-start")
5224
+ class: "cm-code cm-codeblock-line cm-codeblock-start"
5050
5225
  });
5051
5226
  var fencedCodeLineLast = Decoration11.line({
5052
- class: mx6("cm-code cm-codeblock-line", "cm-codeblock-end")
5227
+ class: "cm-code cm-codeblock-line cm-codeblock-end"
5053
5228
  });
5054
5229
  var commentBlockLine = fencedCodeLine;
5055
5230
  var commentBlockLineFirst = fencedCodeLineFirst;
@@ -5081,15 +5256,7 @@ var buildDecorations2 = (view, options, focus2) => {
5081
5256
  const { state } = view;
5082
5257
  const headerLevels = [];
5083
5258
  const getHeaderLevels = (node, level) => {
5084
- invariant4(level > 0, void 0, {
5085
- F: __dxlog_file11,
5086
- L: 180,
5087
- S: void 0,
5088
- A: [
5089
- "level > 0",
5090
- ""
5091
- ]
5092
- });
5259
+ invariant4(level > 0, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 160, S: void 0, A: ["level > 0", ""] });
5093
5260
  if (level > headerLevels.length) {
5094
5261
  const len = headerLevels.length;
5095
5262
  headerLevels.length = level;
@@ -5120,15 +5287,7 @@ var buildDecorations2 = (view, options, focus2) => {
5120
5287
  listLevels.pop();
5121
5288
  };
5122
5289
  const getCurrentListLevel = () => {
5123
- invariant4(listLevels.length, void 0, {
5124
- F: __dxlog_file11,
5125
- L: 202,
5126
- S: void 0,
5127
- A: [
5128
- "listLevels.length",
5129
- ""
5130
- ]
5131
- });
5290
+ invariant4(listLevels.length, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 192, S: void 0, A: ["listLevels.length", ""] });
5132
5291
  return listLevels[listLevels.length - 1];
5133
5292
  };
5134
5293
  const enterNode = (node) => {
@@ -5166,13 +5325,13 @@ var buildDecorations2 = (view, options, focus2) => {
5166
5325
  deco: hide
5167
5326
  });
5168
5327
  } else {
5169
- const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + " ";
5328
+ const num = headers.slice(from - 1).map((level2) => level2?.number ?? 0).join(".") + "). ";
5170
5329
  if (num.length) {
5171
5330
  atomicDecoRanges.push({
5172
5331
  from: mark.from,
5173
5332
  to: mark.from + len,
5174
5333
  deco: Decoration11.replace({
5175
- widget: new TextWidget(num, markdownTheme.heading(level))
5334
+ widget: new TextWidget(num, markdownTheme.heading(level).className)
5176
5335
  })
5177
5336
  });
5178
5337
  }
@@ -5349,11 +5508,11 @@ var buildDecorations2 = (view, options, focus2) => {
5349
5508
  }
5350
5509
  decoRanges.push({
5351
5510
  from: marks[0].to,
5352
- to: marks[1].from,
5511
+ to: !editing && options.renderLinkButton ? node.to : marks[1].from,
5353
5512
  deco: Decoration11.mark({
5354
5513
  tagName: "a",
5355
5514
  attributes: {
5356
- class: "cm-link",
5515
+ class: options.renderLinkButton ? "cm-link cm-link-with-button" : "cm-link",
5357
5516
  href: url,
5358
5517
  rel: "noreferrer",
5359
5518
  target: "_blank"
@@ -5431,8 +5590,11 @@ var buildDecorations2 = (view, options, focus2) => {
5431
5590
  deco.add(from, to, d);
5432
5591
  }
5433
5592
  const atomicDeco = new RangeSetBuilder5();
5434
- for (const { from, to, deco: d } of atomicDecoRanges) {
5435
- atomicDeco.add(from, to, d);
5593
+ for (const { from, to, deco: deco2 } of atomicDecoRanges) {
5594
+ if (from < to && state.doc.lineAt(from).number !== state.doc.lineAt(to).number) {
5595
+ continue;
5596
+ }
5597
+ atomicDeco.add(from, to, deco2);
5436
5598
  }
5437
5599
  return {
5438
5600
  deco: deco.finish(),
@@ -5442,7 +5604,7 @@ var buildDecorations2 = (view, options, focus2) => {
5442
5604
  var forceUpdate = StateEffect7.define();
5443
5605
  var decorateMarkdown = (options = {}) => {
5444
5606
  return [
5445
- ViewPlugin14.fromClass(class {
5607
+ ViewPlugin15.fromClass(class {
5446
5608
  deco;
5447
5609
  atomicDeco;
5448
5610
  pendingUpdate;
@@ -5477,9 +5639,9 @@ var decorateMarkdown = (options = {}) => {
5477
5639
  }
5478
5640
  }, {
5479
5641
  provide: (plugin) => [
5480
- Prec4.low(EditorView23.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5481
- EditorView23.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5482
- EditorView23.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5642
+ Prec4.low(EditorView24.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5643
+ EditorView24.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5644
+ EditorView24.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5483
5645
  ]
5484
5646
  }),
5485
5647
  image(),
@@ -5511,8 +5673,7 @@ var linkTooltip = (renderTooltip) => {
5511
5673
  return {
5512
5674
  pos: link.from,
5513
5675
  end: link.to,
5514
- // NOTE: Forcing above causes the tooltip to flicker.
5515
- // above: true,
5676
+ above: true,
5516
5677
  create: () => {
5517
5678
  const el = document.createElement("div");
5518
5679
  el.className = tooltipContent({});
@@ -5528,16 +5689,13 @@ var linkTooltip = (renderTooltip) => {
5528
5689
  };
5529
5690
  }
5530
5691
  };
5531
- }, {
5532
- // NOTE: 0 = default of 300ms.
5533
- hoverTime: 1
5534
5692
  });
5535
5693
  };
5536
5694
 
5537
5695
  // src/extensions/mention.ts
5538
5696
  import { autocompletion } from "@codemirror/autocomplete";
5539
- import { log as log8 } from "@dxos/log";
5540
- var __dxlog_file12 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/mention.ts";
5697
+ import { log as log9 } from "@dxos/log";
5698
+ var __dxlog_file13 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/mention.ts";
5541
5699
  var mention = ({ debug, onSearch }) => {
5542
5700
  return autocompletion({
5543
5701
  // TODO(burdon): Not working.
@@ -5549,14 +5707,9 @@ var mention = ({ debug, onSearch }) => {
5549
5707
  icons: false,
5550
5708
  override: [
5551
5709
  (context) => {
5552
- log8.info("completion context", {
5710
+ log9.info("completion context", {
5553
5711
  context
5554
- }, {
5555
- F: __dxlog_file12,
5556
- L: 27,
5557
- S: void 0,
5558
- C: (f, a) => f(...a)
5559
- });
5712
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file13, L: 18, S: void 0 });
5560
5713
  const match = context.matchBefore(/@(\w+)?/);
5561
5714
  if (!match || match.from === match.to && !context.explicit) {
5562
5715
  return null;
@@ -5635,7 +5788,7 @@ import { syntaxTree as syntaxTree9 } from "@codemirror/language";
5635
5788
  import { StateField as StateField10 } from "@codemirror/state";
5636
5789
  import { Facet as Facet2 } from "@codemirror/state";
5637
5790
  import { invariant as invariant5 } from "@dxos/invariant";
5638
- var __dxlog_file13 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/tree.ts";
5791
+ var __dxlog_file14 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/tree.ts";
5639
5792
  var itemToJSON = ({ type, index, level, lineRange, contentRange, children }) => {
5640
5793
  return {
5641
5794
  type,
@@ -5789,15 +5942,7 @@ var outlinerTree = (_options = {}) => {
5789
5942
  break;
5790
5943
  }
5791
5944
  case "BulletList": {
5792
- invariant5(current, void 0, {
5793
- F: __dxlog_file13,
5794
- L: 219,
5795
- S: void 0,
5796
- A: [
5797
- "current",
5798
- ""
5799
- ]
5800
- });
5945
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 169, S: void 0, A: ["current", ""] });
5801
5946
  parent = current;
5802
5947
  if (current) {
5803
5948
  current.lineRange.to = current.node.from;
@@ -5806,15 +5951,7 @@ var outlinerTree = (_options = {}) => {
5806
5951
  break;
5807
5952
  }
5808
5953
  case "ListItem": {
5809
- invariant5(parent, void 0, {
5810
- F: __dxlog_file13,
5811
- L: 228,
5812
- S: void 0,
5813
- A: [
5814
- "parent",
5815
- ""
5816
- ]
5817
- });
5954
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 179, S: void 0, A: ["parent", ""] });
5818
5955
  const nextSibling = node.node.nextSibling ?? node.node.parent?.nextSibling;
5819
5956
  const docRange = {
5820
5957
  from: state.doc.lineAt(node.from).from,
@@ -5848,42 +5985,18 @@ var outlinerTree = (_options = {}) => {
5848
5985
  break;
5849
5986
  }
5850
5987
  case "ListMark": {
5851
- invariant5(current, void 0, {
5852
- F: __dxlog_file13,
5853
- L: 272,
5854
- S: void 0,
5855
- A: [
5856
- "current",
5857
- ""
5858
- ]
5859
- });
5988
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 219, S: void 0, A: ["current", ""] });
5860
5989
  current.type = "bullet";
5861
5990
  current.contentRange.from = node.from + "- ".length;
5862
5991
  break;
5863
5992
  }
5864
5993
  case "Task": {
5865
- invariant5(current, void 0, {
5866
- F: __dxlog_file13,
5867
- L: 278,
5868
- S: void 0,
5869
- A: [
5870
- "current",
5871
- ""
5872
- ]
5873
- });
5994
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 226, S: void 0, A: ["current", ""] });
5874
5995
  current.type = "task";
5875
5996
  break;
5876
5997
  }
5877
5998
  case "TaskMarker": {
5878
- invariant5(current, void 0, {
5879
- F: __dxlog_file13,
5880
- L: 283,
5881
- S: void 0,
5882
- A: [
5883
- "current",
5884
- ""
5885
- ]
5886
- });
5999
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 232, S: void 0, A: ["current", ""] });
5887
6000
  current.contentRange.from = node.from + "[ ] ".length;
5888
6001
  break;
5889
6002
  }
@@ -5891,29 +6004,13 @@ var outlinerTree = (_options = {}) => {
5891
6004
  },
5892
6005
  leave: (node) => {
5893
6006
  if (node.name === "BulletList") {
5894
- invariant5(parent, void 0, {
5895
- F: __dxlog_file13,
5896
- L: 291,
5897
- S: void 0,
5898
- A: [
5899
- "parent",
5900
- ""
5901
- ]
5902
- });
6007
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 240, S: void 0, A: ["parent", ""] });
5903
6008
  prevSiblings[level--] = void 0;
5904
6009
  parent = parent.parent;
5905
6010
  }
5906
6011
  }
5907
6012
  });
5908
- invariant5(tree, void 0, {
5909
- F: __dxlog_file13,
5910
- L: 298,
5911
- S: void 0,
5912
- A: [
5913
- "tree",
5914
- ""
5915
- ]
5916
- });
6013
+ invariant5(tree, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 246, S: void 0, A: ["tree", ""] });
5917
6014
  return tree;
5918
6015
  };
5919
6016
  return [
@@ -6198,17 +6295,17 @@ var commands = () => keymap11.of([
6198
6295
 
6199
6296
  // src/extensions/outliner/outliner.ts
6200
6297
  import { Prec as Prec5 } from "@codemirror/state";
6201
- import { Decoration as Decoration12, EditorView as EditorView25, ViewPlugin as ViewPlugin17 } from "@codemirror/view";
6202
- import { mx as mx7 } from "@dxos/ui-theme";
6298
+ import { Decoration as Decoration12, EditorView as EditorView26, ViewPlugin as ViewPlugin18 } from "@codemirror/view";
6299
+ import { mx as mx6 } from "@dxos/ui-theme";
6203
6300
 
6204
6301
  // src/extensions/outliner/editor.ts
6205
6302
  import { EditorSelection as EditorSelection4, EditorState as EditorState2 } from "@codemirror/state";
6206
- import { ViewPlugin as ViewPlugin15 } from "@codemirror/view";
6207
- import { log as log9 } from "@dxos/log";
6208
- var __dxlog_file14 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/editor.ts";
6303
+ import { ViewPlugin as ViewPlugin16 } from "@codemirror/view";
6304
+ import { log as log10 } from "@dxos/log";
6305
+ var __dxlog_file15 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/editor.ts";
6209
6306
  var LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
6210
6307
  var initialize = () => {
6211
- return ViewPlugin15.fromClass(class {
6308
+ return ViewPlugin16.fromClass(class {
6212
6309
  constructor(view) {
6213
6310
  const first = view.state.doc.lineAt(0);
6214
6311
  const text = view.state.sliceDoc(first.from, first.to);
@@ -6337,7 +6434,7 @@ var editor = () => [
6337
6434
  cancel = true;
6338
6435
  return;
6339
6436
  }
6340
- log9("change", {
6437
+ log10("change", {
6341
6438
  item,
6342
6439
  line: {
6343
6440
  from: line.from,
@@ -6355,35 +6452,20 @@ var editor = () => [
6355
6452
  text: insert.toString(),
6356
6453
  length: insert.length
6357
6454
  }
6358
- }, {
6359
- F: __dxlog_file14,
6360
- L: 164,
6361
- S: void 0,
6362
- C: (f, a) => f(...a)
6363
- });
6455
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 174, S: void 0 });
6364
6456
  }
6365
6457
  });
6366
6458
  if (changes.length > 0) {
6367
- log9("modified,", {
6459
+ log10("modified,", {
6368
6460
  changes
6369
- }, {
6370
- F: __dxlog_file14,
6371
- L: 175,
6372
- S: void 0,
6373
- C: (f, a) => f(...a)
6374
- });
6461
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 196, S: void 0 });
6375
6462
  return [
6376
6463
  {
6377
6464
  changes
6378
6465
  }
6379
6466
  ];
6380
6467
  } else if (cancel) {
6381
- log9("cancel", void 0, {
6382
- F: __dxlog_file14,
6383
- L: 178,
6384
- S: void 0,
6385
- C: (f, a) => f(...a)
6386
- });
6468
+ log10("cancel", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 205, S: void 0 });
6387
6469
  return [];
6388
6470
  }
6389
6471
  return tr;
@@ -6391,10 +6473,10 @@ var editor = () => [
6391
6473
  ];
6392
6474
 
6393
6475
  // src/extensions/outliner/menu.ts
6394
- import { EditorView as EditorView24, ViewPlugin as ViewPlugin16 } from "@codemirror/view";
6395
- import { addEventListener } from "@dxos/async";
6476
+ import { EditorView as EditorView25, ViewPlugin as ViewPlugin17 } from "@codemirror/view";
6477
+ import { addEventListener as addEventListener2 } from "@dxos/async";
6396
6478
  var menu = (options = {}) => [
6397
- ViewPlugin16.fromClass(class {
6479
+ ViewPlugin17.fromClass(class {
6398
6480
  view;
6399
6481
  tag;
6400
6482
  rafId;
@@ -6414,7 +6496,7 @@ var menu = (options = {}) => [
6414
6496
  }
6415
6497
  container.appendChild(this.tag);
6416
6498
  const handler = () => this.scheduleUpdate();
6417
- this.cleanup = addEventListener(container, "scroll", handler);
6499
+ this.cleanup = addEventListener2(container, "scroll", handler);
6418
6500
  this.scheduleUpdate();
6419
6501
  }
6420
6502
  destroy() {
@@ -6456,7 +6538,7 @@ var menu = (options = {}) => [
6456
6538
  this.rafId = requestAnimationFrame(this.updateButtonPosition.bind(this));
6457
6539
  }
6458
6540
  }),
6459
- EditorView24.theme({
6541
+ EditorView25.theme({
6460
6542
  ".cm-popover-trigger": {
6461
6543
  position: "fixed",
6462
6544
  padding: "0",
@@ -6492,12 +6574,12 @@ var outliner = (_options = {}) => [
6492
6574
  listPaddingLeft: 8
6493
6575
  }),
6494
6576
  // Researve space for menu.
6495
- EditorView25.contentAttributes.of({
6496
- class: "is-full !mr-[3rem]"
6577
+ EditorView26.contentAttributes.of({
6578
+ class: "w-full !mr-[3rem]"
6497
6579
  })
6498
6580
  ];
6499
6581
  var decorations = () => [
6500
- ViewPlugin17.fromClass(class {
6582
+ ViewPlugin18.fromClass(class {
6501
6583
  decorations = Decoration12.none;
6502
6584
  constructor(view) {
6503
6585
  this.updateDecorations(view.state, view);
@@ -6522,7 +6604,7 @@ var decorations = () => [
6522
6604
  const lineTo = doc.lineAt(item.contentRange.to);
6523
6605
  const isSelected = selection.includes(item.index) || item === current;
6524
6606
  decorations2.push(Decoration12.line({
6525
- class: mx7("cm-list-item", lineFrom.number === line.number && "cm-list-item-start", lineTo.number === line.number && "cm-list-item-end", isSelected && (hasFocus ? "cm-list-item-focused" : "cm-list-item-selected"))
6607
+ class: mx6("cm-list-item", lineFrom.number === line.number && "cm-list-item-start", lineTo.number === line.number && "cm-list-item-end", isSelected && (hasFocus ? "cm-list-item-focused" : "cm-list-item-selected"))
6526
6608
  }).range(line.from, line.from));
6527
6609
  }
6528
6610
  }
@@ -6532,7 +6614,7 @@ var decorations = () => [
6532
6614
  decorations: (v) => v.decorations
6533
6615
  }),
6534
6616
  // Theme.
6535
- EditorView25.theme(Object.assign({
6617
+ EditorView26.theme(Object.assign({
6536
6618
  ".cm-list-item": {
6537
6619
  borderLeftWidth: "1px",
6538
6620
  borderRightWidth: "1px",
@@ -6557,38 +6639,67 @@ var decorations = () => [
6557
6639
  marginBottom: "2px"
6558
6640
  },
6559
6641
  ".cm-list-item-focused": {
6560
- borderColor: "var(--dx-neutralFocusIndicator)"
6642
+ borderColor: "var(--color-neutral-focus-indicator)"
6561
6643
  },
6562
6644
  "&:focus-within .cm-list-item-selected": {
6563
- borderColor: "var(--dx-separator)"
6645
+ borderColor: "var(--color-separator)"
6564
6646
  }
6565
6647
  }))
6566
6648
  ];
6567
6649
 
6568
6650
  // src/extensions/preview/preview.ts
6569
6651
  import { syntaxTree as syntaxTree10 } from "@codemirror/language";
6570
- import { RangeSetBuilder as RangeSetBuilder6, StateField as StateField11 } from "@codemirror/state";
6571
- import { Decoration as Decoration13, EditorView as EditorView26, WidgetType as WidgetType8 } from "@codemirror/view";
6652
+ import { RangeSetBuilder as RangeSetBuilder6, StateEffect as StateEffect9, StateField as StateField11 } from "@codemirror/state";
6653
+ import { Decoration as Decoration13, EditorView as EditorView27, ViewPlugin as ViewPlugin19, WidgetType as WidgetType8 } from "@codemirror/view";
6654
+ import { DXN, Entity } from "@dxos/echo";
6655
+ var labelResolvedEffect = StateEffect9.define();
6572
6656
  var preview = (options = {}) => {
6657
+ const viewRef = {
6658
+ current: void 0
6659
+ };
6573
6660
  return [
6574
6661
  // NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
6575
6662
  // "Block decorations may not be specified via plugins".
6576
6663
  StateField11.define({
6577
- create: (state) => buildDecorations3(state, options),
6664
+ create: (state) => buildDecorations3(state, options, viewRef),
6578
6665
  update: (decorations2, tr) => {
6579
- if (tr.docChanged) {
6580
- return buildDecorations3(tr.state, options);
6666
+ if (tr.docChanged || tr.effects.some((effect) => effect.is(labelResolvedEffect))) {
6667
+ return buildDecorations3(tr.state, options, viewRef);
6581
6668
  }
6582
6669
  return decorations2.map(tr.changes);
6583
6670
  },
6584
6671
  provide: (field) => [
6585
- EditorView26.decorations.from(field),
6586
- EditorView26.atomicRanges.of((view) => view.state.field(field))
6672
+ EditorView27.decorations.from(field),
6673
+ EditorView27.atomicRanges.of((view) => view.state.field(field))
6587
6674
  ]
6675
+ }),
6676
+ ViewPlugin19.define((view) => {
6677
+ viewRef.current = view;
6678
+ return {
6679
+ destroy() {
6680
+ viewRef.current = void 0;
6681
+ }
6682
+ };
6588
6683
  })
6589
6684
  ];
6590
6685
  };
6591
- var buildDecorations3 = (state, options) => {
6686
+ var resolveLabel = (db, dxnStr, viewRef) => {
6687
+ const dxn = DXN.tryParse(dxnStr);
6688
+ if (!dxn) {
6689
+ return;
6690
+ }
6691
+ const ref = db.makeRef(dxn);
6692
+ const target = ref.target;
6693
+ if (target) {
6694
+ return Entity.getLabel(target);
6695
+ }
6696
+ void ref.tryLoad().then(() => {
6697
+ viewRef.current?.dispatch({
6698
+ effects: labelResolvedEffect.of(void 0)
6699
+ });
6700
+ });
6701
+ };
6702
+ var buildDecorations3 = (state, options, viewRef) => {
6592
6703
  const builder = new RangeSetBuilder6();
6593
6704
  syntaxTree10(state).iterate({
6594
6705
  enter: (node) => {
@@ -6600,8 +6711,13 @@ var buildDecorations3 = (state, options) => {
6600
6711
  case "Link": {
6601
6712
  const link = getLinkRef(state, node.node);
6602
6713
  if (link) {
6714
+ const resolved = options.db ? resolveLabel(options.db, link.dxn, viewRef) : void 0;
6715
+ const displayLink = resolved ? {
6716
+ ...link,
6717
+ label: resolved
6718
+ } : link;
6603
6719
  builder.add(node.from, node.to, Decoration13.replace({
6604
- widget: new PreviewInlineWidget(options, link),
6720
+ widget: new PreviewInlineWidget(options, displayLink),
6605
6721
  side: 1
6606
6722
  }));
6607
6723
  }
@@ -6677,7 +6793,7 @@ var PreviewBlockWidget = class extends WidgetType8 {
6677
6793
  }
6678
6794
  toDOM(_view) {
6679
6795
  const root = document.createElement("div");
6680
- root.classList.add("cm-preview-block", "density-fine");
6796
+ root.classList.add("cm-preview-block", "dx-density-fine");
6681
6797
  this._options.addBlockContainer?.({
6682
6798
  link: this._link,
6683
6799
  el: root
@@ -6693,7 +6809,7 @@ var PreviewBlockWidget = class extends WidgetType8 {
6693
6809
  };
6694
6810
 
6695
6811
  // src/extensions/replacer.ts
6696
- import { EditorView as EditorView27 } from "@codemirror/view";
6812
+ import { EditorView as EditorView28 } from "@codemirror/view";
6697
6813
  var defaultReplacements = [
6698
6814
  {
6699
6815
  input: "--",
@@ -6756,7 +6872,7 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
6756
6872
  const sortedReplacements = [
6757
6873
  ...replacements
6758
6874
  ].sort((a, b) => b.input.length - a.input.length);
6759
- return EditorView27.inputHandler.of((view, from, to, insert) => {
6875
+ return EditorView28.inputHandler.of((view, from, to, insert) => {
6760
6876
  if (insert.length !== 1) {
6761
6877
  return false;
6762
6878
  }
@@ -6790,12 +6906,69 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
6790
6906
  });
6791
6907
  };
6792
6908
 
6909
+ // src/extensions/snippets.ts
6910
+ import { keymap as keymap12 } from "@codemirror/view";
6911
+ var defaultItems = [
6912
+ "hello world!",
6913
+ "this is a test.",
6914
+ "this is [DXOS](https://dxos.org)"
6915
+ ];
6916
+ var snippets2 = ({ delay = 75, items = defaultItems } = {}) => {
6917
+ let timer;
6918
+ let index = 0;
6919
+ return [
6920
+ keymap12.of([
6921
+ {
6922
+ // Reset.
6923
+ key: "alt-meta-'",
6924
+ run: () => {
6925
+ clearTimeout(timer);
6926
+ index = 0;
6927
+ return true;
6928
+ }
6929
+ },
6930
+ {
6931
+ // Next snippet.
6932
+ // TODO(burdon): Press 1-9 to select snippet?
6933
+ key: "Shift-Meta-'",
6934
+ run: (view) => {
6935
+ clearTimeout(timer);
6936
+ const text = items[index++];
6937
+ if (index === items?.length) {
6938
+ index = 0;
6939
+ }
6940
+ let offset = 0;
6941
+ const insert = (delayMs = 0) => {
6942
+ timer = setTimeout(() => {
6943
+ const pos = view.state.selection.main.head;
6944
+ view.dispatch({
6945
+ changes: {
6946
+ from: pos,
6947
+ insert: text[offset++]
6948
+ },
6949
+ selection: {
6950
+ anchor: pos + 1
6951
+ }
6952
+ });
6953
+ if (offset < text.length) {
6954
+ insert(Math.random() * delay * (text[offset] === " " ? 2 : 1));
6955
+ }
6956
+ }, delayMs);
6957
+ };
6958
+ insert();
6959
+ return true;
6960
+ }
6961
+ }
6962
+ ])
6963
+ ];
6964
+ };
6965
+
6793
6966
  // src/extensions/submit.ts
6794
6967
  import { Prec as Prec6 } from "@codemirror/state";
6795
- import { keymap as keymap12 } from "@codemirror/view";
6968
+ import { keymap as keymap13 } from "@codemirror/view";
6796
6969
  var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
6797
6970
  return [
6798
- Prec6.highest(keymap12.of([
6971
+ Prec6.highest(keymap13.of([
6799
6972
  {
6800
6973
  key: "Enter",
6801
6974
  preventDefault: true,
@@ -6840,6 +7013,7 @@ var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
6840
7013
  // src/extensions/tags/extended-markdown.ts
6841
7014
  import { xmlLanguage } from "@codemirror/lang-xml";
6842
7015
  import { parseMixed } from "@lezer/common";
7016
+ var escapeRegExpSource = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6843
7017
  var extendedMarkdown = ({ registry } = {}) => {
6844
7018
  return [
6845
7019
  createMarkdownExtensions({
@@ -6851,13 +7025,65 @@ var extendedMarkdown = ({ registry } = {}) => {
6851
7025
  {
6852
7026
  name: "SetextHeading",
6853
7027
  parse: () => false
6854
- }
7028
+ },
7029
+ // Custom XML block parser that keeps registered tags as a single HTMLBlock
7030
+ // even when their content contains blank lines.
7031
+ ...xmlBlockParsers(registry)
6855
7032
  ]
6856
7033
  }
6857
7034
  ]
6858
7035
  })
6859
7036
  ];
6860
7037
  };
7038
+ var xmlBlockParsers = (registry) => {
7039
+ const customTags = Object.keys(registry ?? {});
7040
+ if (customTags.length === 0) {
7041
+ return [];
7042
+ }
7043
+ const tagPattern = customTags.map(escapeRegExpSource).join("|");
7044
+ const selfClosePattern = new RegExp(`^\\s*<(${tagPattern})(\\s[^>]*)?\\/>\\s*$`);
7045
+ const openPattern = new RegExp(`^\\s*<(${tagPattern})(\\s[^>]*)?\\/?>`);
7046
+ return [
7047
+ {
7048
+ name: "XMLBlock",
7049
+ before: "HTMLBlock",
7050
+ parse: (cx, line) => {
7051
+ const match = openPattern.exec(line.text);
7052
+ if (!match) {
7053
+ return false;
7054
+ }
7055
+ if (selfClosePattern.test(line.text)) {
7056
+ const end2 = cx.lineStart + line.text.length;
7057
+ cx.addElement(cx.elt("HTMLBlock", cx.lineStart, end2));
7058
+ cx.nextLine();
7059
+ return true;
7060
+ }
7061
+ if (match[0].trimEnd().endsWith("/>")) {
7062
+ return false;
7063
+ }
7064
+ const tagName = match[1];
7065
+ const closeTag = `</${tagName}>`;
7066
+ const start = cx.lineStart;
7067
+ if (line.text.includes(closeTag)) {
7068
+ cx.addElement(cx.elt("HTMLBlock", start, start + line.text.length));
7069
+ cx.nextLine();
7070
+ return true;
7071
+ }
7072
+ let end = cx.lineStart + line.text.length;
7073
+ while (cx.nextLine()) {
7074
+ end = cx.lineStart + line.text.length;
7075
+ if (line.text.includes(closeTag)) {
7076
+ cx.addElement(cx.elt("HTMLBlock", start, end));
7077
+ cx.nextLine();
7078
+ return true;
7079
+ }
7080
+ }
7081
+ cx.addElement(cx.elt("HTMLBlock", start, end));
7082
+ return true;
7083
+ }
7084
+ }
7085
+ ];
7086
+ };
6861
7087
  var mixedParser = (registry) => {
6862
7088
  const customTags = Object.keys(registry ?? {});
6863
7089
  const tagPattern = new RegExp(`<(${customTags.join("|")})`);
@@ -6891,219 +7117,793 @@ var mixedParser = (registry) => {
6891
7117
  });
6892
7118
  };
6893
7119
 
6894
- // src/extensions/tags/streamer.ts
6895
- import { StateEffect as StateEffect9, StateField as StateField12 } from "@codemirror/state";
6896
- import { Decoration as Decoration14, EditorView as EditorView28, ViewPlugin as ViewPlugin18, WidgetType as WidgetType9 } from "@codemirror/view";
6897
- import { Domino as Domino3 } from "@dxos/ui";
6898
- import { isTruthy as isTruthy4 } from "@dxos/util";
6899
- var BLINK_RATE = 2e3;
6900
- var streamer = (options = {}) => {
7120
+ // src/extensions/tags/fader.ts
7121
+ import { StateEffect as StateEffect10, StateField as StateField12 } from "@codemirror/state";
7122
+ import { Decoration as Decoration14, EditorView as EditorView29, ViewPlugin as ViewPlugin20 } from "@codemirror/view";
7123
+ var DEFAULT_REMOVAL_DELAY = 5e3;
7124
+ var DEFAULT_COALESCE_WINDOW = 100;
7125
+ var CLEANUP_INTERVAL = 1e3;
7126
+ var fader = (options = {}) => {
7127
+ const removalDelay = DEFAULT_REMOVAL_DELAY;
7128
+ const coalesceWindow = options.coalesce ?? DEFAULT_COALESCE_WINDOW;
7129
+ let lastCount = -1;
7130
+ const log12 = (expiries) => {
7131
+ if (expiries.length !== lastCount) {
7132
+ lastCount = expiries.length;
7133
+ }
7134
+ };
7135
+ const dequeue = StateEffect10.define();
7136
+ const fadeField = StateField12.define({
7137
+ create: () => ({
7138
+ decorations: Decoration14.none,
7139
+ expiries: [],
7140
+ batchStart: 0
7141
+ }),
7142
+ update: ({ decorations: decorations2, expiries, batchStart }, tr) => {
7143
+ for (const effect of tr.effects) {
7144
+ if (effect.is(dequeue)) {
7145
+ const now2 = effect.value;
7146
+ let removeCount = 0;
7147
+ while (removeCount < expiries.length && expiries[removeCount] <= now2) {
7148
+ removeCount++;
7149
+ }
7150
+ if (removeCount > 0) {
7151
+ expiries = expiries.slice(removeCount);
7152
+ let skipped = 0;
7153
+ decorations2 = decorations2.update({
7154
+ filter: () => {
7155
+ if (skipped < removeCount) {
7156
+ skipped++;
7157
+ return false;
7158
+ }
7159
+ return true;
7160
+ }
7161
+ });
7162
+ }
7163
+ }
7164
+ }
7165
+ if (!tr.docChanged) {
7166
+ log12(expiries);
7167
+ return {
7168
+ decorations: decorations2,
7169
+ expiries,
7170
+ batchStart
7171
+ };
7172
+ }
7173
+ let isReset = tr.state.doc.length === 0;
7174
+ if (!isReset && tr.startState.doc.length > 0) {
7175
+ tr.changes.iterChanges((fromA, toA) => {
7176
+ if (fromA === 0 && toA === tr.startState.doc.length) {
7177
+ isReset = true;
7178
+ }
7179
+ });
7180
+ }
7181
+ if (isReset) {
7182
+ log12([]);
7183
+ return {
7184
+ decorations: Decoration14.none,
7185
+ expiries: [],
7186
+ batchStart: 0
7187
+ };
7188
+ }
7189
+ const now = Date.now();
7190
+ const add = [];
7191
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7192
+ if (toA === tr.startState.doc.length && inserted.length > 0) {
7193
+ add.push({
7194
+ from: fromB,
7195
+ to: toB
7196
+ });
7197
+ }
7198
+ });
7199
+ if (add.length > 0) {
7200
+ const canCoalesce = expiries.length > 0 && batchStart > 0 && now - batchStart < coalesceWindow;
7201
+ if (canCoalesce) {
7202
+ let lastFrom = -1;
7203
+ let lastTo = -1;
7204
+ decorations2.between(0, tr.state.doc.length, (from, to) => {
7205
+ lastFrom = from;
7206
+ lastTo = to;
7207
+ });
7208
+ if (lastFrom >= 0) {
7209
+ decorations2 = decorations2.update({
7210
+ filter: (from, to) => !(from === lastFrom && to === lastTo)
7211
+ });
7212
+ const mergedFrom = Math.min(lastFrom, add[0].from);
7213
+ const mergedTo = add[add.length - 1].to;
7214
+ decorations2 = decorations2.update({
7215
+ add: [
7216
+ Decoration14.mark({
7217
+ class: "cm-fader"
7218
+ }).range(mergedFrom, mergedTo)
7219
+ ]
7220
+ });
7221
+ expiries = [
7222
+ ...expiries.slice(0, -1),
7223
+ now + removalDelay
7224
+ ];
7225
+ }
7226
+ } else {
7227
+ batchStart = now;
7228
+ expiries = [
7229
+ ...expiries,
7230
+ now + removalDelay
7231
+ ];
7232
+ decorations2 = decorations2.update({
7233
+ add: add.map(({ from, to }) => Decoration14.mark({
7234
+ class: "cm-fader"
7235
+ }).range(from, to))
7236
+ });
7237
+ }
7238
+ }
7239
+ log12(expiries);
7240
+ return {
7241
+ decorations: decorations2,
7242
+ expiries,
7243
+ batchStart
7244
+ };
7245
+ },
7246
+ provide: (f) => EditorView29.decorations.from(f, (value) => value.decorations)
7247
+ });
7248
+ const cleanup = ViewPlugin20.fromClass(class {
7249
+ view;
7250
+ #timer;
7251
+ constructor(view) {
7252
+ this.view = view;
7253
+ this.#schedule();
7254
+ }
7255
+ update() {
7256
+ this.#schedule();
7257
+ }
7258
+ #schedule() {
7259
+ const { expiries } = this.view.state.field(fadeField);
7260
+ if (expiries.length === 0) {
7261
+ clearTimeout(this.#timer);
7262
+ this.#timer = void 0;
7263
+ return;
7264
+ }
7265
+ if (this.#timer !== void 0) {
7266
+ return;
7267
+ }
7268
+ const delay = Math.max(CLEANUP_INTERVAL, expiries[0] - Date.now());
7269
+ this.#timer = setTimeout(() => {
7270
+ this.#timer = void 0;
7271
+ this.view.dispatch({
7272
+ effects: dequeue.of(Date.now())
7273
+ });
7274
+ }, delay);
7275
+ }
7276
+ destroy() {
7277
+ clearTimeout(this.#timer);
7278
+ }
7279
+ });
6901
7280
  return [
6902
- options.cursor && cursor(),
6903
- options.fadeIn && fadeIn(typeof options.fadeIn === "object" ? options.fadeIn : {})
6904
- ].filter(isTruthy4);
6905
- };
6906
- var cursor = () => {
6907
- const hideCursor = StateEffect9.define();
6908
- const showCursor = StateField12.define({
6909
- create: () => true,
7281
+ fadeField,
7282
+ cleanup,
7283
+ EditorView29.theme({
7284
+ ".cm-fader": {
7285
+ animation: "fader 1s ease-out forwards"
7286
+ },
7287
+ "@keyframes fader": {
7288
+ "0%": {
7289
+ textShadow: "0 0 16px rgba(100, 200, 255, 1), 0 0 32px rgba(100, 200, 255, 0.6)"
7290
+ },
7291
+ "100%": {}
7292
+ }
7293
+ })
7294
+ ];
7295
+ };
7296
+
7297
+ // src/extensions/tags/typewriter.ts
7298
+ import { Annotation as Annotation3, ChangeSet as ChangeSet2, EditorState as EditorState3, StateEffect as StateEffect11, StateField as StateField13 } from "@codemirror/state";
7299
+ import { Decoration as Decoration15, EditorView as EditorView30, ViewPlugin as ViewPlugin21, WidgetType as WidgetType9 } from "@codemirror/view";
7300
+ import { Domino as Domino3 } from "@dxos/ui";
7301
+ var typewriterBypass = Annotation3.define();
7302
+ var typewriterDrainingEffect = StateEffect11.define();
7303
+ var CURSOR_LINGER = 3e3;
7304
+ var FRAME_BUDGET_MS = 4;
7305
+ var CHARS_PER_FRAME = 5;
7306
+ var FLUSH_THRESHOLD = 2e3;
7307
+ var COMPACT_HEAD_THRESHOLD = 4096;
7308
+ var typewriter = (options = {}) => {
7309
+ const streamingTags = options.streamingTags ?? /* @__PURE__ */ new Set();
7310
+ const flushThreshold = options.flushThreshold ?? FLUSH_THRESHOLD;
7311
+ const frameBudgetMs = options.frameBudgetMs ?? FRAME_BUDGET_MS;
7312
+ const charsPerFrame = options.charsPerFrame ?? CHARS_PER_FRAME;
7313
+ const suppressAppend = StateEffect11.define();
7314
+ const insertChunk = StateEffect11.define();
7315
+ const bufferField = StateField13.define({
7316
+ create: () => ({
7317
+ text: "",
7318
+ head: 0,
7319
+ insertAt: 0
7320
+ }),
6910
7321
  update: (value, tr) => {
7322
+ let { text, head, insertAt } = value;
6911
7323
  for (const effect of tr.effects) {
6912
- if (effect.is(hideCursor)) {
6913
- return false;
7324
+ if (effect.is(suppressAppend)) {
7325
+ if (text.length === head) {
7326
+ insertAt = effect.value.from;
7327
+ }
7328
+ text += effect.value.text;
7329
+ }
7330
+ if (effect.is(insertChunk)) {
7331
+ head += effect.value.text.length;
7332
+ insertAt = effect.value.from + effect.value.text.length;
7333
+ if (head >= COMPACT_HEAD_THRESHOLD || head > 0 && head * 2 >= text.length) {
7334
+ text = text.slice(head);
7335
+ head = 0;
7336
+ }
6914
7337
  }
6915
7338
  }
6916
7339
  if (tr.docChanged) {
6917
- return true;
7340
+ let isReset = tr.state.doc.length === 0;
7341
+ if (!isReset && tr.startState.doc.length > 0) {
7342
+ tr.changes.iterChanges((fromA, toA) => {
7343
+ if (fromA === 0 && toA === tr.startState.doc.length) {
7344
+ isReset = true;
7345
+ }
7346
+ });
7347
+ }
7348
+ if (isReset) {
7349
+ return {
7350
+ text: "",
7351
+ head: 0,
7352
+ insertAt: 0
7353
+ };
7354
+ }
7355
+ if (!tr.effects.some((effect) => effect.is(insertChunk))) {
7356
+ insertAt = tr.changes.mapPos(Math.min(insertAt, tr.startState.doc.length));
7357
+ }
6918
7358
  }
6919
- return value;
7359
+ return {
7360
+ text,
7361
+ head,
7362
+ insertAt
7363
+ };
6920
7364
  }
6921
7365
  });
6922
- const timerPlugin = ViewPlugin18.fromClass(class {
7366
+ const filter = EditorState3.transactionFilter.of((tr) => {
7367
+ if (!tr.docChanged) {
7368
+ return tr;
7369
+ }
7370
+ if (tr.annotation(typewriterBypass) || tr.effects.some((effect) => effect.is(insertChunk))) {
7371
+ return tr;
7372
+ }
7373
+ let appendedText = "";
7374
+ let appendFrom = -1;
7375
+ let isAppendOnly = true;
7376
+ tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
7377
+ if (toA === tr.startState.doc.length && fromA === toA && inserted.length > 0) {
7378
+ appendedText += inserted.sliceString(0);
7379
+ if (appendFrom === -1) {
7380
+ appendFrom = fromA;
7381
+ }
7382
+ } else {
7383
+ isAppendOnly = false;
7384
+ }
7385
+ });
7386
+ if (!isAppendOnly || appendedText.length === 0) {
7387
+ return tr;
7388
+ }
7389
+ return {
7390
+ changes: ChangeSet2.empty(tr.startState.doc.length),
7391
+ effects: suppressAppend.of({
7392
+ from: appendFrom,
7393
+ text: appendedText
7394
+ })
7395
+ };
7396
+ });
7397
+ const drainPlugin = ViewPlugin21.fromClass(class {
6923
7398
  view;
6924
- timer;
7399
+ _raf;
7400
+ _activeStreamTag = null;
6925
7401
  constructor(view) {
6926
7402
  this.view = view;
6927
7403
  }
6928
7404
  update(update2) {
6929
- if (update2.docChanged) {
6930
- clearTimeout(this.timer);
6931
- this.timer = setTimeout(() => {
6932
- this.view.dispatch({
6933
- effects: hideCursor.of(null)
6934
- });
6935
- }, BLINK_RATE);
7405
+ const { text, head } = update2.state.field(bufferField);
7406
+ const pending = text.length - head;
7407
+ if (pending === 0) {
7408
+ this._activeStreamTag = null;
7409
+ }
7410
+ if (pending > 0 && this._raf === void 0) {
7411
+ this._start();
6936
7412
  }
6937
7413
  }
7414
+ _start() {
7415
+ queueMicrotask(() => {
7416
+ this.view.dispatch({
7417
+ effects: typewriterDrainingEffect.of(true),
7418
+ annotations: typewriterBypass.of(true)
7419
+ });
7420
+ });
7421
+ this._raf = requestAnimationFrame(this._tick);
7422
+ }
7423
+ _tick = () => {
7424
+ const { text, head, insertAt } = this.view.state.field(bufferField);
7425
+ const pending = text.length - head;
7426
+ if (pending === 0) {
7427
+ this.view.dispatch({
7428
+ effects: typewriterDrainingEffect.of(false),
7429
+ annotations: typewriterBypass.of(true)
7430
+ });
7431
+ this._raf = void 0;
7432
+ return;
7433
+ }
7434
+ if (pending > flushThreshold) {
7435
+ const chunk = text.slice(head);
7436
+ this._activeStreamTag = null;
7437
+ this.view.dispatch({
7438
+ changes: {
7439
+ from: insertAt,
7440
+ insert: chunk
7441
+ },
7442
+ effects: insertChunk.of({
7443
+ from: insertAt,
7444
+ text: chunk
7445
+ })
7446
+ });
7447
+ this._raf = requestAnimationFrame(this._tick);
7448
+ return;
7449
+ }
7450
+ const startTime = performance.now();
7451
+ let pos = head;
7452
+ let activeTag = this._activeStreamTag;
7453
+ let charsEmitted = 0;
7454
+ while (pos < text.length && performance.now() - startTime < frameBudgetMs) {
7455
+ const result = flushable(text, pos, streamingTags, activeTag);
7456
+ if (result.count === 0) {
7457
+ break;
7458
+ }
7459
+ if (charsEmitted > 0 && charsEmitted + result.count > charsPerFrame) {
7460
+ break;
7461
+ }
7462
+ if (result.enterTag) {
7463
+ activeTag = result.enterTag;
7464
+ }
7465
+ if (result.exitTag) {
7466
+ activeTag = null;
7467
+ }
7468
+ pos += result.count;
7469
+ charsEmitted += result.count;
7470
+ }
7471
+ const totalCount = pos - head;
7472
+ if (totalCount > 0) {
7473
+ const chunk = text.slice(head, head + totalCount);
7474
+ this._activeStreamTag = activeTag;
7475
+ this.view.dispatch({
7476
+ changes: {
7477
+ from: insertAt,
7478
+ insert: chunk
7479
+ },
7480
+ effects: insertChunk.of({
7481
+ from: insertAt,
7482
+ text: chunk
7483
+ })
7484
+ });
7485
+ }
7486
+ this._raf = requestAnimationFrame(this._tick);
7487
+ };
6938
7488
  destroy() {
6939
- clearTimeout(this.timer);
7489
+ if (this._raf !== void 0) {
7490
+ cancelAnimationFrame(this._raf);
7491
+ }
6940
7492
  }
6941
7493
  });
6942
- const cursorDecoration = StateField12.define({
6943
- create: () => Decoration14.none,
6944
- update: (_decorations, tr) => {
6945
- const show = tr.state.field(showCursor);
6946
- if (!show) {
6947
- return Decoration14.none;
7494
+ return [
7495
+ bufferField,
7496
+ filter,
7497
+ drainPlugin,
7498
+ options.cursor && typewriterCursor(bufferField)
7499
+ ].filter(Boolean);
7500
+ };
7501
+ var typewriterCursor = (bufferField) => {
7502
+ const hideCursor = StateEffect11.define();
7503
+ const visibilityField = StateField13.define({
7504
+ create: () => ({
7505
+ visible: false,
7506
+ insertAt: 0,
7507
+ lastNonWsAt: 0
7508
+ }),
7509
+ update: (value, tr) => {
7510
+ const { text, head, insertAt } = tr.state.field(bufferField);
7511
+ const pending = text.length - head;
7512
+ if (pending > 0) {
7513
+ let lastNonWsAt = tr.changes.mapPos(Math.min(value.lastNonWsAt, tr.startState.doc.length));
7514
+ if (tr.docChanged) {
7515
+ tr.changes.iterChanges((_fromA, _toA, _fromB, _toB, inserted) => {
7516
+ const chunk = inserted.sliceString(0);
7517
+ if (chunk.trim().length > 0) {
7518
+ lastNonWsAt = _fromB + chunk.length;
7519
+ }
7520
+ });
7521
+ }
7522
+ return {
7523
+ visible: true,
7524
+ insertAt,
7525
+ lastNonWsAt
7526
+ };
7527
+ }
7528
+ for (const effect of tr.effects) {
7529
+ if (effect.is(hideCursor)) {
7530
+ return {
7531
+ ...value,
7532
+ visible: false
7533
+ };
7534
+ }
6948
7535
  }
6949
- const endPos = tr.state.doc.length;
6950
- return Decoration14.set([
6951
- Decoration14.widget({
7536
+ return value;
7537
+ }
7538
+ });
7539
+ const decorationField = StateField13.define({
7540
+ create: () => Decoration15.none,
7541
+ update: (_decorations, tr) => {
7542
+ const { visible, insertAt, lastNonWsAt } = tr.state.field(visibilityField);
7543
+ if (!visible) {
7544
+ return Decoration15.none;
7545
+ }
7546
+ const { text, head } = tr.state.field(bufferField);
7547
+ const cursorAt = text.length > head ? insertAt : lastNonWsAt;
7548
+ const pos = Math.min(cursorAt, tr.state.doc.length);
7549
+ return Decoration15.set([
7550
+ Decoration15.widget({
6952
7551
  widget: new CursorWidget(),
6953
7552
  side: 1
6954
- }).range(endPos)
7553
+ }).range(pos)
6955
7554
  ]);
6956
7555
  },
6957
- provide: (f) => EditorView28.decorations.from(f)
7556
+ provide: (field) => EditorView30.decorations.from(field)
7557
+ });
7558
+ const timerPlugin = ViewPlugin21.fromClass(class {
7559
+ view;
7560
+ _timer;
7561
+ constructor(view) {
7562
+ this.view = view;
7563
+ }
7564
+ update(update2) {
7565
+ const { text, head } = update2.state.field(bufferField);
7566
+ const { visible } = update2.state.field(visibilityField);
7567
+ const pending = text.length - head;
7568
+ if (pending > 0) {
7569
+ clearTimeout(this._timer);
7570
+ this._timer = void 0;
7571
+ } else if (visible && this._timer === void 0) {
7572
+ this._timer = setTimeout(() => {
7573
+ this.view.dispatch({
7574
+ effects: hideCursor.of(null)
7575
+ });
7576
+ this._timer = void 0;
7577
+ }, CURSOR_LINGER);
7578
+ }
7579
+ }
7580
+ destroy() {
7581
+ clearTimeout(this._timer);
7582
+ }
6958
7583
  });
6959
7584
  return [
6960
- showCursor,
6961
- timerPlugin,
6962
- cursorDecoration
7585
+ visibilityField,
7586
+ decorationField,
7587
+ timerPlugin
6963
7588
  ];
6964
7589
  };
6965
- var CursorWidget = class extends WidgetType9 {
7590
+ var CursorWidget = class _CursorWidget extends WidgetType9 {
7591
+ // All instances are interchangeable — let CM reuse the existing DOM across drips so
7592
+ // the blink animation isn't restarted on every transaction.
7593
+ eq(other) {
7594
+ return other instanceof _CursorWidget;
7595
+ }
6966
7596
  toDOM() {
6967
- const inner = Domino3.of("span").text("\u258F").style({
6968
- animation: "blink 2s infinite"
7597
+ const inner = Domino3.of("span").text("\u2217").style({
7598
+ animation: "blink 1s infinite",
7599
+ animationDelay: "250ms"
6969
7600
  });
6970
7601
  return Domino3.of("span").style({
6971
7602
  opacity: "0.8"
6972
- }).children(inner).root;
7603
+ }).append(inner).root;
6973
7604
  }
6974
7605
  };
6975
- var fadeIn = (options = {}) => {
6976
- const FADE_IN_DURATION = 1e3;
6977
- const DEFAULT_REMOVAL_DELAY = 5e3;
6978
- const removalDelay = options.removalDelay ?? DEFAULT_REMOVAL_DELAY;
6979
- const removeDecoration = StateEffect9.define();
6980
- const fadeField = StateField12.define({
6981
- create: () => Decoration14.none,
6982
- update: (decorations2, tr) => {
6983
- let next = decorations2;
6984
- for (const effect of tr.effects) {
6985
- if (effect.is(removeDecoration)) {
6986
- const target = effect.value;
6987
- next = next.update({
6988
- filter: (from, to) => !(from === target.from && to === target.to)
6989
- });
6990
- }
6991
- }
6992
- if (!tr.docChanged) {
6993
- return next;
7606
+ var OPENING_TAG_NAME = /^<([a-zA-Z][\w-]*)/;
7607
+ var TAG_NAME_PROBE = 64;
7608
+ var escapeRegExpSource2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7609
+ var flushable = (buffer, start, streamingTags, activeStreamTag) => {
7610
+ if (start >= buffer.length) {
7611
+ return {
7612
+ count: 0
7613
+ };
7614
+ }
7615
+ if (activeStreamTag) {
7616
+ const closeTag = `</${activeStreamTag}>`;
7617
+ if (buffer.startsWith(closeTag, start)) {
7618
+ return {
7619
+ count: closeTag.length,
7620
+ exitTag: true
7621
+ };
7622
+ }
7623
+ if (buffer[start] === "<") {
7624
+ return {
7625
+ count: xmlElementLength(buffer, start)
7626
+ };
7627
+ }
7628
+ return {
7629
+ count: 1
7630
+ };
7631
+ }
7632
+ const ch = buffer[start];
7633
+ if (ch === "<") {
7634
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7635
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7636
+ if (nameMatch && streamingTags.has(nameMatch[1])) {
7637
+ const close = buffer.indexOf(">", start);
7638
+ if (close === -1) {
7639
+ return {
7640
+ count: 0
7641
+ };
6994
7642
  }
6995
- let isReset = tr.state.doc.length === 0;
6996
- if (!isReset) {
6997
- tr.changes.iterChanges((fromA, toA) => {
6998
- if (fromA === 0 && toA === tr.startState.doc.length) {
6999
- isReset = true;
7000
- }
7001
- });
7643
+ if (buffer[close - 1] === "/") {
7644
+ return {
7645
+ count: close + 1 - start
7646
+ };
7002
7647
  }
7003
- if (isReset) {
7004
- return Decoration14.none;
7648
+ return {
7649
+ count: close + 1 - start,
7650
+ enterTag: nameMatch[1]
7651
+ };
7652
+ }
7653
+ return {
7654
+ count: xmlElementLength(buffer, start)
7655
+ };
7656
+ }
7657
+ if (ch === "!" && buffer.length > start + 1 && buffer[start + 1] === "[") {
7658
+ return {
7659
+ count: linkLength(buffer, start, start + 1)
7660
+ };
7661
+ }
7662
+ if (ch === "[") {
7663
+ return {
7664
+ count: linkLength(buffer, start, start)
7665
+ };
7666
+ }
7667
+ return {
7668
+ count: 1
7669
+ };
7670
+ };
7671
+ var xmlElementLength = (buffer, start = 0) => {
7672
+ const close = buffer.indexOf(">", start);
7673
+ if (close === -1) {
7674
+ return 0;
7675
+ }
7676
+ if (buffer[close - 1] === "/") {
7677
+ return close + 1 - start;
7678
+ }
7679
+ if (buffer[start + 1] === "/") {
7680
+ return close + 1 - start;
7681
+ }
7682
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7683
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7684
+ if (!nameMatch) {
7685
+ return 1;
7686
+ }
7687
+ const tagName = nameMatch[1];
7688
+ let depth = 0;
7689
+ const tagPattern = new RegExp(`<(/?)${escapeRegExpSource2(tagName)}(\\s[^>]*)?>`, "g");
7690
+ tagPattern.lastIndex = start;
7691
+ let match;
7692
+ while ((match = tagPattern.exec(buffer)) !== null) {
7693
+ const isSelfClosing = match[0].endsWith("/>");
7694
+ const isClosing = match[1] === "/";
7695
+ if (isSelfClosing) {
7696
+ if (depth === 0) {
7697
+ return match.index + match[0].length - start;
7698
+ }
7699
+ } else if (isClosing) {
7700
+ depth--;
7701
+ if (depth === 0) {
7702
+ return match.index + match[0].length - start;
7005
7703
  }
7006
- const add = [];
7007
- tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7008
- if (fromB === 0 && toB === inserted.length) {
7704
+ } else {
7705
+ depth++;
7706
+ }
7707
+ }
7708
+ return 0;
7709
+ };
7710
+ var linkLength = (buffer, start, bracketAt) => {
7711
+ const bracketClose = buffer.indexOf("]", bracketAt + 1);
7712
+ if (bracketClose === -1) {
7713
+ return 0;
7714
+ }
7715
+ if (bracketClose + 1 >= buffer.length) {
7716
+ return 0;
7717
+ }
7718
+ if (buffer[bracketClose + 1] !== "(") {
7719
+ return 1;
7720
+ }
7721
+ const parenClose = buffer.indexOf(")", bracketClose + 2);
7722
+ if (parenClose === -1) {
7723
+ return 0;
7724
+ }
7725
+ return parenClose + 1 - start;
7726
+ };
7727
+
7728
+ // src/extensions/tags/xml-block-decoration.ts
7729
+ import { xmlLanguage as xmlLanguage2 } from "@codemirror/lang-xml";
7730
+ import { Decoration as Decoration16, ViewPlugin as ViewPlugin22 } from "@codemirror/view";
7731
+ var xmlBlockDecoration = ({ tag, lineClass, contentClass, hideTags }) => {
7732
+ const lineDecoration = lineClass ? Decoration16.line({
7733
+ class: lineClass
7734
+ }) : void 0;
7735
+ const contentDecoration = contentClass ? Decoration16.mark({
7736
+ class: contentClass
7737
+ }) : void 0;
7738
+ const hideDecoration = hideTags ? Decoration16.replace({}) : void 0;
7739
+ const buildDecorations5 = (view) => {
7740
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7741
+ if (!text.includes(`<${tag}`)) {
7742
+ return Decoration16.none;
7743
+ }
7744
+ const tree = xmlLanguage2.parser.parse(text);
7745
+ const ranges = [];
7746
+ tree.iterate({
7747
+ enter: (node) => {
7748
+ if (node.type.name !== "Element") {
7009
7749
  return;
7010
7750
  }
7011
- if (toA === tr.startState.doc.length && inserted.length > 0) {
7012
- add.push(Decoration14.mark({
7013
- class: "cm-fade-in"
7014
- }).range(fromB, toB));
7751
+ const openTag = node.node.getChild("OpenTag");
7752
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7753
+ const tagNameNode = openTag?.getChild("TagName");
7754
+ if (!openTag || !tagNameNode) {
7755
+ return;
7015
7756
  }
7016
- });
7017
- return next.update({
7018
- add
7019
- });
7020
- },
7021
- provide: (f) => EditorView28.decorations.from(f)
7022
- });
7023
- const timerPlugin = ViewPlugin18.fromClass(class {
7024
- view;
7025
- // Map a simple key "from-to" to timer id.
7026
- _timers = /* @__PURE__ */ new Map();
7757
+ if (text.slice(tagNameNode.from, tagNameNode.to) !== tag) {
7758
+ return;
7759
+ }
7760
+ const contentFrom = openTag.to;
7761
+ const contentTo = closeTag?.from ?? node.node.to;
7762
+ if (hideDecoration) {
7763
+ ranges.push(hideDecoration.range(openTag.from, openTag.to));
7764
+ if (closeTag) {
7765
+ ranges.push(hideDecoration.range(closeTag.from, closeTag.to));
7766
+ }
7767
+ }
7768
+ if (contentDecoration && contentFrom < contentTo) {
7769
+ ranges.push(contentDecoration.range(contentFrom, contentTo));
7770
+ }
7771
+ if (lineDecoration && contentFrom <= view.state.doc.length) {
7772
+ let pos = contentFrom;
7773
+ while (pos <= contentTo && pos <= view.state.doc.length) {
7774
+ const line = view.state.doc.lineAt(pos);
7775
+ ranges.push(lineDecoration.range(line.from));
7776
+ if (line.to >= contentTo) {
7777
+ break;
7778
+ }
7779
+ pos = line.to + 1;
7780
+ }
7781
+ }
7782
+ }
7783
+ });
7784
+ return Decoration16.set(ranges, true);
7785
+ };
7786
+ return ViewPlugin22.fromClass(class {
7787
+ decorations;
7027
7788
  constructor(view) {
7028
- this.view = view;
7789
+ this.decorations = buildDecorations5(view);
7029
7790
  }
7030
7791
  update(update2) {
7031
- if (!update2.docChanged) {
7032
- return;
7792
+ if (update2.docChanged) {
7793
+ this.decorations = buildDecorations5(update2.view);
7033
7794
  }
7034
- update2.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
7035
- if (toA !== update2.startState.doc.length || inserted.length === 0) {
7795
+ }
7796
+ }, {
7797
+ decorations: (instance) => instance.decorations
7798
+ });
7799
+ };
7800
+
7801
+ // src/extensions/tags/xml-formatting.ts
7802
+ import { xmlLanguage as xmlLanguage3 } from "@codemirror/lang-xml";
7803
+ import { Decoration as Decoration17, EditorView as EditorView31, ViewPlugin as ViewPlugin23 } from "@codemirror/view";
7804
+ var XML_TAG_NODES = /* @__PURE__ */ new Set([
7805
+ "OpenTag",
7806
+ "CloseTag",
7807
+ "SelfClosingTag",
7808
+ "MismatchedCloseTag"
7809
+ ]);
7810
+ var xmlElementMark = Decoration17.mark({
7811
+ class: "cm-xml-element"
7812
+ });
7813
+ var xmlTagMark = Decoration17.mark({
7814
+ class: "cm-xml-tag"
7815
+ });
7816
+ var xmlContentMark = Decoration17.mark({
7817
+ class: "cm-xml-content"
7818
+ });
7819
+ var xmlFormatting = ({ skip } = {}) => {
7820
+ const skipSet = skip && skip.length > 0 ? new Set(skip) : void 0;
7821
+ const buildDecorations5 = (view) => {
7822
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7823
+ if (!text.includes("<")) {
7824
+ return Decoration17.none;
7825
+ }
7826
+ const tagNameAt = (node) => text.slice(node.from, node.to);
7827
+ const tree = xmlLanguage3.parser.parse(text);
7828
+ const ranges = [];
7829
+ tree.iterate({
7830
+ enter: (node) => {
7831
+ const name = node.type.name;
7832
+ if (name === "SelfClosingTag" && node.from < node.to) {
7833
+ if (skipSet) {
7834
+ const tagNameNode = node.node.getChild("TagName");
7835
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7836
+ return false;
7837
+ }
7838
+ }
7839
+ ranges.push(xmlElementMark.range(node.from, node.to));
7840
+ ranges.push(xmlTagMark.range(node.from, node.to));
7036
7841
  return;
7037
7842
  }
7038
- const key = `${fromB}-${toB}`;
7039
- if (this._timers.has(key)) {
7040
- clearTimeout(this._timers.get(key));
7843
+ if (XML_TAG_NODES.has(name) && node.from < node.to) {
7844
+ ranges.push(xmlTagMark.range(node.from, node.to));
7845
+ return;
7846
+ }
7847
+ if (name === "Element" && node.from < node.to) {
7848
+ const openTag = node.node.getChild("OpenTag");
7849
+ if (openTag && skipSet) {
7850
+ const tagNameNode = openTag.getChild("TagName");
7851
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7852
+ return false;
7853
+ }
7854
+ }
7855
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7856
+ ranges.push(xmlElementMark.range(node.from, node.to));
7857
+ if (openTag && closeTag && openTag.to < closeTag.from) {
7858
+ ranges.push(xmlContentMark.range(openTag.to, closeTag.from));
7859
+ }
7041
7860
  }
7042
- const totalDelay = FADE_IN_DURATION + removalDelay;
7043
- const id = setTimeout(() => {
7044
- this.view.dispatch({
7045
- effects: removeDecoration.of({
7046
- from: fromB,
7047
- to: toB
7048
- })
7049
- });
7050
- this._timers.delete(key);
7051
- }, totalDelay);
7052
- this._timers.set(key, id);
7053
- });
7054
- }
7055
- destroy() {
7056
- for (const id of this._timers.values()) {
7057
- clearTimeout(id);
7058
7861
  }
7059
- this._timers.clear();
7060
- }
7061
- });
7862
+ });
7863
+ return Decoration17.set(ranges, true);
7864
+ };
7062
7865
  return [
7063
- fadeField,
7064
- timerPlugin,
7065
- EditorView28.theme({
7066
- ".cm-line > span": {
7067
- opacity: "0.8"
7068
- },
7069
- ".cm-fade-in": {
7070
- animation: "fade-in 3s ease-out forwards"
7071
- },
7072
- "@keyframes fade-in": {
7073
- "0%": {
7074
- opacity: "0"
7075
- },
7076
- "80%": {
7077
- opacity: "1"
7078
- },
7079
- "100%": {
7080
- opacity: "0.8"
7866
+ ViewPlugin23.fromClass(class {
7867
+ decorations;
7868
+ constructor(view) {
7869
+ this.decorations = buildDecorations5(view);
7870
+ }
7871
+ update(update2) {
7872
+ if (update2.docChanged) {
7873
+ this.decorations = buildDecorations5(update2.view);
7081
7874
  }
7082
7875
  }
7876
+ }, {
7877
+ decorations: (instance) => instance.decorations
7878
+ }),
7879
+ EditorView31.baseTheme({
7880
+ ".cm-xml-element": {
7881
+ backgroundColor: "var(--color-active-surface)",
7882
+ borderRadius: "0.25rem",
7883
+ padding: "0.25rem"
7884
+ },
7885
+ ".cm-xml-tag": {
7886
+ color: "var(--color-blue-500)",
7887
+ fontFamily: "var(--font-mono)"
7888
+ },
7889
+ ".cm-xml-content": {}
7083
7890
  })
7084
7891
  ];
7085
7892
  };
7086
7893
 
7087
7894
  // src/extensions/tags/xml-tags.ts
7088
7895
  import { syntaxTree as syntaxTree11 } from "@codemirror/language";
7089
- import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect10, StateField as StateField13 } from "@codemirror/state";
7090
- import { Decoration as Decoration15, EditorView as EditorView29, ViewPlugin as ViewPlugin19, WidgetType as WidgetType10, keymap as keymap13 } from "@codemirror/view";
7896
+ import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect12, StateField as StateField14 } from "@codemirror/state";
7897
+ import { Decoration as Decoration18, EditorView as EditorView32, ViewPlugin as ViewPlugin24, WidgetType as WidgetType10, keymap as keymap14 } from "@codemirror/view";
7091
7898
  import { invariant as invariant7 } from "@dxos/invariant";
7092
- import { log as log10 } from "@dxos/log";
7899
+ import { log as log11 } from "@dxos/log";
7900
+ import { Domino as Domino4 } from "@dxos/ui";
7093
7901
 
7094
7902
  // src/extensions/tags/xml-util.ts
7095
7903
  import { invariant as invariant6 } from "@dxos/invariant";
7096
- var __dxlog_file15 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-util.ts";
7904
+ var __dxlog_file16 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-util.ts";
7097
7905
  var nodeToJson = (state, node) => {
7098
- invariant6(node.type.name === "Element", "Node is not an Element", {
7099
- F: __dxlog_file15,
7100
- L: 18,
7101
- S: void 0,
7102
- A: [
7103
- "node.type.name === 'Element'",
7104
- "'Node is not an Element'"
7105
- ]
7106
- });
7906
+ invariant6(node.type.name === "Element", "Node is not an Element", { "~LogMeta": "~LogMeta", F: __dxlog_file16, L: 8, S: void 0, A: ["node.type.name === 'Element'", "'Node is not an Element'"] });
7107
7907
  const openTag = node.node.getChild("OpenTag") || node.node.getChild("SelfClosingTag");
7108
7908
  if (openTag) {
7109
7909
  const tagName = openTag.getChild("TagName");
@@ -7135,13 +7935,23 @@ var nodeToJson = (state, node) => {
7135
7935
  if (node.type.name === "Element" && openTag.type.name !== "SelfClosingTag") {
7136
7936
  const children = [];
7137
7937
  let child = node.node.firstChild;
7938
+ const appendText = (raw) => {
7939
+ if (raw.length === 0) {
7940
+ return;
7941
+ }
7942
+ const last = children[children.length - 1];
7943
+ if (typeof last === "string") {
7944
+ children[children.length - 1] = last + raw;
7945
+ } else {
7946
+ children.push(raw);
7947
+ }
7948
+ };
7138
7949
  while (child) {
7139
7950
  if (child.type.name !== "OpenTag" && child.type.name !== "CloseTag") {
7140
7951
  if (child.type.name === "Text") {
7141
- const text = state.doc.sliceString(child.from, child.to).trim();
7142
- if (text) {
7143
- children.push(text);
7144
- }
7952
+ appendText(state.doc.sliceString(child.from, child.to));
7953
+ } else if (child.type.name === "EntityReference" || child.type.name === "CharacterReference") {
7954
+ appendText(decodeXmlEntity(state.doc.sliceString(child.from, child.to)));
7145
7955
  } else if (child.type.name === "Element") {
7146
7956
  const data = nodeToJson(state, child);
7147
7957
  if (data) {
@@ -7151,26 +7961,63 @@ var nodeToJson = (state, node) => {
7151
7961
  }
7152
7962
  child = child.nextSibling;
7153
7963
  }
7964
+ if (children.length > 0 && typeof children[0] === "string") {
7965
+ children[0] = children[0].trimStart();
7966
+ }
7154
7967
  if (children.length > 0) {
7155
- tag.children = children;
7968
+ const lastIndex = children.length - 1;
7969
+ const last = children[lastIndex];
7970
+ if (typeof last === "string") {
7971
+ children[lastIndex] = last.trimEnd();
7972
+ }
7973
+ }
7974
+ const trimmed = children.filter((value) => typeof value !== "string" || value.length > 0);
7975
+ if (trimmed.length > 0) {
7976
+ tag.children = trimmed;
7156
7977
  }
7157
7978
  }
7158
7979
  return tag;
7159
7980
  }
7160
7981
  };
7982
+ var XML_NAMED_ENTITIES = {
7983
+ "&lt;": "<",
7984
+ "&gt;": ">",
7985
+ "&amp;": "&",
7986
+ "&quot;": '"',
7987
+ "&apos;": "'"
7988
+ };
7989
+ var decodeXmlEntity = (raw) => {
7990
+ const named = XML_NAMED_ENTITIES[raw];
7991
+ if (named !== void 0) {
7992
+ return named;
7993
+ }
7994
+ const numeric = /^&#(x?)([0-9a-fA-F]+);$/.exec(raw);
7995
+ if (numeric) {
7996
+ const code = parseInt(numeric[2], numeric[1] ? 16 : 10);
7997
+ if (Number.isFinite(code)) {
7998
+ try {
7999
+ return String.fromCodePoint(code);
8000
+ } catch {
8001
+ }
8002
+ }
8003
+ }
8004
+ return raw;
8005
+ };
7161
8006
 
7162
8007
  // src/extensions/tags/xml-tags.ts
7163
- var __dxlog_file16 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-tags.ts";
7164
- var navigatePreviousEffect = StateEffect10.define();
7165
- var navigateNextEffect = StateEffect10.define();
8008
+ var __dxlog_file17 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-tags.ts";
8009
+ var navigatePreviousEffect = StateEffect12.define();
8010
+ var navigateNextEffect = StateEffect12.define();
7166
8011
  var getXmlTextChild = (children) => {
7167
8012
  const child = children?.[0];
7168
8013
  return typeof child === "string" ? child : null;
7169
8014
  };
7170
- var xmlTagContextEffect = StateEffect10.define();
7171
- var xmlTagResetEffect = StateEffect10.define();
7172
- var xmlTagUpdateEffect = StateEffect10.define();
7173
- var widgetContextStateField = StateField13.define({
8015
+ var xmlWidgetId = (explicit, fallback) => typeof explicit === "string" && explicit.length > 0 ? explicit : fallback;
8016
+ var escapeRegExpSource3 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8017
+ var xmlTagContextEffect = StateEffect12.define();
8018
+ var xmlTagResetEffect = StateEffect12.define();
8019
+ var xmlTagUpdateEffect = StateEffect12.define();
8020
+ var widgetContextStateField = StateField14.define({
7174
8021
  create: () => void 0,
7175
8022
  update: (value, tr) => {
7176
8023
  for (const effect of tr.effects) {
@@ -7181,7 +8028,7 @@ var widgetContextStateField = StateField13.define({
7181
8028
  return value;
7182
8029
  }
7183
8030
  });
7184
- var widgetStateMapStateField = StateField13.define({
8031
+ var widgetStateMapStateField = StateField14.define({
7185
8032
  create: () => ({}),
7186
8033
  update: (map, tr) => {
7187
8034
  for (const effect of tr.effects) {
@@ -7190,15 +8037,10 @@ var widgetStateMapStateField = StateField13.define({
7190
8037
  }
7191
8038
  if (effect.is(xmlTagUpdateEffect)) {
7192
8039
  const { id, value } = effect.value;
7193
- log10("widget updated", {
8040
+ log11("widget updated", {
7194
8041
  id,
7195
8042
  value
7196
- }, {
7197
- F: __dxlog_file16,
7198
- L: 153,
7199
- S: void 0,
7200
- C: (f, a) => f(...a)
7201
- });
8043
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 59, S: void 0 });
7202
8044
  const state = typeof value === "function" ? value(map[id]) : value;
7203
8045
  return {
7204
8046
  ...map,
@@ -7225,15 +8067,10 @@ var createWidgetMap = (setWidgets) => {
7225
8067
  const widgets = /* @__PURE__ */ new Map();
7226
8068
  const notifier = {
7227
8069
  mounted: (state) => {
7228
- log10("widget mounted", {
8070
+ log11("widget mounted", {
7229
8071
  id: state.id,
7230
8072
  tag: state.props._tag
7231
- }, {
7232
- F: __dxlog_file16,
7233
- L: 206,
7234
- S: void 0,
7235
- C: (f, a) => f(...a)
7236
- });
8073
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 101, S: void 0 });
7237
8074
  widgets.set(state.id, state);
7238
8075
  setWidgets?.([
7239
8076
  ...widgets.values()
@@ -7241,15 +8078,10 @@ var createWidgetMap = (setWidgets) => {
7241
8078
  },
7242
8079
  unmounted: (id) => {
7243
8080
  const state = widgets.get(id);
7244
- log10("widget unmounted", {
8081
+ log11("widget unmounted", {
7245
8082
  id,
7246
8083
  tag: state?.props._tag
7247
- }, {
7248
- F: __dxlog_file16,
7249
- L: 212,
7250
- S: void 0,
7251
- C: (f, a) => f(...a)
7252
- });
8084
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 112, S: void 0 });
7253
8085
  widgets.delete(id);
7254
8086
  setWidgets?.([
7255
8087
  ...widgets.values()
@@ -7258,7 +8090,7 @@ var createWidgetMap = (setWidgets) => {
7258
8090
  };
7259
8091
  return notifier;
7260
8092
  };
7261
- var keyHandlers = keymap13.of([
8093
+ var keyHandlers = keymap14.of([
7262
8094
  {
7263
8095
  key: "Mod-ArrowUp",
7264
8096
  run: (view) => {
@@ -7279,7 +8111,7 @@ var keyHandlers = keymap13.of([
7279
8111
  }
7280
8112
  ]);
7281
8113
  var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7282
- return EditorView29.updateListener.of((update2) => {
8114
+ return EditorView32.updateListener.of((update2) => {
7283
8115
  update2.transactions.forEach((transaction) => {
7284
8116
  for (const effect of transaction.effects) {
7285
8117
  if (effect.is(navigatePreviousEffect)) {
@@ -7307,11 +8139,9 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7307
8139
  anchor: line.from,
7308
8140
  head: line.from
7309
8141
  },
7310
- effects: scrollToLineEffect.of({
7311
- line: line.number,
7312
- options: {
7313
- offset: -16
7314
- }
8142
+ effects: scrollerLineEffect.of({
8143
+ line: line.number - 1,
8144
+ offset: -16
7315
8145
  })
7316
8146
  });
7317
8147
  continue;
@@ -7342,11 +8172,9 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7342
8172
  anchor: line.to,
7343
8173
  head: line.to
7344
8174
  },
7345
- effects: scrollToLineEffect.of({
7346
- line: line.number,
7347
- options: {
7348
- offset: -16
7349
- }
8175
+ effects: scrollerLineEffect.of({
8176
+ line: line.number - 1,
8177
+ offset: -16
7350
8178
  })
7351
8179
  });
7352
8180
  } else {
@@ -7356,11 +8184,9 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7356
8184
  anchor: line.to,
7357
8185
  head: line.to
7358
8186
  },
7359
- effects: scrollToLineEffect.of({
7360
- line: line.number,
7361
- options: {
7362
- position: "end"
7363
- }
8187
+ effects: scrollerLineEffect.of({
8188
+ line: line.number - 1,
8189
+ position: "end"
7364
8190
  })
7365
8191
  });
7366
8192
  }
@@ -7370,7 +8196,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7370
8196
  });
7371
8197
  });
7372
8198
  };
7373
- var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin19.fromClass(class {
8199
+ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin24.fromClass(class {
7374
8200
  update(update2) {
7375
8201
  const widgetStateMap = update2.state.field(widgetStateMapStateField);
7376
8202
  const { decorations: decorations2 } = update2.state.field(widgetDecorationsField);
@@ -7397,19 +8223,25 @@ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin1
7397
8223
  }
7398
8224
  }
7399
8225
  });
7400
- var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.define({
8226
+ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.define({
7401
8227
  create: (state) => {
7402
8228
  return buildDecorations4(state, {
7403
8229
  from: 0,
7404
8230
  to: state.doc.length
7405
8231
  }, registry, notifier);
7406
8232
  },
7407
- update: ({ from, decorations: decorations2 }, tr) => {
8233
+ update: ({ from, streamingFrom, decorations: decorations2 }, tr) => {
7408
8234
  for (const effect of tr.effects) {
7409
8235
  if (effect.is(xmlTagResetEffect)) {
8236
+ if (tr.docChanged) {
8237
+ return buildDecorations4(tr.state, {
8238
+ from: 0,
8239
+ to: tr.state.doc.length
8240
+ }, registry, notifier);
8241
+ }
7410
8242
  return {
7411
8243
  from: 0,
7412
- decorations: Decoration15.none
8244
+ decorations: Decoration18.none
7413
8245
  };
7414
8246
  }
7415
8247
  }
@@ -7417,27 +8249,26 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.def
7417
8249
  const { state } = tr;
7418
8250
  const reset = tr.changes.touchesRange(0, from);
7419
8251
  if (reset) {
7420
- log10("document reset", {
8252
+ log11("document reset", {
7421
8253
  from,
7422
8254
  to: state.doc.length
7423
- }, {
7424
- F: __dxlog_file16,
7425
- L: 371,
7426
- S: void 0,
7427
- C: (f, a) => f(...a)
7428
- });
8255
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 298, S: void 0 });
7429
8256
  return buildDecorations4(state, {
7430
8257
  from: 0,
7431
8258
  to: state.doc.length
7432
8259
  }, registry, notifier);
7433
8260
  } else {
8261
+ const rebuildFrom = streamingFrom ?? from;
7434
8262
  const result = buildDecorations4(state, {
7435
- from,
8263
+ from: rebuildFrom,
7436
8264
  to: state.doc.length
7437
8265
  }, registry, notifier);
7438
8266
  return {
7439
8267
  from: result.from,
8268
+ streamingFrom: result.streamingFrom,
7440
8269
  decorations: decorations2.update({
8270
+ // Remove old streaming decorations — they are rebuilt each tick.
8271
+ filter: (_f, _t, deco) => !deco.spec.streaming,
7441
8272
  add: decorationSetToArray(result.decorations)
7442
8273
  })
7443
8274
  };
@@ -7445,12 +8276,13 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField13.def
7445
8276
  }
7446
8277
  return {
7447
8278
  from,
8279
+ streamingFrom,
7448
8280
  decorations: decorations2
7449
8281
  };
7450
8282
  },
7451
8283
  provide: (field) => [
7452
- EditorView29.decorations.from(field, (v) => v.decorations),
7453
- EditorView29.atomicRanges.of((view) => view.state.field(field).decorations || Decoration15.none)
8284
+ EditorView32.decorations.from(field, (v) => v.decorations),
8285
+ EditorView32.atomicRanges.of((view) => view.state.field(field).decorations || Decoration18.none)
7454
8286
  ]
7455
8287
  });
7456
8288
  var buildDecorations4 = (state, range, registry, notifier) => {
@@ -7461,10 +8293,11 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7461
8293
  if (!tree || tree.type.name === "Program" && tree.length === 0) {
7462
8294
  return {
7463
8295
  from: range.from,
7464
- decorations: Decoration15.none
8296
+ decorations: Decoration18.none
7465
8297
  };
7466
8298
  }
7467
8299
  let last = range.from;
8300
+ let streamingFrom;
7468
8301
  tree.iterate({
7469
8302
  from: range.from,
7470
8303
  to: range.to,
@@ -7477,21 +8310,26 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7477
8310
  if (args) {
7478
8311
  const def = registry[args._tag];
7479
8312
  if (def) {
8313
+ if (def.streaming && !node.node.getChild("CloseTag")) {
8314
+ return false;
8315
+ }
7480
8316
  const { block, factory, Component } = def;
7481
- const widgetState = args.id ? widgetStateMap[args.id] : void 0;
7482
8317
  const nodeRange = {
7483
8318
  from: node.node.from,
7484
8319
  to: node.node.to
7485
8320
  };
8321
+ const widgetId = xmlWidgetId(args.id, def.streaming ? `cm-xml-${nodeRange.from}` : `cm-xml-${nodeRange.from}-${nodeRange.to}`);
8322
+ const widgetState = widgetStateMap[widgetId];
7486
8323
  const props = {
7487
- context,
8324
+ id: widgetId,
7488
8325
  range: nodeRange,
8326
+ context,
7489
8327
  ...args,
7490
8328
  ...widgetState
7491
8329
  };
7492
- const widget = factory ? factory(props) : Component ? args.id && new PlaceholderWidget2(args.id, Component, props, notifier) : void 0;
8330
+ const widget = factory ? factory(props) ?? void 0 : Component ? new PlaceholderWidget2(widgetId, Component, props, notifier) : void 0;
7493
8331
  if (widget) {
7494
- builder.add(nodeRange.from, nodeRange.to, Decoration15.replace({
8332
+ builder.add(nodeRange.from, nodeRange.to, Decoration18.replace({
7495
8333
  widget,
7496
8334
  block,
7497
8335
  atomic: true,
@@ -7503,20 +8341,72 @@ var buildDecorations4 = (state, range, registry, notifier) => {
7503
8341
  }
7504
8342
  }
7505
8343
  } catch (err) {
7506
- log10.catch(err, void 0, {
7507
- F: __dxlog_file16,
7508
- L: 456,
7509
- S: void 0,
7510
- C: (f, a) => f(...a)
7511
- });
8344
+ log11.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 401, S: void 0 });
7512
8345
  }
7513
8346
  return false;
7514
8347
  }
7515
8348
  }
7516
8349
  }
7517
8350
  });
8351
+ const streamingTagNames = Object.entries(registry).filter(([, def]) => def.streaming).map(([name]) => name).sort((a, b) => b.length - a.length);
8352
+ if (streamingTagNames.length > 0) {
8353
+ const tailText = state.sliceDoc(range.from, range.to);
8354
+ const streamingPattern = streamingTagNames.map(escapeRegExpSource3).join("|");
8355
+ const tagPattern = new RegExp(`<(${streamingPattern})(\\s[^>]*)?>`, "g");
8356
+ let match;
8357
+ while ((match = tagPattern.exec(tailText)) !== null) {
8358
+ const tagName = match[1];
8359
+ const closeTag = `</${tagName}>`;
8360
+ const afterOpen = match.index + match[0].length;
8361
+ if (tailText.indexOf(closeTag, afterOpen) === -1) {
8362
+ const absoluteFrom = range.from + match.index;
8363
+ const contentFrom = range.from + afterOpen;
8364
+ const innerText = state.sliceDoc(contentFrom, range.to).trim();
8365
+ const def = registry[tagName];
8366
+ const props = {
8367
+ _tag: tagName,
8368
+ context,
8369
+ range: {
8370
+ from: absoluteFrom,
8371
+ to: range.to
8372
+ },
8373
+ children: innerText ? [
8374
+ innerText
8375
+ ] : void 0
8376
+ };
8377
+ const attrPattern = /(\w+)="([^"]*)"/g;
8378
+ let attrMatch;
8379
+ while ((attrMatch = attrPattern.exec(match[0])) !== null) {
8380
+ props[attrMatch[1]] = attrMatch[2];
8381
+ }
8382
+ const widgetId = xmlWidgetId(props.id, `cm-xml-${absoluteFrom}`);
8383
+ const widgetState = widgetStateMap[widgetId];
8384
+ const mergedProps = {
8385
+ ...props,
8386
+ id: widgetId,
8387
+ ...widgetState
8388
+ };
8389
+ const widget = def.factory ? def.factory(mergedProps) ?? void 0 : def.Component ? new PlaceholderWidget2(widgetId, def.Component, mergedProps, notifier, true) : void 0;
8390
+ if (widget) {
8391
+ builder.add(absoluteFrom, range.to, Decoration18.replace({
8392
+ widget,
8393
+ block: def.block,
8394
+ atomic: true,
8395
+ inclusive: true,
8396
+ tag: tagName,
8397
+ streaming: true,
8398
+ contentFrom
8399
+ }));
8400
+ streamingFrom = absoluteFrom;
8401
+ last = absoluteFrom;
8402
+ }
8403
+ break;
8404
+ }
8405
+ }
8406
+ }
7518
8407
  return {
7519
8408
  from: last,
8409
+ streamingFrom,
7520
8410
  decorations: builder.finish()
7521
8411
  };
7522
8412
  };
@@ -7525,108 +8415,62 @@ var PlaceholderWidget2 = class extends WidgetType10 {
7525
8415
  Component;
7526
8416
  props;
7527
8417
  notifier;
7528
- _root = null;
7529
- constructor(id, Component, props, notifier) {
7530
- super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier;
7531
- invariant7(id, void 0, {
7532
- F: __dxlog_file16,
7533
- L: 482,
7534
- S: this,
7535
- A: [
7536
- "id",
7537
- ""
7538
- ]
7539
- });
8418
+ streaming;
8419
+ #root = null;
8420
+ #view;
8421
+ constructor(id, Component, props, notifier, streaming) {
8422
+ super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier, this.streaming = streaming;
8423
+ invariant7(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 488, S: this, A: ["id", ""] });
7540
8424
  }
7541
8425
  get root() {
7542
- return this._root;
8426
+ return this.#root;
7543
8427
  }
7544
8428
  eq(other) {
8429
+ if (this.streaming) {
8430
+ return false;
8431
+ }
7545
8432
  return this.id === other.id;
7546
8433
  }
7547
8434
  ignoreEvent() {
7548
8435
  return true;
7549
8436
  }
7550
- toDOM(_view) {
7551
- this._root = document.createElement("span");
8437
+ toDOM(view) {
8438
+ this.#view = view;
8439
+ this.#root = Domino4.of("div").classNames("min-h-[24px]").root;
8440
+ const props = Object.assign({}, this.props, {
8441
+ view
8442
+ });
8443
+ this.notifier.mounted({
8444
+ id: this.id,
8445
+ root: this.#root,
8446
+ props,
8447
+ Component: this.Component
8448
+ });
8449
+ return this.#root;
8450
+ }
8451
+ updateDOM(dom) {
8452
+ this.#root = dom;
8453
+ const props = Object.assign({}, this.props, {
8454
+ view: this.#view
8455
+ });
7552
8456
  this.notifier.mounted({
7553
8457
  id: this.id,
7554
- root: this._root,
7555
- props: this.props,
8458
+ root: this.#root,
8459
+ props,
7556
8460
  Component: this.Component
7557
8461
  });
7558
- return this._root;
8462
+ return true;
7559
8463
  }
7560
8464
  destroy(_dom) {
7561
8465
  this.notifier.unmounted(this.id);
7562
- this._root = null;
8466
+ this.#root = null;
8467
+ this.#view = void 0;
7563
8468
  }
7564
8469
  };
7565
-
7566
- // src/extensions/typewriter.ts
7567
- import { keymap as keymap14 } from "@codemirror/view";
7568
- var defaultItems = [
7569
- "hello world!",
7570
- "this is a test.",
7571
- "this is [DXOS](https://dxos.org)"
7572
- ];
7573
- var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
7574
- let t;
7575
- let idx = 0;
7576
- return [
7577
- keymap14.of([
7578
- {
7579
- // Reset.
7580
- key: "alt-meta-'",
7581
- run: () => {
7582
- clearTimeout(t);
7583
- idx = 0;
7584
- return true;
7585
- }
7586
- },
7587
- {
7588
- // Next prompt.
7589
- // TODO(burdon): Press 1-9 to select prompt?
7590
- key: "Shift-Meta-'",
7591
- run: (view) => {
7592
- clearTimeout(t);
7593
- const text = items[idx++];
7594
- if (idx === items?.length) {
7595
- idx = 0;
7596
- }
7597
- let i = 0;
7598
- const insert = (d = 0) => {
7599
- t = setTimeout(() => {
7600
- const pos = view.state.selection.main.head;
7601
- view.dispatch({
7602
- changes: {
7603
- from: pos,
7604
- insert: text[i++]
7605
- },
7606
- selection: {
7607
- anchor: pos + 1
7608
- }
7609
- });
7610
- if (i < text.length) {
7611
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
7612
- }
7613
- }, d);
7614
- };
7615
- insert();
7616
- return true;
7617
- }
7618
- }
7619
- ])
7620
- ];
7621
- };
7622
8470
  export {
7623
8471
  Cursor,
7624
- EditorInputMode,
7625
- EditorInputModes,
7626
- EditorState3 as EditorState,
7627
- EditorView30 as EditorView,
7628
- EditorViewMode,
7629
- EditorViewModes,
8472
+ EditorState4 as EditorState,
8473
+ EditorView33 as EditorView,
7630
8474
  Inline,
7631
8475
  InputModeExtensions,
7632
8476
  List,
@@ -7643,6 +8487,7 @@ export {
7643
8487
  addStyle,
7644
8488
  annotations,
7645
8489
  autoScroll,
8490
+ autoScrollEffect,
7646
8491
  autocomplete,
7647
8492
  automerge,
7648
8493
  awareness,
@@ -7656,9 +8501,11 @@ export {
7656
8501
  commentClickedEffect,
7657
8502
  comments,
7658
8503
  commentsState,
8504
+ compactSlots,
7659
8505
  convertTreeToJson,
7660
8506
  createBasicExtensions,
7661
8507
  createComment,
8508
+ createCrawler,
7662
8509
  createDataExtensions,
7663
8510
  createEditorStateStore,
7664
8511
  createEditorStateTransaction,
@@ -7678,12 +8525,12 @@ export {
7678
8525
  defaultThemeSlots,
7679
8526
  deleteItem,
7680
8527
  documentId,
8528
+ documentSlots,
7681
8529
  dropFile,
8530
+ editorClassNames,
7682
8531
  editorInputMode,
7683
- editorSlots,
7684
- editorWidth,
7685
- editorWithToolbarLayout,
7686
8532
  extendedMarkdown,
8533
+ fader,
7687
8534
  filterChars,
7688
8535
  flattenRect,
7689
8536
  focus,
@@ -7719,6 +8566,7 @@ export {
7719
8566
  markdownTagsExtensions,
7720
8567
  matchCompletion,
7721
8568
  mention,
8569
+ mobileSlots,
7722
8570
  modalStateEffect,
7723
8571
  modalStateField,
7724
8572
  moveItemDown,
@@ -7738,10 +8586,12 @@ export {
7738
8586
  removeList,
7739
8587
  removeStyle,
7740
8588
  replacer,
8589
+ scrollPastEnd,
7741
8590
  scrollThreadIntoView,
7742
- scrollToBottomEffect,
7743
8591
  scrollToLine,
7744
- scrollToLineEffect,
8592
+ scroller,
8593
+ scrollerCrawlEffect,
8594
+ scrollerLineEffect,
7745
8595
  selectionState,
7746
8596
  setBlockquote,
7747
8597
  setComments,
@@ -7749,10 +8599,8 @@ export {
7749
8599
  setSelection,
7750
8600
  setStyle,
7751
8601
  singleValueFacet,
7752
- smoothScroll,
7753
- stackItemContentEditorClassNames,
8602
+ snippets2 as snippets,
7754
8603
  staticCompletion,
7755
- streamer,
7756
8604
  submit,
7757
8605
  tabbable,
7758
8606
  table,
@@ -7771,7 +8619,12 @@ export {
7771
8619
  treeFacet,
7772
8620
  typeahead,
7773
8621
  typewriter,
8622
+ typewriterBypass,
8623
+ typewriterDrainingEffect,
7774
8624
  wrapWithCatch,
8625
+ xmlBlockDecoration,
8626
+ xmlElementLength,
8627
+ xmlFormatting,
7775
8628
  xmlTagContextEffect,
7776
8629
  xmlTagResetEffect,
7777
8630
  xmlTagUpdateEffect,