@dxos/ui-editor 0.8.4-main.fcfe5033a5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/index.mjs +1258 -1004
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +26 -6
  7. package/dist/lib/browser/types/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/index.mjs +1258 -1003
  9. package/dist/lib/node-esm/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/meta.json +1 -1
  11. package/dist/lib/node-esm/types/index.mjs +27 -6
  12. package/dist/lib/node-esm/types/index.mjs.map +4 -4
  13. package/dist/types/src/defaults.d.ts.map +1 -1
  14. package/dist/types/src/extensions/annotations.d.ts.map +1 -1
  15. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -1
  16. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -1
  17. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +5 -2
  18. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -1
  19. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -1
  20. package/dist/types/src/extensions/automerge/automerge.d.ts +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 +1 -1
  23. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  25. package/dist/types/src/extensions/automerge/sync.d.ts +1 -1
  26. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  27. package/dist/types/src/extensions/automerge/update-automerge.d.ts +1 -1
  28. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  29. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  30. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
  31. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  32. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  33. package/dist/types/src/extensions/comments.d.ts +19 -1
  34. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  35. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  36. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  37. package/dist/types/src/extensions/factories.d.ts +3 -2
  38. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  39. package/dist/types/src/extensions/factories.test.d.ts +2 -0
  40. package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
  41. package/dist/types/src/extensions/focus.d.ts +1 -1
  42. package/dist/types/src/extensions/index.d.ts +3 -4
  43. package/dist/types/src/extensions/index.d.ts.map +1 -1
  44. package/dist/types/src/extensions/json.d.ts +1 -1
  45. package/dist/types/src/extensions/json.d.ts.map +1 -1
  46. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  51. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  53. package/dist/types/src/extensions/markdown/image.d.ts +13 -2
  54. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  55. package/dist/types/src/extensions/markdown/image.test.d.ts +2 -0
  56. package/dist/types/src/extensions/markdown/image.test.d.ts.map +1 -0
  57. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  58. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  59. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  60. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -1
  61. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
  62. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -1
  63. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
  64. package/dist/types/src/extensions/preview/preview.d.ts +2 -2
  65. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  66. package/dist/types/src/extensions/replacer.d.ts.map +1 -1
  67. package/dist/types/src/extensions/scrolling/auto-scroll.d.ts +18 -0
  68. package/dist/types/src/extensions/scrolling/auto-scroll.d.ts.map +1 -0
  69. package/dist/types/src/extensions/scrolling/crawler.d.ts +83 -0
  70. package/dist/types/src/extensions/scrolling/crawler.d.ts.map +1 -0
  71. package/dist/types/src/extensions/scrolling/index.d.ts +6 -0
  72. package/dist/types/src/extensions/scrolling/index.d.ts.map +1 -0
  73. package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts.map +1 -0
  74. package/dist/types/src/extensions/scrolling/scrollbar-autohide.d.ts +15 -0
  75. package/dist/types/src/extensions/scrolling/scrollbar-autohide.d.ts.map +1 -0
  76. package/dist/types/src/extensions/scrolling/scroller.d.ts +16 -0
  77. package/dist/types/src/extensions/scrolling/scroller.d.ts.map +1 -0
  78. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  79. package/dist/types/src/extensions/snippets.d.ts +10 -0
  80. package/dist/types/src/extensions/snippets.d.ts.map +1 -0
  81. package/dist/types/src/extensions/spacing.d.ts +3 -0
  82. package/dist/types/src/extensions/spacing.d.ts.map +1 -0
  83. package/dist/types/src/extensions/submit.d.ts.map +1 -1
  84. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -1
  85. package/dist/types/src/extensions/tags/fader.d.ts.map +1 -1
  86. package/dist/types/src/extensions/tags/index.d.ts +3 -1
  87. package/dist/types/src/extensions/tags/index.d.ts.map +1 -1
  88. package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
  89. package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
  90. package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
  91. package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
  92. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
  93. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
  94. package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
  95. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
  96. package/dist/types/src/extensions/tags/xml-tags.d.ts +1 -8
  97. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  98. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -1
  99. package/dist/types/src/index.d.ts +0 -1
  100. package/dist/types/src/index.d.ts.map +1 -1
  101. package/dist/types/src/styles/theme.d.ts.map +1 -1
  102. package/dist/types/src/types/types.d.ts +2 -2
  103. package/dist/types/src/types/types.d.ts.map +1 -1
  104. package/dist/types/src/util/cursor.d.ts.map +1 -1
  105. package/dist/types/src/util/debug.d.ts.map +1 -1
  106. package/dist/types/src/util/decorations.d.ts.map +1 -1
  107. package/dist/types/src/util/dom.d.ts.map +1 -1
  108. package/dist/types/src/util/facet.d.ts.map +1 -1
  109. package/dist/types/src/util/util.d.ts.map +1 -1
  110. package/dist/types/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +55 -57
  112. package/src/defaults.ts +6 -4
  113. package/src/extensions/autocomplete/placeholder.ts +37 -18
  114. package/src/extensions/automerge/automerge.test.tsx +35 -9
  115. package/src/extensions/automerge/automerge.ts +1 -1
  116. package/src/extensions/automerge/cursor.ts +1 -1
  117. package/src/extensions/automerge/sync.ts +1 -1
  118. package/src/extensions/automerge/update-automerge.ts +1 -1
  119. package/src/extensions/comments.ts +54 -31
  120. package/src/extensions/factories.test.ts +88 -0
  121. package/src/extensions/factories.ts +22 -4
  122. package/src/extensions/index.ts +3 -4
  123. package/src/extensions/json.ts +1 -1
  124. package/src/extensions/markdown/decorate.ts +1 -1
  125. package/src/extensions/markdown/image.test.ts +54 -0
  126. package/src/extensions/markdown/image.ts +70 -9
  127. package/src/extensions/markdown/link.ts +7 -2
  128. package/src/extensions/outliner/outliner.ts +1 -1
  129. package/src/extensions/preview/preview.ts +14 -12
  130. package/src/extensions/scrolling/auto-scroll.ts +261 -0
  131. package/src/extensions/{scroller.ts → scrolling/crawler.ts} +89 -48
  132. package/src/extensions/scrolling/index.ts +9 -0
  133. package/src/extensions/{scroll-past-end.ts → scrolling/scroll-past-end.ts} +6 -6
  134. package/src/extensions/scrolling/scrollbar-autohide.ts +61 -0
  135. package/src/extensions/scrolling/scroller.ts +27 -0
  136. package/src/extensions/snippets.ts +67 -0
  137. package/src/extensions/spacing.ts +15 -0
  138. package/src/extensions/tags/index.ts +3 -1
  139. package/src/extensions/tags/testing/text.md +36 -0
  140. package/src/extensions/tags/testing/text.txt +35 -0
  141. package/src/extensions/tags/{wire.test.ts → typewriter.test.ts} +2 -2
  142. package/src/extensions/tags/typewriter.ts +594 -0
  143. package/src/extensions/tags/xml-block-decoration.ts +123 -0
  144. package/src/extensions/tags/xml-formatting.ts +125 -0
  145. package/src/extensions/tags/xml-tags.ts +6 -32
  146. package/src/extensions/tags/xml-util.test.ts +90 -3
  147. package/src/extensions/tags/xml-util.ts +62 -5
  148. package/src/index.ts +0 -1
  149. package/src/styles/theme.ts +23 -13
  150. package/src/typings.d.ts +8 -0
  151. package/dist/lib/browser/chunk-D724USEC.mjs +0 -34
  152. package/dist/lib/browser/chunk-D724USEC.mjs.map +0 -7
  153. package/dist/lib/node-esm/chunk-JRVJWKQF.mjs +0 -36
  154. package/dist/lib/node-esm/chunk-JRVJWKQF.mjs.map +0 -7
  155. package/dist/types/src/extensions/auto-scroll.d.ts +0 -8
  156. package/dist/types/src/extensions/auto-scroll.d.ts.map +0 -1
  157. package/dist/types/src/extensions/scroll-past-end.d.ts.map +0 -1
  158. package/dist/types/src/extensions/scroller.d.ts +0 -63
  159. package/dist/types/src/extensions/scroller.d.ts.map +0 -1
  160. package/dist/types/src/extensions/tags/wire.d.ts +0 -23
  161. package/dist/types/src/extensions/tags/wire.d.ts.map +0 -1
  162. package/dist/types/src/extensions/tags/wire.test.d.ts +0 -2
  163. package/dist/types/src/extensions/tags/wire.test.d.ts.map +0 -1
  164. package/dist/types/src/extensions/typewriter.d.ts +0 -10
  165. package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
  166. package/src/extensions/auto-scroll.ts +0 -179
  167. package/src/extensions/tags/wire.ts +0 -459
  168. package/src/extensions/typewriter.ts +0 -68
  169. /package/dist/types/src/extensions/{scroll-past-end.d.ts → scrolling/scroll-past-end.d.ts} +0 -0
@@ -1,20 +1,14 @@
1
1
  import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
- import {
3
- EditorInputMode,
4
- EditorInputModes,
5
- EditorViewMode,
6
- EditorViewModes
7
- } from "./chunk-JRVJWKQF.mjs";
8
2
 
9
3
  // src/index.ts
10
4
  import { EditorState as EditorState4 } from "@codemirror/state";
11
- import { EditorView as EditorView32, keymap as keymap15 } from "@codemirror/view";
5
+ import { EditorView as EditorView35, keymap as keymap15 } from "@codemirror/view";
12
6
  import { tags as tags2 } from "@lezer/highlight";
13
7
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
14
8
 
15
9
  // src/defaults.ts
16
10
  import { mx } from "@dxos/ui-theme";
17
- 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");
11
+ var editorClassNames = (role) => mx("dx-attention-surface data-[toolbar=disabled]:pt-2 dx-focus-ring-inset", role === "section" ? "[&_.cm-scroller]:overflow-hidden [&_.cm-scroller]:min-h-24" : "dx-container overflow-hidden");
18
12
  var documentSlots = {
19
13
  content: {
20
14
  /**
@@ -24,8 +18,11 @@ var documentSlots = {
24
18
  * NOTE: Max width - 4rem = 2rem left/right margin (or 2rem gutter plus 1rem left/right margin).
25
19
  */
26
20
  className: mx(
27
- // NOTE: Container for widget sizing (must have `max-w-[100cqi]`).
28
- "dx-size-container",
21
+ // Inline-size container for widget sizing (children use `max-w-[100cqi]`).
22
+ // NOTE: Use inline-size, not full size containment — `container-type: size` on the
23
+ // editor content breaks CodeMirror's viewport measurement, leaving blank gaps during
24
+ // scroll until a click forces a re-measure.
25
+ "dx-inline-size-container",
29
26
  // Wider margin for web (vs. mobile).
30
27
  "pointer-fine:max-w-[min(50rem,100%-4rem)] pointer-coarse:max-w-[min(50rem,100%-2rem)]",
31
28
  "mx-auto! w-full"
@@ -276,12 +273,7 @@ var wrapWithCatch = (fn, label) => {
276
273
  } catch (err) {
277
274
  log.catch(err, {
278
275
  label
279
- }, {
280
- F: __dxlog_file,
281
- L: 20,
282
- S: void 0,
283
- C: (f, a) => f(...a)
284
- });
276
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 13, S: void 0 });
285
277
  }
286
278
  };
287
279
  };
@@ -307,12 +299,7 @@ var logChanges = (trs) => {
307
299
  if (changes.length) {
308
300
  log("changes", {
309
301
  changes
310
- }, {
311
- F: __dxlog_file,
312
- L: 54,
313
- S: void 0,
314
- C: (f, a) => f(...a)
315
- });
302
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 44, S: void 0 });
316
303
  }
317
304
  };
318
305
 
@@ -378,30 +365,37 @@ var insertAtLineStart = (view, from, insert) => {
378
365
  };
379
366
 
380
367
  // src/extensions/autocomplete/placeholder.ts
381
- var placeholder = ({ content, delay = 3e3 }) => {
368
+ var placeholder = ({ content, delay = 3e3, focusOnly = false }) => {
382
369
  const plugin = ViewPlugin3.fromClass(class {
383
370
  _timeout;
384
371
  _decorations = Decoration3.none;
385
372
  update(update2) {
373
+ if (!update2.docChanged && !update2.selectionSet && !update2.focusChanged) {
374
+ return;
375
+ }
386
376
  if (this._timeout) {
387
377
  window.clearTimeout(this._timeout);
388
378
  this._timeout = void 0;
389
379
  }
380
+ this._decorations = Decoration3.none;
381
+ if (focusOnly && !update2.view.hasFocus) {
382
+ return;
383
+ }
390
384
  const activeLine = update2.view.state.doc.lineAt(update2.view.state.selection.main.head);
391
- const isEmpty = activeLine.text.trim() === "";
392
- if (isEmpty) {
393
- const lineStart = activeLine.from;
394
- this._timeout = setTimeout(() => {
395
- this._decorations = Decoration3.set([
396
- Decoration3.widget({
397
- widget: new PlaceholderWidget(content),
398
- side: 1
399
- }).range(lineStart)
400
- ]);
401
- update2.view.update([]);
402
- }, delay);
385
+ if (activeLine.text.trim() !== "") {
386
+ return;
403
387
  }
404
- this._decorations = Decoration3.none;
388
+ const lineStart = activeLine.from;
389
+ const view = update2.view;
390
+ this._timeout = setTimeout(() => {
391
+ this._decorations = Decoration3.set([
392
+ Decoration3.widget({
393
+ widget: new PlaceholderWidget(content),
394
+ side: 1
395
+ }).range(lineStart)
396
+ ]);
397
+ view.update([]);
398
+ }, delay);
405
399
  }
406
400
  destroy() {
407
401
  if (this._timeout) {
@@ -519,321 +513,26 @@ var typeahead = ({ onComplete } = {}) => {
519
513
  ];
520
514
  };
521
515
 
522
- // src/extensions/auto-scroll.ts
523
- import { StateEffect as StateEffect2 } from "@codemirror/state";
524
- import { EditorView as EditorView5, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
525
- import { addEventListener, combine, throttle } from "@dxos/async";
526
- import { Domino } from "@dxos/ui";
527
- import { getSize } from "@dxos/ui-theme";
528
-
529
- // src/extensions/scroller.ts
530
- import { StateEffect } from "@codemirror/state";
531
- import { EditorView as EditorView4, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
532
- import { log as log2 } from "@dxos/log";
533
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/scroller.ts";
534
- var scrollerLineEffect = StateEffect.define();
535
- var scrollerCrawlEffect = StateEffect.define();
536
- var scrollToLine = (view, options) => {
537
- view.dispatch({
538
- effects: scrollerLineEffect.of(options)
539
- });
540
- };
541
- var scroller = ({ overScroll = 0 } = {}) => {
542
- const scrollPlugin = ViewPlugin5.fromClass(class ScrollerPlugin {
543
- view;
544
- crawler;
545
- constructor(view) {
546
- this.view = view;
547
- this.crawler = createCrawler(this.view);
548
- }
549
- // No-op.
550
- destroy() {
551
- this.crawler.cancel();
552
- }
553
- cancel() {
554
- this.crawler.cancel();
555
- }
556
- crawl(start = false) {
557
- if (start) {
558
- this.crawler.scroll();
559
- } else {
560
- this.crawler.cancel();
561
- }
562
- }
563
- scroll({ line, offset = 0, position, behavior = "instant" }) {
564
- const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
565
- const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
566
- const doc = this.view.state.doc;
567
- let targetScrollTop = scrollHeight - clientHeight + offset;
568
- if (line >= 0 && line <= doc.lines - 1) {
569
- const lineStart = doc.line(line + 1).from;
570
- const coords = this.view.coordsAtPos(lineStart);
571
- if (coords) {
572
- const currentScrollTop = scrollTop;
573
- const maxScrollTop = scrollHeight - clientHeight;
574
- if (position === "end") {
575
- targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
576
- } else {
577
- targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
578
- }
579
- targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
580
- }
581
- }
582
- requestAnimationFrame(() => {
583
- this.view.scrollDOM.scrollTo({
584
- top: targetScrollTop
585
- });
586
- });
587
- }
588
- });
589
- return [
590
- scrollPlugin,
591
- // Listen for effect.s
592
- EditorView4.updateListener.of((update2) => {
593
- update2.transactions.forEach((transaction) => {
594
- try {
595
- const plugin = update2.view.plugin(scrollPlugin);
596
- if (plugin) {
597
- for (const effect of transaction.effects) {
598
- if (effect.is(scrollerCrawlEffect)) {
599
- plugin.crawl(effect.value);
600
- } else if (effect.is(scrollerLineEffect)) {
601
- plugin.scroll(effect.value);
602
- }
603
- }
604
- }
605
- } catch (err) {
606
- log2.catch(err, void 0, {
607
- F: __dxlog_file2,
608
- L: 146,
609
- S: void 0,
610
- C: (f, a) => f(...a)
611
- });
612
- }
613
- });
614
- }),
615
- // Styles.
616
- EditorView4.theme({
617
- ".cm-content": {
618
- paddingBottom: `${overScroll}px`
619
- },
620
- ".cm-scroller": {
621
- overflowY: "scroll",
622
- overflowAnchor: "none",
623
- paddingBottom: "0"
624
- },
625
- ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
626
- display: "none"
627
- },
628
- ".cm-scroller::-webkit-scrollbar-thumb": {
629
- background: "transparent",
630
- transition: "background 0.15s"
631
- },
632
- "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
633
- background: "var(--color-scrollbar-thumb)"
634
- },
635
- ".cm-scroll-button": {
636
- position: "absolute",
637
- bottom: "0.5rem",
638
- right: "1rem"
639
- }
640
- })
641
- ];
642
- };
643
- function createCrawler(view, accel = 0.15, maxVelocity = 1, snapThreshold = 0.5) {
644
- const el = view.scrollDOM;
645
- let currentTop = 0;
646
- let velocity = 0;
647
- let rafId = null;
648
- function frame() {
649
- const targetTop = el.scrollHeight - el.clientHeight;
650
- const delta = targetTop - currentTop;
651
- const absDelta = Math.abs(delta);
652
- if (absDelta < snapThreshold && Math.abs(velocity) < snapThreshold) {
653
- el.scrollTop = targetTop;
654
- currentTop = targetTop;
655
- velocity = 0;
656
- rafId = null;
657
- return;
658
- }
659
- const stoppingDistance = velocity * velocity / (2 * accel);
660
- const direction = Math.sign(delta);
661
- if (velocity !== 0 && (absDelta <= stoppingDistance || direction !== Math.sign(velocity))) {
662
- velocity -= Math.sign(velocity) * Math.min(accel, Math.abs(velocity));
663
- } else {
664
- velocity += direction * accel;
665
- velocity = Math.sign(velocity) * Math.min(Math.abs(velocity), maxVelocity);
666
- }
667
- currentTop += velocity;
668
- el.scrollTop = currentTop;
669
- rafId = requestAnimationFrame(frame);
670
- }
671
- return {
672
- scroll: () => {
673
- if (rafId === null) {
674
- currentTop = el.scrollTop;
675
- rafId = requestAnimationFrame(frame);
676
- }
677
- },
678
- cancel: () => {
679
- if (rafId !== null) {
680
- cancelAnimationFrame(rafId);
681
- rafId = null;
682
- velocity = 0;
683
- }
684
- }
685
- };
686
- }
687
-
688
- // src/extensions/auto-scroll.ts
689
- var autoScrollEffect = StateEffect2.define();
690
- var autoScroll = (_ = {}) => {
691
- let buttonContainer;
692
- let isPinned = true;
693
- let jumpPending = false;
694
- let enabled = true;
695
- let firstUpdate = true;
696
- const setPinned = (pinned) => {
697
- buttonContainer?.classList.toggle("opacity-0", pinned);
698
- isPinned = pinned;
699
- };
700
- return [
701
- // Update listener for scrolling when content changes.
702
- EditorView5.updateListener.of((update2) => {
703
- const { view, heightChanged, state, startState } = update2;
704
- for (const tr of update2.transactions) {
705
- for (const effect of tr.effects) {
706
- if (effect.is(autoScrollEffect)) {
707
- enabled = effect.value;
708
- if (enabled) {
709
- setPinned(true);
710
- view.dispatch({
711
- effects: scrollerCrawlEffect.of(true)
712
- });
713
- } else {
714
- view.dispatch({
715
- effects: scrollerCrawlEffect.of(false)
716
- });
717
- }
718
- }
719
- }
720
- }
721
- if (!enabled) {
722
- return;
723
- }
724
- if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
725
- firstUpdate = false;
726
- jumpPending = true;
727
- requestAnimationFrame(() => {
728
- view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
729
- jumpPending = false;
730
- });
731
- return;
732
- }
733
- firstUpdate = false;
734
- if (jumpPending) {
735
- return;
736
- }
737
- if (heightChanged) {
738
- if (isPinned) {
739
- const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
740
- const delta = scrollHeight - scrollTop - clientHeight;
741
- if (delta > 0) {
742
- setPinned(true);
743
- view.dispatch({
744
- effects: scrollerCrawlEffect.of(true)
745
- });
746
- } else if (delta < -1) {
747
- setPinned(false);
748
- }
749
- } else {
750
- if (state.doc.length === 0) {
751
- setPinned(true);
752
- }
753
- }
754
- }
755
- }),
756
- // Detect user scroll and unpin (or re-pin if scrolled to the bottom).
757
- ViewPlugin6.fromClass(class {
758
- cleanup;
759
- constructor(view) {
760
- this.cleanup = createUserScrollDetector(view.scrollDOM, throttle(() => {
761
- requestAnimationFrame(() => {
762
- const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
763
- const delta = scrollHeight - scrollTop - clientHeight;
764
- const pinned = delta === 0;
765
- setPinned(pinned);
766
- if (!pinned) {
767
- view.dispatch({
768
- effects: scrollerCrawlEffect.of(false)
769
- });
770
- }
771
- });
772
- }, 500));
773
- }
774
- destroy() {
775
- this.cleanup();
776
- }
777
- }),
778
- // Scroll button.
779
- ViewPlugin6.fromClass(class {
780
- constructor(view) {
781
- const icon = Domino.of("dx-icon").classNames(getSize(4)).attributes({
782
- icon: "ph--arrow-down--regular"
783
- });
784
- const button = Domino.of("button").classNames("dx-button bg-accent-surface").attributes({
785
- "data-density": "fine"
786
- }).append(icon).on("click", () => {
787
- setPinned(true);
788
- view.dispatch({
789
- effects: scrollerLineEffect.of({
790
- line: -1,
791
- position: "end",
792
- behavior: "smooth"
793
- })
794
- });
795
- });
796
- buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").append(button).root;
797
- view.scrollDOM.parentElement.appendChild(buttonContainer);
798
- }
799
- })
800
- ];
801
- };
802
- function createUserScrollDetector(element, onUserScroll) {
803
- return combine(addEventListener(element, "wheel", () => onUserScroll(), {
804
- passive: true
805
- }), addEventListener(element, "pointerdown", (event) => {
806
- if (event.clientX > element.getBoundingClientRect().right - (element.offsetWidth - element.clientWidth)) {
807
- onUserScroll();
808
- }
809
- }));
810
- }
811
-
812
516
  // src/extensions/automerge/automerge.ts
813
517
  import { next as A3 } from "@automerge/automerge";
814
518
  import { StateField, Transaction as Transaction2 } from "@codemirror/state";
815
- import { EditorView as EditorView6, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
816
- import { DocAccessor } from "@dxos/echo-db";
519
+ import { EditorView as EditorView4, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
520
+ import { DocAccessor } from "@dxos/echo-client";
817
521
 
818
522
  // src/extensions/state.ts
819
523
  import { Transaction } from "@codemirror/state";
820
524
  var initialSync = Transaction.userEvent.of("initial.sync");
821
525
 
822
526
  // src/extensions/automerge/cursor.ts
823
- import { fromCursor, toCursor } from "@dxos/echo-db";
824
- import { log as log3 } from "@dxos/log";
825
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/cursor.ts";
527
+ import { fromCursor, toCursor } from "@dxos/echo-client";
528
+ import { log as log2 } from "@dxos/log";
529
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/cursor.ts";
826
530
  var cursorConverter = (accessor) => ({
827
531
  toCursor: (pos, assoc) => {
828
532
  try {
829
533
  return toCursor(accessor, pos, assoc);
830
534
  } catch (err) {
831
- log3.catch(err, void 0, {
832
- F: __dxlog_file3,
833
- L: 15,
834
- S: void 0,
835
- C: (f, a) => f(...a)
836
- });
535
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 11, S: void 0 });
837
536
  return "";
838
537
  }
839
538
  },
@@ -841,22 +540,17 @@ var cursorConverter = (accessor) => ({
841
540
  try {
842
541
  return fromCursor(accessor, cursor);
843
542
  } catch (err) {
844
- log3.catch(err, void 0, {
845
- F: __dxlog_file3,
846
- L: 24,
847
- S: void 0,
848
- C: (f, a) => f(...a)
849
- });
543
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 19, S: void 0 });
850
544
  return 0;
851
545
  }
852
546
  }
853
547
  });
854
548
 
855
549
  // src/extensions/automerge/defs.ts
856
- import { Annotation, StateEffect as StateEffect3 } from "@codemirror/state";
550
+ import { Annotation, StateEffect } from "@codemirror/state";
857
551
  var getPath = (state, field) => state.field(field).path;
858
552
  var getLastHeads = (state, field) => state.field(field).lastHeads;
859
- var updateHeadsEffect = StateEffect3.define({});
553
+ var updateHeadsEffect = StateEffect.define({});
860
554
  var updateHeads = (newHeads) => updateHeadsEffect.of({
861
555
  newHeads
862
556
  });
@@ -867,7 +561,7 @@ var isReconcile = (tr) => {
867
561
 
868
562
  // src/extensions/automerge/sync.ts
869
563
  import { next as A2 } from "@automerge/automerge";
870
- import { log as log4 } from "@dxos/log";
564
+ import { log as log3 } from "@dxos/log";
871
565
 
872
566
  // src/extensions/automerge/update-automerge.ts
873
567
  import { next as A } from "@automerge/automerge";
@@ -1008,7 +702,7 @@ var charPath = (textPath, candidatePath) => {
1008
702
  };
1009
703
 
1010
704
  // src/extensions/automerge/sync.ts
1011
- var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/sync.ts";
705
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/sync.ts";
1012
706
  var Syncer = class {
1013
707
  _handle;
1014
708
  _state;
@@ -1031,12 +725,7 @@ var Syncer = class {
1031
725
  this._pending = false;
1032
726
  }
1033
727
  onEditorChange(view) {
1034
- log4("onEditorChange", void 0, {
1035
- F: __dxlog_file4,
1036
- L: 45,
1037
- S: this,
1038
- C: (f, a) => f(...a)
1039
- });
728
+ log3("onEditorChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 35, S: this });
1040
729
  const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
1041
730
  const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
1042
731
  if (newHeads) {
@@ -1047,12 +736,7 @@ var Syncer = class {
1047
736
  }
1048
737
  }
1049
738
  onAutomergeChange(view) {
1050
- log4("onAutomergeChange", void 0, {
1051
- F: __dxlog_file4,
1052
- L: 60,
1053
- S: this,
1054
- C: (f, a) => f(...a)
1055
- });
739
+ log3("onAutomergeChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 47, S: this });
1056
740
  const oldHeads = getLastHeads(view.state, this._state);
1057
741
  const newHeads = A2.getHeads(this._handle.doc());
1058
742
  const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.doc(), oldHeads, newHeads);
@@ -1105,7 +789,7 @@ var automerge = (accessor) => {
1105
789
  // Track heads.
1106
790
  syncState,
1107
791
  // Reconcile external updates.
1108
- ViewPlugin7.fromClass(class {
792
+ ViewPlugin5.fromClass(class {
1109
793
  _view;
1110
794
  constructor(_view) {
1111
795
  this._view = _view;
@@ -1136,7 +820,7 @@ var automerge = (accessor) => {
1136
820
  };
1137
821
  }),
1138
822
  // Reconcile local updates.
1139
- EditorView6.updateListener.of(({ view, changes, transactions }) => {
823
+ EditorView4.updateListener.of(({ view, changes, transactions }) => {
1140
824
  if (!changes.empty) {
1141
825
  const isInitialSync = transactions.some((tr) => tr.annotation(Transaction2.userEvent) === initialSync.value);
1142
826
  if (!isInitialSync) {
@@ -1149,10 +833,10 @@ var automerge = (accessor) => {
1149
833
 
1150
834
  // src/extensions/awareness/awareness.ts
1151
835
  import { Annotation as Annotation2, RangeSet } from "@codemirror/state";
1152
- import { Decoration as Decoration5, EditorView as EditorView7, ViewPlugin as ViewPlugin8, WidgetType as WidgetType3 } from "@codemirror/view";
836
+ import { Decoration as Decoration5, EditorView as EditorView5, ViewPlugin as ViewPlugin6, WidgetType as WidgetType3 } from "@codemirror/view";
1153
837
  import { Event } from "@dxos/async";
1154
838
  import { Context } from "@dxos/context";
1155
- var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness.ts";
839
+ var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness.ts";
1156
840
  var dummyProvider = {
1157
841
  remoteStateChange: new Event(),
1158
842
  open: () => {
@@ -1168,17 +852,14 @@ var RemoteSelectionChangedAnnotation = Annotation2.define();
1168
852
  var awareness = (provider = dummyProvider) => {
1169
853
  return [
1170
854
  awarenessProvider.of(provider),
1171
- ViewPlugin8.fromClass(RemoteSelectionsDecorator, {
855
+ ViewPlugin6.fromClass(RemoteSelectionsDecorator, {
1172
856
  decorations: (value) => value.decorations
1173
857
  }),
1174
858
  styles
1175
859
  ];
1176
860
  };
1177
861
  var RemoteSelectionsDecorator = class {
1178
- _ctx = new Context(void 0, {
1179
- F: __dxlog_file5,
1180
- L: 80
1181
- });
862
+ _ctx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 33 });
1182
863
  _cursorConverter;
1183
864
  _provider;
1184
865
  _lastAnchor;
@@ -1327,7 +1008,7 @@ var RemoteCaretWidget = class extends WidgetType3 {
1327
1008
  return true;
1328
1009
  }
1329
1010
  };
1330
- var styles = EditorView7.theme({
1011
+ var styles = EditorView5.theme({
1331
1012
  ".cm-collab-selection": {},
1332
1013
  ".cm-collab-selectionLine": {
1333
1014
  padding: 0,
@@ -1389,8 +1070,8 @@ var styles = EditorView7.theme({
1389
1070
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
1390
1071
  import { Context as Context2 } from "@dxos/context";
1391
1072
  import { invariant } from "@dxos/invariant";
1392
- import { log as log5 } from "@dxos/log";
1393
- var __dxlog_file6 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness-provider.ts";
1073
+ import { log as log4 } from "@dxos/log";
1074
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/awareness/awareness-provider.ts";
1394
1075
  var DEBOUNCE_INTERVAL = 100;
1395
1076
  var SpaceAwarenessProvider = class {
1396
1077
  _remoteStates = /* @__PURE__ */ new Map();
@@ -1409,10 +1090,7 @@ var SpaceAwarenessProvider = class {
1409
1090
  this._info = info;
1410
1091
  }
1411
1092
  open() {
1412
- this._ctx = new Context2(void 0, {
1413
- F: __dxlog_file6,
1414
- L: 57
1415
- });
1093
+ this._ctx = new Context2(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 28 });
1416
1094
  this._postTask = new DeferredTask(this._ctx, async () => {
1417
1095
  if (this._localState) {
1418
1096
  await this._messenger.postMessage(this._channel, {
@@ -1437,14 +1115,9 @@ var SpaceAwarenessProvider = class {
1437
1115
  void this._messenger.postMessage(this._channel, {
1438
1116
  kind: "query"
1439
1117
  }).catch((err) => {
1440
- log5.debug("failed to query awareness", {
1118
+ log4.debug("failed to query awareness", {
1441
1119
  err
1442
- }, {
1443
- F: __dxlog_file6,
1444
- L: 91,
1445
- S: this,
1446
- C: (f, a) => f(...a)
1447
- });
1120
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 57, S: this });
1448
1121
  });
1449
1122
  }
1450
1123
  close() {
@@ -1456,15 +1129,7 @@ var SpaceAwarenessProvider = class {
1456
1129
  return Array.from(this._remoteStates.values());
1457
1130
  }
1458
1131
  update(position) {
1459
- invariant(this._postTask, void 0, {
1460
- F: __dxlog_file6,
1461
- L: 106,
1462
- S: this,
1463
- A: [
1464
- "this._postTask",
1465
- ""
1466
- ]
1467
- });
1132
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 71, S: this, A: ["this._postTask", ""] });
1468
1133
  this._localState = {
1469
1134
  peerId: this._peerId,
1470
1135
  position,
@@ -1473,38 +1138,22 @@ var SpaceAwarenessProvider = class {
1473
1138
  this._postTask.schedule();
1474
1139
  }
1475
1140
  _handleQueryMessage() {
1476
- invariant(this._postTask, void 0, {
1477
- F: __dxlog_file6,
1478
- L: 117,
1479
- S: this,
1480
- A: [
1481
- "this._postTask",
1482
- ""
1483
- ]
1484
- });
1141
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 80, S: this, A: ["this._postTask", ""] });
1485
1142
  this._postTask.schedule();
1486
1143
  }
1487
1144
  _handlePostMessage(message) {
1488
- invariant(message.kind === "post", void 0, {
1489
- F: __dxlog_file6,
1490
- L: 122,
1491
- S: this,
1492
- A: [
1493
- "message.kind === 'post'",
1494
- ""
1495
- ]
1496
- });
1145
+ invariant(message.kind === "post", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 84, S: this, A: ["message.kind === 'post'", ""] });
1497
1146
  this._remoteStates.set(message.state.peerId, message.state);
1498
1147
  this.remoteStateChange.emit();
1499
1148
  }
1500
1149
  };
1501
1150
 
1502
1151
  // src/extensions/blast.ts
1503
- import { EditorView as EditorView8, keymap as keymap3 } from "@codemirror/view";
1152
+ import { EditorView as EditorView6, keymap as keymap3 } from "@codemirror/view";
1504
1153
  import defaultsDeep from "lodash.defaultsdeep";
1505
- import { throttle as throttle2 } from "@dxos/async";
1154
+ import { throttle } from "@dxos/async";
1506
1155
  import { invariant as invariant2 } from "@dxos/invariant";
1507
- var __dxlog_file7 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/blast.ts";
1156
+ var __dxlog_file6 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/blast.ts";
1508
1157
  var defaultOptions = {
1509
1158
  effect: 2,
1510
1159
  maxParticles: 200,
@@ -1549,7 +1198,7 @@ var blast = (options = defaultOptions) => {
1549
1198
  };
1550
1199
  return [
1551
1200
  // Cursor moved.
1552
- EditorView8.updateListener.of((update2) => {
1201
+ EditorView6.updateListener.of((update2) => {
1553
1202
  if (blaster?.node !== update2.view.scrollDOM) {
1554
1203
  if (blaster) {
1555
1204
  blaster.destroy();
@@ -1622,15 +1271,7 @@ var Blaster = class {
1622
1271
  return this._node;
1623
1272
  }
1624
1273
  initialize() {
1625
- invariant2(!this._canvas && !this._ctx, void 0, {
1626
- F: __dxlog_file7,
1627
- L: 142,
1628
- S: this,
1629
- A: [
1630
- "!this._canvas && !this._ctx",
1631
- ""
1632
- ]
1633
- });
1274
+ invariant2(!this._canvas && !this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 134, S: this, A: ["!this._canvas && !this._ctx", ""] });
1634
1275
  this._canvas = document.createElement("canvas");
1635
1276
  this._canvas.id = "code-blast-canvas";
1636
1277
  this._canvas.style.position = "absolute";
@@ -1659,15 +1300,7 @@ var Blaster = class {
1659
1300
  }
1660
1301
  }
1661
1302
  start() {
1662
- invariant2(this._canvas && this._ctx, void 0, {
1663
- F: __dxlog_file7,
1664
- L: 181,
1665
- S: this,
1666
- A: [
1667
- "this._canvas && this._ctx",
1668
- ""
1669
- ]
1670
- });
1303
+ invariant2(this._canvas && this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 166, S: this, A: ["this._canvas && this._ctx", ""] });
1671
1304
  this._running = true;
1672
1305
  this.loop();
1673
1306
  }
@@ -1694,11 +1327,11 @@ var Blaster = class {
1694
1327
  this.drawParticles();
1695
1328
  requestAnimationFrame(this.loop.bind(this));
1696
1329
  }
1697
- shake = throttle2(({ time }) => {
1330
+ shake = throttle(({ time }) => {
1698
1331
  this._shakeTime = this._shakeTimeMax || time;
1699
1332
  this._shakeTimeMax = time;
1700
1333
  }, 100);
1701
- spawn = throttle2(({ element, point }) => {
1334
+ spawn = throttle(({ element, point }) => {
1702
1335
  const color = getRGBComponents(element, this._options.color);
1703
1336
  const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
1704
1337
  const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
@@ -1807,9 +1440,9 @@ var random = (min, max) => {
1807
1440
 
1808
1441
  // src/extensions/blocks.ts
1809
1442
  import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
1810
- import { Decoration as Decoration6, EditorView as EditorView9, ViewPlugin as ViewPlugin9 } from "@codemirror/view";
1443
+ import { Decoration as Decoration6, EditorView as EditorView7, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
1811
1444
  import { mx as mx2 } from "@dxos/ui-theme";
1812
- var paragraphBlockPlugin = ViewPlugin9.fromClass(class {
1445
+ var paragraphBlockPlugin = ViewPlugin7.fromClass(class {
1813
1446
  decorations;
1814
1447
  constructor(view) {
1815
1448
  this.decorations = this.build(view);
@@ -1868,7 +1501,7 @@ var paragraphBlockPlugin = ViewPlugin9.fromClass(class {
1868
1501
  });
1869
1502
  var blocks = () => [
1870
1503
  paragraphBlockPlugin,
1871
- EditorView9.baseTheme({
1504
+ EditorView7.baseTheme({
1872
1505
  ".cm-line.block-line": {
1873
1506
  paddingLeft: "0.75rem",
1874
1507
  paddingRight: "0.75rem",
@@ -1902,13 +1535,13 @@ var blocks = () => [
1902
1535
  ];
1903
1536
 
1904
1537
  // src/extensions/bookmarks.ts
1905
- import { Prec as Prec3, StateEffect as StateEffect4, StateField as StateField2 } from "@codemirror/state";
1538
+ import { Prec as Prec3, StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1906
1539
  import { keymap as keymap4 } from "@codemirror/view";
1907
- import { log as log6 } from "@dxos/log";
1908
- var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/bookmarks.ts";
1909
- var addBookmark = StateEffect4.define();
1910
- var removeBookmark = StateEffect4.define();
1911
- var clearBookmarks = StateEffect4.define();
1540
+ import { log as log5 } from "@dxos/log";
1541
+ var __dxlog_file7 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/bookmarks.ts";
1542
+ var addBookmark = StateEffect2.define();
1543
+ var removeBookmark = StateEffect2.define();
1544
+ var clearBookmarks = StateEffect2.define();
1912
1545
  var bookmarks = () => {
1913
1546
  return [
1914
1547
  bookmarksField,
@@ -1917,12 +1550,7 @@ var bookmarks = () => {
1917
1550
  key: "Mod-ArrowUp",
1918
1551
  run: (view) => {
1919
1552
  const bookmarks2 = view.state.field(bookmarksField);
1920
- log6("up", bookmarks2, {
1921
- F: __dxlog_file8,
1922
- L: 29,
1923
- S: void 0,
1924
- C: (f, a) => f(...a)
1925
- });
1553
+ log5("up", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 18, S: void 0 });
1926
1554
  return true;
1927
1555
  }
1928
1556
  },
@@ -1930,12 +1558,7 @@ var bookmarks = () => {
1930
1558
  key: "Mod-ArrowDown",
1931
1559
  run: (view) => {
1932
1560
  const bookmarks2 = view.state.field(bookmarksField);
1933
- log6("down", bookmarks2, {
1934
- F: __dxlog_file8,
1935
- L: 37,
1936
- S: void 0,
1937
- C: (f, a) => f(...a)
1938
- });
1561
+ log5("down", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 26, S: void 0 });
1939
1562
  return true;
1940
1563
  }
1941
1564
  }
@@ -1972,27 +1595,27 @@ var bookmarksField = StateField2.define({
1972
1595
 
1973
1596
  // src/extensions/comments.ts
1974
1597
  import { invertedEffects } from "@codemirror/commands";
1975
- import { StateEffect as StateEffect5, StateField as StateField3 } from "@codemirror/state";
1976
- import { Decoration as Decoration7, EditorView as EditorView11, ViewPlugin as ViewPlugin10, hoverTooltip, keymap as keymap6 } from "@codemirror/view";
1598
+ import { StateEffect as StateEffect3, StateField as StateField3 } from "@codemirror/state";
1599
+ import { Decoration as Decoration7, EditorView as EditorView9, ViewPlugin as ViewPlugin8, hoverTooltip, keymap as keymap6 } from "@codemirror/view";
1977
1600
  import sortBy from "lodash.sortby";
1978
1601
  import { debounce as debounce2 } from "@dxos/async";
1979
- import { log as log7 } from "@dxos/log";
1602
+ import { log as log6 } from "@dxos/log";
1980
1603
  import { isNonNullable } from "@dxos/util";
1981
1604
 
1982
1605
  // src/extensions/selection.ts
1983
1606
  import { Transaction as Transaction3 } from "@codemirror/state";
1984
- import { EditorView as EditorView10, keymap as keymap5 } from "@codemirror/view";
1607
+ import { EditorView as EditorView8, keymap as keymap5 } from "@codemirror/view";
1985
1608
  import { debounce } from "@dxos/async";
1986
1609
  import { invariant as invariant3 } from "@dxos/invariant";
1987
1610
  import { isTruthy } from "@dxos/util";
1988
- var __dxlog_file9 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/selection.ts";
1611
+ var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/selection.ts";
1989
1612
  var documentId = singleValueFacet();
1990
1613
  var stateRestoreAnnotation = "org.dxos.cm.state-restore";
1991
1614
  var createEditorStateTransaction = ({ scrollTo, selection }) => {
1992
1615
  return {
1993
1616
  selection,
1994
1617
  scrollIntoView: !scrollTo,
1995
- effects: scrollTo ? EditorView10.scrollIntoView(scrollTo, {
1618
+ effects: scrollTo ? EditorView8.scrollIntoView(scrollTo, {
1996
1619
  yMargin: 96
1997
1620
  }) : void 0,
1998
1621
  annotations: Transaction3.userEvent.of(stateRestoreAnnotation)
@@ -2000,28 +1623,12 @@ var createEditorStateTransaction = ({ scrollTo, selection }) => {
2000
1623
  };
2001
1624
  var createEditorStateStore = (keyPrefix) => ({
2002
1625
  getState: (id) => {
2003
- invariant3(id, void 0, {
2004
- F: __dxlog_file9,
2005
- L: 47,
2006
- S: void 0,
2007
- A: [
2008
- "id",
2009
- ""
2010
- ]
2011
- });
1626
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 26, S: void 0, A: ["id", ""] });
2012
1627
  const state = localStorage.getItem(`${keyPrefix}/${id}`);
2013
1628
  return state ? JSON.parse(state) : void 0;
2014
1629
  },
2015
1630
  setState: (id, state) => {
2016
- invariant3(id, void 0, {
2017
- F: __dxlog_file9,
2018
- L: 53,
2019
- S: void 0,
2020
- A: [
2021
- "id",
2022
- ""
2023
- ]
2024
- });
1631
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 31, S: void 0, A: ["id", ""] });
2025
1632
  localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
2026
1633
  }
2027
1634
  });
@@ -2034,7 +1641,7 @@ var selectionState = ({ getState, setState } = {}) => {
2034
1641
  // setStateDebounced(id, {});
2035
1642
  // },
2036
1643
  // }),
2037
- EditorView10.updateListener.of(({ view, transactions }) => {
1644
+ EditorView8.updateListener.of(({ view, transactions }) => {
2038
1645
  const id = view.state.facet(documentId);
2039
1646
  if (!id || transactions.some((tr) => tr.isUserEvent(stateRestoreAnnotation))) {
2040
1647
  return;
@@ -2073,10 +1680,10 @@ var selectionState = ({ getState, setState } = {}) => {
2073
1680
  };
2074
1681
 
2075
1682
  // src/extensions/comments.ts
2076
- var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
2077
- var setComments = StateEffect5.define();
2078
- var setSelection = StateEffect5.define();
2079
- var setCommentState = StateEffect5.define();
1683
+ var __dxlog_file9 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
1684
+ var setComments = StateEffect3.define();
1685
+ var setSelection = StateEffect3.define();
1686
+ var setCommentState = StateEffect3.define();
2080
1687
  var commentsState = StateField3.define({
2081
1688
  create: (state) => ({
2082
1689
  id: state.facet(documentId),
@@ -2115,40 +1722,35 @@ var commentsState = StateField3.define({
2115
1722
  return value;
2116
1723
  }
2117
1724
  });
2118
- var styles2 = EditorView11.theme({
2119
- ".cm-comment, .cm-comment-current": {
2120
- padding: "3px 0",
2121
- color: "var(--color-cm-comment-text)",
2122
- backgroundColor: "var(--color-cm-comment-surface)"
2123
- },
2124
- ".cm-comment > span, .cm-comment-current > span": {
1725
+ var styles2 = EditorView9.theme({
1726
+ ".cm-comment > span": {
2125
1727
  boxDecorationBreak: "clone",
2126
- boxShadow: "0 0 1px 3px var(--color-cm-comment-surface)",
1728
+ boxShadow: "0 0 0 3px var(--color-cm-comment-surface)",
2127
1729
  backgroundColor: "var(--color-cm-comment-surface)",
2128
- color: "var(--color-cm-comment-text)",
1730
+ color: "var(--color-cm-comment-text) !important",
2129
1731
  cursor: "pointer"
1732
+ },
1733
+ '.cm-comment[data-current="1"] > span': {
1734
+ boxShadow: "0 0 0 3px var(--color-cm-comment-current-surface)",
1735
+ backgroundColor: "var(--color-cm-comment-current-surface)"
2130
1736
  }
2131
1737
  });
2132
1738
  var createCommentMark = (id, isCurrent) => Decoration7.mark({
2133
- class: isCurrent ? "cm-comment-current" : "cm-comment",
1739
+ class: "cm-comment",
2134
1740
  attributes: {
2135
1741
  "data-testid": "cm-comment",
2136
- "data-comment-id": id
1742
+ "data-comment-id": id,
1743
+ "data-current": isCurrent ? "1" : "0"
2137
1744
  }
2138
1745
  });
2139
- var commentsDecorations = EditorView11.decorations.compute([
1746
+ var commentsDecorations = EditorView9.decorations.compute([
2140
1747
  commentsState
2141
1748
  ], (state) => {
2142
1749
  const { selection: { current }, comments: comments2 } = state.field(commentsState);
2143
1750
  const decorations2 = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
2144
1751
  const range = comment.range;
2145
1752
  if (!range) {
2146
- log7.warn("Invalid range:", range, {
2147
- F: __dxlog_file10,
2148
- L: 139,
2149
- S: void 0,
2150
- C: (f, a) => f(...a)
2151
- });
1753
+ log6.warn("Invalid range:", range, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 93, S: void 0 });
2152
1754
  return void 0;
2153
1755
  } else if (range.from === range.to) {
2154
1756
  return void 0;
@@ -2158,8 +1760,8 @@ var commentsDecorations = EditorView11.decorations.compute([
2158
1760
  }).filter(isNonNullable);
2159
1761
  return Decoration7.set(decorations2);
2160
1762
  });
2161
- var commentClickedEffect = StateEffect5.define();
2162
- var handleCommentClick = EditorView11.domEventHandlers({
1763
+ var commentClickedEffect = StateEffect3.define();
1764
+ var handleCommentClick = EditorView9.domEventHandlers({
2163
1765
  click: (event, view) => {
2164
1766
  let target = event.target;
2165
1767
  const editorRoot = view.dom;
@@ -2198,7 +1800,7 @@ var trackPastedComments = (onUpdate) => {
2198
1800
  }
2199
1801
  };
2200
1802
  return [
2201
- EditorView11.domEventHandlers({
1803
+ EditorView9.domEventHandlers({
2202
1804
  cut: handleTrack,
2203
1805
  copy: handleTrack
2204
1806
  }),
@@ -2220,7 +1822,7 @@ var trackPastedComments = (onUpdate) => {
2220
1822
  return effects;
2221
1823
  }),
2222
1824
  // Handle paste or the undo of comment deletion.
2223
- EditorView11.updateListener.of((update2) => {
1825
+ EditorView9.updateListener.of((update2) => {
2224
1826
  const restore = [];
2225
1827
  for (let i = 0; i < update2.transactions.length; i++) {
2226
1828
  const tr = update2.transactions[i];
@@ -2276,7 +1878,7 @@ var mapTrackedComment = (comment, changes) => ({
2276
1878
  from: changes.mapPos(comment.from, 1),
2277
1879
  to: changes.mapPos(comment.to, 1)
2278
1880
  });
2279
- var restoreCommentEffect = StateEffect5.define({
1881
+ var restoreCommentEffect = StateEffect3.define({
2280
1882
  map: mapTrackedComment
2281
1883
  });
2282
1884
  var createComment = (view) => {
@@ -2362,7 +1964,7 @@ var comments = (options = {}) => {
2362
1964
  //
2363
1965
  // Track deleted ranges and update ranges for decorations.
2364
1966
  //
2365
- EditorView11.updateListener.of(({ view, state, changes }) => {
1967
+ EditorView9.updateListener.of(({ view, state, changes }) => {
2366
1968
  let mod = false;
2367
1969
  const { comments: comments2, ...value } = state.field(commentsState);
2368
1970
  changes.iterChanges((from, to, from2, to2) => {
@@ -2394,7 +1996,7 @@ var comments = (options = {}) => {
2394
1996
  //
2395
1997
  // Track selection/proximity.
2396
1998
  //
2397
- EditorView11.updateListener.of(({ view, state }) => {
1999
+ EditorView9.updateListener.of(({ view, state }) => {
2398
2000
  let min = Infinity;
2399
2001
  const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
2400
2002
  const { head } = state.selection.main;
@@ -2430,34 +2032,41 @@ var comments = (options = {}) => {
2430
2032
  options.onUpdate && trackPastedComments(options.onUpdate)
2431
2033
  ].filter(isNonNullable);
2432
2034
  };
2433
- var scrollThreadIntoView = (view, id, center = true) => {
2035
+ var isRangeVisible = (view, range) => {
2036
+ const from = view.coordsAtPos(range.from);
2037
+ const to = view.coordsAtPos(range.to);
2038
+ if (!from || !to) {
2039
+ return false;
2040
+ }
2041
+ const { top, bottom } = view.scrollDOM.getBoundingClientRect();
2042
+ return from.top >= top && to.bottom <= bottom;
2043
+ };
2044
+ var scrollThreadIntoView = (view, id, { y = "center", yMargin } = {}) => {
2434
2045
  const comment = view.state.field(commentsState).comments.find((range2) => range2.comment.id === id);
2435
2046
  if (!comment?.comment.cursor) {
2436
2047
  return;
2437
2048
  }
2438
2049
  const range = Cursor.getRangeFromCursor(view.state, comment.comment.cursor);
2439
- if (range) {
2440
- const currentSelection = view.state.selection.main;
2441
- const currentScrollPosition = view.scrollDOM.scrollTop;
2442
- const targetScrollPosition = view.coordsAtPos(range.from)?.top;
2443
- const needsScroll = targetScrollPosition !== void 0 && (targetScrollPosition < currentScrollPosition || targetScrollPosition > currentScrollPosition + view.scrollDOM.clientHeight);
2444
- const needsSelectionUpdate = currentSelection.from !== range.from || currentSelection.to !== range.from;
2445
- if (needsScroll || needsSelectionUpdate) {
2446
- view.dispatch({
2447
- selection: needsSelectionUpdate ? {
2448
- anchor: range.from
2449
- } : void 0,
2450
- effects: [
2451
- needsScroll ? EditorView11.scrollIntoView(range.from, center ? {
2452
- y: "center"
2453
- } : void 0) : [],
2454
- needsSelectionUpdate ? setSelection.of({
2455
- current: id
2456
- }) : []
2457
- ].flat()
2458
- });
2459
- }
2050
+ if (!range) {
2051
+ return;
2460
2052
  }
2053
+ const { from, to } = view.state.selection.main;
2054
+ const needsSelectionUpdate = from !== range.from || to !== range.from;
2055
+ view.dispatch({
2056
+ selection: needsSelectionUpdate ? {
2057
+ anchor: range.from
2058
+ } : void 0,
2059
+ effects: [
2060
+ isRangeVisible(view, range) ? [] : EditorView9.scrollIntoView(range.from, {
2061
+ y,
2062
+ yMargin
2063
+ }),
2064
+ // Always mark this thread current so the highlight follows the selected thread.
2065
+ setSelection.of({
2066
+ current: id
2067
+ })
2068
+ ].flat()
2069
+ });
2461
2070
  };
2462
2071
  var ExternalCommentSync = class {
2463
2072
  unsubscribe;
@@ -2479,7 +2088,7 @@ var ExternalCommentSync = class {
2479
2088
  this.unsubscribe();
2480
2089
  };
2481
2090
  };
2482
- var createExternalCommentSync = (id, subscribe, getComments) => ViewPlugin10.fromClass(class {
2091
+ var createExternalCommentSync = (id, subscribe, getComments) => ViewPlugin8.fromClass(class {
2483
2092
  constructor(view) {
2484
2093
  return new ExternalCommentSync(view, id, subscribe, getComments);
2485
2094
  }
@@ -2499,12 +2108,12 @@ var debugNodeLogger = (log12 = console.log) => {
2499
2108
  };
2500
2109
 
2501
2110
  // src/extensions/dnd.ts
2502
- import { EditorView as EditorView12, dropCursor } from "@codemirror/view";
2111
+ import { EditorView as EditorView10, dropCursor } from "@codemirror/view";
2503
2112
  var dropFile = (options = {}) => {
2504
2113
  return [
2505
2114
  styles3,
2506
2115
  dropCursor(),
2507
- EditorView12.domEventHandlers({
2116
+ EditorView10.domEventHandlers({
2508
2117
  drop: (event, view) => {
2509
2118
  event.preventDefault();
2510
2119
  const files = event.dataTransfer?.files;
@@ -2523,7 +2132,7 @@ var dropFile = (options = {}) => {
2523
2132
  })
2524
2133
  ];
2525
2134
  };
2526
- var styles3 = EditorView12.theme({
2135
+ var styles3 = EditorView10.theme({
2527
2136
  ".cm-dropCursor": {
2528
2137
  borderLeft: "2px solid var(--color-accent-text)",
2529
2138
  color: "var(--color-accent-text)",
@@ -2540,15 +2149,15 @@ import { defaultKeymap, history, historyKeymap, indentWithTab, standardKeymap }
2540
2149
  import { HighlightStyle, bracketMatching, syntaxHighlighting } from "@codemirror/language";
2541
2150
  import { searchKeymap } from "@codemirror/search";
2542
2151
  import { EditorState } from "@codemirror/state";
2543
- import { EditorView as EditorView16, ViewPlugin as ViewPlugin12, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap7, lineNumbers, placeholder as placeholder2 } from "@codemirror/view";
2152
+ import { EditorView as EditorView17, ViewPlugin as ViewPlugin13, drawSelection, dropCursor as dropCursor2, highlightActiveLine, keymap as keymap7, lineNumbers, placeholder as placeholder2 } from "@codemirror/view";
2544
2153
  import { vscodeDarkStyle, vscodeLightStyle } from "@uiw/codemirror-theme-vscode";
2545
2154
  import defaultsDeep2 from "lodash.defaultsdeep";
2546
2155
  import { generateName } from "@dxos/display-name";
2547
2156
  import { log as log8 } from "@dxos/log";
2548
- import { hexToHue, isTruthy as isTruthy2 } from "@dxos/util";
2157
+ import { hexToHue, isTruthy as isTruthy3 } from "@dxos/util";
2549
2158
 
2550
2159
  // src/styles/theme.ts
2551
- import { EditorView as EditorView13 } from "@codemirror/view";
2160
+ import { EditorView as EditorView11 } from "@codemirror/view";
2552
2161
  import { mx as mx3 } from "@dxos/ui-theme";
2553
2162
  var headings = {
2554
2163
  1: {
@@ -2596,7 +2205,7 @@ var markdownTheme = {
2596
2205
  fontWeight: "100 !important"
2597
2206
  })
2598
2207
  };
2599
- var baseTheme = EditorView13.baseTheme({
2208
+ var baseTheme = EditorView11.baseTheme({
2600
2209
  /**
2601
2210
  * Outer frame.
2602
2211
  */
@@ -2608,12 +2217,21 @@ var baseTheme = EditorView13.baseTheme({
2608
2217
  * Scroller
2609
2218
  */
2610
2219
  ".cm-scroller": {
2611
- overflowAnchor: "none"
2220
+ // Browser scroll-anchoring: see comment in `scrolling/crawler.ts`. `auto` lets the browser pin a
2221
+ // stable element near the viewport top so widget resizes (e.g. tool-block TogglePanel
2222
+ // open/close) don't jump the user's view.
2223
+ overflowAnchor: "auto"
2612
2224
  },
2613
2225
  ".cm-scroller::-webkit-scrollbar": {
2614
- width: "8px"
2226
+ width: "var(--scrollbar-size,8px)",
2227
+ height: "var(--scrollbar-size,8px)"
2228
+ },
2229
+ ".cm-scroller::-webkit-scrollbar-corner": {
2230
+ background: "transparent"
2231
+ },
2232
+ ".cm-scroller::-webkit-scrollbar-track": {
2233
+ background: "transparent"
2615
2234
  },
2616
- ".cm-scroller::-webkit-scrollbar-track": {},
2617
2235
  ".cm-scroller::-webkit-scrollbar-thumb": {
2618
2236
  background: "transparent",
2619
2237
  transition: "background 0.15s"
@@ -2650,7 +2268,7 @@ var baseTheme = EditorView13.baseTheme({
2650
2268
  * Height is set to match the corresponding line (which may have wrapped).
2651
2269
  */
2652
2270
  ".cm-gutterElement": {
2653
- lineHeight: 1.5,
2271
+ lineHeight: "24px",
2654
2272
  fontSize: "12px"
2655
2273
  },
2656
2274
  /**
@@ -2710,7 +2328,8 @@ var baseTheme = EditorView13.baseTheme({
2710
2328
  textDecorationThickness: "1px",
2711
2329
  textDecorationColor: "var(--color-separator)",
2712
2330
  textUnderlineOffset: "2px",
2713
- borderRadius: ".125rem"
2331
+ borderRadius: ".125rem",
2332
+ cursor: "pointer"
2714
2333
  },
2715
2334
  ".cm-link > span": {
2716
2335
  color: "var(--color-accent-text)"
@@ -2748,12 +2367,12 @@ var baseTheme = EditorView13.baseTheme({
2748
2367
  padding: "4px"
2749
2368
  },
2750
2369
  ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {
2751
- background: "var(--color-active-surface)",
2752
- color: "var(--color-base-surface-text)"
2370
+ background: "var(--color-current-surface)",
2371
+ color: "var(--color-base-fg)"
2753
2372
  },
2754
2373
  ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
2755
2374
  paddingLeft: "4px !important",
2756
- color: "var(--color-base-surface-text)"
2375
+ color: "var(--color-base-fg)"
2757
2376
  },
2758
2377
  /**
2759
2378
  * Completion info.
@@ -2772,7 +2391,7 @@ var baseTheme = EditorView13.baseTheme({
2772
2391
  padding: "0 4px"
2773
2392
  },
2774
2393
  ".cm-completionMatchedText": {
2775
- color: "var(--color-base-surface-text)",
2394
+ color: "var(--color-base-fg)",
2776
2395
  textDecoration: "none !important"
2777
2396
  },
2778
2397
  /**
@@ -2807,7 +2426,7 @@ var baseTheme = EditorView13.baseTheme({
2807
2426
  backgroundColor: "var(--color-input-surface)"
2808
2427
  },
2809
2428
  ".cm-panel input:focus, .cm-panel button:focus": {
2810
- outline: "1px solid var(--color-neutral-focus-indicator)"
2429
+ outline: "1px solid var(--color-focus-ring-subtle)"
2811
2430
  },
2812
2431
  ".cm-panel label": {
2813
2432
  display: "inline-flex",
@@ -2820,15 +2439,15 @@ var baseTheme = EditorView13.baseTheme({
2820
2439
  height: "8px",
2821
2440
  marginRight: "6px !important",
2822
2441
  padding: "2px !important",
2823
- color: "var(--color-neutral-focus-indicator)"
2442
+ color: "var(--color-focus-ring-subtle)"
2824
2443
  },
2825
2444
  ".cm-panel button": {
2826
2445
  "&:hover": {
2827
- // TODO(burdon): Replace with layer and @apply bg-accent-surface-hover
2828
- backgroundColor: "var(--color-accent-surface-hover) !important"
2446
+ // TODO(burdon): Replace with layer and @apply bg-accent-bg-hover
2447
+ backgroundColor: "var(--color-accent-bg-hover) !important"
2829
2448
  },
2830
2449
  "&:active": {
2831
- backgroundColor: "var(--color-accent-surface-hover)"
2450
+ backgroundColor: "var(--color-accent-bg-hover)"
2832
2451
  }
2833
2452
  },
2834
2453
  ".cm-panel.cm-search": {
@@ -2836,14 +2455,14 @@ var baseTheme = EditorView13.baseTheme({
2836
2455
  borderTop: "1px solid var(--color-separator)"
2837
2456
  }
2838
2457
  });
2839
- var editorGutter = EditorView13.theme({
2458
+ var editorGutter = EditorView11.theme({
2840
2459
  ".cm-gutters": {
2841
2460
  // NOTE: Non-transparent background required to cover content if scrolling horizontally.
2842
2461
  background: "var(--color-base-surface) !important",
2843
2462
  paddingRight: "1rem"
2844
2463
  }
2845
2464
  });
2846
- var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2465
+ var createFontTheme = ({ monospace } = {}) => EditorView11.theme({
2847
2466
  // Main content.
2848
2467
  ".cm-scroller": {
2849
2468
  fontFamily: monospace ? fontMono : fontBody
@@ -2856,9 +2475,9 @@ var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2856
2475
  });
2857
2476
 
2858
2477
  // src/extensions/focus.ts
2859
- import { StateEffect as StateEffect6, StateField as StateField5 } from "@codemirror/state";
2860
- import { EditorView as EditorView14 } from "@codemirror/view";
2861
- var focusEffect = StateEffect6.define();
2478
+ import { StateEffect as StateEffect4, StateField as StateField5 } from "@codemirror/state";
2479
+ import { EditorView as EditorView12 } from "@codemirror/view";
2480
+ var focusEffect = StateEffect4.define();
2862
2481
  var focusField = StateField5.define({
2863
2482
  create: () => false,
2864
2483
  update: (value, tr) => {
@@ -2872,7 +2491,7 @@ var focusField = StateField5.define({
2872
2491
  });
2873
2492
  var focus = [
2874
2493
  focusField,
2875
- EditorView14.domEventHandlers({
2494
+ EditorView12.domEventHandlers({
2876
2495
  focus: (_event, view) => {
2877
2496
  requestAnimationFrame(() => view.dispatch({
2878
2497
  effects: focusEffect.of(true)
@@ -2886,19 +2505,403 @@ var focus = [
2886
2505
  })
2887
2506
  ];
2888
2507
 
2889
- // src/extensions/scroll-past-end.ts
2508
+ // src/extensions/scrolling/auto-scroll.ts
2509
+ import { StateEffect as StateEffect6 } from "@codemirror/state";
2510
+ import { EditorView as EditorView14, ViewPlugin as ViewPlugin10 } from "@codemirror/view";
2511
+ import { addEventListener, combine, throttle as throttle2 } from "@dxos/async";
2512
+ import { Domino } from "@dxos/ui";
2513
+ import { getSize } from "@dxos/ui-theme";
2514
+
2515
+ // src/extensions/scrolling/crawler.ts
2516
+ import { StateEffect as StateEffect5 } from "@codemirror/state";
2517
+ import { EditorView as EditorView13, ViewPlugin as ViewPlugin9 } from "@codemirror/view";
2518
+ import { log as log7 } from "@dxos/log";
2519
+ var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/scrolling/crawler.ts";
2520
+ var crawlerLineEffect = StateEffect5.define();
2521
+ var crawlerActiveEffect = StateEffect5.define();
2522
+ var scrollToLine = (view, options) => {
2523
+ view.dispatch({
2524
+ effects: crawlerLineEffect.of(options)
2525
+ });
2526
+ };
2527
+ var crawler = ({ overScroll = 0 } = {}) => {
2528
+ const crawlerPlugin = ViewPlugin9.fromClass(class CrawlerPlugin {
2529
+ view;
2530
+ crawler;
2531
+ constructor(view) {
2532
+ this.view = view;
2533
+ this.crawler = createCrawler(this.view);
2534
+ }
2535
+ // No-op.
2536
+ destroy() {
2537
+ this.crawler.cancel();
2538
+ }
2539
+ cancel() {
2540
+ this.crawler.cancel();
2541
+ }
2542
+ crawl({ active, instant } = {
2543
+ active: false
2544
+ }) {
2545
+ if (active) {
2546
+ this.crawler.scroll(instant);
2547
+ } else {
2548
+ this.crawler.cancel();
2549
+ }
2550
+ }
2551
+ scroll({ line, offset = 0, position, behavior = "instant" }) {
2552
+ const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
2553
+ const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
2554
+ const doc = this.view.state.doc;
2555
+ let targetScrollTop = scrollHeight - clientHeight + offset;
2556
+ if (line >= 0 && line <= doc.lines - 1) {
2557
+ const lineStart = doc.line(line + 1).from;
2558
+ const coords = this.view.coordsAtPos(lineStart);
2559
+ if (coords) {
2560
+ const currentScrollTop = scrollTop;
2561
+ const maxScrollTop = scrollHeight - clientHeight;
2562
+ if (position === "end") {
2563
+ targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
2564
+ } else {
2565
+ targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
2566
+ }
2567
+ targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
2568
+ }
2569
+ }
2570
+ requestAnimationFrame(() => {
2571
+ this.view.scrollDOM.scrollTo({
2572
+ top: targetScrollTop
2573
+ });
2574
+ });
2575
+ }
2576
+ });
2577
+ return [
2578
+ crawlerPlugin,
2579
+ // Listen for effect.
2580
+ EditorView13.updateListener.of((update2) => {
2581
+ update2.transactions.forEach((transaction) => {
2582
+ try {
2583
+ const plugin = update2.view.plugin(crawlerPlugin);
2584
+ if (plugin) {
2585
+ for (const effect of transaction.effects) {
2586
+ if (effect.is(crawlerActiveEffect)) {
2587
+ plugin.crawl(effect.value);
2588
+ } else if (effect.is(crawlerLineEffect)) {
2589
+ plugin.scroll(effect.value);
2590
+ }
2591
+ }
2592
+ }
2593
+ } catch (err) {
2594
+ log7.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file10, L: 105, S: void 0 });
2595
+ }
2596
+ });
2597
+ }),
2598
+ // Styles.
2599
+ EditorView13.theme({
2600
+ ".cm-scroller": {
2601
+ overflowY: "scroll",
2602
+ // Browser scroll-anchoring: when widgets above the viewport resize (e.g. tool blocks
2603
+ // expanding their TogglePanel), the browser picks a stable element near the viewport
2604
+ // top and adjusts `scrollTop` so the user's view doesn't jump. Auto-scroll's pinning
2605
+ // logic still has the final word when pinned (forces scrollTop to scrollHeight).
2606
+ overflowAnchor: "auto"
2607
+ },
2608
+ ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
2609
+ display: "none"
2610
+ },
2611
+ ".cm-scroller::-webkit-scrollbar-thumb": {
2612
+ background: "transparent",
2613
+ transition: "background 0.15s"
2614
+ },
2615
+ "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
2616
+ background: "var(--color-scrollbar-thumb)"
2617
+ },
2618
+ // Spacer below the last text line. Implemented as a real block pseudo-element
2619
+ // (rather than `padding-bottom` on `.cm-content`) so it materializes in the
2620
+ // scroller's `scrollHeight` regardless of how `padding` is reset by the base
2621
+ // theme or downstream classes — this is what gives auto-scroll its head-room
2622
+ // so the last line stays `overScroll` px above the viewport bottom.
2623
+ ".cm-content::after": {
2624
+ content: '""',
2625
+ display: "block",
2626
+ height: `${overScroll}px`
2627
+ },
2628
+ ".cm-scroll-button": {
2629
+ position: "absolute",
2630
+ bottom: "0.5rem",
2631
+ right: "1rem"
2632
+ }
2633
+ })
2634
+ ];
2635
+ };
2636
+ function createCrawler(view, omega = 5, snapThreshold = 5, snapVelocity = 50) {
2637
+ const el = view.scrollDOM;
2638
+ let currentTop = 0;
2639
+ let velocity = 0;
2640
+ let rafId = null;
2641
+ let lastTime = 0;
2642
+ let instant = false;
2643
+ function frame(now) {
2644
+ const dt = lastTime === 0 ? 1 / 60 : Math.min(0.1, (now - lastTime) / 1e3);
2645
+ lastTime = now;
2646
+ const targetTop = el.scrollHeight - el.clientHeight;
2647
+ const delta = targetTop - currentTop;
2648
+ if (instant) {
2649
+ el.scrollTop = targetTop;
2650
+ currentTop = targetTop;
2651
+ velocity = 0;
2652
+ if (Math.abs(delta) < snapThreshold) {
2653
+ rafId = null;
2654
+ lastTime = 0;
2655
+ return;
2656
+ }
2657
+ rafId = requestAnimationFrame(frame);
2658
+ return;
2659
+ }
2660
+ if (Math.abs(delta) < snapThreshold && Math.abs(velocity) < snapVelocity) {
2661
+ el.scrollTop = targetTop;
2662
+ currentTop = targetTop;
2663
+ velocity = 0;
2664
+ rafId = null;
2665
+ lastTime = 0;
2666
+ return;
2667
+ }
2668
+ const accel = omega * omega * delta - 2 * omega * velocity;
2669
+ velocity += accel * dt;
2670
+ currentTop += velocity * dt;
2671
+ el.scrollTop = currentTop;
2672
+ rafId = requestAnimationFrame(frame);
2673
+ }
2674
+ return {
2675
+ scroll: (useInstant = false) => {
2676
+ instant = useInstant;
2677
+ if (rafId === null) {
2678
+ currentTop = el.scrollTop;
2679
+ lastTime = 0;
2680
+ rafId = requestAnimationFrame(frame);
2681
+ }
2682
+ },
2683
+ cancel: () => {
2684
+ if (rafId !== null) {
2685
+ cancelAnimationFrame(rafId);
2686
+ velocity = 0;
2687
+ lastTime = 0;
2688
+ rafId = null;
2689
+ }
2690
+ }
2691
+ };
2692
+ }
2693
+
2694
+ // src/extensions/scrolling/auto-scroll.ts
2695
+ var autoScrollEffect = StateEffect6.define();
2696
+ var autoScroll = ({ scrollOnResize = true } = {}) => {
2697
+ let buttonContainer;
2698
+ let isPinned = true;
2699
+ let jumpPending = false;
2700
+ let enabled = true;
2701
+ let firstUpdate = true;
2702
+ let streamed = false;
2703
+ const setPinned = (pinned) => {
2704
+ buttonContainer?.classList.toggle("opacity-0", pinned);
2705
+ isPinned = pinned;
2706
+ };
2707
+ return [
2708
+ // Update listener for scrolling when content changes.
2709
+ EditorView14.updateListener.of((update2) => {
2710
+ const { view, heightChanged, state, startState } = update2;
2711
+ for (const tr of update2.transactions) {
2712
+ for (const effect of tr.effects) {
2713
+ if (effect.is(autoScrollEffect)) {
2714
+ enabled = effect.value;
2715
+ if (enabled) {
2716
+ setPinned(true);
2717
+ view.dispatch({
2718
+ effects: crawlerActiveEffect.of({
2719
+ active: true
2720
+ })
2721
+ });
2722
+ } else {
2723
+ view.dispatch({
2724
+ effects: crawlerActiveEffect.of({
2725
+ active: false
2726
+ })
2727
+ });
2728
+ }
2729
+ }
2730
+ }
2731
+ }
2732
+ if (!enabled) {
2733
+ return;
2734
+ }
2735
+ if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
2736
+ firstUpdate = false;
2737
+ jumpPending = true;
2738
+ requestAnimationFrame(() => {
2739
+ view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
2740
+ jumpPending = false;
2741
+ });
2742
+ return;
2743
+ }
2744
+ firstUpdate = false;
2745
+ if (jumpPending) {
2746
+ return;
2747
+ }
2748
+ if (heightChanged) {
2749
+ if (isPinned) {
2750
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
2751
+ const delta = scrollHeight - scrollTop - clientHeight;
2752
+ if (update2.docChanged) {
2753
+ streamed = true;
2754
+ }
2755
+ if (delta > 0) {
2756
+ setPinned(true);
2757
+ view.dispatch({
2758
+ effects: crawlerActiveEffect.of({
2759
+ active: true,
2760
+ instant: !streamed
2761
+ })
2762
+ });
2763
+ } else if (delta < -1) {
2764
+ setPinned(false);
2765
+ }
2766
+ } else {
2767
+ if (state.doc.length === 0) {
2768
+ setPinned(true);
2769
+ }
2770
+ }
2771
+ }
2772
+ }),
2773
+ // Re-pin and jump to bottom when the scroll container itself resizes (e.g. sidebar toggle,
2774
+ // window resize). Doc-driven height changes are handled by the updateListener above; this
2775
+ // observer covers the case where the viewport changes while the doc length is unchanged.
2776
+ scrollOnResize ? ViewPlugin10.fromClass(class {
2777
+ observer;
2778
+ firstObservation = true;
2779
+ destroyed = false;
2780
+ constructor(view) {
2781
+ const onResize = throttle2(() => {
2782
+ if (this.destroyed || !enabled) {
2783
+ return;
2784
+ }
2785
+ setPinned(true);
2786
+ requestAnimationFrame(() => {
2787
+ if (this.destroyed) {
2788
+ return;
2789
+ }
2790
+ view.scrollDOM.scrollTo({
2791
+ top: view.scrollDOM.scrollHeight,
2792
+ behavior: "instant"
2793
+ });
2794
+ view.dispatch({
2795
+ effects: crawlerActiveEffect.of({
2796
+ active: false
2797
+ })
2798
+ });
2799
+ });
2800
+ }, 50);
2801
+ this.observer = new ResizeObserver(() => {
2802
+ if (this.firstObservation) {
2803
+ this.firstObservation = false;
2804
+ return;
2805
+ }
2806
+ onResize();
2807
+ });
2808
+ this.observer.observe(view.scrollDOM);
2809
+ }
2810
+ destroy() {
2811
+ this.destroyed = true;
2812
+ this.observer.disconnect();
2813
+ }
2814
+ }) : [],
2815
+ // Detect user scroll and unpin (or re-pin if scrolled to the bottom).
2816
+ ViewPlugin10.fromClass(class {
2817
+ cleanup;
2818
+ constructor(view) {
2819
+ const onUserScroll = throttle2(() => {
2820
+ requestAnimationFrame(() => {
2821
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
2822
+ const delta = scrollHeight - scrollTop - clientHeight;
2823
+ const pinned = Math.abs(delta) <= 1;
2824
+ setPinned(pinned);
2825
+ if (!pinned) {
2826
+ view.dispatch({
2827
+ effects: crawlerActiveEffect.of({
2828
+ active: false
2829
+ })
2830
+ });
2831
+ }
2832
+ });
2833
+ }, 500);
2834
+ this.cleanup = createUserScrollDetector(view.scrollDOM, () => {
2835
+ if (isPinned) {
2836
+ setPinned(false);
2837
+ view.dispatch({
2838
+ effects: crawlerActiveEffect.of({
2839
+ active: false
2840
+ })
2841
+ });
2842
+ }
2843
+ onUserScroll();
2844
+ });
2845
+ }
2846
+ destroy() {
2847
+ this.cleanup();
2848
+ }
2849
+ }),
2850
+ // Scroll button.
2851
+ ViewPlugin10.fromClass(class {
2852
+ constructor(view) {
2853
+ const icon = Domino.of("dx-icon").classNames(getSize(4)).attributes({
2854
+ icon: "ph--arrow-down--regular"
2855
+ });
2856
+ const button = Domino.of("button").classNames("dx-button bg-accent-bg").attributes({
2857
+ "data-density": "md"
2858
+ }).append(icon).on("click", () => {
2859
+ setPinned(true);
2860
+ view.dispatch({
2861
+ effects: [
2862
+ crawlerLineEffect.of({
2863
+ line: -1,
2864
+ position: "end",
2865
+ behavior: "smooth"
2866
+ }),
2867
+ // Re-engage the follower so it keeps tracking the bottom as content continues
2868
+ // to stream after the catch-up jump.
2869
+ crawlerActiveEffect.of({
2870
+ active: true,
2871
+ instant: !streamed
2872
+ })
2873
+ ]
2874
+ });
2875
+ });
2876
+ buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0 z-1").append(button).root;
2877
+ view.scrollDOM.parentElement.appendChild(buttonContainer);
2878
+ }
2879
+ })
2880
+ ];
2881
+ };
2882
+ function createUserScrollDetector(element, onUserScroll) {
2883
+ return combine(addEventListener(element, "wheel", () => onUserScroll(), {
2884
+ passive: true
2885
+ }), addEventListener(element, "pointerdown", (event) => {
2886
+ if (event.clientX > element.getBoundingClientRect().right - (element.offsetWidth - element.clientWidth)) {
2887
+ onUserScroll();
2888
+ }
2889
+ }));
2890
+ }
2891
+
2892
+ // src/extensions/scrolling/scroll-past-end.ts
2890
2893
  import { EditorView as EditorView15, ViewPlugin as ViewPlugin11 } from "@codemirror/view";
2891
2894
  var scrollPastEndPlugin = ViewPlugin11.fromClass(class {
2892
- height = 1e3;
2893
- attrs = {
2894
- style: "padding-bottom: 1000px"
2895
+ _height = 1e3;
2896
+ _attrs = {
2897
+ style: `padding-bottom: ${this._height}px`
2895
2898
  };
2896
2899
  update({ view }) {
2897
2900
  const lastLineBlock = view.lineBlockAt(view.state.doc.length);
2898
2901
  const height = view.dom.clientHeight - lastLineBlock.height - view.documentPadding.top - 0.5;
2899
- if (height >= 0 && height !== this.height) {
2900
- this.height = height;
2901
- this.attrs = {
2902
+ if (height >= 0 && height !== this._height) {
2903
+ this._height = height;
2904
+ this._attrs = {
2902
2905
  style: `padding-bottom: ${height}px`
2903
2906
  };
2904
2907
  }
@@ -2906,12 +2909,68 @@ var scrollPastEndPlugin = ViewPlugin11.fromClass(class {
2906
2909
  });
2907
2910
  var scrollPastEnd = () => [
2908
2911
  scrollPastEndPlugin,
2909
- EditorView15.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?.attrs ?? null)
2912
+ EditorView15.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?._attrs ?? null)
2910
2913
  ];
2911
2914
 
2915
+ // src/extensions/scrolling/scrollbar-autohide.ts
2916
+ import { EditorView as EditorView16, ViewPlugin as ViewPlugin12 } from "@codemirror/view";
2917
+ var scrollbarAutohide = ({ timeout = 800 } = {}) => [
2918
+ ViewPlugin12.fromClass(
2919
+ // NOTE: Uses TS `private`/plain fields rather than ES `#private`. CodeMirror's plugin lifecycle
2920
+ // (and the source/prebundled double-load in dev) can invoke `destroy` with a `this` that the
2921
+ // WeakMap-based `#private` transpilation rejects ("private field on non-instance"), crashing
2922
+ // editor teardown. Plain fields avoid the membership check.
2923
+ class {
2924
+ _scroller;
2925
+ _timer;
2926
+ constructor(view) {
2927
+ this._scroller = view.scrollDOM;
2928
+ this._scroller.addEventListener("scroll", this._handleScroll, {
2929
+ passive: true
2930
+ });
2931
+ }
2932
+ destroy() {
2933
+ this._scroller.removeEventListener("scroll", this._handleScroll);
2934
+ clearTimeout(this._timer);
2935
+ this._scroller.classList.remove("cm-scrolling");
2936
+ }
2937
+ // Show the thumb while scrolling; remove the class once scrolling has been idle for `timeout`.
2938
+ _handleScroll = () => {
2939
+ this._scroller.classList.add("cm-scrolling");
2940
+ clearTimeout(this._timer);
2941
+ this._timer = setTimeout(() => this._scroller.classList.remove("cm-scrolling"), timeout);
2942
+ };
2943
+ }
2944
+ ),
2945
+ EditorView16.theme({
2946
+ // Reveal the thumb only while actively scrolling.
2947
+ ".cm-scroller.cm-scrolling::-webkit-scrollbar-thumb": {
2948
+ background: "var(--color-scrollbar-thumb)"
2949
+ },
2950
+ // Suppress the base theme's hover-reveal (higher specificity via `:not(.cm-scrolling)`), so a
2951
+ // hovered/focused-but-idle editor keeps the thumb hidden.
2952
+ "&:hover .cm-scroller:not(.cm-scrolling)::-webkit-scrollbar-thumb": {
2953
+ background: "transparent"
2954
+ }
2955
+ })
2956
+ ];
2957
+
2958
+ // src/extensions/scrolling/scroller.ts
2959
+ import { isTruthy as isTruthy2 } from "@dxos/util";
2960
+ var scroller = ({ overScroll, scrollOnResize, autoScroll: autoScroll2 = true } = {}) => {
2961
+ return [
2962
+ crawler({
2963
+ overScroll
2964
+ }),
2965
+ autoScroll2 && autoScroll({
2966
+ scrollOnResize
2967
+ })
2968
+ ].filter(isTruthy2);
2969
+ };
2970
+
2912
2971
  // src/extensions/factories.ts
2913
2972
  var __dxlog_file11 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/factories.ts";
2914
- var tabbable = EditorView16.contentAttributes.of({
2973
+ var tabbable = EditorView17.contentAttributes.of({
2915
2974
  tabindex: "0"
2916
2975
  });
2917
2976
  var filterChars = (chars) => {
@@ -2964,13 +3023,8 @@ var createBasicExtensions = (propsProp) => {
2964
3023
  const props = defaultsDeep2({}, propsProp, defaultBasicOptions);
2965
3024
  return [
2966
3025
  // NOTE: Doesn't catch errors in keymap functions.
2967
- EditorView16.exceptionSink.of((err) => {
2968
- log8.catch(err, void 0, {
2969
- F: __dxlog_file11,
2970
- L: 130,
2971
- S: void 0,
2972
- C: (f, a) => f(...a)
2973
- });
3026
+ EditorView17.exceptionSink.of((err) => {
3027
+ log8.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file11, L: 79, S: void 0 });
2974
3028
  }),
2975
3029
  props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
2976
3030
  props.bracketMatching && bracketMatching(),
@@ -2979,7 +3033,7 @@ var createBasicExtensions = (propsProp) => {
2979
3033
  props.drawSelection && drawSelection({
2980
3034
  cursorBlinkRate: 1200
2981
3035
  }),
2982
- props.editable !== void 0 && EditorView16.editable.of(props.editable),
3036
+ props.editable !== void 0 && EditorView17.editable.of(props.editable),
2983
3037
  props.focus && focus,
2984
3038
  props.highlightActiveLine && highlightActiveLine(),
2985
3039
  props.history && history(),
@@ -2987,9 +3041,16 @@ var createBasicExtensions = (propsProp) => {
2987
3041
  lineNumbers(),
2988
3042
  editorGutter
2989
3043
  ],
2990
- props.lineWrapping && EditorView16.lineWrapping,
3044
+ props.lineWrapping && EditorView17.lineWrapping,
2991
3045
  props.placeholder && placeholder2(props.placeholder),
2992
3046
  props.readOnly !== void 0 && EditorState.readOnly.of(props.readOnly),
3047
+ // `EditorState.readOnly` is advisory — CodeMirror doesn't auto-reject doc-changing
3048
+ // transactions. Some extensions (e.g. `@codemirror/lang-markdown`'s Enter handler that
3049
+ // continues a list) dispatch programmatic edits regardless. Drop user-initiated edits
3050
+ // (`input` / `delete` keymap dispatches plus `undo` / `redo` from the history extension)
3051
+ // but pass programmatic dispatches — streaming `MarkdownStream` and similar consumers
3052
+ // depend on being able to populate the doc themselves.
3053
+ props.readOnly && EditorState.transactionFilter.of((tr) => tr.docChanged && (tr.isUserEvent("input") || tr.isUserEvent("delete") || tr.isUserEvent("undo") || tr.isUserEvent("redo")) ? [] : tr),
2993
3054
  props.scrollPastEnd && scrollPastEnd(),
2994
3055
  props.tabbable && tabbable,
2995
3056
  props.tabSize && EditorState.tabSize.of(props.tabSize),
@@ -3014,8 +3075,8 @@ var createBasicExtensions = (propsProp) => {
3014
3075
  preventDefault: true,
3015
3076
  run: () => true
3016
3077
  }
3017
- ].filter(isTruthy2))
3018
- ].filter(isTruthy2);
3078
+ ].filter(isTruthy3))
3079
+ ].filter(isTruthy3);
3019
3080
  };
3020
3081
  var grow = {
3021
3082
  editor: {
@@ -3032,29 +3093,32 @@ var defaultStyles = {
3032
3093
  dark: vscodeDarkStyle,
3033
3094
  light: vscodeLightStyle
3034
3095
  };
3035
- var createThemeExtensions = ({ monospace, themeMode, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp } = {}) => {
3096
+ var createThemeExtensions = ({ monospace, scrollbarThin, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp, themeMode } = {}) => {
3036
3097
  const slots = defaultsDeep2({}, slotsProp, defaultThemeSlots);
3037
3098
  return [
3038
3099
  baseTheme,
3039
- EditorView16.darkTheme.of(themeMode === "dark"),
3100
+ EditorView17.darkTheme.of(themeMode === "dark"),
3040
3101
  createFontTheme({
3041
3102
  monospace
3042
3103
  }),
3043
3104
  syntaxHighlightingProp && syntaxHighlighting(HighlightStyle.define(themeMode === "dark" ? defaultStyles.dark : defaultStyles.light)),
3044
- slots.editor?.className && EditorView16.editorAttributes.of({
3105
+ slots.editor?.className && EditorView17.editorAttributes.of({
3045
3106
  class: slots.editor.className
3046
3107
  }),
3047
- slots.content?.className && EditorView16.contentAttributes.of({
3108
+ slots.content?.className && EditorView17.contentAttributes.of({
3048
3109
  class: slots.content.className
3049
3110
  }),
3050
- slots.scroller?.className && ViewPlugin12.fromClass(class {
3111
+ (slots.scroller?.className || scrollbarThin) && ViewPlugin13.fromClass(class {
3051
3112
  constructor(view) {
3052
3113
  if (slots.scroller?.className) {
3053
3114
  view.scrollDOM.classList.add(...slots.scroller.className.split(/\s+/));
3054
3115
  }
3116
+ if (scrollbarThin) {
3117
+ view.scrollDOM.style.setProperty("--scrollbar-size", "4px");
3118
+ }
3055
3119
  }
3056
3120
  })
3057
- ].filter(isTruthy2);
3121
+ ].filter(isTruthy3);
3058
3122
  };
3059
3123
  var createDataExtensions = ({ id, text, messenger, identity }) => {
3060
3124
  const extensions = [];
@@ -3080,7 +3144,7 @@ var createDataExtensions = ({ id, text, messenger, identity }) => {
3080
3144
 
3081
3145
  // src/extensions/folding.ts
3082
3146
  import { codeFolding, foldGutter } from "@codemirror/language";
3083
- import { EditorView as EditorView17 } from "@codemirror/view";
3147
+ import { EditorView as EditorView18 } from "@codemirror/view";
3084
3148
  import { Domino as Domino2, mx as mx4 } from "@dxos/ui";
3085
3149
  var folding = () => {
3086
3150
  return [
@@ -3095,7 +3159,7 @@ var folding = () => {
3095
3159
  }))).root;
3096
3160
  }
3097
3161
  }),
3098
- EditorView17.theme({
3162
+ EditorView18.theme({
3099
3163
  ".cm-foldGutter": {
3100
3164
  opacity: 0.3,
3101
3165
  transition: "opacity 0.3s",
@@ -3109,7 +3173,7 @@ var folding = () => {
3109
3173
  };
3110
3174
 
3111
3175
  // src/extensions/hashtag.ts
3112
- import { Decoration as Decoration8, EditorView as EditorView18, MatchDecorator, ViewPlugin as ViewPlugin13, WidgetType as WidgetType4 } from "@codemirror/view";
3176
+ import { Decoration as Decoration8, EditorView as EditorView19, MatchDecorator, ViewPlugin as ViewPlugin14, WidgetType as WidgetType4 } from "@codemirror/view";
3113
3177
  import { getHashStyles, mx as mx5 } from "@dxos/ui-theme";
3114
3178
  var TagWidget = class extends WidgetType4 {
3115
3179
  _text;
@@ -3130,7 +3194,7 @@ var tagMatcher = new MatchDecorator({
3130
3194
  })
3131
3195
  });
3132
3196
  var hashtag = () => [
3133
- ViewPlugin13.fromClass(class {
3197
+ ViewPlugin14.fromClass(class {
3134
3198
  tags;
3135
3199
  constructor(view) {
3136
3200
  this.tags = tagMatcher.createDeco(view);
@@ -3140,11 +3204,11 @@ var hashtag = () => [
3140
3204
  }
3141
3205
  }, {
3142
3206
  decorations: (instance) => instance.tags,
3143
- provide: (plugin) => EditorView18.atomicRanges.of((view) => {
3207
+ provide: (plugin) => EditorView19.atomicRanges.of((view) => {
3144
3208
  return view.plugin(plugin)?.tags || Decoration8.none;
3145
3209
  })
3146
3210
  }),
3147
- EditorView18.theme({
3211
+ EditorView19.theme({
3148
3212
  ".cm-tag": {
3149
3213
  borderRadius: "4px",
3150
3214
  marginRight: "6px",
@@ -3199,18 +3263,18 @@ var schemaLinter = (validate) => (view) => {
3199
3263
  };
3200
3264
 
3201
3265
  // src/extensions/listener.ts
3202
- import { EditorView as EditorView19 } from "@codemirror/view";
3266
+ import { EditorView as EditorView20 } from "@codemirror/view";
3203
3267
  import { isNonNullable as isNonNullable2 } from "@dxos/util";
3204
3268
  var listener = ({ onFocus, onChange }) => {
3205
3269
  return [
3206
- onFocus && EditorView19.focusChangeEffect.of((state, focusing) => {
3270
+ onFocus && EditorView20.focusChangeEffect.of((state, focusing) => {
3207
3271
  onFocus({
3208
3272
  id: state.facet(documentId),
3209
3273
  focusing
3210
3274
  });
3211
3275
  return null;
3212
3276
  }),
3213
- onChange && EditorView19.updateListener.of(({ state, docChanged }) => {
3277
+ onChange && EditorView20.updateListener.of(({ state, docChanged }) => {
3214
3278
  if (docChanged) {
3215
3279
  onChange({
3216
3280
  id: state.facet(documentId),
@@ -3225,7 +3289,7 @@ var listener = ({ onFocus, onChange }) => {
3225
3289
  import { snippet } from "@codemirror/autocomplete";
3226
3290
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
3227
3291
  import { EditorSelection as EditorSelection2 } from "@codemirror/state";
3228
- import { EditorView as EditorView20, keymap as keymap8 } from "@codemirror/view";
3292
+ import { EditorView as EditorView21, keymap as keymap8 } from "@codemirror/view";
3229
3293
  import { debounceAndThrottle } from "@dxos/async";
3230
3294
  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;
3231
3295
  var Inline = /* @__PURE__ */ (function(Inline2) {
@@ -4314,7 +4378,7 @@ var getFormatting = (state) => {
4314
4378
  };
4315
4379
  };
4316
4380
  var formattingListener = (onStateChange, delay = 100) => {
4317
- return EditorView20.updateListener.of(debounceAndThrottle((update2) => {
4381
+ return EditorView21.updateListener.of(debounceAndThrottle((update2) => {
4318
4382
  if (update2.docChanged || update2.selectionSet) {
4319
4383
  onStateChange(getFormatting(update2.state));
4320
4384
  }
@@ -4378,7 +4442,7 @@ import { markdown, markdownLanguage as markdownLanguage2 } from "@codemirror/lan
4378
4442
  import { foldNodeProp, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4379
4443
  import { languages } from "@codemirror/language-data";
4380
4444
  import { keymap as keymap9 } from "@codemirror/view";
4381
- import { isTruthy as isTruthy3 } from "@dxos/util";
4445
+ import { isTruthy as isTruthy4 } from "@dxos/util";
4382
4446
 
4383
4447
  // src/extensions/markdown/highlight.ts
4384
4448
  import { markdownLanguage } from "@codemirror/lang-markdown";
@@ -4610,7 +4674,7 @@ var createMarkdownExtensions = (options = {}) => {
4610
4674
  ...defaultKeymap2,
4611
4675
  // TODO(burdon): Remove?
4612
4676
  ...completionKeymap
4613
- ].filter(isTruthy3))
4677
+ ].filter(isTruthy4))
4614
4678
  ];
4615
4679
  };
4616
4680
  var noFencedCodeFolding = {
@@ -4660,16 +4724,16 @@ var convertTreeToJson = (state) => {
4660
4724
 
4661
4725
  // src/extensions/markdown/decorate.ts
4662
4726
  import { syntaxTree as syntaxTree7 } from "@codemirror/language";
4663
- import { Prec as Prec4, RangeSetBuilder as RangeSetBuilder5, StateEffect as StateEffect7 } from "@codemirror/state";
4664
- import { Decoration as Decoration11, EditorView as EditorView24, ViewPlugin as ViewPlugin15, WidgetType as WidgetType7 } from "@codemirror/view";
4727
+ import { Prec as Prec4, RangeSetBuilder as RangeSetBuilder5, StateEffect as StateEffect8 } from "@codemirror/state";
4728
+ import { Decoration as Decoration11, EditorView as EditorView25, ViewPlugin as ViewPlugin17, WidgetType as WidgetType7 } from "@codemirror/view";
4665
4729
  import { invariant as invariant4 } from "@dxos/invariant";
4666
4730
 
4667
4731
  // src/extensions/markdown/changes.ts
4668
4732
  import { syntaxTree as syntaxTree4 } from "@codemirror/language";
4669
4733
  import { Transaction as Transaction4 } from "@codemirror/state";
4670
- import { ViewPlugin as ViewPlugin14 } from "@codemirror/view";
4734
+ import { ViewPlugin as ViewPlugin15 } from "@codemirror/view";
4671
4735
  var adjustChanges = () => {
4672
- return ViewPlugin14.fromClass(class {
4736
+ return ViewPlugin15.fromClass(class {
4673
4737
  update(update2) {
4674
4738
  const tree = syntaxTree4(update2.state);
4675
4739
  const adjustments = [];
@@ -4810,15 +4874,19 @@ var getValidUrl = (str) => {
4810
4874
 
4811
4875
  // src/extensions/markdown/image.ts
4812
4876
  import { syntaxTree as syntaxTree5 } from "@codemirror/language";
4813
- import { StateField as StateField7 } from "@codemirror/state";
4814
- import { Decoration as Decoration9, EditorView as EditorView21, WidgetType as WidgetType5 } from "@codemirror/view";
4815
- var image = (_options = {}) => {
4877
+ import { StateEffect as StateEffect7, StateField as StateField7 } from "@codemirror/state";
4878
+ import { Decoration as Decoration9, EditorView as EditorView22, ViewPlugin as ViewPlugin16, WidgetType as WidgetType5 } from "@codemirror/view";
4879
+ var rebuildEffect = StateEffect7.define();
4880
+ var image = (options = {}) => {
4816
4881
  return [
4817
4882
  StateField7.define({
4818
4883
  create: (state) => {
4819
- return Decoration9.set(buildDecorations(state, 0, state.doc.length));
4884
+ return Decoration9.set(buildDecorations(state, 0, state.doc.length, options));
4820
4885
  },
4821
4886
  update: (value, tr) => {
4887
+ if (tr.effects.some((effect) => effect.is(rebuildEffect))) {
4888
+ return Decoration9.set(buildDecorations(tr.state, 0, tr.state.doc.length, options));
4889
+ }
4822
4890
  if (!tr.docChanged && !tr.selection) {
4823
4891
  return value;
4824
4892
  }
@@ -4836,14 +4904,26 @@ var image = (_options = {}) => {
4836
4904
  filterFrom: from,
4837
4905
  filterTo: to,
4838
4906
  filter: () => false,
4839
- add: buildDecorations(tr.state, from, to)
4907
+ add: buildDecorations(tr.state, from, to, options)
4840
4908
  });
4841
4909
  },
4842
- provide: (field) => EditorView21.decorations.from(field)
4843
- })
4910
+ provide: (field) => EditorView22.decorations.from(field)
4911
+ }),
4912
+ // Block-replace decorations have to live in a state field, but viewport changes are only
4913
+ // observable from a view plugin. Bridge the two by dispatching a rebuild effect whenever
4914
+ // the viewport extends so newly-parsed image nodes get widgetized without requiring focus.
4915
+ ViewPlugin16.define((view) => ({
4916
+ update: (update2) => {
4917
+ if (update2.viewportChanged) {
4918
+ queueMicrotask(() => view.dispatch({
4919
+ effects: rebuildEffect.of(void 0)
4920
+ }));
4921
+ }
4922
+ }
4923
+ }))
4844
4924
  ];
4845
4925
  };
4846
- var buildDecorations = (state, from, to) => {
4926
+ var buildDecorations = (state, from, to, options = {}) => {
4847
4927
  const decorations2 = [];
4848
4928
  const cursor = state.selection.main.head;
4849
4929
  syntaxTree5(state).iterate({
@@ -4856,6 +4936,12 @@ var buildDecorations = (state, from, to) => {
4856
4936
  if (url.match(/^https?:\/\//) === null && url.match(/^file?:\/\//) === null) {
4857
4937
  return;
4858
4938
  }
4939
+ if (options.skip?.({
4940
+ name: "Image",
4941
+ url
4942
+ })) {
4943
+ return;
4944
+ }
4859
4945
  preloadImage(url);
4860
4946
  decorations2.push(Decoration9.replace({
4861
4947
  block: true,
@@ -4889,20 +4975,34 @@ var ImageWidget = class extends WidgetType5 {
4889
4975
  const img = document.createElement("img");
4890
4976
  img.setAttribute("src", this._url);
4891
4977
  img.setAttribute("class", "cm-image");
4892
- if (view.state.field(focusField)) {
4893
- img.onload = () => img.classList.add("cm-loaded-image");
4978
+ const focused = view.state.field(focusField);
4979
+ if (focused) {
4980
+ img.onload = () => {
4981
+ img.classList.add("cm-loaded-image");
4982
+ collapseIfTrackingPixel(img);
4983
+ };
4894
4984
  } else {
4895
4985
  img.classList.add("cm-loaded-image");
4986
+ img.onload = () => collapseIfTrackingPixel(img);
4896
4987
  }
4988
+ img.onerror = () => collapseLine(img);
4897
4989
  return img;
4898
4990
  }
4899
4991
  };
4992
+ var collapseIfTrackingPixel = (img) => {
4993
+ if (img.naturalWidth <= 1 && img.naturalHeight <= 1) {
4994
+ collapseLine(img);
4995
+ }
4996
+ };
4997
+ var collapseLine = (img) => {
4998
+ img.style.display = "none";
4999
+ };
4900
5000
 
4901
5001
  // src/extensions/markdown/styles.ts
4902
- import { EditorView as EditorView22 } from "@codemirror/view";
5002
+ import { EditorView as EditorView23 } from "@codemirror/view";
4903
5003
  var bulletListIndentationWidth = 24;
4904
5004
  var orderedListIndentationWidth = 36;
4905
- var formattingStyles = EditorView22.theme({
5005
+ var formattingStyles = EditorView23.theme({
4906
5006
  /**
4907
5007
  * Horizontal rule.
4908
5008
  */
@@ -5057,12 +5157,12 @@ var formattingStyles = EditorView22.theme({
5057
5157
  // src/extensions/markdown/table.ts
5058
5158
  import { syntaxTree as syntaxTree6 } from "@codemirror/language";
5059
5159
  import { RangeSetBuilder as RangeSetBuilder4, StateField as StateField8 } from "@codemirror/state";
5060
- import { Decoration as Decoration10, EditorView as EditorView23, WidgetType as WidgetType6 } from "@codemirror/view";
5160
+ import { Decoration as Decoration10, EditorView as EditorView24, WidgetType as WidgetType6 } from "@codemirror/view";
5061
5161
  var table = (options = {}) => {
5062
5162
  return StateField8.define({
5063
5163
  create: (state) => update(state, options),
5064
5164
  update: (_, tr) => update(tr.state, options),
5065
- provide: (field) => EditorView23.decorations.from(field)
5165
+ provide: (field) => EditorView24.decorations.from(field)
5066
5166
  });
5067
5167
  };
5068
5168
  var update = (state, _options) => {
@@ -5308,15 +5408,7 @@ var buildDecorations2 = (view, options, focus2) => {
5308
5408
  const { state } = view;
5309
5409
  const headerLevels = [];
5310
5410
  const getHeaderLevels = (node, level) => {
5311
- invariant4(level > 0, void 0, {
5312
- F: __dxlog_file12,
5313
- L: 177,
5314
- S: void 0,
5315
- A: [
5316
- "level > 0",
5317
- ""
5318
- ]
5319
- });
5411
+ invariant4(level > 0, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 160, S: void 0, A: ["level > 0", ""] });
5320
5412
  if (level > headerLevels.length) {
5321
5413
  const len = headerLevels.length;
5322
5414
  headerLevels.length = level;
@@ -5347,15 +5439,7 @@ var buildDecorations2 = (view, options, focus2) => {
5347
5439
  listLevels.pop();
5348
5440
  };
5349
5441
  const getCurrentListLevel = () => {
5350
- invariant4(listLevels.length, void 0, {
5351
- F: __dxlog_file12,
5352
- L: 199,
5353
- S: void 0,
5354
- A: [
5355
- "listLevels.length",
5356
- ""
5357
- ]
5358
- });
5442
+ invariant4(listLevels.length, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 192, S: void 0, A: ["listLevels.length", ""] });
5359
5443
  return listLevels[listLevels.length - 1];
5360
5444
  };
5361
5445
  const enterNode = (node) => {
@@ -5669,10 +5753,10 @@ var buildDecorations2 = (view, options, focus2) => {
5669
5753
  atomicDeco: atomicDeco.finish()
5670
5754
  };
5671
5755
  };
5672
- var forceUpdate = StateEffect7.define();
5756
+ var forceUpdate = StateEffect8.define();
5673
5757
  var decorateMarkdown = (options = {}) => {
5674
5758
  return [
5675
- ViewPlugin15.fromClass(class {
5759
+ ViewPlugin17.fromClass(class {
5676
5760
  deco;
5677
5761
  atomicDeco;
5678
5762
  pendingUpdate;
@@ -5707,12 +5791,14 @@ var decorateMarkdown = (options = {}) => {
5707
5791
  }
5708
5792
  }, {
5709
5793
  provide: (plugin) => [
5710
- Prec4.low(EditorView24.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5711
- EditorView24.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5712
- EditorView24.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5794
+ Prec4.low(EditorView25.decorations.of((view) => view.plugin(plugin)?.deco ?? Decoration11.none)),
5795
+ EditorView25.decorations.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none),
5796
+ EditorView25.atomicRanges.of((view) => view.plugin(plugin)?.atomicDeco ?? Decoration11.none)
5713
5797
  ]
5714
5798
  }),
5715
- image(),
5799
+ image({
5800
+ skip: options.skip ? (node) => !!options.skip?.(node) : void 0
5801
+ }),
5716
5802
  table(),
5717
5803
  adjustChanges(),
5718
5804
  formattingStyles
@@ -5722,7 +5808,10 @@ var decorateMarkdown = (options = {}) => {
5722
5808
  // src/extensions/markdown/link.ts
5723
5809
  import { syntaxTree as syntaxTree8 } from "@codemirror/language";
5724
5810
  import { hoverTooltip as hoverTooltip2 } from "@codemirror/view";
5725
- import { tooltipContent } from "@dxos/ui-theme";
5811
+ import { mx as mx6, surfaceShadow } from "@dxos/ui-theme";
5812
+ var tooltipClassName = mx6("inline-flex items-center p-1 max-w-64 text-sm bg-inverse-surface text-inverse-fg rounded-sm", surfaceShadow({
5813
+ elevation: "positioned"
5814
+ }));
5726
5815
  var linkTooltip = (renderTooltip) => {
5727
5816
  return hoverTooltip2((view, pos, side) => {
5728
5817
  const syntax = syntaxTree8(view.state).resolveInner(pos, side);
@@ -5744,7 +5833,7 @@ var linkTooltip = (renderTooltip) => {
5744
5833
  above: true,
5745
5834
  create: () => {
5746
5835
  const el = document.createElement("div");
5747
- el.className = tooltipContent({});
5836
+ el.className = tooltipClassName;
5748
5837
  renderTooltip(el, {
5749
5838
  url: urlText
5750
5839
  }, view);
@@ -5777,12 +5866,7 @@ var mention = ({ debug, onSearch }) => {
5777
5866
  (context) => {
5778
5867
  log9.info("completion context", {
5779
5868
  context
5780
- }, {
5781
- F: __dxlog_file13,
5782
- L: 27,
5783
- S: void 0,
5784
- C: (f, a) => f(...a)
5785
- });
5869
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file13, L: 18, S: void 0 });
5786
5870
  const match = context.matchBefore(/@(\w+)?/);
5787
5871
  if (!match || match.from === match.to && !context.explicit) {
5788
5872
  return null;
@@ -5799,8 +5883,8 @@ var mention = ({ debug, onSearch }) => {
5799
5883
  };
5800
5884
 
5801
5885
  // src/extensions/modal.ts
5802
- import { StateEffect as StateEffect8, StateField as StateField9 } from "@codemirror/state";
5803
- var modalStateEffect = StateEffect8.define();
5886
+ import { StateEffect as StateEffect9, StateField as StateField9 } from "@codemirror/state";
5887
+ var modalStateEffect = StateEffect9.define();
5804
5888
  var modalStateField = StateField9.define({
5805
5889
  create: () => false,
5806
5890
  update: (value, tr) => {
@@ -6015,15 +6099,7 @@ var outlinerTree = (_options = {}) => {
6015
6099
  break;
6016
6100
  }
6017
6101
  case "BulletList": {
6018
- invariant5(current, void 0, {
6019
- F: __dxlog_file14,
6020
- L: 219,
6021
- S: void 0,
6022
- A: [
6023
- "current",
6024
- ""
6025
- ]
6026
- });
6102
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 169, S: void 0, A: ["current", ""] });
6027
6103
  parent = current;
6028
6104
  if (current) {
6029
6105
  current.lineRange.to = current.node.from;
@@ -6032,15 +6108,7 @@ var outlinerTree = (_options = {}) => {
6032
6108
  break;
6033
6109
  }
6034
6110
  case "ListItem": {
6035
- invariant5(parent, void 0, {
6036
- F: __dxlog_file14,
6037
- L: 228,
6038
- S: void 0,
6039
- A: [
6040
- "parent",
6041
- ""
6042
- ]
6043
- });
6111
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 179, S: void 0, A: ["parent", ""] });
6044
6112
  const nextSibling = node.node.nextSibling ?? node.node.parent?.nextSibling;
6045
6113
  const docRange = {
6046
6114
  from: state.doc.lineAt(node.from).from,
@@ -6074,42 +6142,18 @@ var outlinerTree = (_options = {}) => {
6074
6142
  break;
6075
6143
  }
6076
6144
  case "ListMark": {
6077
- invariant5(current, void 0, {
6078
- F: __dxlog_file14,
6079
- L: 272,
6080
- S: void 0,
6081
- A: [
6082
- "current",
6083
- ""
6084
- ]
6085
- });
6145
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 219, S: void 0, A: ["current", ""] });
6086
6146
  current.type = "bullet";
6087
6147
  current.contentRange.from = node.from + "- ".length;
6088
6148
  break;
6089
6149
  }
6090
6150
  case "Task": {
6091
- invariant5(current, void 0, {
6092
- F: __dxlog_file14,
6093
- L: 278,
6094
- S: void 0,
6095
- A: [
6096
- "current",
6097
- ""
6098
- ]
6099
- });
6151
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 226, S: void 0, A: ["current", ""] });
6100
6152
  current.type = "task";
6101
6153
  break;
6102
6154
  }
6103
6155
  case "TaskMarker": {
6104
- invariant5(current, void 0, {
6105
- F: __dxlog_file14,
6106
- L: 283,
6107
- S: void 0,
6108
- A: [
6109
- "current",
6110
- ""
6111
- ]
6112
- });
6156
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 232, S: void 0, A: ["current", ""] });
6113
6157
  current.contentRange.from = node.from + "[ ] ".length;
6114
6158
  break;
6115
6159
  }
@@ -6117,29 +6161,13 @@ var outlinerTree = (_options = {}) => {
6117
6161
  },
6118
6162
  leave: (node) => {
6119
6163
  if (node.name === "BulletList") {
6120
- invariant5(parent, void 0, {
6121
- F: __dxlog_file14,
6122
- L: 291,
6123
- S: void 0,
6124
- A: [
6125
- "parent",
6126
- ""
6127
- ]
6128
- });
6164
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 240, S: void 0, A: ["parent", ""] });
6129
6165
  prevSiblings[level--] = void 0;
6130
6166
  parent = parent.parent;
6131
6167
  }
6132
6168
  }
6133
6169
  });
6134
- invariant5(tree, void 0, {
6135
- F: __dxlog_file14,
6136
- L: 298,
6137
- S: void 0,
6138
- A: [
6139
- "tree",
6140
- ""
6141
- ]
6142
- });
6170
+ invariant5(tree, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 246, S: void 0, A: ["tree", ""] });
6143
6171
  return tree;
6144
6172
  };
6145
6173
  return [
@@ -6424,17 +6452,17 @@ var commands = () => keymap11.of([
6424
6452
 
6425
6453
  // src/extensions/outliner/outliner.ts
6426
6454
  import { Prec as Prec5 } from "@codemirror/state";
6427
- import { Decoration as Decoration12, EditorView as EditorView26, ViewPlugin as ViewPlugin18 } from "@codemirror/view";
6428
- import { mx as mx6 } from "@dxos/ui-theme";
6455
+ import { Decoration as Decoration12, EditorView as EditorView27, ViewPlugin as ViewPlugin20 } from "@codemirror/view";
6456
+ import { mx as mx7 } from "@dxos/ui-theme";
6429
6457
 
6430
6458
  // src/extensions/outliner/editor.ts
6431
6459
  import { EditorSelection as EditorSelection4, EditorState as EditorState2 } from "@codemirror/state";
6432
- import { ViewPlugin as ViewPlugin16 } from "@codemirror/view";
6460
+ import { ViewPlugin as ViewPlugin18 } from "@codemirror/view";
6433
6461
  import { log as log10 } from "@dxos/log";
6434
6462
  var __dxlog_file15 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/outliner/editor.ts";
6435
6463
  var LIST_ITEM_REGEX = /^\s*- (\[ \]|\[x\])? /;
6436
6464
  var initialize = () => {
6437
- return ViewPlugin16.fromClass(class {
6465
+ return ViewPlugin18.fromClass(class {
6438
6466
  constructor(view) {
6439
6467
  const first = view.state.doc.lineAt(0);
6440
6468
  const text = view.state.sliceDoc(first.from, first.to);
@@ -6581,35 +6609,20 @@ var editor = () => [
6581
6609
  text: insert.toString(),
6582
6610
  length: insert.length
6583
6611
  }
6584
- }, {
6585
- F: __dxlog_file15,
6586
- L: 164,
6587
- S: void 0,
6588
- C: (f, a) => f(...a)
6589
- });
6612
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 174, S: void 0 });
6590
6613
  }
6591
6614
  });
6592
6615
  if (changes.length > 0) {
6593
6616
  log10("modified,", {
6594
6617
  changes
6595
- }, {
6596
- F: __dxlog_file15,
6597
- L: 175,
6598
- S: void 0,
6599
- C: (f, a) => f(...a)
6600
- });
6618
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 196, S: void 0 });
6601
6619
  return [
6602
6620
  {
6603
6621
  changes
6604
6622
  }
6605
6623
  ];
6606
6624
  } else if (cancel) {
6607
- log10("cancel", void 0, {
6608
- F: __dxlog_file15,
6609
- L: 178,
6610
- S: void 0,
6611
- C: (f, a) => f(...a)
6612
- });
6625
+ log10("cancel", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 205, S: void 0 });
6613
6626
  return [];
6614
6627
  }
6615
6628
  return tr;
@@ -6617,10 +6630,10 @@ var editor = () => [
6617
6630
  ];
6618
6631
 
6619
6632
  // src/extensions/outliner/menu.ts
6620
- import { EditorView as EditorView25, ViewPlugin as ViewPlugin17 } from "@codemirror/view";
6633
+ import { EditorView as EditorView26, ViewPlugin as ViewPlugin19 } from "@codemirror/view";
6621
6634
  import { addEventListener as addEventListener2 } from "@dxos/async";
6622
6635
  var menu = (options = {}) => [
6623
- ViewPlugin17.fromClass(class {
6636
+ ViewPlugin19.fromClass(class {
6624
6637
  view;
6625
6638
  tag;
6626
6639
  rafId;
@@ -6682,7 +6695,7 @@ var menu = (options = {}) => [
6682
6695
  this.rafId = requestAnimationFrame(this.updateButtonPosition.bind(this));
6683
6696
  }
6684
6697
  }),
6685
- EditorView25.theme({
6698
+ EditorView26.theme({
6686
6699
  ".cm-popover-trigger": {
6687
6700
  position: "fixed",
6688
6701
  padding: "0",
@@ -6718,12 +6731,12 @@ var outliner = (_options = {}) => [
6718
6731
  listPaddingLeft: 8
6719
6732
  }),
6720
6733
  // Researve space for menu.
6721
- EditorView26.contentAttributes.of({
6734
+ EditorView27.contentAttributes.of({
6722
6735
  class: "w-full !mr-[3rem]"
6723
6736
  })
6724
6737
  ];
6725
6738
  var decorations = () => [
6726
- ViewPlugin18.fromClass(class {
6739
+ ViewPlugin20.fromClass(class {
6727
6740
  decorations = Decoration12.none;
6728
6741
  constructor(view) {
6729
6742
  this.updateDecorations(view.state, view);
@@ -6748,7 +6761,7 @@ var decorations = () => [
6748
6761
  const lineTo = doc.lineAt(item.contentRange.to);
6749
6762
  const isSelected = selection.includes(item.index) || item === current;
6750
6763
  decorations2.push(Decoration12.line({
6751
- 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"))
6764
+ 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"))
6752
6765
  }).range(line.from, line.from));
6753
6766
  }
6754
6767
  }
@@ -6758,7 +6771,7 @@ var decorations = () => [
6758
6771
  decorations: (v) => v.decorations
6759
6772
  }),
6760
6773
  // Theme.
6761
- EditorView26.theme(Object.assign({
6774
+ EditorView27.theme(Object.assign({
6762
6775
  ".cm-list-item": {
6763
6776
  borderLeftWidth: "1px",
6764
6777
  borderRightWidth: "1px",
@@ -6783,7 +6796,7 @@ var decorations = () => [
6783
6796
  marginBottom: "2px"
6784
6797
  },
6785
6798
  ".cm-list-item-focused": {
6786
- borderColor: "var(--color-neutral-focus-indicator)"
6799
+ borderColor: "var(--color-focus-ring-subtle)"
6787
6800
  },
6788
6801
  "&:focus-within .cm-list-item-selected": {
6789
6802
  borderColor: "var(--color-separator)"
@@ -6793,10 +6806,11 @@ var decorations = () => [
6793
6806
 
6794
6807
  // src/extensions/preview/preview.ts
6795
6808
  import { syntaxTree as syntaxTree10 } from "@codemirror/language";
6796
- import { RangeSetBuilder as RangeSetBuilder6, StateEffect as StateEffect9, StateField as StateField11 } from "@codemirror/state";
6797
- import { Decoration as Decoration13, EditorView as EditorView27, ViewPlugin as ViewPlugin19, WidgetType as WidgetType8 } from "@codemirror/view";
6798
- import { DXN, Entity } from "@dxos/echo";
6799
- var labelResolvedEffect = StateEffect9.define();
6809
+ import { RangeSetBuilder as RangeSetBuilder6, StateEffect as StateEffect10, StateField as StateField11 } from "@codemirror/state";
6810
+ import { Decoration as Decoration13, EditorView as EditorView28, ViewPlugin as ViewPlugin21, WidgetType as WidgetType8 } from "@codemirror/view";
6811
+ import { Entity } from "@dxos/echo";
6812
+ import { EID, URI } from "@dxos/keys";
6813
+ var labelResolvedEffect = StateEffect10.define();
6800
6814
  var preview = (options = {}) => {
6801
6815
  const viewRef = {
6802
6816
  current: void 0
@@ -6813,11 +6827,11 @@ var preview = (options = {}) => {
6813
6827
  return decorations2.map(tr.changes);
6814
6828
  },
6815
6829
  provide: (field) => [
6816
- EditorView27.decorations.from(field),
6817
- EditorView27.atomicRanges.of((view) => view.state.field(field))
6830
+ EditorView28.decorations.from(field),
6831
+ EditorView28.atomicRanges.of((view) => view.state.field(field))
6818
6832
  ]
6819
6833
  }),
6820
- ViewPlugin19.define((view) => {
6834
+ ViewPlugin21.define((view) => {
6821
6835
  viewRef.current = view;
6822
6836
  return {
6823
6837
  destroy() {
@@ -6828,11 +6842,12 @@ var preview = (options = {}) => {
6828
6842
  ];
6829
6843
  };
6830
6844
  var resolveLabel = (db, dxnStr, viewRef) => {
6831
- const dxn = DXN.tryParse(dxnStr);
6832
- if (!dxn) {
6845
+ const echoUri = EID.tryParse(dxnStr);
6846
+ const dxnRef = echoUri ?? (dxnStr.startsWith("dxn:") ? URI.make(dxnStr) : void 0);
6847
+ if (!dxnRef) {
6833
6848
  return;
6834
6849
  }
6835
- const ref = db.makeRef(dxn);
6850
+ const ref = db.makeRef(dxnRef);
6836
6851
  const target = ref.target;
6837
6852
  if (target) {
6838
6853
  return Entity.getLabel(target);
@@ -6850,7 +6865,7 @@ var buildDecorations3 = (state, options, viewRef) => {
6850
6865
  switch (node.name) {
6851
6866
  //
6852
6867
  // Inline widget.
6853
- // [Label](dxn:echo:123)
6868
+ // [Label](echo:/123)
6854
6869
  //
6855
6870
  case "Link": {
6856
6871
  const link = getLinkRef(state, node.node);
@@ -6869,7 +6884,7 @@ var buildDecorations3 = (state, options, viewRef) => {
6869
6884
  }
6870
6885
  //
6871
6886
  // Block widget (transclusion).
6872
- // ![Label](dxn:echo:123)
6887
+ // ![Label](echo:/123)
6873
6888
  //
6874
6889
  case "Image": {
6875
6890
  if (options.addBlockContainer && options.removeBlockContainer) {
@@ -6893,7 +6908,7 @@ var getLinkRef = (state, node) => {
6893
6908
  const urlNode = node.getChild("URL");
6894
6909
  if (mark && urlNode) {
6895
6910
  const dxn = state.sliceDoc(urlNode.from, urlNode.to);
6896
- if (dxn.startsWith("dxn:")) {
6911
+ if (dxn.startsWith("dxn:") || dxn.startsWith("echo:")) {
6897
6912
  const label = state.sliceDoc(mark[0].to, mark[1].from);
6898
6913
  return {
6899
6914
  block: state.sliceDoc(mark[0].from, mark[0].from + 1) === "!",
@@ -6937,7 +6952,7 @@ var PreviewBlockWidget = class extends WidgetType8 {
6937
6952
  }
6938
6953
  toDOM(_view) {
6939
6954
  const root = document.createElement("div");
6940
- root.classList.add("cm-preview-block", "dx-density-fine");
6955
+ root.classList.add("cm-preview-block", "dx-density-md");
6941
6956
  this._options.addBlockContainer?.({
6942
6957
  link: this._link,
6943
6958
  el: root
@@ -6953,7 +6968,7 @@ var PreviewBlockWidget = class extends WidgetType8 {
6953
6968
  };
6954
6969
 
6955
6970
  // src/extensions/replacer.ts
6956
- import { EditorView as EditorView28 } from "@codemirror/view";
6971
+ import { EditorView as EditorView29 } from "@codemirror/view";
6957
6972
  var defaultReplacements = [
6958
6973
  {
6959
6974
  input: "--",
@@ -7016,7 +7031,7 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
7016
7031
  const sortedReplacements = [
7017
7032
  ...replacements
7018
7033
  ].sort((a, b) => b.input.length - a.input.length);
7019
- return EditorView28.inputHandler.of((view, from, to, insert) => {
7034
+ return EditorView29.inputHandler.of((view, from, to, insert) => {
7020
7035
  if (insert.length !== 1) {
7021
7036
  return false;
7022
7037
  }
@@ -7050,12 +7065,80 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
7050
7065
  });
7051
7066
  };
7052
7067
 
7068
+ // src/extensions/spacing.ts
7069
+ import { EditorView as EditorView30 } from "@codemirror/view";
7070
+ function lineSpacing(verticalPadding = 2) {
7071
+ return EditorView30.theme({
7072
+ ".cm-line": {
7073
+ paddingTop: `${verticalPadding}px`,
7074
+ paddingBottom: `${verticalPadding}px`
7075
+ }
7076
+ });
7077
+ }
7078
+
7079
+ // src/extensions/snippets.ts
7080
+ import { keymap as keymap12 } from "@codemirror/view";
7081
+ var defaultItems = [
7082
+ "hello world!",
7083
+ "this is a test.",
7084
+ "this is [DXOS](https://dxos.org)"
7085
+ ];
7086
+ var snippets2 = ({ delay = 75, items = defaultItems } = {}) => {
7087
+ let timer;
7088
+ let index = 0;
7089
+ return [
7090
+ keymap12.of([
7091
+ {
7092
+ // Reset.
7093
+ key: "alt-meta-'",
7094
+ run: () => {
7095
+ clearTimeout(timer);
7096
+ index = 0;
7097
+ return true;
7098
+ }
7099
+ },
7100
+ {
7101
+ // Next snippet.
7102
+ // TODO(burdon): Press 1-9 to select snippet?
7103
+ key: "Shift-Meta-'",
7104
+ run: (view) => {
7105
+ clearTimeout(timer);
7106
+ const text = items[index++];
7107
+ if (index === items?.length) {
7108
+ index = 0;
7109
+ }
7110
+ let offset = 0;
7111
+ const insert = (delayMs = 0) => {
7112
+ timer = setTimeout(() => {
7113
+ const pos = view.state.selection.main.head;
7114
+ view.dispatch({
7115
+ changes: {
7116
+ from: pos,
7117
+ insert: text[offset++]
7118
+ },
7119
+ selection: {
7120
+ anchor: pos + 1
7121
+ }
7122
+ });
7123
+ if (offset < text.length) {
7124
+ insert(Math.random() * delay * (text[offset] === " " ? 2 : 1));
7125
+ }
7126
+ }, delayMs);
7127
+ };
7128
+ insert();
7129
+ return true;
7130
+ }
7131
+ }
7132
+ ])
7133
+ ];
7134
+ };
7135
+
7053
7136
  // src/extensions/submit.ts
7054
7137
  import { Prec as Prec6 } from "@codemirror/state";
7055
- import { keymap as keymap12 } from "@codemirror/view";
7138
+ import { keymap as keymap13 } from "@codemirror/view";
7056
7139
  var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
7057
7140
  return [
7058
- Prec6.highest(keymap12.of([
7141
+ Prec6.highest(keymap13.of([
7059
7142
  {
7060
7143
  key: "Enter",
7061
7144
  preventDefault: true,
@@ -7205,8 +7288,8 @@ var mixedParser = (registry) => {
7205
7288
  };
7206
7289
 
7207
7290
  // src/extensions/tags/fader.ts
7208
- import { StateEffect as StateEffect10, StateField as StateField12 } from "@codemirror/state";
7209
- import { Decoration as Decoration14, EditorView as EditorView29, ViewPlugin as ViewPlugin20 } from "@codemirror/view";
7291
+ import { StateEffect as StateEffect11, StateField as StateField12 } from "@codemirror/state";
7292
+ import { Decoration as Decoration14, EditorView as EditorView31, ViewPlugin as ViewPlugin22 } from "@codemirror/view";
7210
7293
  var DEFAULT_REMOVAL_DELAY = 5e3;
7211
7294
  var DEFAULT_COALESCE_WINDOW = 100;
7212
7295
  var CLEANUP_INTERVAL = 1e3;
@@ -7219,7 +7302,7 @@ var fader = (options = {}) => {
7219
7302
  lastCount = expiries.length;
7220
7303
  }
7221
7304
  };
7222
- const dequeue = StateEffect10.define();
7305
+ const dequeue = StateEffect11.define();
7223
7306
  const fadeField = StateField12.define({
7224
7307
  create: () => ({
7225
7308
  decorations: Decoration14.none,
@@ -7330,9 +7413,9 @@ var fader = (options = {}) => {
7330
7413
  batchStart
7331
7414
  };
7332
7415
  },
7333
- provide: (f) => EditorView29.decorations.from(f, (value) => value.decorations)
7416
+ provide: (f) => EditorView31.decorations.from(f, (value) => value.decorations)
7334
7417
  });
7335
- const cleanup = ViewPlugin20.fromClass(class {
7418
+ const cleanup = ViewPlugin22.fromClass(class {
7336
7419
  view;
7337
7420
  #timer;
7338
7421
  constructor(view) {
@@ -7367,7 +7450,7 @@ var fader = (options = {}) => {
7367
7450
  return [
7368
7451
  fadeField,
7369
7452
  cleanup,
7370
- EditorView29.theme({
7453
+ EditorView31.theme({
7371
7454
  ".cm-fader": {
7372
7455
  animation: "fader 1s ease-out forwards"
7373
7456
  },
@@ -7381,36 +7464,46 @@ var fader = (options = {}) => {
7381
7464
  ];
7382
7465
  };
7383
7466
 
7384
- // src/extensions/tags/wire.ts
7385
- import { Annotation as Annotation3, ChangeSet as ChangeSet2, EditorState as EditorState3, StateEffect as StateEffect11, StateField as StateField13 } from "@codemirror/state";
7386
- import { Decoration as Decoration15, EditorView as EditorView30, ViewPlugin as ViewPlugin21, WidgetType as WidgetType9 } from "@codemirror/view";
7467
+ // src/extensions/tags/typewriter.ts
7468
+ import { Annotation as Annotation3, ChangeSet as ChangeSet2, EditorState as EditorState3, StateEffect as StateEffect12, StateField as StateField13 } from "@codemirror/state";
7469
+ import { Decoration as Decoration15, EditorView as EditorView32, ViewPlugin as ViewPlugin23, WidgetType as WidgetType9 } from "@codemirror/view";
7387
7470
  import { Domino as Domino3 } from "@dxos/ui";
7388
- var wireBypass = Annotation3.define();
7389
- var DEFAULT_RATE = 200;
7471
+ var typewriterBypass = Annotation3.define();
7472
+ var typewriterDrainingEffect = StateEffect12.define();
7390
7473
  var CURSOR_LINGER = 3e3;
7391
- var wire = (options = {}) => {
7392
- const rate = options.rate ?? DEFAULT_RATE;
7393
- const interval = 1e3 / rate;
7474
+ var FRAME_BUDGET_MS = 4;
7475
+ var CHARS_PER_FRAME = 5;
7476
+ var FLUSH_THRESHOLD = 2e3;
7477
+ var COMPACT_HEAD_THRESHOLD = 4096;
7478
+ var typewriter = (options = {}) => {
7394
7479
  const streamingTags = options.streamingTags ?? /* @__PURE__ */ new Set();
7395
- const suppressAppend = StateEffect11.define();
7396
- const insertChunk = StateEffect11.define();
7480
+ const flushThreshold = options.flushThreshold ?? FLUSH_THRESHOLD;
7481
+ const frameBudgetMs = options.frameBudgetMs ?? FRAME_BUDGET_MS;
7482
+ const charsPerFrame = options.charsPerFrame ?? CHARS_PER_FRAME;
7483
+ const suppressAppend = StateEffect12.define();
7484
+ const insertChunk = StateEffect12.define();
7397
7485
  const bufferField = StateField13.define({
7398
7486
  create: () => ({
7399
7487
  text: "",
7488
+ head: 0,
7400
7489
  insertAt: 0
7401
7490
  }),
7402
7491
  update: (value, tr) => {
7403
- let { text, insertAt } = value;
7492
+ let { text, head, insertAt } = value;
7404
7493
  for (const effect of tr.effects) {
7405
7494
  if (effect.is(suppressAppend)) {
7406
- text += effect.value.text;
7407
- if (text.length === effect.value.text.length) {
7495
+ if (text.length === head) {
7408
7496
  insertAt = effect.value.from;
7409
7497
  }
7498
+ text += effect.value.text;
7410
7499
  }
7411
7500
  if (effect.is(insertChunk)) {
7412
- text = text.slice(effect.value.text.length);
7501
+ head += effect.value.text.length;
7413
7502
  insertAt = effect.value.from + effect.value.text.length;
7503
+ if (head >= COMPACT_HEAD_THRESHOLD || head > 0 && head * 2 >= text.length) {
7504
+ text = text.slice(head);
7505
+ head = 0;
7506
+ }
7414
7507
  }
7415
7508
  }
7416
7509
  if (tr.docChanged) {
@@ -7425,6 +7518,7 @@ var wire = (options = {}) => {
7425
7518
  if (isReset) {
7426
7519
  return {
7427
7520
  text: "",
7521
+ head: 0,
7428
7522
  insertAt: 0
7429
7523
  };
7430
7524
  }
@@ -7434,6 +7528,7 @@ var wire = (options = {}) => {
7434
7528
  }
7435
7529
  return {
7436
7530
  text,
7531
+ head,
7437
7532
  insertAt
7438
7533
  };
7439
7534
  }
@@ -7442,7 +7537,7 @@ var wire = (options = {}) => {
7442
7537
  if (!tr.docChanged) {
7443
7538
  return tr;
7444
7539
  }
7445
- if (tr.annotation(wireBypass) || tr.effects.some((effect) => effect.is(insertChunk))) {
7540
+ if (tr.annotation(typewriterBypass) || tr.effects.some((effect) => effect.is(insertChunk))) {
7446
7541
  return tr;
7447
7542
  }
7448
7543
  let appendedText = "";
@@ -7469,42 +7564,84 @@ var wire = (options = {}) => {
7469
7564
  })
7470
7565
  };
7471
7566
  });
7472
- const drainPlugin = ViewPlugin21.fromClass(class {
7567
+ const drainPlugin = ViewPlugin23.fromClass(class {
7473
7568
  view;
7474
- #timer;
7475
- #activeStreamTag = null;
7569
+ _raf;
7570
+ _activeStreamTag = null;
7476
7571
  constructor(view) {
7477
7572
  this.view = view;
7478
- this.#start();
7479
7573
  }
7480
7574
  update(update2) {
7481
- const buffer = update2.state.field(bufferField);
7482
- if (buffer.text.length === 0) {
7483
- this.#activeStreamTag = null;
7575
+ const { text, head } = update2.state.field(bufferField);
7576
+ const pending = text.length - head;
7577
+ if (pending === 0) {
7578
+ this._activeStreamTag = null;
7484
7579
  }
7485
- if (buffer.text.length > 0 && this.#timer === void 0) {
7486
- this.#start();
7580
+ if (pending > 0 && this._raf === void 0) {
7581
+ this._start();
7487
7582
  }
7488
7583
  }
7489
- #start() {
7490
- this.#timer = setInterval(() => {
7491
- const { text, insertAt } = this.view.state.field(bufferField);
7492
- if (text.length === 0) {
7493
- clearInterval(this.#timer);
7494
- this.#timer = void 0;
7495
- return;
7496
- }
7497
- const result = flushable(text, streamingTags, this.#activeStreamTag);
7584
+ _start() {
7585
+ queueMicrotask(() => {
7586
+ this.view.dispatch({
7587
+ effects: typewriterDrainingEffect.of(true),
7588
+ annotations: typewriterBypass.of(true)
7589
+ });
7590
+ });
7591
+ this._raf = requestAnimationFrame(this._tick);
7592
+ }
7593
+ _tick = () => {
7594
+ const { text, head, insertAt } = this.view.state.field(bufferField);
7595
+ const pending = text.length - head;
7596
+ if (pending === 0) {
7597
+ this.view.dispatch({
7598
+ effects: typewriterDrainingEffect.of(false),
7599
+ annotations: typewriterBypass.of(true)
7600
+ });
7601
+ this._raf = void 0;
7602
+ return;
7603
+ }
7604
+ if (pending > flushThreshold) {
7605
+ const chunk = text.slice(head);
7606
+ this._activeStreamTag = null;
7607
+ this.view.dispatch({
7608
+ changes: {
7609
+ from: insertAt,
7610
+ insert: chunk
7611
+ },
7612
+ effects: insertChunk.of({
7613
+ from: insertAt,
7614
+ text: chunk
7615
+ })
7616
+ });
7617
+ this._raf = requestAnimationFrame(this._tick);
7618
+ return;
7619
+ }
7620
+ const startTime = performance.now();
7621
+ let pos = head;
7622
+ let activeTag = this._activeStreamTag;
7623
+ let charsEmitted = 0;
7624
+ while (pos < text.length && performance.now() - startTime < frameBudgetMs) {
7625
+ const result = flushable(text, pos, streamingTags, activeTag);
7498
7626
  if (result.count === 0) {
7499
- return;
7627
+ break;
7628
+ }
7629
+ if (charsEmitted > 0 && charsEmitted + result.count > charsPerFrame) {
7630
+ break;
7500
7631
  }
7501
7632
  if (result.enterTag) {
7502
- this.#activeStreamTag = result.enterTag;
7633
+ activeTag = result.enterTag;
7503
7634
  }
7504
7635
  if (result.exitTag) {
7505
- this.#activeStreamTag = null;
7636
+ activeTag = null;
7506
7637
  }
7507
- const chunk = text.slice(0, result.count);
7638
+ pos += result.count;
7639
+ charsEmitted += result.count;
7640
+ }
7641
+ const totalCount = pos - head;
7642
+ if (totalCount > 0) {
7643
+ const chunk = text.slice(head, head + totalCount);
7644
+ this._activeStreamTag = activeTag;
7508
7645
  this.view.dispatch({
7509
7646
  changes: {
7510
7647
  from: insertAt,
@@ -7515,21 +7652,24 @@ var wire = (options = {}) => {
7515
7652
  text: chunk
7516
7653
  })
7517
7654
  });
7518
- }, interval);
7519
- }
7655
+ }
7656
+ this._raf = requestAnimationFrame(this._tick);
7657
+ };
7520
7658
  destroy() {
7521
- clearInterval(this.#timer);
7659
+ if (this._raf !== void 0) {
7660
+ cancelAnimationFrame(this._raf);
7661
+ }
7522
7662
  }
7523
7663
  });
7524
7664
  return [
7525
7665
  bufferField,
7526
7666
  filter,
7527
7667
  drainPlugin,
7528
- options.cursor && wireCursor(bufferField)
7668
+ options.cursor && typewriterCursor(bufferField)
7529
7669
  ].filter(Boolean);
7530
7670
  };
7531
- var wireCursor = (bufferField) => {
7532
- const hideCursor = StateEffect11.define();
7671
+ var typewriterCursor = (bufferField) => {
7672
+ const hideCursor = StateEffect12.define();
7533
7673
  const visibilityField = StateField13.define({
7534
7674
  create: () => ({
7535
7675
  visible: false,
@@ -7537,8 +7677,9 @@ var wireCursor = (bufferField) => {
7537
7677
  lastNonWsAt: 0
7538
7678
  }),
7539
7679
  update: (value, tr) => {
7540
- const { text, insertAt } = tr.state.field(bufferField);
7541
- if (text.length > 0) {
7680
+ const { text, head, insertAt } = tr.state.field(bufferField);
7681
+ const pending = text.length - head;
7682
+ if (pending > 0) {
7542
7683
  let lastNonWsAt = tr.changes.mapPos(Math.min(value.lastNonWsAt, tr.startState.doc.length));
7543
7684
  if (tr.docChanged) {
7544
7685
  tr.changes.iterChanges((_fromA, _toA, _fromB, _toB, inserted) => {
@@ -7572,8 +7713,8 @@ var wireCursor = (bufferField) => {
7572
7713
  if (!visible) {
7573
7714
  return Decoration15.none;
7574
7715
  }
7575
- const { text } = tr.state.field(bufferField);
7576
- const cursorAt = text.length > 0 ? insertAt : lastNonWsAt;
7716
+ const { text, head } = tr.state.field(bufferField);
7717
+ const cursorAt = text.length > head ? insertAt : lastNonWsAt;
7577
7718
  const pos = Math.min(cursorAt, tr.state.doc.length);
7578
7719
  return Decoration15.set([
7579
7720
  Decoration15.widget({
@@ -7582,31 +7723,32 @@ var wireCursor = (bufferField) => {
7582
7723
  }).range(pos)
7583
7724
  ]);
7584
7725
  },
7585
- provide: (field) => EditorView30.decorations.from(field)
7726
+ provide: (field) => EditorView32.decorations.from(field)
7586
7727
  });
7587
- const timerPlugin = ViewPlugin21.fromClass(class {
7728
+ const timerPlugin = ViewPlugin23.fromClass(class {
7588
7729
  view;
7589
- #timer;
7730
+ _timer;
7590
7731
  constructor(view) {
7591
7732
  this.view = view;
7592
7733
  }
7593
7734
  update(update2) {
7594
- const { text } = update2.state.field(bufferField);
7735
+ const { text, head } = update2.state.field(bufferField);
7595
7736
  const { visible } = update2.state.field(visibilityField);
7596
- if (text.length > 0) {
7597
- clearTimeout(this.#timer);
7598
- this.#timer = void 0;
7599
- } else if (visible && this.#timer === void 0) {
7600
- this.#timer = setTimeout(() => {
7737
+ const pending = text.length - head;
7738
+ if (pending > 0) {
7739
+ clearTimeout(this._timer);
7740
+ this._timer = void 0;
7741
+ } else if (visible && this._timer === void 0) {
7742
+ this._timer = setTimeout(() => {
7601
7743
  this.view.dispatch({
7602
7744
  effects: hideCursor.of(null)
7603
7745
  });
7604
- this.#timer = void 0;
7746
+ this._timer = void 0;
7605
7747
  }, CURSOR_LINGER);
7606
7748
  }
7607
7749
  }
7608
7750
  destroy() {
7609
- clearTimeout(this.#timer);
7751
+ clearTimeout(this._timer);
7610
7752
  }
7611
7753
  });
7612
7754
  return [
@@ -7615,7 +7757,12 @@ var wireCursor = (bufferField) => {
7615
7757
  timerPlugin
7616
7758
  ];
7617
7759
  };
7618
- var CursorWidget = class extends WidgetType9 {
7760
+ var CursorWidget = class _CursorWidget extends WidgetType9 {
7761
+ // All instances are interchangeable — let CM reuse the existing DOM across drips so
7762
+ // the blink animation isn't restarted on every transaction.
7763
+ eq(other) {
7764
+ return other instanceof _CursorWidget;
7765
+ }
7619
7766
  toDOM() {
7620
7767
  const inner = Domino3.of("span").text("\u2217").style({
7621
7768
  animation: "blink 1s infinite",
@@ -7627,35 +7774,37 @@ var CursorWidget = class extends WidgetType9 {
7627
7774
  }
7628
7775
  };
7629
7776
  var OPENING_TAG_NAME = /^<([a-zA-Z][\w-]*)/;
7777
+ var TAG_NAME_PROBE = 64;
7630
7778
  var escapeRegExpSource2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7631
- var flushable = (buffer, streamingTags, activeStreamTag) => {
7632
- if (buffer.length === 0) {
7779
+ var flushable = (buffer, start, streamingTags, activeStreamTag) => {
7780
+ if (start >= buffer.length) {
7633
7781
  return {
7634
7782
  count: 0
7635
7783
  };
7636
7784
  }
7637
7785
  if (activeStreamTag) {
7638
7786
  const closeTag = `</${activeStreamTag}>`;
7639
- if (buffer.startsWith(closeTag)) {
7787
+ if (buffer.startsWith(closeTag, start)) {
7640
7788
  return {
7641
7789
  count: closeTag.length,
7642
7790
  exitTag: true
7643
7791
  };
7644
7792
  }
7645
- if (buffer[0] === "<") {
7793
+ if (buffer[start] === "<") {
7646
7794
  return {
7647
- count: xmlElementLength(buffer)
7795
+ count: xmlElementLength(buffer, start)
7648
7796
  };
7649
7797
  }
7650
7798
  return {
7651
7799
  count: 1
7652
7800
  };
7653
7801
  }
7654
- const ch = buffer[0];
7802
+ const ch = buffer[start];
7655
7803
  if (ch === "<") {
7656
- const nameMatch = buffer.match(OPENING_TAG_NAME);
7804
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7805
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7657
7806
  if (nameMatch && streamingTags.has(nameMatch[1])) {
7658
- const close = buffer.indexOf(">");
7807
+ const close = buffer.indexOf(">", start);
7659
7808
  if (close === -1) {
7660
7809
  return {
7661
7810
  count: 0
@@ -7663,62 +7812,64 @@ var flushable = (buffer, streamingTags, activeStreamTag) => {
7663
7812
  }
7664
7813
  if (buffer[close - 1] === "/") {
7665
7814
  return {
7666
- count: close + 1
7815
+ count: close + 1 - start
7667
7816
  };
7668
7817
  }
7669
7818
  return {
7670
- count: close + 1,
7819
+ count: close + 1 - start,
7671
7820
  enterTag: nameMatch[1]
7672
7821
  };
7673
7822
  }
7674
7823
  return {
7675
- count: xmlElementLength(buffer)
7824
+ count: xmlElementLength(buffer, start)
7676
7825
  };
7677
7826
  }
7678
- if (ch === "!" && buffer.length > 1 && buffer[1] === "[") {
7827
+ if (ch === "!" && buffer.length > start + 1 && buffer[start + 1] === "[") {
7679
7828
  return {
7680
- count: linkLength(buffer, 1)
7829
+ count: linkLength(buffer, start, start + 1)
7681
7830
  };
7682
7831
  }
7683
7832
  if (ch === "[") {
7684
7833
  return {
7685
- count: linkLength(buffer, 0)
7834
+ count: linkLength(buffer, start, start)
7686
7835
  };
7687
7836
  }
7688
7837
  return {
7689
7838
  count: 1
7690
7839
  };
7691
7840
  };
7692
- var xmlElementLength = (buffer) => {
7693
- const close = buffer.indexOf(">");
7841
+ var xmlElementLength = (buffer, start = 0) => {
7842
+ const close = buffer.indexOf(">", start);
7694
7843
  if (close === -1) {
7695
7844
  return 0;
7696
7845
  }
7697
7846
  if (buffer[close - 1] === "/") {
7698
- return close + 1;
7847
+ return close + 1 - start;
7699
7848
  }
7700
- if (buffer[1] === "/") {
7701
- return close + 1;
7849
+ if (buffer[start + 1] === "/") {
7850
+ return close + 1 - start;
7702
7851
  }
7703
- const nameMatch = buffer.match(OPENING_TAG_NAME);
7852
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7853
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7704
7854
  if (!nameMatch) {
7705
7855
  return 1;
7706
7856
  }
7707
7857
  const tagName = nameMatch[1];
7708
7858
  let depth = 0;
7709
7859
  const tagPattern = new RegExp(`<(/?)${escapeRegExpSource2(tagName)}(\\s[^>]*)?>`, "g");
7860
+ tagPattern.lastIndex = start;
7710
7861
  let match;
7711
7862
  while ((match = tagPattern.exec(buffer)) !== null) {
7712
7863
  const isSelfClosing = match[0].endsWith("/>");
7713
7864
  const isClosing = match[1] === "/";
7714
7865
  if (isSelfClosing) {
7715
7866
  if (depth === 0) {
7716
- return match.index + match[0].length;
7867
+ return match.index + match[0].length - start;
7717
7868
  }
7718
7869
  } else if (isClosing) {
7719
7870
  depth--;
7720
7871
  if (depth === 0) {
7721
- return match.index + match[0].length;
7872
+ return match.index + match[0].length - start;
7722
7873
  }
7723
7874
  } else {
7724
7875
  depth++;
@@ -7726,8 +7877,8 @@ var xmlElementLength = (buffer) => {
7726
7877
  }
7727
7878
  return 0;
7728
7879
  };
7729
- var linkLength = (buffer, offset) => {
7730
- const bracketClose = buffer.indexOf("]", offset + 1);
7880
+ var linkLength = (buffer, start, bracketAt) => {
7881
+ const bracketClose = buffer.indexOf("]", bracketAt + 1);
7731
7882
  if (bracketClose === -1) {
7732
7883
  return 0;
7733
7884
  }
@@ -7741,13 +7892,179 @@ var linkLength = (buffer, offset) => {
7741
7892
  if (parenClose === -1) {
7742
7893
  return 0;
7743
7894
  }
7744
- return parenClose + 1;
7895
+ return parenClose + 1 - start;
7896
+ };
7897
+
7898
+ // src/extensions/tags/xml-block-decoration.ts
7899
+ import { xmlLanguage as xmlLanguage2 } from "@codemirror/lang-xml";
7900
+ import { Decoration as Decoration16, ViewPlugin as ViewPlugin24 } from "@codemirror/view";
7901
+ var xmlBlockDecoration = ({ tag, lineClass, contentClass, hideTags }) => {
7902
+ const lineDecoration = lineClass ? Decoration16.line({
7903
+ class: lineClass
7904
+ }) : void 0;
7905
+ const contentDecoration = contentClass ? Decoration16.mark({
7906
+ class: contentClass
7907
+ }) : void 0;
7908
+ const hideDecoration = hideTags ? Decoration16.replace({}) : void 0;
7909
+ const buildDecorations5 = (view) => {
7910
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7911
+ if (!text.includes(`<${tag}`)) {
7912
+ return Decoration16.none;
7913
+ }
7914
+ const tree = xmlLanguage2.parser.parse(text);
7915
+ const ranges = [];
7916
+ tree.iterate({
7917
+ enter: (node) => {
7918
+ if (node.type.name !== "Element") {
7919
+ return;
7920
+ }
7921
+ const openTag = node.node.getChild("OpenTag");
7922
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7923
+ const tagNameNode = openTag?.getChild("TagName");
7924
+ if (!openTag || !tagNameNode) {
7925
+ return;
7926
+ }
7927
+ if (text.slice(tagNameNode.from, tagNameNode.to) !== tag) {
7928
+ return;
7929
+ }
7930
+ const contentFrom = openTag.to;
7931
+ const contentTo = closeTag?.from ?? node.node.to;
7932
+ if (hideDecoration) {
7933
+ ranges.push(hideDecoration.range(openTag.from, openTag.to));
7934
+ if (closeTag) {
7935
+ ranges.push(hideDecoration.range(closeTag.from, closeTag.to));
7936
+ }
7937
+ }
7938
+ if (contentDecoration && contentFrom < contentTo) {
7939
+ ranges.push(contentDecoration.range(contentFrom, contentTo));
7940
+ }
7941
+ if (lineDecoration && contentFrom <= view.state.doc.length) {
7942
+ let pos = contentFrom;
7943
+ while (pos <= contentTo && pos <= view.state.doc.length) {
7944
+ const line = view.state.doc.lineAt(pos);
7945
+ ranges.push(lineDecoration.range(line.from));
7946
+ if (line.to >= contentTo) {
7947
+ break;
7948
+ }
7949
+ pos = line.to + 1;
7950
+ }
7951
+ }
7952
+ }
7953
+ });
7954
+ return Decoration16.set(ranges, true);
7955
+ };
7956
+ return ViewPlugin24.fromClass(class {
7957
+ decorations;
7958
+ constructor(view) {
7959
+ this.decorations = buildDecorations5(view);
7960
+ }
7961
+ update(update2) {
7962
+ if (update2.docChanged) {
7963
+ this.decorations = buildDecorations5(update2.view);
7964
+ }
7965
+ }
7966
+ }, {
7967
+ decorations: (instance) => instance.decorations
7968
+ });
7969
+ };
7970
+
7971
+ // src/extensions/tags/xml-formatting.ts
7972
+ import { xmlLanguage as xmlLanguage3 } from "@codemirror/lang-xml";
7973
+ import { Decoration as Decoration17, EditorView as EditorView33, ViewPlugin as ViewPlugin25 } from "@codemirror/view";
7974
+ var XML_TAG_NODES = /* @__PURE__ */ new Set([
7975
+ "OpenTag",
7976
+ "CloseTag",
7977
+ "SelfClosingTag",
7978
+ "MismatchedCloseTag"
7979
+ ]);
7980
+ var xmlElementMark = Decoration17.mark({
7981
+ class: "cm-xml-element"
7982
+ });
7983
+ var xmlTagMark = Decoration17.mark({
7984
+ class: "cm-xml-tag"
7985
+ });
7986
+ var xmlContentMark = Decoration17.mark({
7987
+ class: "cm-xml-content"
7988
+ });
7989
+ var xmlFormatting = ({ skip } = {}) => {
7990
+ const skipSet = skip && skip.length > 0 ? new Set(skip) : void 0;
7991
+ const buildDecorations5 = (view) => {
7992
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7993
+ if (!text.includes("<")) {
7994
+ return Decoration17.none;
7995
+ }
7996
+ const tagNameAt = (node) => text.slice(node.from, node.to);
7997
+ const tree = xmlLanguage3.parser.parse(text);
7998
+ const ranges = [];
7999
+ tree.iterate({
8000
+ enter: (node) => {
8001
+ const name = node.type.name;
8002
+ if (name === "SelfClosingTag" && node.from < node.to) {
8003
+ if (skipSet) {
8004
+ const tagNameNode = node.node.getChild("TagName");
8005
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
8006
+ return false;
8007
+ }
8008
+ }
8009
+ ranges.push(xmlElementMark.range(node.from, node.to));
8010
+ ranges.push(xmlTagMark.range(node.from, node.to));
8011
+ return;
8012
+ }
8013
+ if (XML_TAG_NODES.has(name) && node.from < node.to) {
8014
+ ranges.push(xmlTagMark.range(node.from, node.to));
8015
+ return;
8016
+ }
8017
+ if (name === "Element" && node.from < node.to) {
8018
+ const openTag = node.node.getChild("OpenTag");
8019
+ if (openTag && skipSet) {
8020
+ const tagNameNode = openTag.getChild("TagName");
8021
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
8022
+ return false;
8023
+ }
8024
+ }
8025
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
8026
+ ranges.push(xmlElementMark.range(node.from, node.to));
8027
+ if (openTag && closeTag && openTag.to < closeTag.from) {
8028
+ ranges.push(xmlContentMark.range(openTag.to, closeTag.from));
8029
+ }
8030
+ }
8031
+ }
8032
+ });
8033
+ return Decoration17.set(ranges, true);
8034
+ };
8035
+ return [
8036
+ ViewPlugin25.fromClass(class {
8037
+ decorations;
8038
+ constructor(view) {
8039
+ this.decorations = buildDecorations5(view);
8040
+ }
8041
+ update(update2) {
8042
+ if (update2.docChanged) {
8043
+ this.decorations = buildDecorations5(update2.view);
8044
+ }
8045
+ }
8046
+ }, {
8047
+ decorations: (instance) => instance.decorations
8048
+ }),
8049
+ EditorView33.baseTheme({
8050
+ ".cm-xml-element": {
8051
+ backgroundColor: "var(--color-current-surface)",
8052
+ borderRadius: "0.25rem",
8053
+ padding: "0.25rem"
8054
+ },
8055
+ ".cm-xml-tag": {
8056
+ color: "var(--color-blue-500)",
8057
+ fontFamily: "var(--font-mono)"
8058
+ },
8059
+ ".cm-xml-content": {}
8060
+ })
8061
+ ];
7745
8062
  };
7746
8063
 
7747
8064
  // src/extensions/tags/xml-tags.ts
7748
8065
  import { syntaxTree as syntaxTree11 } from "@codemirror/language";
7749
- import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect12, StateField as StateField14 } from "@codemirror/state";
7750
- import { Decoration as Decoration16, EditorView as EditorView31, ViewPlugin as ViewPlugin22, WidgetType as WidgetType10, keymap as keymap13 } from "@codemirror/view";
8066
+ import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect13, StateField as StateField14 } from "@codemirror/state";
8067
+ import { Decoration as Decoration18, EditorView as EditorView34, ViewPlugin as ViewPlugin26, WidgetType as WidgetType10, keymap as keymap14 } from "@codemirror/view";
7751
8068
  import { invariant as invariant7 } from "@dxos/invariant";
7752
8069
  import { log as log11 } from "@dxos/log";
7753
8070
  import { Domino as Domino4 } from "@dxos/ui";
@@ -7756,15 +8073,7 @@ import { Domino as Domino4 } from "@dxos/ui";
7756
8073
  import { invariant as invariant6 } from "@dxos/invariant";
7757
8074
  var __dxlog_file16 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-util.ts";
7758
8075
  var nodeToJson = (state, node) => {
7759
- invariant6(node.type.name === "Element", "Node is not an Element", {
7760
- F: __dxlog_file16,
7761
- L: 18,
7762
- S: void 0,
7763
- A: [
7764
- "node.type.name === 'Element'",
7765
- "'Node is not an Element'"
7766
- ]
7767
- });
8076
+ 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'"] });
7768
8077
  const openTag = node.node.getChild("OpenTag") || node.node.getChild("SelfClosingTag");
7769
8078
  if (openTag) {
7770
8079
  const tagName = openTag.getChild("TagName");
@@ -7796,13 +8105,23 @@ var nodeToJson = (state, node) => {
7796
8105
  if (node.type.name === "Element" && openTag.type.name !== "SelfClosingTag") {
7797
8106
  const children = [];
7798
8107
  let child = node.node.firstChild;
8108
+ const appendText = (raw) => {
8109
+ if (raw.length === 0) {
8110
+ return;
8111
+ }
8112
+ const last = children[children.length - 1];
8113
+ if (typeof last === "string") {
8114
+ children[children.length - 1] = last + raw;
8115
+ } else {
8116
+ children.push(raw);
8117
+ }
8118
+ };
7799
8119
  while (child) {
7800
8120
  if (child.type.name !== "OpenTag" && child.type.name !== "CloseTag") {
7801
8121
  if (child.type.name === "Text") {
7802
- const text = state.doc.sliceString(child.from, child.to).trim();
7803
- if (text) {
7804
- children.push(text);
7805
- }
8122
+ appendText(state.doc.sliceString(child.from, child.to));
8123
+ } else if (child.type.name === "EntityReference" || child.type.name === "CharacterReference") {
8124
+ appendText(decodeXmlEntity(state.doc.sliceString(child.from, child.to)));
7806
8125
  } else if (child.type.name === "Element") {
7807
8126
  const data = nodeToJson(state, child);
7808
8127
  if (data) {
@@ -7812,27 +8131,62 @@ var nodeToJson = (state, node) => {
7812
8131
  }
7813
8132
  child = child.nextSibling;
7814
8133
  }
8134
+ if (children.length > 0 && typeof children[0] === "string") {
8135
+ children[0] = children[0].trimStart();
8136
+ }
7815
8137
  if (children.length > 0) {
7816
- tag.children = children;
8138
+ const lastIndex = children.length - 1;
8139
+ const last = children[lastIndex];
8140
+ if (typeof last === "string") {
8141
+ children[lastIndex] = last.trimEnd();
8142
+ }
8143
+ }
8144
+ const trimmed = children.filter((value) => typeof value !== "string" || value.length > 0);
8145
+ if (trimmed.length > 0) {
8146
+ tag.children = trimmed;
7817
8147
  }
7818
8148
  }
7819
8149
  return tag;
7820
8150
  }
7821
8151
  };
8152
+ var XML_NAMED_ENTITIES = {
8153
+ "&lt;": "<",
8154
+ "&gt;": ">",
8155
+ "&amp;": "&",
8156
+ "&quot;": '"',
8157
+ "&apos;": "'"
8158
+ };
8159
+ var decodeXmlEntity = (raw) => {
8160
+ const named = XML_NAMED_ENTITIES[raw];
8161
+ if (named !== void 0) {
8162
+ return named;
8163
+ }
8164
+ const numeric = /^&#(x?)([0-9a-fA-F]+);$/.exec(raw);
8165
+ if (numeric) {
8166
+ const code = parseInt(numeric[2], numeric[1] ? 16 : 10);
8167
+ if (Number.isFinite(code)) {
8168
+ try {
8169
+ return String.fromCodePoint(code);
8170
+ } catch {
8171
+ }
8172
+ }
8173
+ }
8174
+ return raw;
8175
+ };
7822
8176
 
7823
8177
  // src/extensions/tags/xml-tags.ts
7824
8178
  var __dxlog_file17 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-tags.ts";
7825
- var navigatePreviousEffect = StateEffect12.define();
7826
- var navigateNextEffect = StateEffect12.define();
8179
+ var navigatePreviousEffect = StateEffect13.define();
8180
+ var navigateNextEffect = StateEffect13.define();
7827
8181
  var getXmlTextChild = (children) => {
7828
8182
  const child = children?.[0];
7829
8183
  return typeof child === "string" ? child : null;
7830
8184
  };
7831
8185
  var xmlWidgetId = (explicit, fallback) => typeof explicit === "string" && explicit.length > 0 ? explicit : fallback;
7832
8186
  var escapeRegExpSource3 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7833
- var xmlTagContextEffect = StateEffect12.define();
7834
- var xmlTagResetEffect = StateEffect12.define();
7835
- var xmlTagUpdateEffect = StateEffect12.define();
8187
+ var xmlTagContextEffect = StateEffect13.define();
8188
+ var xmlTagResetEffect = StateEffect13.define();
8189
+ var xmlTagUpdateEffect = StateEffect13.define();
7836
8190
  var widgetContextStateField = StateField14.define({
7837
8191
  create: () => void 0,
7838
8192
  update: (value, tr) => {
@@ -7856,12 +8210,7 @@ var widgetStateMapStateField = StateField14.define({
7856
8210
  log11("widget updated", {
7857
8211
  id,
7858
8212
  value
7859
- }, {
7860
- F: __dxlog_file17,
7861
- L: 184,
7862
- S: void 0,
7863
- C: (f, a) => f(...a)
7864
- });
8213
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 59, S: void 0 });
7865
8214
  const state = typeof value === "function" ? value(map[id]) : value;
7866
8215
  return {
7867
8216
  ...map,
@@ -7872,25 +8221,16 @@ var widgetStateMapStateField = StateField14.define({
7872
8221
  return map;
7873
8222
  }
7874
8223
  });
7875
- var XML_WIDGET_DATA_ATTR = "data-xml-widget";
7876
- var xmlTags = ({ registry, setWidgets, bookmarks: bookmarks2, paragraphToWidgetGapRem } = {}) => {
8224
+ var xmlTags = ({ registry, setWidgets, bookmarks: bookmarks2 } = {}) => {
7877
8225
  const notifier = createWidgetMap(setWidgets);
7878
8226
  const widgetDecorationsField = createWidgetDecorationsField(registry, notifier);
7879
- const paragraphGapTheme = paragraphToWidgetGapRem != null && paragraphToWidgetGapRem > 0 ? EditorView31.baseTheme({
7880
- [`& .cm-content > .cm-line:not(:has([${XML_WIDGET_DATA_ATTR}])) + .cm-line:has([${XML_WIDGET_DATA_ATTR}])`]: {
7881
- marginTop: `${paragraphToWidgetGapRem}rem`
7882
- }
7883
- }) : null;
7884
8227
  return [
7885
8228
  widgetContextStateField,
7886
8229
  widgetStateMapStateField,
7887
8230
  widgetDecorationsField,
7888
8231
  createWidgetUpdatePlugin(widgetDecorationsField, notifier),
7889
8232
  createNavigationEffectPlugin(widgetDecorationsField, bookmarks2),
7890
- bookmarks2?.length ? Prec7.highest(keyHandlers) : [],
7891
- ...paragraphGapTheme ? [
7892
- paragraphGapTheme
7893
- ] : []
8233
+ bookmarks2?.length ? Prec7.highest(keyHandlers) : []
7894
8234
  ];
7895
8235
  };
7896
8236
  var createWidgetMap = (setWidgets) => {
@@ -7900,12 +8240,7 @@ var createWidgetMap = (setWidgets) => {
7900
8240
  log11("widget mounted", {
7901
8241
  id: state.id,
7902
8242
  tag: state.props._tag
7903
- }, {
7904
- F: __dxlog_file17,
7905
- L: 261,
7906
- S: void 0,
7907
- C: (f, a) => f(...a)
7908
- });
8243
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 101, S: void 0 });
7909
8244
  widgets.set(state.id, state);
7910
8245
  setWidgets?.([
7911
8246
  ...widgets.values()
@@ -7916,12 +8251,7 @@ var createWidgetMap = (setWidgets) => {
7916
8251
  log11("widget unmounted", {
7917
8252
  id,
7918
8253
  tag: state?.props._tag
7919
- }, {
7920
- F: __dxlog_file17,
7921
- L: 267,
7922
- S: void 0,
7923
- C: (f, a) => f(...a)
7924
- });
8254
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 112, S: void 0 });
7925
8255
  widgets.delete(id);
7926
8256
  setWidgets?.([
7927
8257
  ...widgets.values()
@@ -7930,7 +8260,7 @@ var createWidgetMap = (setWidgets) => {
7930
8260
  };
7931
8261
  return notifier;
7932
8262
  };
7933
- var keyHandlers = keymap13.of([
8263
+ var keyHandlers = keymap14.of([
7934
8264
  {
7935
8265
  key: "Mod-ArrowUp",
7936
8266
  run: (view) => {
@@ -7951,7 +8281,7 @@ var keyHandlers = keymap13.of([
7951
8281
  }
7952
8282
  ]);
7953
8283
  var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7954
- return EditorView31.updateListener.of((update2) => {
8284
+ return EditorView34.updateListener.of((update2) => {
7955
8285
  update2.transactions.forEach((transaction) => {
7956
8286
  for (const effect of transaction.effects) {
7957
8287
  if (effect.is(navigatePreviousEffect)) {
@@ -7979,7 +8309,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7979
8309
  anchor: line.from,
7980
8310
  head: line.from
7981
8311
  },
7982
- effects: scrollerLineEffect.of({
8312
+ effects: crawlerLineEffect.of({
7983
8313
  line: line.number - 1,
7984
8314
  offset: -16
7985
8315
  })
@@ -8012,7 +8342,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
8012
8342
  anchor: line.to,
8013
8343
  head: line.to
8014
8344
  },
8015
- effects: scrollerLineEffect.of({
8345
+ effects: crawlerLineEffect.of({
8016
8346
  line: line.number - 1,
8017
8347
  offset: -16
8018
8348
  })
@@ -8024,7 +8354,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
8024
8354
  anchor: line.to,
8025
8355
  head: line.to
8026
8356
  },
8027
- effects: scrollerLineEffect.of({
8357
+ effects: crawlerLineEffect.of({
8028
8358
  line: line.number - 1,
8029
8359
  position: "end"
8030
8360
  })
@@ -8036,7 +8366,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
8036
8366
  });
8037
8367
  });
8038
8368
  };
8039
- var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin22.fromClass(class {
8369
+ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin26.fromClass(class {
8040
8370
  update(update2) {
8041
8371
  const widgetStateMap = update2.state.field(widgetStateMapStateField);
8042
8372
  const { decorations: decorations2 } = update2.state.field(widgetDecorationsField);
@@ -8081,7 +8411,7 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.def
8081
8411
  }
8082
8412
  return {
8083
8413
  from: 0,
8084
- decorations: Decoration16.none
8414
+ decorations: Decoration18.none
8085
8415
  };
8086
8416
  }
8087
8417
  }
@@ -8092,12 +8422,7 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.def
8092
8422
  log11("document reset", {
8093
8423
  from,
8094
8424
  to: state.doc.length
8095
- }, {
8096
- F: __dxlog_file17,
8097
- L: 429,
8098
- S: void 0,
8099
- C: (f, a) => f(...a)
8100
- });
8425
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 298, S: void 0 });
8101
8426
  return buildDecorations4(state, {
8102
8427
  from: 0,
8103
8428
  to: state.doc.length
@@ -8126,8 +8451,8 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.def
8126
8451
  };
8127
8452
  },
8128
8453
  provide: (field) => [
8129
- EditorView31.decorations.from(field, (v) => v.decorations),
8130
- EditorView31.atomicRanges.of((view) => view.state.field(field).decorations || Decoration16.none)
8454
+ EditorView34.decorations.from(field, (v) => v.decorations),
8455
+ EditorView34.atomicRanges.of((view) => view.state.field(field).decorations || Decoration18.none)
8131
8456
  ]
8132
8457
  });
8133
8458
  var buildDecorations4 = (state, range, registry, notifier) => {
@@ -8138,7 +8463,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8138
8463
  if (!tree || tree.type.name === "Program" && tree.length === 0) {
8139
8464
  return {
8140
8465
  from: range.from,
8141
- decorations: Decoration16.none
8466
+ decorations: Decoration18.none
8142
8467
  };
8143
8468
  }
8144
8469
  let last = range.from;
@@ -8174,7 +8499,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8174
8499
  };
8175
8500
  const widget = factory ? factory(props) ?? void 0 : Component ? new PlaceholderWidget2(widgetId, Component, props, notifier) : void 0;
8176
8501
  if (widget) {
8177
- builder.add(nodeRange.from, nodeRange.to, Decoration16.replace({
8502
+ builder.add(nodeRange.from, nodeRange.to, Decoration18.replace({
8178
8503
  widget,
8179
8504
  block,
8180
8505
  atomic: true,
@@ -8186,12 +8511,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8186
8511
  }
8187
8512
  }
8188
8513
  } catch (err) {
8189
- log11.catch(err, void 0, {
8190
- F: __dxlog_file17,
8191
- L: 538,
8192
- S: void 0,
8193
- C: (f, a) => f(...a)
8194
- });
8514
+ log11.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 401, S: void 0 });
8195
8515
  }
8196
8516
  return false;
8197
8517
  }
@@ -8238,7 +8558,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8238
8558
  };
8239
8559
  const widget = def.factory ? def.factory(mergedProps) ?? void 0 : def.Component ? new PlaceholderWidget2(widgetId, def.Component, mergedProps, notifier, true) : void 0;
8240
8560
  if (widget) {
8241
- builder.add(absoluteFrom, range.to, Decoration16.replace({
8561
+ builder.add(absoluteFrom, range.to, Decoration18.replace({
8242
8562
  widget,
8243
8563
  block: def.block,
8244
8564
  atomic: true,
@@ -8270,15 +8590,7 @@ var PlaceholderWidget2 = class extends WidgetType10 {
8270
8590
  #view;
8271
8591
  constructor(id, Component, props, notifier, streaming) {
8272
8592
  super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier, this.streaming = streaming;
8273
- invariant7(id, void 0, {
8274
- F: __dxlog_file17,
8275
- L: 641,
8276
- S: this,
8277
- A: [
8278
- "id",
8279
- ""
8280
- ]
8281
- });
8593
+ invariant7(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 488, S: this, A: ["id", ""] });
8282
8594
  }
8283
8595
  get root() {
8284
8596
  return this.#root;
@@ -8294,9 +8606,7 @@ var PlaceholderWidget2 = class extends WidgetType10 {
8294
8606
  }
8295
8607
  toDOM(view) {
8296
8608
  this.#view = view;
8297
- this.#root = Domino4.of("div").classNames("min-h-[24px]").attributes({
8298
- [XML_WIDGET_DATA_ATTR]: ""
8299
- }).root;
8609
+ this.#root = Domino4.of("div").classNames("min-h-[24px]").root;
8300
8610
  const props = Object.assign({}, this.props, {
8301
8611
  view
8302
8612
  });
@@ -8327,71 +8637,10 @@ var PlaceholderWidget2 = class extends WidgetType10 {
8327
8637
  this.#view = void 0;
8328
8638
  }
8329
8639
  };
8330
-
8331
- // src/extensions/typewriter.ts
8332
- import { keymap as keymap14 } from "@codemirror/view";
8333
- var defaultItems = [
8334
- "hello world!",
8335
- "this is a test.",
8336
- "this is [DXOS](https://dxos.org)"
8337
- ];
8338
- var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
8339
- let t;
8340
- let idx = 0;
8341
- return [
8342
- keymap14.of([
8343
- {
8344
- // Reset.
8345
- key: "alt-meta-'",
8346
- run: () => {
8347
- clearTimeout(t);
8348
- idx = 0;
8349
- return true;
8350
- }
8351
- },
8352
- {
8353
- // Next prompt.
8354
- // TODO(burdon): Press 1-9 to select prompt?
8355
- key: "Shift-Meta-'",
8356
- run: (view) => {
8357
- clearTimeout(t);
8358
- const text = items[idx++];
8359
- if (idx === items?.length) {
8360
- idx = 0;
8361
- }
8362
- let i = 0;
8363
- const insert = (d = 0) => {
8364
- t = setTimeout(() => {
8365
- const pos = view.state.selection.main.head;
8366
- view.dispatch({
8367
- changes: {
8368
- from: pos,
8369
- insert: text[i++]
8370
- },
8371
- selection: {
8372
- anchor: pos + 1
8373
- }
8374
- });
8375
- if (i < text.length) {
8376
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
8377
- }
8378
- }, d);
8379
- };
8380
- insert();
8381
- return true;
8382
- }
8383
- }
8384
- ])
8385
- ];
8386
- };
8387
8640
  export {
8388
8641
  Cursor,
8389
- EditorInputMode,
8390
- EditorInputModes,
8391
8642
  EditorState4 as EditorState,
8392
- EditorView32 as EditorView,
8393
- EditorViewMode,
8394
- EditorViewModes,
8643
+ EditorView35 as EditorView,
8395
8644
  Inline,
8396
8645
  InputModeExtensions,
8397
8646
  List,
@@ -8400,7 +8649,6 @@ export {
8400
8649
  SpaceAwarenessProvider,
8401
8650
  TextKind,
8402
8651
  Tree,
8403
- XML_WIDGET_DATA_ATTR,
8404
8652
  addBlockquote,
8405
8653
  addBookmark,
8406
8654
  addCodeblock,
@@ -8425,6 +8673,9 @@ export {
8425
8673
  commentsState,
8426
8674
  compactSlots,
8427
8675
  convertTreeToJson,
8676
+ crawler,
8677
+ crawlerActiveEffect,
8678
+ crawlerLineEffect,
8428
8679
  createBasicExtensions,
8429
8680
  createComment,
8430
8681
  createCrawler,
@@ -8476,9 +8727,11 @@ export {
8476
8727
  insertAtCursor,
8477
8728
  insertAtLineStart,
8478
8729
  insertTable,
8730
+ isRangeVisible,
8479
8731
  itemToJSON,
8480
8732
  join,
8481
8733
  keymap15 as keymap,
8734
+ lineSpacing,
8482
8735
  linkTooltip,
8483
8736
  listItemToString,
8484
8737
  listener,
@@ -8511,9 +8764,8 @@ export {
8511
8764
  scrollPastEnd,
8512
8765
  scrollThreadIntoView,
8513
8766
  scrollToLine,
8767
+ scrollbarAutohide,
8514
8768
  scroller,
8515
- scrollerCrawlEffect,
8516
- scrollerLineEffect,
8517
8769
  selectionState,
8518
8770
  setBlockquote,
8519
8771
  setComments,
@@ -8521,6 +8773,7 @@ export {
8521
8773
  setSelection,
8522
8774
  setStyle,
8523
8775
  singleValueFacet,
8776
+ snippets2 as snippets,
8524
8777
  staticCompletion,
8525
8778
  submit,
8526
8779
  tabbable,
@@ -8540,10 +8793,12 @@ export {
8540
8793
  treeFacet,
8541
8794
  typeahead,
8542
8795
  typewriter,
8543
- wire,
8544
- wireBypass,
8796
+ typewriterBypass,
8797
+ typewriterDrainingEffect,
8545
8798
  wrapWithCatch,
8799
+ xmlBlockDecoration,
8546
8800
  xmlElementLength,
8801
+ xmlFormatting,
8547
8802
  xmlTagContextEffect,
8548
8803
  xmlTagResetEffect,
8549
8804
  xmlTagUpdateEffect,