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