@dxos/ui-editor 0.8.4-main.9be5663bfe → 0.8.4-main.bc2380dfbc

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 (144) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/index.mjs +995 -879
  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 +995 -878
  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 +1 -0
  14. package/dist/types/src/defaults.d.ts.map +1 -1
  15. package/dist/types/src/extensions/annotations.d.ts.map +1 -1
  16. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -1
  17. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -1
  18. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +5 -2
  19. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -1
  20. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -1
  21. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  23. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  25. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  26. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  27. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
  28. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  29. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  30. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  31. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  32. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  33. package/dist/types/src/extensions/factories.d.ts +2 -1
  34. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  35. package/dist/types/src/extensions/factories.test.d.ts +2 -0
  36. package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
  37. package/dist/types/src/extensions/focus.d.ts +1 -1
  38. package/dist/types/src/extensions/index.d.ts +2 -4
  39. package/dist/types/src/extensions/index.d.ts.map +1 -1
  40. package/dist/types/src/extensions/json.d.ts.map +1 -1
  41. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  42. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  44. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  45. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  46. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  47. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  51. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  52. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -1
  53. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
  54. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -1
  55. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
  56. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  57. package/dist/types/src/extensions/replacer.d.ts.map +1 -1
  58. package/dist/types/src/extensions/scrolling/auto-scroll.d.ts +18 -0
  59. package/dist/types/src/extensions/scrolling/auto-scroll.d.ts.map +1 -0
  60. package/dist/types/src/extensions/scrolling/crawler.d.ts +75 -0
  61. package/dist/types/src/extensions/scrolling/crawler.d.ts.map +1 -0
  62. package/dist/types/src/extensions/scrolling/index.d.ts +5 -0
  63. package/dist/types/src/extensions/scrolling/index.d.ts.map +1 -0
  64. package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts.map +1 -0
  65. package/dist/types/src/extensions/scrolling/scroller.d.ts +16 -0
  66. package/dist/types/src/extensions/scrolling/scroller.d.ts.map +1 -0
  67. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  68. package/dist/types/src/extensions/snippets.d.ts +10 -0
  69. package/dist/types/src/extensions/snippets.d.ts.map +1 -0
  70. package/dist/types/src/extensions/submit.d.ts.map +1 -1
  71. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -1
  72. package/dist/types/src/extensions/tags/fader.d.ts.map +1 -1
  73. package/dist/types/src/extensions/tags/index.d.ts +3 -1
  74. package/dist/types/src/extensions/tags/index.d.ts.map +1 -1
  75. package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
  76. package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
  77. package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
  78. package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
  79. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
  80. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
  81. package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
  82. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
  83. package/dist/types/src/extensions/tags/xml-tags.d.ts +1 -8
  84. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  85. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -1
  86. package/dist/types/src/index.d.ts +0 -1
  87. package/dist/types/src/index.d.ts.map +1 -1
  88. package/dist/types/src/styles/theme.d.ts.map +1 -1
  89. package/dist/types/src/types/types.d.ts +4 -4
  90. package/dist/types/src/types/types.d.ts.map +1 -1
  91. package/dist/types/src/util/cursor.d.ts.map +1 -1
  92. package/dist/types/src/util/debug.d.ts.map +1 -1
  93. package/dist/types/src/util/decorations.d.ts.map +1 -1
  94. package/dist/types/src/util/dom.d.ts.map +1 -1
  95. package/dist/types/src/util/facet.d.ts.map +1 -1
  96. package/dist/types/src/util/util.d.ts.map +1 -1
  97. package/dist/types/tsconfig.tsbuildinfo +1 -1
  98. package/package.json +33 -36
  99. package/src/defaults.ts +12 -3
  100. package/src/extensions/autocomplete/placeholder.ts +37 -18
  101. package/src/extensions/automerge/automerge.test.tsx +35 -9
  102. package/src/extensions/automerge/automerge.ts +2 -4
  103. package/src/extensions/factories.test.ts +88 -0
  104. package/src/extensions/factories.ts +25 -6
  105. package/src/extensions/index.ts +2 -4
  106. package/src/extensions/outliner/outliner.ts +1 -1
  107. package/src/extensions/{auto-scroll.ts → scrolling/auto-scroll.ts} +88 -23
  108. package/src/extensions/{scroller.ts → scrolling/crawler.ts} +63 -45
  109. package/src/extensions/scrolling/index.ts +8 -0
  110. package/src/extensions/{scroll-past-end.ts → scrolling/scroll-past-end.ts} +6 -6
  111. package/src/extensions/scrolling/scroller.ts +27 -0
  112. package/src/extensions/snippets.ts +67 -0
  113. package/src/extensions/tags/index.ts +3 -1
  114. package/src/extensions/tags/testing/text.md +36 -0
  115. package/src/extensions/tags/testing/text.txt +35 -0
  116. package/src/extensions/tags/{wire.test.ts → typewriter.test.ts} +2 -2
  117. package/src/extensions/tags/typewriter.ts +594 -0
  118. package/src/extensions/tags/xml-block-decoration.ts +123 -0
  119. package/src/extensions/tags/xml-formatting.ts +125 -0
  120. package/src/extensions/tags/xml-tags.ts +6 -32
  121. package/src/extensions/tags/xml-util.test.ts +90 -3
  122. package/src/extensions/tags/xml-util.ts +62 -5
  123. package/src/index.ts +0 -1
  124. package/src/styles/theme.ts +20 -10
  125. package/src/types/types.ts +10 -2
  126. package/src/typings.d.ts +8 -0
  127. package/dist/lib/browser/chunk-HL3YF6WC.mjs +0 -22
  128. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +0 -7
  129. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs +0 -24
  130. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +0 -7
  131. package/dist/types/src/extensions/auto-scroll.d.ts +0 -8
  132. package/dist/types/src/extensions/auto-scroll.d.ts.map +0 -1
  133. package/dist/types/src/extensions/scroll-past-end.d.ts.map +0 -1
  134. package/dist/types/src/extensions/scroller.d.ts +0 -63
  135. package/dist/types/src/extensions/scroller.d.ts.map +0 -1
  136. package/dist/types/src/extensions/tags/wire.d.ts +0 -23
  137. package/dist/types/src/extensions/tags/wire.d.ts.map +0 -1
  138. package/dist/types/src/extensions/tags/wire.test.d.ts +0 -2
  139. package/dist/types/src/extensions/tags/wire.test.d.ts.map +0 -1
  140. package/dist/types/src/extensions/typewriter.d.ts +0 -10
  141. package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
  142. package/src/extensions/tags/wire.ts +0 -459
  143. package/src/extensions/typewriter.ts +0 -68
  144. /package/dist/types/src/extensions/{scroll-past-end.d.ts → scrolling/scroll-past-end.d.ts} +0 -0
@@ -1,13 +1,6 @@
1
- import {
2
- EditorInputMode,
3
- EditorInputModes,
4
- EditorViewMode,
5
- EditorViewModes
6
- } from "./chunk-HL3YF6WC.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 EditorView33, keymap as keymap15 } from "@codemirror/view";
11
4
  import { tags as tags2 } from "@lezer/highlight";
12
5
  import { TextKind } from "@dxos/protocols/proto/dxos/echo/model/text";
13
6
 
@@ -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"
@@ -33,7 +29,12 @@ var documentSlots = {
33
29
  };
34
30
  var compactSlots = {
35
31
  content: {
36
- className: "mx-2! w-full"
32
+ className: "mx-2!"
33
+ }
34
+ };
35
+ var mobileSlots = {
36
+ content: {
37
+ className: "mx-2!"
37
38
  }
38
39
  };
39
40
 
@@ -270,12 +271,7 @@ var wrapWithCatch = (fn, label) => {
270
271
  } catch (err) {
271
272
  log.catch(err, {
272
273
  label
273
- }, {
274
- F: __dxlog_file,
275
- L: 20,
276
- S: void 0,
277
- C: (f, a) => f(...a)
278
- });
274
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 13, S: void 0 });
279
275
  }
280
276
  };
281
277
  };
@@ -301,12 +297,7 @@ var logChanges = (trs) => {
301
297
  if (changes.length) {
302
298
  log("changes", {
303
299
  changes
304
- }, {
305
- F: __dxlog_file,
306
- L: 54,
307
- S: void 0,
308
- C: (f, a) => f(...a)
309
- });
300
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 44, S: void 0 });
310
301
  }
311
302
  };
312
303
 
@@ -372,30 +363,37 @@ var insertAtLineStart = (view, from, insert) => {
372
363
  };
373
364
 
374
365
  // src/extensions/autocomplete/placeholder.ts
375
- var placeholder = ({ content, delay = 3e3 }) => {
366
+ var placeholder = ({ content, delay = 3e3, focusOnly = false }) => {
376
367
  const plugin = ViewPlugin3.fromClass(class {
377
368
  _timeout;
378
369
  _decorations = Decoration3.none;
379
370
  update(update2) {
371
+ if (!update2.docChanged && !update2.selectionSet && !update2.focusChanged) {
372
+ return;
373
+ }
380
374
  if (this._timeout) {
381
375
  window.clearTimeout(this._timeout);
382
376
  this._timeout = void 0;
383
377
  }
378
+ this._decorations = Decoration3.none;
379
+ if (focusOnly && !update2.view.hasFocus) {
380
+ return;
381
+ }
384
382
  const activeLine = update2.view.state.doc.lineAt(update2.view.state.selection.main.head);
385
- const isEmpty = activeLine.text.trim() === "";
386
- if (isEmpty) {
387
- const lineStart = activeLine.from;
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
- update2.view.update([]);
396
- }, delay);
383
+ if (activeLine.text.trim() !== "") {
384
+ return;
397
385
  }
398
- 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);
399
397
  }
400
398
  destroy() {
401
399
  if (this._timeout) {
@@ -513,300 +511,10 @@ var typeahead = ({ onComplete } = {}) => {
513
511
  ];
514
512
  };
515
513
 
516
- // src/extensions/auto-scroll.ts
517
- import { StateEffect as StateEffect2 } from "@codemirror/state";
518
- import { EditorView as EditorView5, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
519
- import { addEventListener, combine, throttle } from "@dxos/async";
520
- import { Domino } from "@dxos/ui";
521
- import { getSize } from "@dxos/ui-theme";
522
-
523
- // src/extensions/scroller.ts
524
- import { StateEffect } from "@codemirror/state";
525
- import { EditorView as EditorView4, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
526
- import { log as log2 } from "@dxos/log";
527
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/scroller.ts";
528
- var scrollerLineEffect = StateEffect.define();
529
- var scrollerCrawlEffect = StateEffect.define();
530
- var scrollToLine = (view, options) => {
531
- view.dispatch({
532
- effects: scrollerLineEffect.of(options)
533
- });
534
- };
535
- var scroller = ({ overScroll = 0 } = {}) => {
536
- const scrollPlugin = ViewPlugin5.fromClass(class ScrollerPlugin {
537
- view;
538
- crawler;
539
- constructor(view) {
540
- this.view = view;
541
- this.crawler = createCrawler(this.view);
542
- }
543
- // No-op.
544
- destroy() {
545
- this.crawler.cancel();
546
- }
547
- cancel() {
548
- this.crawler.cancel();
549
- }
550
- crawl(start = false) {
551
- if (start) {
552
- this.crawler.scroll();
553
- } else {
554
- this.crawler.cancel();
555
- }
556
- }
557
- scroll({ line, offset = 0, position, behavior = "instant" }) {
558
- const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
559
- const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
560
- const doc = this.view.state.doc;
561
- let targetScrollTop = scrollHeight - clientHeight + offset;
562
- if (line >= 0 && line <= doc.lines - 1) {
563
- const lineStart = doc.line(line + 1).from;
564
- const coords = this.view.coordsAtPos(lineStart);
565
- if (coords) {
566
- const currentScrollTop = scrollTop;
567
- const maxScrollTop = scrollHeight - clientHeight;
568
- if (position === "end") {
569
- targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
570
- } else {
571
- targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
572
- }
573
- targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
574
- }
575
- }
576
- requestAnimationFrame(() => {
577
- this.view.scrollDOM.scrollTo({
578
- top: targetScrollTop
579
- });
580
- });
581
- }
582
- });
583
- return [
584
- scrollPlugin,
585
- // Listen for effect.s
586
- EditorView4.updateListener.of((update2) => {
587
- update2.transactions.forEach((transaction) => {
588
- try {
589
- const plugin = update2.view.plugin(scrollPlugin);
590
- if (plugin) {
591
- for (const effect of transaction.effects) {
592
- if (effect.is(scrollerCrawlEffect)) {
593
- plugin.crawl(effect.value);
594
- } else if (effect.is(scrollerLineEffect)) {
595
- plugin.scroll(effect.value);
596
- }
597
- }
598
- }
599
- } catch (err) {
600
- log2.catch(err, void 0, {
601
- F: __dxlog_file2,
602
- L: 146,
603
- S: void 0,
604
- C: (f, a) => f(...a)
605
- });
606
- }
607
- });
608
- }),
609
- // Styles.
610
- EditorView4.theme({
611
- ".cm-content": {
612
- paddingBottom: `${overScroll}px`
613
- },
614
- ".cm-scroller": {
615
- overflowY: "scroll",
616
- overflowAnchor: "none",
617
- paddingBottom: "0"
618
- },
619
- ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
620
- display: "none"
621
- },
622
- ".cm-scroller::-webkit-scrollbar-thumb": {
623
- background: "transparent",
624
- transition: "background 0.15s"
625
- },
626
- "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
627
- background: "var(--color-scrollbar-thumb)"
628
- },
629
- ".cm-scroll-button": {
630
- position: "absolute",
631
- bottom: "0.5rem",
632
- right: "1rem"
633
- }
634
- })
635
- ];
636
- };
637
- function createCrawler(view, accel = 0.15, maxVelocity = 1, snapThreshold = 0.5) {
638
- const el = view.scrollDOM;
639
- let currentTop = 0;
640
- let velocity = 0;
641
- let rafId = null;
642
- function frame() {
643
- const targetTop = el.scrollHeight - el.clientHeight;
644
- const delta = targetTop - currentTop;
645
- const absDelta = Math.abs(delta);
646
- if (absDelta < snapThreshold && Math.abs(velocity) < snapThreshold) {
647
- el.scrollTop = targetTop;
648
- currentTop = targetTop;
649
- velocity = 0;
650
- rafId = null;
651
- return;
652
- }
653
- const stoppingDistance = velocity * velocity / (2 * accel);
654
- const direction = Math.sign(delta);
655
- if (velocity !== 0 && (absDelta <= stoppingDistance || direction !== Math.sign(velocity))) {
656
- velocity -= Math.sign(velocity) * Math.min(accel, Math.abs(velocity));
657
- } else {
658
- velocity += direction * accel;
659
- velocity = Math.sign(velocity) * Math.min(Math.abs(velocity), maxVelocity);
660
- }
661
- currentTop += velocity;
662
- el.scrollTop = currentTop;
663
- rafId = requestAnimationFrame(frame);
664
- }
665
- return {
666
- scroll: () => {
667
- if (rafId === null) {
668
- currentTop = el.scrollTop;
669
- rafId = requestAnimationFrame(frame);
670
- }
671
- },
672
- cancel: () => {
673
- if (rafId !== null) {
674
- cancelAnimationFrame(rafId);
675
- rafId = null;
676
- velocity = 0;
677
- }
678
- }
679
- };
680
- }
681
-
682
- // src/extensions/auto-scroll.ts
683
- var autoScrollEffect = StateEffect2.define();
684
- var autoScroll = (_ = {}) => {
685
- let buttonContainer;
686
- let isPinned = true;
687
- let jumpPending = false;
688
- let enabled = true;
689
- let firstUpdate = true;
690
- const setPinned = (pinned) => {
691
- buttonContainer?.classList.toggle("opacity-0", pinned);
692
- isPinned = pinned;
693
- };
694
- return [
695
- // Update listener for scrolling when content changes.
696
- EditorView5.updateListener.of((update2) => {
697
- const { view, heightChanged, state, startState } = update2;
698
- for (const tr of update2.transactions) {
699
- for (const effect of tr.effects) {
700
- if (effect.is(autoScrollEffect)) {
701
- enabled = effect.value;
702
- if (enabled) {
703
- setPinned(true);
704
- view.dispatch({
705
- effects: scrollerCrawlEffect.of(true)
706
- });
707
- } else {
708
- view.dispatch({
709
- effects: scrollerCrawlEffect.of(false)
710
- });
711
- }
712
- }
713
- }
714
- }
715
- if (!enabled) {
716
- return;
717
- }
718
- if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
719
- firstUpdate = false;
720
- jumpPending = true;
721
- requestAnimationFrame(() => {
722
- view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
723
- jumpPending = false;
724
- });
725
- return;
726
- }
727
- firstUpdate = false;
728
- if (jumpPending) {
729
- return;
730
- }
731
- if (heightChanged) {
732
- if (isPinned) {
733
- const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
734
- const delta = scrollHeight - scrollTop - clientHeight;
735
- if (delta > 0) {
736
- setPinned(true);
737
- view.dispatch({
738
- effects: scrollerCrawlEffect.of(true)
739
- });
740
- } else if (delta < -1) {
741
- setPinned(false);
742
- }
743
- } else {
744
- if (state.doc.length === 0) {
745
- setPinned(true);
746
- }
747
- }
748
- }
749
- }),
750
- // Detect user scroll and unpin (or re-pin if scrolled to the bottom).
751
- ViewPlugin6.fromClass(class {
752
- cleanup;
753
- constructor(view) {
754
- this.cleanup = createUserScrollDetector(view.scrollDOM, throttle(() => {
755
- requestAnimationFrame(() => {
756
- const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
757
- const delta = scrollHeight - scrollTop - clientHeight;
758
- const pinned = delta === 0;
759
- setPinned(pinned);
760
- if (!pinned) {
761
- view.dispatch({
762
- effects: scrollerCrawlEffect.of(false)
763
- });
764
- }
765
- });
766
- }, 500));
767
- }
768
- destroy() {
769
- this.cleanup();
770
- }
771
- }),
772
- // Scroll button.
773
- ViewPlugin6.fromClass(class {
774
- constructor(view) {
775
- const icon = Domino.of("dx-icon").classNames(getSize(4)).attributes({
776
- icon: "ph--arrow-down--regular"
777
- });
778
- const button = Domino.of("button").classNames("dx-button bg-accent-surface").attributes({
779
- "data-density": "fine"
780
- }).append(icon).on("click", () => {
781
- setPinned(true);
782
- view.dispatch({
783
- effects: scrollerLineEffect.of({
784
- line: -1,
785
- position: "end",
786
- behavior: "smooth"
787
- })
788
- });
789
- });
790
- buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").append(button).root;
791
- view.scrollDOM.parentElement.appendChild(buttonContainer);
792
- }
793
- })
794
- ];
795
- };
796
- function createUserScrollDetector(element, onUserScroll) {
797
- return combine(addEventListener(element, "wheel", () => onUserScroll(), {
798
- passive: true
799
- }), addEventListener(element, "pointerdown", (event) => {
800
- if (event.clientX > element.getBoundingClientRect().right - (element.offsetWidth - element.clientWidth)) {
801
- onUserScroll();
802
- }
803
- }));
804
- }
805
-
806
514
  // src/extensions/automerge/automerge.ts
807
515
  import { next as A3 } from "@automerge/automerge";
808
516
  import { StateField, Transaction as Transaction2 } from "@codemirror/state";
809
- import { EditorView as EditorView6, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
517
+ import { EditorView as EditorView4, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
810
518
  import { DocAccessor } from "@dxos/echo-db";
811
519
 
812
520
  // src/extensions/state.ts
@@ -815,19 +523,14 @@ var initialSync = Transaction.userEvent.of("initial.sync");
815
523
 
816
524
  // src/extensions/automerge/cursor.ts
817
525
  import { fromCursor, toCursor } from "@dxos/echo-db";
818
- import { log as log3 } from "@dxos/log";
819
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/cursor.ts";
526
+ import { log as log2 } from "@dxos/log";
527
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/automerge/cursor.ts";
820
528
  var cursorConverter = (accessor) => ({
821
529
  toCursor: (pos, assoc) => {
822
530
  try {
823
531
  return toCursor(accessor, pos, assoc);
824
532
  } catch (err) {
825
- log3.catch(err, void 0, {
826
- F: __dxlog_file3,
827
- L: 15,
828
- S: void 0,
829
- C: (f, a) => f(...a)
830
- });
533
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 11, S: void 0 });
831
534
  return "";
832
535
  }
833
536
  },
@@ -835,22 +538,17 @@ var cursorConverter = (accessor) => ({
835
538
  try {
836
539
  return fromCursor(accessor, cursor);
837
540
  } catch (err) {
838
- log3.catch(err, void 0, {
839
- F: __dxlog_file3,
840
- L: 24,
841
- S: void 0,
842
- C: (f, a) => f(...a)
843
- });
541
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 19, S: void 0 });
844
542
  return 0;
845
543
  }
846
544
  }
847
545
  });
848
546
 
849
547
  // src/extensions/automerge/defs.ts
850
- import { Annotation, StateEffect as StateEffect3 } from "@codemirror/state";
548
+ import { Annotation, StateEffect } from "@codemirror/state";
851
549
  var getPath = (state, field) => state.field(field).path;
852
550
  var getLastHeads = (state, field) => state.field(field).lastHeads;
853
- var updateHeadsEffect = StateEffect3.define({});
551
+ var updateHeadsEffect = StateEffect.define({});
854
552
  var updateHeads = (newHeads) => updateHeadsEffect.of({
855
553
  newHeads
856
554
  });
@@ -861,7 +559,7 @@ var isReconcile = (tr) => {
861
559
 
862
560
  // src/extensions/automerge/sync.ts
863
561
  import { next as A2 } from "@automerge/automerge";
864
- import { log as log4 } from "@dxos/log";
562
+ import { log as log3 } from "@dxos/log";
865
563
 
866
564
  // src/extensions/automerge/update-automerge.ts
867
565
  import { next as A } from "@automerge/automerge";
@@ -1002,7 +700,7 @@ var charPath = (textPath, candidatePath) => {
1002
700
  };
1003
701
 
1004
702
  // src/extensions/automerge/sync.ts
1005
- 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";
1006
704
  var Syncer = class {
1007
705
  _handle;
1008
706
  _state;
@@ -1025,12 +723,7 @@ var Syncer = class {
1025
723
  this._pending = false;
1026
724
  }
1027
725
  onEditorChange(view) {
1028
- log4("onEditorChange", void 0, {
1029
- F: __dxlog_file4,
1030
- L: 45,
1031
- S: this,
1032
- C: (f, a) => f(...a)
1033
- });
726
+ log3("onEditorChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 35, S: this });
1034
727
  const transactions = view.state.field(this._state).unreconciledTransactions.filter((tx) => !isReconcile(tx));
1035
728
  const newHeads = updateAutomerge(this._state, this._handle, transactions, view.state);
1036
729
  if (newHeads) {
@@ -1041,12 +734,7 @@ var Syncer = class {
1041
734
  }
1042
735
  }
1043
736
  onAutomergeChange(view) {
1044
- log4("onAutomergeChange", void 0, {
1045
- F: __dxlog_file4,
1046
- L: 60,
1047
- S: this,
1048
- C: (f, a) => f(...a)
1049
- });
737
+ log3("onAutomergeChange", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 47, S: this });
1050
738
  const oldHeads = getLastHeads(view.state, this._state);
1051
739
  const newHeads = A2.getHeads(this._handle.doc());
1052
740
  const diff = A2.equals(oldHeads, newHeads) ? [] : A2.diff(this._handle.doc(), oldHeads, newHeads);
@@ -1099,7 +787,7 @@ var automerge = (accessor) => {
1099
787
  // Track heads.
1100
788
  syncState,
1101
789
  // Reconcile external updates.
1102
- ViewPlugin7.fromClass(class {
790
+ ViewPlugin5.fromClass(class {
1103
791
  _view;
1104
792
  constructor(_view) {
1105
793
  this._view = _view;
@@ -1108,14 +796,16 @@ var automerge = (accessor) => {
1108
796
  const value = DocAccessor.getValue(accessor);
1109
797
  const current = this._view.state.doc.toString();
1110
798
  if (value !== current) {
1111
- console.warn("ENABLING INITIAL SYNC -- THIS MAY BE A REGRESSION");
1112
799
  this._view.dispatch({
1113
800
  changes: {
1114
801
  from: 0,
1115
802
  to: this._view.state.doc.length,
1116
803
  insert: value
1117
804
  },
1118
- annotations: initialSync
805
+ annotations: [
806
+ initialSync,
807
+ reconcileAnnotation.of(true)
808
+ ]
1119
809
  });
1120
810
  }
1121
811
  });
@@ -1128,7 +818,7 @@ var automerge = (accessor) => {
1128
818
  };
1129
819
  }),
1130
820
  // Reconcile local updates.
1131
- EditorView6.updateListener.of(({ view, changes, transactions }) => {
821
+ EditorView4.updateListener.of(({ view, changes, transactions }) => {
1132
822
  if (!changes.empty) {
1133
823
  const isInitialSync = transactions.some((tr) => tr.annotation(Transaction2.userEvent) === initialSync.value);
1134
824
  if (!isInitialSync) {
@@ -1141,10 +831,10 @@ var automerge = (accessor) => {
1141
831
 
1142
832
  // src/extensions/awareness/awareness.ts
1143
833
  import { Annotation as Annotation2, RangeSet } from "@codemirror/state";
1144
- 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";
1145
835
  import { Event } from "@dxos/async";
1146
836
  import { Context } from "@dxos/context";
1147
- 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";
1148
838
  var dummyProvider = {
1149
839
  remoteStateChange: new Event(),
1150
840
  open: () => {
@@ -1160,17 +850,14 @@ var RemoteSelectionChangedAnnotation = Annotation2.define();
1160
850
  var awareness = (provider = dummyProvider) => {
1161
851
  return [
1162
852
  awarenessProvider.of(provider),
1163
- ViewPlugin8.fromClass(RemoteSelectionsDecorator, {
853
+ ViewPlugin6.fromClass(RemoteSelectionsDecorator, {
1164
854
  decorations: (value) => value.decorations
1165
855
  }),
1166
856
  styles
1167
857
  ];
1168
858
  };
1169
859
  var RemoteSelectionsDecorator = class {
1170
- _ctx = new Context(void 0, {
1171
- F: __dxlog_file5,
1172
- L: 80
1173
- });
860
+ _ctx = new Context(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 33 });
1174
861
  _cursorConverter;
1175
862
  _provider;
1176
863
  _lastAnchor;
@@ -1319,7 +1006,7 @@ var RemoteCaretWidget = class extends WidgetType3 {
1319
1006
  return true;
1320
1007
  }
1321
1008
  };
1322
- var styles = EditorView7.theme({
1009
+ var styles = EditorView5.theme({
1323
1010
  ".cm-collab-selection": {},
1324
1011
  ".cm-collab-selectionLine": {
1325
1012
  padding: 0,
@@ -1381,8 +1068,8 @@ var styles = EditorView7.theme({
1381
1068
  import { DeferredTask, Event as Event2, sleep } from "@dxos/async";
1382
1069
  import { Context as Context2 } from "@dxos/context";
1383
1070
  import { invariant } from "@dxos/invariant";
1384
- import { log as log5 } from "@dxos/log";
1385
- 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";
1386
1073
  var DEBOUNCE_INTERVAL = 100;
1387
1074
  var SpaceAwarenessProvider = class {
1388
1075
  _remoteStates = /* @__PURE__ */ new Map();
@@ -1401,10 +1088,7 @@ var SpaceAwarenessProvider = class {
1401
1088
  this._info = info;
1402
1089
  }
1403
1090
  open() {
1404
- this._ctx = new Context2(void 0, {
1405
- F: __dxlog_file6,
1406
- L: 57
1407
- });
1091
+ this._ctx = new Context2(void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 28 });
1408
1092
  this._postTask = new DeferredTask(this._ctx, async () => {
1409
1093
  if (this._localState) {
1410
1094
  await this._messenger.postMessage(this._channel, {
@@ -1429,14 +1113,9 @@ var SpaceAwarenessProvider = class {
1429
1113
  void this._messenger.postMessage(this._channel, {
1430
1114
  kind: "query"
1431
1115
  }).catch((err) => {
1432
- log5.debug("failed to query awareness", {
1116
+ log4.debug("failed to query awareness", {
1433
1117
  err
1434
- }, {
1435
- F: __dxlog_file6,
1436
- L: 91,
1437
- S: this,
1438
- C: (f, a) => f(...a)
1439
- });
1118
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 57, S: this });
1440
1119
  });
1441
1120
  }
1442
1121
  close() {
@@ -1448,15 +1127,7 @@ var SpaceAwarenessProvider = class {
1448
1127
  return Array.from(this._remoteStates.values());
1449
1128
  }
1450
1129
  update(position) {
1451
- invariant(this._postTask, void 0, {
1452
- F: __dxlog_file6,
1453
- L: 106,
1454
- S: this,
1455
- A: [
1456
- "this._postTask",
1457
- ""
1458
- ]
1459
- });
1130
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 71, S: this, A: ["this._postTask", ""] });
1460
1131
  this._localState = {
1461
1132
  peerId: this._peerId,
1462
1133
  position,
@@ -1465,38 +1136,22 @@ var SpaceAwarenessProvider = class {
1465
1136
  this._postTask.schedule();
1466
1137
  }
1467
1138
  _handleQueryMessage() {
1468
- invariant(this._postTask, void 0, {
1469
- F: __dxlog_file6,
1470
- L: 117,
1471
- S: this,
1472
- A: [
1473
- "this._postTask",
1474
- ""
1475
- ]
1476
- });
1139
+ invariant(this._postTask, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 80, S: this, A: ["this._postTask", ""] });
1477
1140
  this._postTask.schedule();
1478
1141
  }
1479
1142
  _handlePostMessage(message) {
1480
- invariant(message.kind === "post", void 0, {
1481
- F: __dxlog_file6,
1482
- L: 122,
1483
- S: this,
1484
- A: [
1485
- "message.kind === 'post'",
1486
- ""
1487
- ]
1488
- });
1143
+ invariant(message.kind === "post", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 84, S: this, A: ["message.kind === 'post'", ""] });
1489
1144
  this._remoteStates.set(message.state.peerId, message.state);
1490
1145
  this.remoteStateChange.emit();
1491
1146
  }
1492
1147
  };
1493
1148
 
1494
1149
  // src/extensions/blast.ts
1495
- import { EditorView as EditorView8, keymap as keymap3 } from "@codemirror/view";
1150
+ import { EditorView as EditorView6, keymap as keymap3 } from "@codemirror/view";
1496
1151
  import defaultsDeep from "lodash.defaultsdeep";
1497
- import { throttle as throttle2 } from "@dxos/async";
1152
+ import { throttle } from "@dxos/async";
1498
1153
  import { invariant as invariant2 } from "@dxos/invariant";
1499
- 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";
1500
1155
  var defaultOptions = {
1501
1156
  effect: 2,
1502
1157
  maxParticles: 200,
@@ -1541,7 +1196,7 @@ var blast = (options = defaultOptions) => {
1541
1196
  };
1542
1197
  return [
1543
1198
  // Cursor moved.
1544
- EditorView8.updateListener.of((update2) => {
1199
+ EditorView6.updateListener.of((update2) => {
1545
1200
  if (blaster?.node !== update2.view.scrollDOM) {
1546
1201
  if (blaster) {
1547
1202
  blaster.destroy();
@@ -1614,15 +1269,7 @@ var Blaster = class {
1614
1269
  return this._node;
1615
1270
  }
1616
1271
  initialize() {
1617
- invariant2(!this._canvas && !this._ctx, void 0, {
1618
- F: __dxlog_file7,
1619
- L: 142,
1620
- S: this,
1621
- A: [
1622
- "!this._canvas && !this._ctx",
1623
- ""
1624
- ]
1625
- });
1272
+ invariant2(!this._canvas && !this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 134, S: this, A: ["!this._canvas && !this._ctx", ""] });
1626
1273
  this._canvas = document.createElement("canvas");
1627
1274
  this._canvas.id = "code-blast-canvas";
1628
1275
  this._canvas.style.position = "absolute";
@@ -1651,15 +1298,7 @@ var Blaster = class {
1651
1298
  }
1652
1299
  }
1653
1300
  start() {
1654
- invariant2(this._canvas && this._ctx, void 0, {
1655
- F: __dxlog_file7,
1656
- L: 181,
1657
- S: this,
1658
- A: [
1659
- "this._canvas && this._ctx",
1660
- ""
1661
- ]
1662
- });
1301
+ invariant2(this._canvas && this._ctx, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file6, L: 166, S: this, A: ["this._canvas && this._ctx", ""] });
1663
1302
  this._running = true;
1664
1303
  this.loop();
1665
1304
  }
@@ -1686,11 +1325,11 @@ var Blaster = class {
1686
1325
  this.drawParticles();
1687
1326
  requestAnimationFrame(this.loop.bind(this));
1688
1327
  }
1689
- shake = throttle2(({ time }) => {
1328
+ shake = throttle(({ time }) => {
1690
1329
  this._shakeTime = this._shakeTimeMax || time;
1691
1330
  this._shakeTimeMax = time;
1692
1331
  }, 100);
1693
- spawn = throttle2(({ element, point }) => {
1332
+ spawn = throttle(({ element, point }) => {
1694
1333
  const color = getRGBComponents(element, this._options.color);
1695
1334
  const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
1696
1335
  const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
@@ -1799,9 +1438,9 @@ var random = (min, max) => {
1799
1438
 
1800
1439
  // src/extensions/blocks.ts
1801
1440
  import { RangeSetBuilder as RangeSetBuilder3 } from "@codemirror/state";
1802
- 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";
1803
1442
  import { mx as mx2 } from "@dxos/ui-theme";
1804
- var paragraphBlockPlugin = ViewPlugin9.fromClass(class {
1443
+ var paragraphBlockPlugin = ViewPlugin7.fromClass(class {
1805
1444
  decorations;
1806
1445
  constructor(view) {
1807
1446
  this.decorations = this.build(view);
@@ -1860,7 +1499,7 @@ var paragraphBlockPlugin = ViewPlugin9.fromClass(class {
1860
1499
  });
1861
1500
  var blocks = () => [
1862
1501
  paragraphBlockPlugin,
1863
- EditorView9.baseTheme({
1502
+ EditorView7.baseTheme({
1864
1503
  ".cm-line.block-line": {
1865
1504
  paddingLeft: "0.75rem",
1866
1505
  paddingRight: "0.75rem",
@@ -1894,13 +1533,13 @@ var blocks = () => [
1894
1533
  ];
1895
1534
 
1896
1535
  // src/extensions/bookmarks.ts
1897
- 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";
1898
1537
  import { keymap as keymap4 } from "@codemirror/view";
1899
- import { log as log6 } from "@dxos/log";
1900
- var __dxlog_file8 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/bookmarks.ts";
1901
- var addBookmark = StateEffect4.define();
1902
- var removeBookmark = StateEffect4.define();
1903
- 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();
1904
1543
  var bookmarks = () => {
1905
1544
  return [
1906
1545
  bookmarksField,
@@ -1909,12 +1548,7 @@ var bookmarks = () => {
1909
1548
  key: "Mod-ArrowUp",
1910
1549
  run: (view) => {
1911
1550
  const bookmarks2 = view.state.field(bookmarksField);
1912
- log6("up", bookmarks2, {
1913
- F: __dxlog_file8,
1914
- L: 29,
1915
- S: void 0,
1916
- C: (f, a) => f(...a)
1917
- });
1551
+ log5("up", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 18, S: void 0 });
1918
1552
  return true;
1919
1553
  }
1920
1554
  },
@@ -1922,12 +1556,7 @@ var bookmarks = () => {
1922
1556
  key: "Mod-ArrowDown",
1923
1557
  run: (view) => {
1924
1558
  const bookmarks2 = view.state.field(bookmarksField);
1925
- log6("down", bookmarks2, {
1926
- F: __dxlog_file8,
1927
- L: 37,
1928
- S: void 0,
1929
- C: (f, a) => f(...a)
1930
- });
1559
+ log5("down", bookmarks2, { "~LogMeta": "~LogMeta", F: __dxlog_file7, L: 26, S: void 0 });
1931
1560
  return true;
1932
1561
  }
1933
1562
  }
@@ -1964,27 +1593,27 @@ var bookmarksField = StateField2.define({
1964
1593
 
1965
1594
  // src/extensions/comments.ts
1966
1595
  import { invertedEffects } from "@codemirror/commands";
1967
- import { StateEffect as StateEffect5, StateField as StateField3 } from "@codemirror/state";
1968
- 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";
1969
1598
  import sortBy from "lodash.sortby";
1970
1599
  import { debounce as debounce2 } from "@dxos/async";
1971
- import { log as log7 } from "@dxos/log";
1600
+ import { log as log6 } from "@dxos/log";
1972
1601
  import { isNonNullable } from "@dxos/util";
1973
1602
 
1974
1603
  // src/extensions/selection.ts
1975
1604
  import { Transaction as Transaction3 } from "@codemirror/state";
1976
- import { EditorView as EditorView10, keymap as keymap5 } from "@codemirror/view";
1605
+ import { EditorView as EditorView8, keymap as keymap5 } from "@codemirror/view";
1977
1606
  import { debounce } from "@dxos/async";
1978
1607
  import { invariant as invariant3 } from "@dxos/invariant";
1979
1608
  import { isTruthy } from "@dxos/util";
1980
- 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";
1981
1610
  var documentId = singleValueFacet();
1982
1611
  var stateRestoreAnnotation = "org.dxos.cm.state-restore";
1983
1612
  var createEditorStateTransaction = ({ scrollTo, selection }) => {
1984
1613
  return {
1985
1614
  selection,
1986
1615
  scrollIntoView: !scrollTo,
1987
- effects: scrollTo ? EditorView10.scrollIntoView(scrollTo, {
1616
+ effects: scrollTo ? EditorView8.scrollIntoView(scrollTo, {
1988
1617
  yMargin: 96
1989
1618
  }) : void 0,
1990
1619
  annotations: Transaction3.userEvent.of(stateRestoreAnnotation)
@@ -1992,28 +1621,12 @@ var createEditorStateTransaction = ({ scrollTo, selection }) => {
1992
1621
  };
1993
1622
  var createEditorStateStore = (keyPrefix) => ({
1994
1623
  getState: (id) => {
1995
- invariant3(id, void 0, {
1996
- F: __dxlog_file9,
1997
- L: 47,
1998
- S: void 0,
1999
- A: [
2000
- "id",
2001
- ""
2002
- ]
2003
- });
1624
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 26, S: void 0, A: ["id", ""] });
2004
1625
  const state = localStorage.getItem(`${keyPrefix}/${id}`);
2005
1626
  return state ? JSON.parse(state) : void 0;
2006
1627
  },
2007
1628
  setState: (id, state) => {
2008
- invariant3(id, void 0, {
2009
- F: __dxlog_file9,
2010
- L: 53,
2011
- S: void 0,
2012
- A: [
2013
- "id",
2014
- ""
2015
- ]
2016
- });
1629
+ invariant3(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file8, L: 31, S: void 0, A: ["id", ""] });
2017
1630
  localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
2018
1631
  }
2019
1632
  });
@@ -2026,7 +1639,7 @@ var selectionState = ({ getState, setState } = {}) => {
2026
1639
  // setStateDebounced(id, {});
2027
1640
  // },
2028
1641
  // }),
2029
- EditorView10.updateListener.of(({ view, transactions }) => {
1642
+ EditorView8.updateListener.of(({ view, transactions }) => {
2030
1643
  const id = view.state.facet(documentId);
2031
1644
  if (!id || transactions.some((tr) => tr.isUserEvent(stateRestoreAnnotation))) {
2032
1645
  return;
@@ -2065,10 +1678,10 @@ var selectionState = ({ getState, setState } = {}) => {
2065
1678
  };
2066
1679
 
2067
1680
  // src/extensions/comments.ts
2068
- var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/comments.ts";
2069
- var setComments = StateEffect5.define();
2070
- var setSelection = StateEffect5.define();
2071
- 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();
2072
1685
  var commentsState = StateField3.define({
2073
1686
  create: (state) => ({
2074
1687
  id: state.facet(documentId),
@@ -2107,7 +1720,7 @@ var commentsState = StateField3.define({
2107
1720
  return value;
2108
1721
  }
2109
1722
  });
2110
- var styles2 = EditorView11.theme({
1723
+ var styles2 = EditorView9.theme({
2111
1724
  ".cm-comment, .cm-comment-current": {
2112
1725
  padding: "3px 0",
2113
1726
  color: "var(--color-cm-comment-text)",
@@ -2128,19 +1741,14 @@ var createCommentMark = (id, isCurrent) => Decoration7.mark({
2128
1741
  "data-comment-id": id
2129
1742
  }
2130
1743
  });
2131
- var commentsDecorations = EditorView11.decorations.compute([
1744
+ var commentsDecorations = EditorView9.decorations.compute([
2132
1745
  commentsState
2133
1746
  ], (state) => {
2134
1747
  const { selection: { current }, comments: comments2 } = state.field(commentsState);
2135
1748
  const decorations2 = sortBy(comments2 ?? [], (range) => range.range.from)?.flatMap((comment) => {
2136
1749
  const range = comment.range;
2137
1750
  if (!range) {
2138
- log7.warn("Invalid range:", range, {
2139
- F: __dxlog_file10,
2140
- L: 139,
2141
- S: void 0,
2142
- C: (f, a) => f(...a)
2143
- });
1751
+ log6.warn("Invalid range:", range, { "~LogMeta": "~LogMeta", F: __dxlog_file9, L: 93, S: void 0 });
2144
1752
  return void 0;
2145
1753
  } else if (range.from === range.to) {
2146
1754
  return void 0;
@@ -2150,8 +1758,8 @@ var commentsDecorations = EditorView11.decorations.compute([
2150
1758
  }).filter(isNonNullable);
2151
1759
  return Decoration7.set(decorations2);
2152
1760
  });
2153
- var commentClickedEffect = StateEffect5.define();
2154
- var handleCommentClick = EditorView11.domEventHandlers({
1761
+ var commentClickedEffect = StateEffect3.define();
1762
+ var handleCommentClick = EditorView9.domEventHandlers({
2155
1763
  click: (event, view) => {
2156
1764
  let target = event.target;
2157
1765
  const editorRoot = view.dom;
@@ -2190,7 +1798,7 @@ var trackPastedComments = (onUpdate) => {
2190
1798
  }
2191
1799
  };
2192
1800
  return [
2193
- EditorView11.domEventHandlers({
1801
+ EditorView9.domEventHandlers({
2194
1802
  cut: handleTrack,
2195
1803
  copy: handleTrack
2196
1804
  }),
@@ -2212,7 +1820,7 @@ var trackPastedComments = (onUpdate) => {
2212
1820
  return effects;
2213
1821
  }),
2214
1822
  // Handle paste or the undo of comment deletion.
2215
- EditorView11.updateListener.of((update2) => {
1823
+ EditorView9.updateListener.of((update2) => {
2216
1824
  const restore = [];
2217
1825
  for (let i = 0; i < update2.transactions.length; i++) {
2218
1826
  const tr = update2.transactions[i];
@@ -2268,7 +1876,7 @@ var mapTrackedComment = (comment, changes) => ({
2268
1876
  from: changes.mapPos(comment.from, 1),
2269
1877
  to: changes.mapPos(comment.to, 1)
2270
1878
  });
2271
- var restoreCommentEffect = StateEffect5.define({
1879
+ var restoreCommentEffect = StateEffect3.define({
2272
1880
  map: mapTrackedComment
2273
1881
  });
2274
1882
  var createComment = (view) => {
@@ -2354,7 +1962,7 @@ var comments = (options = {}) => {
2354
1962
  //
2355
1963
  // Track deleted ranges and update ranges for decorations.
2356
1964
  //
2357
- EditorView11.updateListener.of(({ view, state, changes }) => {
1965
+ EditorView9.updateListener.of(({ view, state, changes }) => {
2358
1966
  let mod = false;
2359
1967
  const { comments: comments2, ...value } = state.field(commentsState);
2360
1968
  changes.iterChanges((from, to, from2, to2) => {
@@ -2386,7 +1994,7 @@ var comments = (options = {}) => {
2386
1994
  //
2387
1995
  // Track selection/proximity.
2388
1996
  //
2389
- EditorView11.updateListener.of(({ view, state }) => {
1997
+ EditorView9.updateListener.of(({ view, state }) => {
2390
1998
  let min = Infinity;
2391
1999
  const { selection: { current, closest }, comments: comments2 } = state.field(commentsState);
2392
2000
  const { head } = state.selection.main;
@@ -2440,7 +2048,7 @@ var scrollThreadIntoView = (view, id, center = true) => {
2440
2048
  anchor: range.from
2441
2049
  } : void 0,
2442
2050
  effects: [
2443
- needsScroll ? EditorView11.scrollIntoView(range.from, center ? {
2051
+ needsScroll ? EditorView9.scrollIntoView(range.from, center ? {
2444
2052
  y: "center"
2445
2053
  } : void 0) : [],
2446
2054
  needsSelectionUpdate ? setSelection.of({
@@ -2471,7 +2079,7 @@ var ExternalCommentSync = class {
2471
2079
  this.unsubscribe();
2472
2080
  };
2473
2081
  };
2474
- var createExternalCommentSync = (id, subscribe, getComments) => ViewPlugin10.fromClass(class {
2082
+ var createExternalCommentSync = (id, subscribe, getComments) => ViewPlugin8.fromClass(class {
2475
2083
  constructor(view) {
2476
2084
  return new ExternalCommentSync(view, id, subscribe, getComments);
2477
2085
  }
@@ -2491,12 +2099,12 @@ var debugNodeLogger = (log12 = console.log) => {
2491
2099
  };
2492
2100
 
2493
2101
  // src/extensions/dnd.ts
2494
- import { EditorView as EditorView12, dropCursor } from "@codemirror/view";
2102
+ import { EditorView as EditorView10, dropCursor } from "@codemirror/view";
2495
2103
  var dropFile = (options = {}) => {
2496
2104
  return [
2497
2105
  styles3,
2498
2106
  dropCursor(),
2499
- EditorView12.domEventHandlers({
2107
+ EditorView10.domEventHandlers({
2500
2108
  drop: (event, view) => {
2501
2109
  event.preventDefault();
2502
2110
  const files = event.dataTransfer?.files;
@@ -2515,7 +2123,7 @@ var dropFile = (options = {}) => {
2515
2123
  })
2516
2124
  ];
2517
2125
  };
2518
- var styles3 = EditorView12.theme({
2126
+ var styles3 = EditorView10.theme({
2519
2127
  ".cm-dropCursor": {
2520
2128
  borderLeft: "2px solid var(--color-accent-text)",
2521
2129
  color: "var(--color-accent-text)",
@@ -2537,10 +2145,10 @@ import { vscodeDarkStyle, vscodeLightStyle } from "@uiw/codemirror-theme-vscode"
2537
2145
  import defaultsDeep2 from "lodash.defaultsdeep";
2538
2146
  import { generateName } from "@dxos/display-name";
2539
2147
  import { log as log8 } from "@dxos/log";
2540
- import { hexToHue, isTruthy as isTruthy2 } from "@dxos/util";
2148
+ import { hexToHue, isTruthy as isTruthy3 } from "@dxos/util";
2541
2149
 
2542
2150
  // src/styles/theme.ts
2543
- import { EditorView as EditorView13 } from "@codemirror/view";
2151
+ import { EditorView as EditorView11 } from "@codemirror/view";
2544
2152
  import { mx as mx3 } from "@dxos/ui-theme";
2545
2153
  var headings = {
2546
2154
  1: {
@@ -2588,7 +2196,7 @@ var markdownTheme = {
2588
2196
  fontWeight: "100 !important"
2589
2197
  })
2590
2198
  };
2591
- var baseTheme = EditorView13.baseTheme({
2199
+ var baseTheme = EditorView11.baseTheme({
2592
2200
  /**
2593
2201
  * Outer frame.
2594
2202
  */
@@ -2600,12 +2208,21 @@ var baseTheme = EditorView13.baseTheme({
2600
2208
  * Scroller
2601
2209
  */
2602
2210
  ".cm-scroller": {
2603
- overflowAnchor: "none"
2211
+ // Browser scroll-anchoring: see comment in `scrolling/crawler.ts`. `auto` lets the browser pin a
2212
+ // stable element near the viewport top so widget resizes (e.g. tool-block TogglePanel
2213
+ // open/close) don't jump the user's view.
2214
+ overflowAnchor: "auto"
2604
2215
  },
2605
2216
  ".cm-scroller::-webkit-scrollbar": {
2606
- width: "8px"
2217
+ width: "var(--scrollbar-size,8px)",
2218
+ height: "var(--scrollbar-size,8px)"
2219
+ },
2220
+ ".cm-scroller::-webkit-scrollbar-corner": {
2221
+ background: "transparent"
2222
+ },
2223
+ ".cm-scroller::-webkit-scrollbar-track": {
2224
+ background: "transparent"
2607
2225
  },
2608
- ".cm-scroller::-webkit-scrollbar-track": {},
2609
2226
  ".cm-scroller::-webkit-scrollbar-thumb": {
2610
2227
  background: "transparent",
2611
2228
  transition: "background 0.15s"
@@ -2642,7 +2259,7 @@ var baseTheme = EditorView13.baseTheme({
2642
2259
  * Height is set to match the corresponding line (which may have wrapped).
2643
2260
  */
2644
2261
  ".cm-gutterElement": {
2645
- lineHeight: 1.5,
2262
+ lineHeight: "24px",
2646
2263
  fontSize: "12px"
2647
2264
  },
2648
2265
  /**
@@ -2702,7 +2319,8 @@ var baseTheme = EditorView13.baseTheme({
2702
2319
  textDecorationThickness: "1px",
2703
2320
  textDecorationColor: "var(--color-separator)",
2704
2321
  textUnderlineOffset: "2px",
2705
- borderRadius: ".125rem"
2322
+ borderRadius: ".125rem",
2323
+ cursor: "pointer"
2706
2324
  },
2707
2325
  ".cm-link > span": {
2708
2326
  color: "var(--color-accent-text)"
@@ -2740,12 +2358,12 @@ var baseTheme = EditorView13.baseTheme({
2740
2358
  padding: "4px"
2741
2359
  },
2742
2360
  ".cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]": {
2743
- background: "var(--color-active-surface)",
2744
- color: "var(--color-base-surface-text)"
2361
+ background: "var(--color-current-surface)",
2362
+ color: "var(--color-base-foreground)"
2745
2363
  },
2746
2364
  ".cm-tooltip.cm-tooltip-autocomplete > ul > completion-section": {
2747
2365
  paddingLeft: "4px !important",
2748
- color: "var(--color-base-surface-text)"
2366
+ color: "var(--color-base-foreground)"
2749
2367
  },
2750
2368
  /**
2751
2369
  * Completion info.
@@ -2764,7 +2382,7 @@ var baseTheme = EditorView13.baseTheme({
2764
2382
  padding: "0 4px"
2765
2383
  },
2766
2384
  ".cm-completionMatchedText": {
2767
- color: "var(--color-base-surface-text)",
2385
+ color: "var(--color-base-foreground)",
2768
2386
  textDecoration: "none !important"
2769
2387
  },
2770
2388
  /**
@@ -2799,7 +2417,7 @@ var baseTheme = EditorView13.baseTheme({
2799
2417
  backgroundColor: "var(--color-input-surface)"
2800
2418
  },
2801
2419
  ".cm-panel input:focus, .cm-panel button:focus": {
2802
- outline: "1px solid var(--color-neutral-focus-indicator)"
2420
+ outline: "1px solid var(--color-focus-ring-subtle)"
2803
2421
  },
2804
2422
  ".cm-panel label": {
2805
2423
  display: "inline-flex",
@@ -2812,7 +2430,7 @@ var baseTheme = EditorView13.baseTheme({
2812
2430
  height: "8px",
2813
2431
  marginRight: "6px !important",
2814
2432
  padding: "2px !important",
2815
- color: "var(--color-neutral-focus-indicator)"
2433
+ color: "var(--color-focus-ring-subtle)"
2816
2434
  },
2817
2435
  ".cm-panel button": {
2818
2436
  "&:hover": {
@@ -2828,14 +2446,14 @@ var baseTheme = EditorView13.baseTheme({
2828
2446
  borderTop: "1px solid var(--color-separator)"
2829
2447
  }
2830
2448
  });
2831
- var editorGutter = EditorView13.theme({
2449
+ var editorGutter = EditorView11.theme({
2832
2450
  ".cm-gutters": {
2833
2451
  // NOTE: Non-transparent background required to cover content if scrolling horizontally.
2834
2452
  background: "var(--color-base-surface) !important",
2835
2453
  paddingRight: "1rem"
2836
2454
  }
2837
2455
  });
2838
- var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2456
+ var createFontTheme = ({ monospace } = {}) => EditorView11.theme({
2839
2457
  // Main content.
2840
2458
  ".cm-scroller": {
2841
2459
  fontFamily: monospace ? fontMono : fontBody
@@ -2848,9 +2466,9 @@ var createFontTheme = ({ monospace } = {}) => EditorView13.theme({
2848
2466
  });
2849
2467
 
2850
2468
  // src/extensions/focus.ts
2851
- import { StateEffect as StateEffect6, StateField as StateField5 } from "@codemirror/state";
2852
- import { EditorView as EditorView14 } from "@codemirror/view";
2853
- var focusEffect = StateEffect6.define();
2469
+ import { StateEffect as StateEffect4, StateField as StateField5 } from "@codemirror/state";
2470
+ import { EditorView as EditorView12 } from "@codemirror/view";
2471
+ var focusEffect = StateEffect4.define();
2854
2472
  var focusField = StateField5.define({
2855
2473
  create: () => false,
2856
2474
  update: (value, tr) => {
@@ -2864,7 +2482,7 @@ var focusField = StateField5.define({
2864
2482
  });
2865
2483
  var focus = [
2866
2484
  focusField,
2867
- EditorView14.domEventHandlers({
2485
+ EditorView12.domEventHandlers({
2868
2486
  focus: (_event, view) => {
2869
2487
  requestAnimationFrame(() => view.dispatch({
2870
2488
  effects: focusEffect.of(true)
@@ -2875,22 +2493,365 @@ var focus = [
2875
2493
  effects: focusEffect.of(false)
2876
2494
  }));
2877
2495
  }
2878
- })
2879
- ];
2496
+ })
2497
+ ];
2498
+
2499
+ // src/extensions/scrolling/auto-scroll.ts
2500
+ import { StateEffect as StateEffect6 } from "@codemirror/state";
2501
+ import { EditorView as EditorView14, ViewPlugin as ViewPlugin10 } from "@codemirror/view";
2502
+ import { addEventListener, combine, throttle as throttle2 } from "@dxos/async";
2503
+ import { Domino } from "@dxos/ui";
2504
+ import { getSize } from "@dxos/ui-theme";
2505
+
2506
+ // src/extensions/scrolling/crawler.ts
2507
+ import { StateEffect as StateEffect5 } from "@codemirror/state";
2508
+ import { EditorView as EditorView13, ViewPlugin as ViewPlugin9 } from "@codemirror/view";
2509
+ import { log as log7 } from "@dxos/log";
2510
+ var __dxlog_file10 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/scrolling/crawler.ts";
2511
+ var crawlerLineEffect = StateEffect5.define();
2512
+ var crawlerActiveEffect = StateEffect5.define();
2513
+ var scrollToLine = (view, options) => {
2514
+ view.dispatch({
2515
+ effects: crawlerLineEffect.of(options)
2516
+ });
2517
+ };
2518
+ var crawler = ({ overScroll = 0 } = {}) => {
2519
+ const crawlerPlugin = ViewPlugin9.fromClass(class CrawlerPlugin {
2520
+ view;
2521
+ crawler;
2522
+ constructor(view) {
2523
+ this.view = view;
2524
+ this.crawler = createCrawler(this.view);
2525
+ }
2526
+ // No-op.
2527
+ destroy() {
2528
+ this.crawler.cancel();
2529
+ }
2530
+ cancel() {
2531
+ this.crawler.cancel();
2532
+ }
2533
+ crawl(start = false) {
2534
+ if (start) {
2535
+ this.crawler.scroll();
2536
+ } else {
2537
+ this.crawler.cancel();
2538
+ }
2539
+ }
2540
+ scroll({ line, offset = 0, position, behavior = "instant" }) {
2541
+ const { scrollTop, scrollHeight, clientHeight } = this.view.scrollDOM;
2542
+ const scrollerRect = this.view.scrollDOM.getBoundingClientRect();
2543
+ const doc = this.view.state.doc;
2544
+ let targetScrollTop = scrollHeight - clientHeight + offset;
2545
+ if (line >= 0 && line <= doc.lines - 1) {
2546
+ const lineStart = doc.line(line + 1).from;
2547
+ const coords = this.view.coordsAtPos(lineStart);
2548
+ if (coords) {
2549
+ const currentScrollTop = scrollTop;
2550
+ const maxScrollTop = scrollHeight - clientHeight;
2551
+ if (position === "end") {
2552
+ targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + offset;
2553
+ } else {
2554
+ targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + offset;
2555
+ }
2556
+ targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
2557
+ }
2558
+ }
2559
+ requestAnimationFrame(() => {
2560
+ this.view.scrollDOM.scrollTo({
2561
+ top: targetScrollTop
2562
+ });
2563
+ });
2564
+ }
2565
+ });
2566
+ return [
2567
+ crawlerPlugin,
2568
+ // Listen for effect.
2569
+ EditorView13.updateListener.of((update2) => {
2570
+ update2.transactions.forEach((transaction) => {
2571
+ try {
2572
+ const plugin = update2.view.plugin(crawlerPlugin);
2573
+ if (plugin) {
2574
+ for (const effect of transaction.effects) {
2575
+ if (effect.is(crawlerActiveEffect)) {
2576
+ plugin.crawl(effect.value);
2577
+ } else if (effect.is(crawlerLineEffect)) {
2578
+ plugin.scroll(effect.value);
2579
+ }
2580
+ }
2581
+ }
2582
+ } catch (err) {
2583
+ log7.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file10, L: 98, S: void 0 });
2584
+ }
2585
+ });
2586
+ }),
2587
+ // Styles.
2588
+ EditorView13.theme({
2589
+ ".cm-scroller": {
2590
+ overflowY: "scroll",
2591
+ // Browser scroll-anchoring: when widgets above the viewport resize (e.g. tool blocks
2592
+ // expanding their TogglePanel), the browser picks a stable element near the viewport
2593
+ // top and adjusts `scrollTop` so the user's view doesn't jump. Auto-scroll's pinning
2594
+ // logic still has the final word when pinned (forces scrollTop to scrollHeight).
2595
+ overflowAnchor: "auto"
2596
+ },
2597
+ ".cm-scroller.cm-hide-scrollbar::-webkit-scrollbar": {
2598
+ display: "none"
2599
+ },
2600
+ ".cm-scroller::-webkit-scrollbar-thumb": {
2601
+ background: "transparent",
2602
+ transition: "background 0.15s"
2603
+ },
2604
+ "&:hover .cm-scroller::-webkit-scrollbar-thumb": {
2605
+ background: "var(--color-scrollbar-thumb)"
2606
+ },
2607
+ // Spacer below the last text line. Implemented as a real block pseudo-element
2608
+ // (rather than `padding-bottom` on `.cm-content`) so it materializes in the
2609
+ // scroller's `scrollHeight` regardless of how `padding` is reset by the base
2610
+ // theme or downstream classes — this is what gives auto-scroll its head-room
2611
+ // so the last line stays `overScroll` px above the viewport bottom.
2612
+ ".cm-content::after": {
2613
+ content: '""',
2614
+ display: "block",
2615
+ height: `${overScroll}px`
2616
+ },
2617
+ ".cm-scroll-button": {
2618
+ position: "absolute",
2619
+ bottom: "0.5rem",
2620
+ right: "1rem"
2621
+ }
2622
+ })
2623
+ ];
2624
+ };
2625
+ function createCrawler(view, omega = 5, snapThreshold = 5, snapVelocity = 50) {
2626
+ const el = view.scrollDOM;
2627
+ let currentTop = 0;
2628
+ let velocity = 0;
2629
+ let rafId = null;
2630
+ let lastTime = 0;
2631
+ function frame(now) {
2632
+ const dt = lastTime === 0 ? 1 / 60 : Math.min(0.1, (now - lastTime) / 1e3);
2633
+ lastTime = now;
2634
+ const targetTop = el.scrollHeight - el.clientHeight;
2635
+ const delta = targetTop - currentTop;
2636
+ if (Math.abs(delta) < snapThreshold && Math.abs(velocity) < snapVelocity) {
2637
+ el.scrollTop = targetTop;
2638
+ currentTop = targetTop;
2639
+ velocity = 0;
2640
+ rafId = null;
2641
+ lastTime = 0;
2642
+ return;
2643
+ }
2644
+ const accel = omega * omega * delta - 2 * omega * velocity;
2645
+ velocity += accel * dt;
2646
+ currentTop += velocity * dt;
2647
+ el.scrollTop = currentTop;
2648
+ rafId = requestAnimationFrame(frame);
2649
+ }
2650
+ return {
2651
+ scroll: () => {
2652
+ if (rafId === null) {
2653
+ currentTop = el.scrollTop;
2654
+ lastTime = 0;
2655
+ rafId = requestAnimationFrame(frame);
2656
+ }
2657
+ },
2658
+ cancel: () => {
2659
+ if (rafId !== null) {
2660
+ cancelAnimationFrame(rafId);
2661
+ velocity = 0;
2662
+ lastTime = 0;
2663
+ rafId = null;
2664
+ }
2665
+ }
2666
+ };
2667
+ }
2668
+
2669
+ // src/extensions/scrolling/auto-scroll.ts
2670
+ var autoScrollEffect = StateEffect6.define();
2671
+ var autoScroll = ({ scrollOnResize = true } = {}) => {
2672
+ let buttonContainer;
2673
+ let isPinned = true;
2674
+ let jumpPending = false;
2675
+ let enabled = true;
2676
+ let firstUpdate = true;
2677
+ const setPinned = (pinned) => {
2678
+ buttonContainer?.classList.toggle("opacity-0", pinned);
2679
+ isPinned = pinned;
2680
+ };
2681
+ return [
2682
+ // Update listener for scrolling when content changes.
2683
+ EditorView14.updateListener.of((update2) => {
2684
+ const { view, heightChanged, state, startState } = update2;
2685
+ for (const tr of update2.transactions) {
2686
+ for (const effect of tr.effects) {
2687
+ if (effect.is(autoScrollEffect)) {
2688
+ enabled = effect.value;
2689
+ if (enabled) {
2690
+ setPinned(true);
2691
+ view.dispatch({
2692
+ effects: crawlerActiveEffect.of(true)
2693
+ });
2694
+ } else {
2695
+ view.dispatch({
2696
+ effects: crawlerActiveEffect.of(false)
2697
+ });
2698
+ }
2699
+ }
2700
+ }
2701
+ }
2702
+ if (!enabled) {
2703
+ return;
2704
+ }
2705
+ if (isPinned && (firstUpdate || startState.doc.length === 0) && state.doc.length > 0) {
2706
+ firstUpdate = false;
2707
+ jumpPending = true;
2708
+ requestAnimationFrame(() => {
2709
+ view.scrollDOM.scrollTop = view.scrollDOM.scrollHeight;
2710
+ jumpPending = false;
2711
+ });
2712
+ return;
2713
+ }
2714
+ firstUpdate = false;
2715
+ if (jumpPending) {
2716
+ return;
2717
+ }
2718
+ if (heightChanged) {
2719
+ if (isPinned) {
2720
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
2721
+ const delta = scrollHeight - scrollTop - clientHeight;
2722
+ if (delta > 0) {
2723
+ setPinned(true);
2724
+ view.dispatch({
2725
+ effects: crawlerActiveEffect.of(true)
2726
+ });
2727
+ } else if (delta < -1) {
2728
+ setPinned(false);
2729
+ }
2730
+ } else {
2731
+ if (state.doc.length === 0) {
2732
+ setPinned(true);
2733
+ }
2734
+ }
2735
+ }
2736
+ }),
2737
+ // Re-pin and jump to bottom when the scroll container itself resizes (e.g. sidebar toggle,
2738
+ // window resize). Doc-driven height changes are handled by the updateListener above; this
2739
+ // observer covers the case where the viewport changes while the doc length is unchanged.
2740
+ scrollOnResize ? ViewPlugin10.fromClass(class {
2741
+ observer;
2742
+ firstObservation = true;
2743
+ destroyed = false;
2744
+ constructor(view) {
2745
+ const onResize = throttle2(() => {
2746
+ if (this.destroyed || !enabled) {
2747
+ return;
2748
+ }
2749
+ setPinned(true);
2750
+ requestAnimationFrame(() => {
2751
+ if (this.destroyed) {
2752
+ return;
2753
+ }
2754
+ view.scrollDOM.scrollTo({
2755
+ top: view.scrollDOM.scrollHeight,
2756
+ behavior: "instant"
2757
+ });
2758
+ view.dispatch({
2759
+ effects: crawlerActiveEffect.of(false)
2760
+ });
2761
+ });
2762
+ }, 50);
2763
+ this.observer = new ResizeObserver(() => {
2764
+ if (this.firstObservation) {
2765
+ this.firstObservation = false;
2766
+ return;
2767
+ }
2768
+ onResize();
2769
+ });
2770
+ this.observer.observe(view.scrollDOM);
2771
+ }
2772
+ destroy() {
2773
+ this.destroyed = true;
2774
+ this.observer.disconnect();
2775
+ }
2776
+ }) : [],
2777
+ // Detect user scroll and unpin (or re-pin if scrolled to the bottom).
2778
+ ViewPlugin10.fromClass(class {
2779
+ cleanup;
2780
+ constructor(view) {
2781
+ const onUserScroll = throttle2(() => {
2782
+ requestAnimationFrame(() => {
2783
+ const { scrollTop, scrollHeight, clientHeight } = view.scrollDOM;
2784
+ const delta = scrollHeight - scrollTop - clientHeight;
2785
+ const pinned = Math.abs(delta) <= 1;
2786
+ setPinned(pinned);
2787
+ if (!pinned) {
2788
+ view.dispatch({
2789
+ effects: crawlerActiveEffect.of(false)
2790
+ });
2791
+ }
2792
+ });
2793
+ }, 500);
2794
+ this.cleanup = createUserScrollDetector(view.scrollDOM, () => {
2795
+ if (isPinned) {
2796
+ setPinned(false);
2797
+ view.dispatch({
2798
+ effects: crawlerActiveEffect.of(false)
2799
+ });
2800
+ }
2801
+ onUserScroll();
2802
+ });
2803
+ }
2804
+ destroy() {
2805
+ this.cleanup();
2806
+ }
2807
+ }),
2808
+ // Scroll button.
2809
+ ViewPlugin10.fromClass(class {
2810
+ constructor(view) {
2811
+ const icon = Domino.of("dx-icon").classNames(getSize(4)).attributes({
2812
+ icon: "ph--arrow-down--regular"
2813
+ });
2814
+ const button = Domino.of("button").classNames("dx-button bg-accent-surface").attributes({
2815
+ "data-density": "fine"
2816
+ }).append(icon).on("click", () => {
2817
+ setPinned(true);
2818
+ view.dispatch({
2819
+ effects: crawlerLineEffect.of({
2820
+ line: -1,
2821
+ position: "end",
2822
+ behavior: "smooth"
2823
+ })
2824
+ });
2825
+ });
2826
+ buttonContainer = Domino.of("div").classNames("cm-scroll-button transition-opacity duration-300 opacity-0").append(button).root;
2827
+ view.scrollDOM.parentElement.appendChild(buttonContainer);
2828
+ }
2829
+ })
2830
+ ];
2831
+ };
2832
+ function createUserScrollDetector(element, onUserScroll) {
2833
+ return combine(addEventListener(element, "wheel", () => onUserScroll(), {
2834
+ passive: true
2835
+ }), addEventListener(element, "pointerdown", (event) => {
2836
+ if (event.clientX > element.getBoundingClientRect().right - (element.offsetWidth - element.clientWidth)) {
2837
+ onUserScroll();
2838
+ }
2839
+ }));
2840
+ }
2880
2841
 
2881
- // src/extensions/scroll-past-end.ts
2842
+ // src/extensions/scrolling/scroll-past-end.ts
2882
2843
  import { EditorView as EditorView15, ViewPlugin as ViewPlugin11 } from "@codemirror/view";
2883
2844
  var scrollPastEndPlugin = ViewPlugin11.fromClass(class {
2884
- height = 1e3;
2885
- attrs = {
2886
- style: "padding-bottom: 1000px"
2845
+ _height = 1e3;
2846
+ _attrs = {
2847
+ style: `padding-bottom: ${this._height}px`
2887
2848
  };
2888
2849
  update({ view }) {
2889
2850
  const lastLineBlock = view.lineBlockAt(view.state.doc.length);
2890
2851
  const height = view.dom.clientHeight - lastLineBlock.height - view.documentPadding.top - 0.5;
2891
- if (height >= 0 && height !== this.height) {
2892
- this.height = height;
2893
- this.attrs = {
2852
+ if (height >= 0 && height !== this._height) {
2853
+ this._height = height;
2854
+ this._attrs = {
2894
2855
  style: `padding-bottom: ${height}px`
2895
2856
  };
2896
2857
  }
@@ -2898,9 +2859,22 @@ var scrollPastEndPlugin = ViewPlugin11.fromClass(class {
2898
2859
  });
2899
2860
  var scrollPastEnd = () => [
2900
2861
  scrollPastEndPlugin,
2901
- EditorView15.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?.attrs ?? null)
2862
+ EditorView15.contentAttributes.of((view) => view.plugin(scrollPastEndPlugin)?._attrs ?? null)
2902
2863
  ];
2903
2864
 
2865
+ // src/extensions/scrolling/scroller.ts
2866
+ import { isTruthy as isTruthy2 } from "@dxos/util";
2867
+ var scroller = ({ overScroll, scrollOnResize, autoScroll: autoScroll2 = true } = {}) => {
2868
+ return [
2869
+ crawler({
2870
+ overScroll
2871
+ }),
2872
+ autoScroll2 && autoScroll({
2873
+ scrollOnResize
2874
+ })
2875
+ ].filter(isTruthy2);
2876
+ };
2877
+
2904
2878
  // src/extensions/factories.ts
2905
2879
  var __dxlog_file11 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/factories.ts";
2906
2880
  var tabbable = EditorView16.contentAttributes.of({
@@ -2957,12 +2931,7 @@ var createBasicExtensions = (propsProp) => {
2957
2931
  return [
2958
2932
  // NOTE: Doesn't catch errors in keymap functions.
2959
2933
  EditorView16.exceptionSink.of((err) => {
2960
- log8.catch(err, void 0, {
2961
- F: __dxlog_file11,
2962
- L: 130,
2963
- S: void 0,
2964
- C: (f, a) => f(...a)
2965
- });
2934
+ log8.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file11, L: 79, S: void 0 });
2966
2935
  }),
2967
2936
  props.allowMultipleSelections && EditorState.allowMultipleSelections.of(true),
2968
2937
  props.bracketMatching && bracketMatching(),
@@ -2982,6 +2951,13 @@ var createBasicExtensions = (propsProp) => {
2982
2951
  props.lineWrapping && EditorView16.lineWrapping,
2983
2952
  props.placeholder && placeholder2(props.placeholder),
2984
2953
  props.readOnly !== void 0 && EditorState.readOnly.of(props.readOnly),
2954
+ // `EditorState.readOnly` is advisory — CodeMirror doesn't auto-reject doc-changing
2955
+ // transactions. Some extensions (e.g. `@codemirror/lang-markdown`'s Enter handler that
2956
+ // continues a list) dispatch programmatic edits regardless. Drop user-initiated edits
2957
+ // (`input` / `delete` keymap dispatches plus `undo` / `redo` from the history extension)
2958
+ // but pass programmatic dispatches — streaming `MarkdownStream` and similar consumers
2959
+ // depend on being able to populate the doc themselves.
2960
+ props.readOnly && EditorState.transactionFilter.of((tr) => tr.docChanged && (tr.isUserEvent("input") || tr.isUserEvent("delete") || tr.isUserEvent("undo") || tr.isUserEvent("redo")) ? [] : tr),
2985
2961
  props.scrollPastEnd && scrollPastEnd(),
2986
2962
  props.tabbable && tabbable,
2987
2963
  props.tabSize && EditorState.tabSize.of(props.tabSize),
@@ -3006,8 +2982,8 @@ var createBasicExtensions = (propsProp) => {
3006
2982
  preventDefault: true,
3007
2983
  run: () => true
3008
2984
  }
3009
- ].filter(isTruthy2))
3010
- ].filter(isTruthy2);
2985
+ ].filter(isTruthy3))
2986
+ ].filter(isTruthy3);
3011
2987
  };
3012
2988
  var grow = {
3013
2989
  editor: {
@@ -3024,7 +3000,7 @@ var defaultStyles = {
3024
3000
  dark: vscodeDarkStyle,
3025
3001
  light: vscodeLightStyle
3026
3002
  };
3027
- var createThemeExtensions = ({ monospace, themeMode, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp } = {}) => {
3003
+ var createThemeExtensions = ({ monospace, scrollbarThin, slots: slotsProp, syntaxHighlighting: syntaxHighlightingProp, themeMode } = {}) => {
3028
3004
  const slots = defaultsDeep2({}, slotsProp, defaultThemeSlots);
3029
3005
  return [
3030
3006
  baseTheme,
@@ -3039,12 +3015,17 @@ var createThemeExtensions = ({ monospace, themeMode, slots: slotsProp, syntaxHig
3039
3015
  slots.content?.className && EditorView16.contentAttributes.of({
3040
3016
  class: slots.content.className
3041
3017
  }),
3042
- slots.scroll?.className && ViewPlugin12.fromClass(class {
3018
+ (slots.scroller?.className || scrollbarThin) && ViewPlugin12.fromClass(class {
3043
3019
  constructor(view) {
3044
- view.scrollDOM.classList.add(...slots.scroll.className.split(/\s+/));
3020
+ if (slots.scroller?.className) {
3021
+ view.scrollDOM.classList.add(...slots.scroller.className.split(/\s+/));
3022
+ }
3023
+ if (scrollbarThin) {
3024
+ view.scrollDOM.style.setProperty("--scrollbar-size", "4px");
3025
+ }
3045
3026
  }
3046
3027
  })
3047
- ].filter(isTruthy2);
3028
+ ].filter(isTruthy3);
3048
3029
  };
3049
3030
  var createDataExtensions = ({ id, text, messenger, identity }) => {
3050
3031
  const extensions = [];
@@ -4368,7 +4349,7 @@ import { markdown, markdownLanguage as markdownLanguage2 } from "@codemirror/lan
4368
4349
  import { foldNodeProp, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
4369
4350
  import { languages } from "@codemirror/language-data";
4370
4351
  import { keymap as keymap9 } from "@codemirror/view";
4371
- import { isTruthy as isTruthy3 } from "@dxos/util";
4352
+ import { isTruthy as isTruthy4 } from "@dxos/util";
4372
4353
 
4373
4354
  // src/extensions/markdown/highlight.ts
4374
4355
  import { markdownLanguage } from "@codemirror/lang-markdown";
@@ -4600,7 +4581,7 @@ var createMarkdownExtensions = (options = {}) => {
4600
4581
  ...defaultKeymap2,
4601
4582
  // TODO(burdon): Remove?
4602
4583
  ...completionKeymap
4603
- ].filter(isTruthy3))
4584
+ ].filter(isTruthy4))
4604
4585
  ];
4605
4586
  };
4606
4587
  var noFencedCodeFolding = {
@@ -5298,15 +5279,7 @@ var buildDecorations2 = (view, options, focus2) => {
5298
5279
  const { state } = view;
5299
5280
  const headerLevels = [];
5300
5281
  const getHeaderLevels = (node, level) => {
5301
- invariant4(level > 0, void 0, {
5302
- F: __dxlog_file12,
5303
- L: 177,
5304
- S: void 0,
5305
- A: [
5306
- "level > 0",
5307
- ""
5308
- ]
5309
- });
5282
+ invariant4(level > 0, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 160, S: void 0, A: ["level > 0", ""] });
5310
5283
  if (level > headerLevels.length) {
5311
5284
  const len = headerLevels.length;
5312
5285
  headerLevels.length = level;
@@ -5337,15 +5310,7 @@ var buildDecorations2 = (view, options, focus2) => {
5337
5310
  listLevels.pop();
5338
5311
  };
5339
5312
  const getCurrentListLevel = () => {
5340
- invariant4(listLevels.length, void 0, {
5341
- F: __dxlog_file12,
5342
- L: 199,
5343
- S: void 0,
5344
- A: [
5345
- "listLevels.length",
5346
- ""
5347
- ]
5348
- });
5313
+ invariant4(listLevels.length, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file12, L: 192, S: void 0, A: ["listLevels.length", ""] });
5349
5314
  return listLevels[listLevels.length - 1];
5350
5315
  };
5351
5316
  const enterNode = (node) => {
@@ -5767,12 +5732,7 @@ var mention = ({ debug, onSearch }) => {
5767
5732
  (context) => {
5768
5733
  log9.info("completion context", {
5769
5734
  context
5770
- }, {
5771
- F: __dxlog_file13,
5772
- L: 27,
5773
- S: void 0,
5774
- C: (f, a) => f(...a)
5775
- });
5735
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file13, L: 18, S: void 0 });
5776
5736
  const match = context.matchBefore(/@(\w+)?/);
5777
5737
  if (!match || match.from === match.to && !context.explicit) {
5778
5738
  return null;
@@ -6005,15 +5965,7 @@ var outlinerTree = (_options = {}) => {
6005
5965
  break;
6006
5966
  }
6007
5967
  case "BulletList": {
6008
- invariant5(current, void 0, {
6009
- F: __dxlog_file14,
6010
- L: 219,
6011
- S: void 0,
6012
- A: [
6013
- "current",
6014
- ""
6015
- ]
6016
- });
5968
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 169, S: void 0, A: ["current", ""] });
6017
5969
  parent = current;
6018
5970
  if (current) {
6019
5971
  current.lineRange.to = current.node.from;
@@ -6022,15 +5974,7 @@ var outlinerTree = (_options = {}) => {
6022
5974
  break;
6023
5975
  }
6024
5976
  case "ListItem": {
6025
- invariant5(parent, void 0, {
6026
- F: __dxlog_file14,
6027
- L: 228,
6028
- S: void 0,
6029
- A: [
6030
- "parent",
6031
- ""
6032
- ]
6033
- });
5977
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 179, S: void 0, A: ["parent", ""] });
6034
5978
  const nextSibling = node.node.nextSibling ?? node.node.parent?.nextSibling;
6035
5979
  const docRange = {
6036
5980
  from: state.doc.lineAt(node.from).from,
@@ -6064,42 +6008,18 @@ var outlinerTree = (_options = {}) => {
6064
6008
  break;
6065
6009
  }
6066
6010
  case "ListMark": {
6067
- invariant5(current, void 0, {
6068
- F: __dxlog_file14,
6069
- L: 272,
6070
- S: void 0,
6071
- A: [
6072
- "current",
6073
- ""
6074
- ]
6075
- });
6011
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 219, S: void 0, A: ["current", ""] });
6076
6012
  current.type = "bullet";
6077
6013
  current.contentRange.from = node.from + "- ".length;
6078
6014
  break;
6079
6015
  }
6080
6016
  case "Task": {
6081
- invariant5(current, void 0, {
6082
- F: __dxlog_file14,
6083
- L: 278,
6084
- S: void 0,
6085
- A: [
6086
- "current",
6087
- ""
6088
- ]
6089
- });
6017
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 226, S: void 0, A: ["current", ""] });
6090
6018
  current.type = "task";
6091
6019
  break;
6092
6020
  }
6093
6021
  case "TaskMarker": {
6094
- invariant5(current, void 0, {
6095
- F: __dxlog_file14,
6096
- L: 283,
6097
- S: void 0,
6098
- A: [
6099
- "current",
6100
- ""
6101
- ]
6102
- });
6022
+ invariant5(current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 232, S: void 0, A: ["current", ""] });
6103
6023
  current.contentRange.from = node.from + "[ ] ".length;
6104
6024
  break;
6105
6025
  }
@@ -6107,29 +6027,13 @@ var outlinerTree = (_options = {}) => {
6107
6027
  },
6108
6028
  leave: (node) => {
6109
6029
  if (node.name === "BulletList") {
6110
- invariant5(parent, void 0, {
6111
- F: __dxlog_file14,
6112
- L: 291,
6113
- S: void 0,
6114
- A: [
6115
- "parent",
6116
- ""
6117
- ]
6118
- });
6030
+ invariant5(parent, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 240, S: void 0, A: ["parent", ""] });
6119
6031
  prevSiblings[level--] = void 0;
6120
6032
  parent = parent.parent;
6121
6033
  }
6122
6034
  }
6123
6035
  });
6124
- invariant5(tree, void 0, {
6125
- F: __dxlog_file14,
6126
- L: 298,
6127
- S: void 0,
6128
- A: [
6129
- "tree",
6130
- ""
6131
- ]
6132
- });
6036
+ invariant5(tree, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file14, L: 246, S: void 0, A: ["tree", ""] });
6133
6037
  return tree;
6134
6038
  };
6135
6039
  return [
@@ -6571,35 +6475,20 @@ var editor = () => [
6571
6475
  text: insert.toString(),
6572
6476
  length: insert.length
6573
6477
  }
6574
- }, {
6575
- F: __dxlog_file15,
6576
- L: 164,
6577
- S: void 0,
6578
- C: (f, a) => f(...a)
6579
- });
6478
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 174, S: void 0 });
6580
6479
  }
6581
6480
  });
6582
6481
  if (changes.length > 0) {
6583
6482
  log10("modified,", {
6584
6483
  changes
6585
- }, {
6586
- F: __dxlog_file15,
6587
- L: 175,
6588
- S: void 0,
6589
- C: (f, a) => f(...a)
6590
- });
6484
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 196, S: void 0 });
6591
6485
  return [
6592
6486
  {
6593
6487
  changes
6594
6488
  }
6595
6489
  ];
6596
6490
  } else if (cancel) {
6597
- log10("cancel", void 0, {
6598
- F: __dxlog_file15,
6599
- L: 178,
6600
- S: void 0,
6601
- C: (f, a) => f(...a)
6602
- });
6491
+ log10("cancel", void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file15, L: 205, S: void 0 });
6603
6492
  return [];
6604
6493
  }
6605
6494
  return tr;
@@ -6773,7 +6662,7 @@ var decorations = () => [
6773
6662
  marginBottom: "2px"
6774
6663
  },
6775
6664
  ".cm-list-item-focused": {
6776
- borderColor: "var(--color-neutral-focus-indicator)"
6665
+ borderColor: "var(--color-focus-ring-subtle)"
6777
6666
  },
6778
6667
  "&:focus-within .cm-list-item-selected": {
6779
6668
  borderColor: "var(--color-separator)"
@@ -7040,12 +6929,69 @@ var replacer = ({ replacements = defaultReplacements } = {}) => {
7040
6929
  });
7041
6930
  };
7042
6931
 
6932
+ // src/extensions/snippets.ts
6933
+ import { keymap as keymap12 } from "@codemirror/view";
6934
+ var defaultItems = [
6935
+ "hello world!",
6936
+ "this is a test.",
6937
+ "this is [DXOS](https://dxos.org)"
6938
+ ];
6939
+ var snippets2 = ({ delay = 75, items = defaultItems } = {}) => {
6940
+ let timer;
6941
+ let index = 0;
6942
+ return [
6943
+ keymap12.of([
6944
+ {
6945
+ // Reset.
6946
+ key: "alt-meta-'",
6947
+ run: () => {
6948
+ clearTimeout(timer);
6949
+ index = 0;
6950
+ return true;
6951
+ }
6952
+ },
6953
+ {
6954
+ // Next snippet.
6955
+ // TODO(burdon): Press 1-9 to select snippet?
6956
+ key: "Shift-Meta-'",
6957
+ run: (view) => {
6958
+ clearTimeout(timer);
6959
+ const text = items[index++];
6960
+ if (index === items?.length) {
6961
+ index = 0;
6962
+ }
6963
+ let offset = 0;
6964
+ const insert = (delayMs = 0) => {
6965
+ timer = setTimeout(() => {
6966
+ const pos = view.state.selection.main.head;
6967
+ view.dispatch({
6968
+ changes: {
6969
+ from: pos,
6970
+ insert: text[offset++]
6971
+ },
6972
+ selection: {
6973
+ anchor: pos + 1
6974
+ }
6975
+ });
6976
+ if (offset < text.length) {
6977
+ insert(Math.random() * delay * (text[offset] === " " ? 2 : 1));
6978
+ }
6979
+ }, delayMs);
6980
+ };
6981
+ insert();
6982
+ return true;
6983
+ }
6984
+ }
6985
+ ])
6986
+ ];
6987
+ };
6988
+
7043
6989
  // src/extensions/submit.ts
7044
6990
  import { Prec as Prec6 } from "@codemirror/state";
7045
- import { keymap as keymap12 } from "@codemirror/view";
6991
+ import { keymap as keymap13 } from "@codemirror/view";
7046
6992
  var submit = ({ fireIfEmpty = false, onSubmit } = {}) => {
7047
6993
  return [
7048
- Prec6.highest(keymap12.of([
6994
+ Prec6.highest(keymap13.of([
7049
6995
  {
7050
6996
  key: "Enter",
7051
6997
  preventDefault: true,
@@ -7371,36 +7317,46 @@ var fader = (options = {}) => {
7371
7317
  ];
7372
7318
  };
7373
7319
 
7374
- // src/extensions/tags/wire.ts
7320
+ // src/extensions/tags/typewriter.ts
7375
7321
  import { Annotation as Annotation3, ChangeSet as ChangeSet2, EditorState as EditorState3, StateEffect as StateEffect11, StateField as StateField13 } from "@codemirror/state";
7376
7322
  import { Decoration as Decoration15, EditorView as EditorView30, ViewPlugin as ViewPlugin21, WidgetType as WidgetType9 } from "@codemirror/view";
7377
7323
  import { Domino as Domino3 } from "@dxos/ui";
7378
- var wireBypass = Annotation3.define();
7379
- var DEFAULT_RATE = 200;
7324
+ var typewriterBypass = Annotation3.define();
7325
+ var typewriterDrainingEffect = StateEffect11.define();
7380
7326
  var CURSOR_LINGER = 3e3;
7381
- var wire = (options = {}) => {
7382
- const rate = options.rate ?? DEFAULT_RATE;
7383
- const interval = 1e3 / rate;
7327
+ var FRAME_BUDGET_MS = 4;
7328
+ var CHARS_PER_FRAME = 5;
7329
+ var FLUSH_THRESHOLD = 2e3;
7330
+ var COMPACT_HEAD_THRESHOLD = 4096;
7331
+ var typewriter = (options = {}) => {
7384
7332
  const streamingTags = options.streamingTags ?? /* @__PURE__ */ new Set();
7333
+ const flushThreshold = options.flushThreshold ?? FLUSH_THRESHOLD;
7334
+ const frameBudgetMs = options.frameBudgetMs ?? FRAME_BUDGET_MS;
7335
+ const charsPerFrame = options.charsPerFrame ?? CHARS_PER_FRAME;
7385
7336
  const suppressAppend = StateEffect11.define();
7386
7337
  const insertChunk = StateEffect11.define();
7387
7338
  const bufferField = StateField13.define({
7388
7339
  create: () => ({
7389
7340
  text: "",
7341
+ head: 0,
7390
7342
  insertAt: 0
7391
7343
  }),
7392
7344
  update: (value, tr) => {
7393
- let { text, insertAt } = value;
7345
+ let { text, head, insertAt } = value;
7394
7346
  for (const effect of tr.effects) {
7395
7347
  if (effect.is(suppressAppend)) {
7396
- text += effect.value.text;
7397
- if (text.length === effect.value.text.length) {
7348
+ if (text.length === head) {
7398
7349
  insertAt = effect.value.from;
7399
7350
  }
7351
+ text += effect.value.text;
7400
7352
  }
7401
7353
  if (effect.is(insertChunk)) {
7402
- text = text.slice(effect.value.text.length);
7354
+ head += effect.value.text.length;
7403
7355
  insertAt = effect.value.from + effect.value.text.length;
7356
+ if (head >= COMPACT_HEAD_THRESHOLD || head > 0 && head * 2 >= text.length) {
7357
+ text = text.slice(head);
7358
+ head = 0;
7359
+ }
7404
7360
  }
7405
7361
  }
7406
7362
  if (tr.docChanged) {
@@ -7415,6 +7371,7 @@ var wire = (options = {}) => {
7415
7371
  if (isReset) {
7416
7372
  return {
7417
7373
  text: "",
7374
+ head: 0,
7418
7375
  insertAt: 0
7419
7376
  };
7420
7377
  }
@@ -7424,6 +7381,7 @@ var wire = (options = {}) => {
7424
7381
  }
7425
7382
  return {
7426
7383
  text,
7384
+ head,
7427
7385
  insertAt
7428
7386
  };
7429
7387
  }
@@ -7432,7 +7390,7 @@ var wire = (options = {}) => {
7432
7390
  if (!tr.docChanged) {
7433
7391
  return tr;
7434
7392
  }
7435
- if (tr.annotation(wireBypass) || tr.effects.some((effect) => effect.is(insertChunk))) {
7393
+ if (tr.annotation(typewriterBypass) || tr.effects.some((effect) => effect.is(insertChunk))) {
7436
7394
  return tr;
7437
7395
  }
7438
7396
  let appendedText = "";
@@ -7461,40 +7419,82 @@ var wire = (options = {}) => {
7461
7419
  });
7462
7420
  const drainPlugin = ViewPlugin21.fromClass(class {
7463
7421
  view;
7464
- #timer;
7465
- #activeStreamTag = null;
7422
+ _raf;
7423
+ _activeStreamTag = null;
7466
7424
  constructor(view) {
7467
7425
  this.view = view;
7468
- this.#start();
7469
7426
  }
7470
7427
  update(update2) {
7471
- const buffer = update2.state.field(bufferField);
7472
- if (buffer.text.length === 0) {
7473
- this.#activeStreamTag = null;
7428
+ const { text, head } = update2.state.field(bufferField);
7429
+ const pending = text.length - head;
7430
+ if (pending === 0) {
7431
+ this._activeStreamTag = null;
7474
7432
  }
7475
- if (buffer.text.length > 0 && this.#timer === void 0) {
7476
- this.#start();
7433
+ if (pending > 0 && this._raf === void 0) {
7434
+ this._start();
7477
7435
  }
7478
7436
  }
7479
- #start() {
7480
- this.#timer = setInterval(() => {
7481
- const { text, insertAt } = this.view.state.field(bufferField);
7482
- if (text.length === 0) {
7483
- clearInterval(this.#timer);
7484
- this.#timer = void 0;
7485
- return;
7486
- }
7487
- const result = flushable(text, streamingTags, this.#activeStreamTag);
7437
+ _start() {
7438
+ queueMicrotask(() => {
7439
+ this.view.dispatch({
7440
+ effects: typewriterDrainingEffect.of(true),
7441
+ annotations: typewriterBypass.of(true)
7442
+ });
7443
+ });
7444
+ this._raf = requestAnimationFrame(this._tick);
7445
+ }
7446
+ _tick = () => {
7447
+ const { text, head, insertAt } = this.view.state.field(bufferField);
7448
+ const pending = text.length - head;
7449
+ if (pending === 0) {
7450
+ this.view.dispatch({
7451
+ effects: typewriterDrainingEffect.of(false),
7452
+ annotations: typewriterBypass.of(true)
7453
+ });
7454
+ this._raf = void 0;
7455
+ return;
7456
+ }
7457
+ if (pending > flushThreshold) {
7458
+ const chunk = text.slice(head);
7459
+ this._activeStreamTag = null;
7460
+ this.view.dispatch({
7461
+ changes: {
7462
+ from: insertAt,
7463
+ insert: chunk
7464
+ },
7465
+ effects: insertChunk.of({
7466
+ from: insertAt,
7467
+ text: chunk
7468
+ })
7469
+ });
7470
+ this._raf = requestAnimationFrame(this._tick);
7471
+ return;
7472
+ }
7473
+ const startTime = performance.now();
7474
+ let pos = head;
7475
+ let activeTag = this._activeStreamTag;
7476
+ let charsEmitted = 0;
7477
+ while (pos < text.length && performance.now() - startTime < frameBudgetMs) {
7478
+ const result = flushable(text, pos, streamingTags, activeTag);
7488
7479
  if (result.count === 0) {
7489
- return;
7480
+ break;
7481
+ }
7482
+ if (charsEmitted > 0 && charsEmitted + result.count > charsPerFrame) {
7483
+ break;
7490
7484
  }
7491
7485
  if (result.enterTag) {
7492
- this.#activeStreamTag = result.enterTag;
7486
+ activeTag = result.enterTag;
7493
7487
  }
7494
7488
  if (result.exitTag) {
7495
- this.#activeStreamTag = null;
7489
+ activeTag = null;
7496
7490
  }
7497
- const chunk = text.slice(0, result.count);
7491
+ pos += result.count;
7492
+ charsEmitted += result.count;
7493
+ }
7494
+ const totalCount = pos - head;
7495
+ if (totalCount > 0) {
7496
+ const chunk = text.slice(head, head + totalCount);
7497
+ this._activeStreamTag = activeTag;
7498
7498
  this.view.dispatch({
7499
7499
  changes: {
7500
7500
  from: insertAt,
@@ -7505,20 +7505,23 @@ var wire = (options = {}) => {
7505
7505
  text: chunk
7506
7506
  })
7507
7507
  });
7508
- }, interval);
7509
- }
7508
+ }
7509
+ this._raf = requestAnimationFrame(this._tick);
7510
+ };
7510
7511
  destroy() {
7511
- clearInterval(this.#timer);
7512
+ if (this._raf !== void 0) {
7513
+ cancelAnimationFrame(this._raf);
7514
+ }
7512
7515
  }
7513
7516
  });
7514
7517
  return [
7515
7518
  bufferField,
7516
7519
  filter,
7517
7520
  drainPlugin,
7518
- options.cursor && wireCursor(bufferField)
7521
+ options.cursor && typewriterCursor(bufferField)
7519
7522
  ].filter(Boolean);
7520
7523
  };
7521
- var wireCursor = (bufferField) => {
7524
+ var typewriterCursor = (bufferField) => {
7522
7525
  const hideCursor = StateEffect11.define();
7523
7526
  const visibilityField = StateField13.define({
7524
7527
  create: () => ({
@@ -7527,8 +7530,9 @@ var wireCursor = (bufferField) => {
7527
7530
  lastNonWsAt: 0
7528
7531
  }),
7529
7532
  update: (value, tr) => {
7530
- const { text, insertAt } = tr.state.field(bufferField);
7531
- if (text.length > 0) {
7533
+ const { text, head, insertAt } = tr.state.field(bufferField);
7534
+ const pending = text.length - head;
7535
+ if (pending > 0) {
7532
7536
  let lastNonWsAt = tr.changes.mapPos(Math.min(value.lastNonWsAt, tr.startState.doc.length));
7533
7537
  if (tr.docChanged) {
7534
7538
  tr.changes.iterChanges((_fromA, _toA, _fromB, _toB, inserted) => {
@@ -7562,8 +7566,8 @@ var wireCursor = (bufferField) => {
7562
7566
  if (!visible) {
7563
7567
  return Decoration15.none;
7564
7568
  }
7565
- const { text } = tr.state.field(bufferField);
7566
- const cursorAt = text.length > 0 ? insertAt : lastNonWsAt;
7569
+ const { text, head } = tr.state.field(bufferField);
7570
+ const cursorAt = text.length > head ? insertAt : lastNonWsAt;
7567
7571
  const pos = Math.min(cursorAt, tr.state.doc.length);
7568
7572
  return Decoration15.set([
7569
7573
  Decoration15.widget({
@@ -7576,27 +7580,28 @@ var wireCursor = (bufferField) => {
7576
7580
  });
7577
7581
  const timerPlugin = ViewPlugin21.fromClass(class {
7578
7582
  view;
7579
- #timer;
7583
+ _timer;
7580
7584
  constructor(view) {
7581
7585
  this.view = view;
7582
7586
  }
7583
7587
  update(update2) {
7584
- const { text } = update2.state.field(bufferField);
7588
+ const { text, head } = update2.state.field(bufferField);
7585
7589
  const { visible } = update2.state.field(visibilityField);
7586
- if (text.length > 0) {
7587
- clearTimeout(this.#timer);
7588
- this.#timer = void 0;
7589
- } else if (visible && this.#timer === void 0) {
7590
- this.#timer = setTimeout(() => {
7590
+ const pending = text.length - head;
7591
+ if (pending > 0) {
7592
+ clearTimeout(this._timer);
7593
+ this._timer = void 0;
7594
+ } else if (visible && this._timer === void 0) {
7595
+ this._timer = setTimeout(() => {
7591
7596
  this.view.dispatch({
7592
7597
  effects: hideCursor.of(null)
7593
7598
  });
7594
- this.#timer = void 0;
7599
+ this._timer = void 0;
7595
7600
  }, CURSOR_LINGER);
7596
7601
  }
7597
7602
  }
7598
7603
  destroy() {
7599
- clearTimeout(this.#timer);
7604
+ clearTimeout(this._timer);
7600
7605
  }
7601
7606
  });
7602
7607
  return [
@@ -7605,7 +7610,12 @@ var wireCursor = (bufferField) => {
7605
7610
  timerPlugin
7606
7611
  ];
7607
7612
  };
7608
- var CursorWidget = class extends WidgetType9 {
7613
+ var CursorWidget = class _CursorWidget extends WidgetType9 {
7614
+ // All instances are interchangeable — let CM reuse the existing DOM across drips so
7615
+ // the blink animation isn't restarted on every transaction.
7616
+ eq(other) {
7617
+ return other instanceof _CursorWidget;
7618
+ }
7609
7619
  toDOM() {
7610
7620
  const inner = Domino3.of("span").text("\u2217").style({
7611
7621
  animation: "blink 1s infinite",
@@ -7617,35 +7627,37 @@ var CursorWidget = class extends WidgetType9 {
7617
7627
  }
7618
7628
  };
7619
7629
  var OPENING_TAG_NAME = /^<([a-zA-Z][\w-]*)/;
7630
+ var TAG_NAME_PROBE = 64;
7620
7631
  var escapeRegExpSource2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7621
- var flushable = (buffer, streamingTags, activeStreamTag) => {
7622
- if (buffer.length === 0) {
7632
+ var flushable = (buffer, start, streamingTags, activeStreamTag) => {
7633
+ if (start >= buffer.length) {
7623
7634
  return {
7624
7635
  count: 0
7625
7636
  };
7626
7637
  }
7627
7638
  if (activeStreamTag) {
7628
7639
  const closeTag = `</${activeStreamTag}>`;
7629
- if (buffer.startsWith(closeTag)) {
7640
+ if (buffer.startsWith(closeTag, start)) {
7630
7641
  return {
7631
7642
  count: closeTag.length,
7632
7643
  exitTag: true
7633
7644
  };
7634
7645
  }
7635
- if (buffer[0] === "<") {
7646
+ if (buffer[start] === "<") {
7636
7647
  return {
7637
- count: xmlElementLength(buffer)
7648
+ count: xmlElementLength(buffer, start)
7638
7649
  };
7639
7650
  }
7640
7651
  return {
7641
7652
  count: 1
7642
7653
  };
7643
7654
  }
7644
- const ch = buffer[0];
7655
+ const ch = buffer[start];
7645
7656
  if (ch === "<") {
7646
- const nameMatch = buffer.match(OPENING_TAG_NAME);
7657
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7658
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7647
7659
  if (nameMatch && streamingTags.has(nameMatch[1])) {
7648
- const close = buffer.indexOf(">");
7660
+ const close = buffer.indexOf(">", start);
7649
7661
  if (close === -1) {
7650
7662
  return {
7651
7663
  count: 0
@@ -7653,62 +7665,64 @@ var flushable = (buffer, streamingTags, activeStreamTag) => {
7653
7665
  }
7654
7666
  if (buffer[close - 1] === "/") {
7655
7667
  return {
7656
- count: close + 1
7668
+ count: close + 1 - start
7657
7669
  };
7658
7670
  }
7659
7671
  return {
7660
- count: close + 1,
7672
+ count: close + 1 - start,
7661
7673
  enterTag: nameMatch[1]
7662
7674
  };
7663
7675
  }
7664
7676
  return {
7665
- count: xmlElementLength(buffer)
7677
+ count: xmlElementLength(buffer, start)
7666
7678
  };
7667
7679
  }
7668
- if (ch === "!" && buffer.length > 1 && buffer[1] === "[") {
7680
+ if (ch === "!" && buffer.length > start + 1 && buffer[start + 1] === "[") {
7669
7681
  return {
7670
- count: linkLength(buffer, 1)
7682
+ count: linkLength(buffer, start, start + 1)
7671
7683
  };
7672
7684
  }
7673
7685
  if (ch === "[") {
7674
7686
  return {
7675
- count: linkLength(buffer, 0)
7687
+ count: linkLength(buffer, start, start)
7676
7688
  };
7677
7689
  }
7678
7690
  return {
7679
7691
  count: 1
7680
7692
  };
7681
7693
  };
7682
- var xmlElementLength = (buffer) => {
7683
- const close = buffer.indexOf(">");
7694
+ var xmlElementLength = (buffer, start = 0) => {
7695
+ const close = buffer.indexOf(">", start);
7684
7696
  if (close === -1) {
7685
7697
  return 0;
7686
7698
  }
7687
7699
  if (buffer[close - 1] === "/") {
7688
- return close + 1;
7700
+ return close + 1 - start;
7689
7701
  }
7690
- if (buffer[1] === "/") {
7691
- return close + 1;
7702
+ if (buffer[start + 1] === "/") {
7703
+ return close + 1 - start;
7692
7704
  }
7693
- const nameMatch = buffer.match(OPENING_TAG_NAME);
7705
+ const probe = buffer.slice(start, start + TAG_NAME_PROBE);
7706
+ const nameMatch = probe.match(OPENING_TAG_NAME);
7694
7707
  if (!nameMatch) {
7695
7708
  return 1;
7696
7709
  }
7697
7710
  const tagName = nameMatch[1];
7698
7711
  let depth = 0;
7699
7712
  const tagPattern = new RegExp(`<(/?)${escapeRegExpSource2(tagName)}(\\s[^>]*)?>`, "g");
7713
+ tagPattern.lastIndex = start;
7700
7714
  let match;
7701
7715
  while ((match = tagPattern.exec(buffer)) !== null) {
7702
7716
  const isSelfClosing = match[0].endsWith("/>");
7703
7717
  const isClosing = match[1] === "/";
7704
7718
  if (isSelfClosing) {
7705
7719
  if (depth === 0) {
7706
- return match.index + match[0].length;
7720
+ return match.index + match[0].length - start;
7707
7721
  }
7708
7722
  } else if (isClosing) {
7709
7723
  depth--;
7710
7724
  if (depth === 0) {
7711
- return match.index + match[0].length;
7725
+ return match.index + match[0].length - start;
7712
7726
  }
7713
7727
  } else {
7714
7728
  depth++;
@@ -7716,8 +7730,8 @@ var xmlElementLength = (buffer) => {
7716
7730
  }
7717
7731
  return 0;
7718
7732
  };
7719
- var linkLength = (buffer, offset) => {
7720
- const bracketClose = buffer.indexOf("]", offset + 1);
7733
+ var linkLength = (buffer, start, bracketAt) => {
7734
+ const bracketClose = buffer.indexOf("]", bracketAt + 1);
7721
7735
  if (bracketClose === -1) {
7722
7736
  return 0;
7723
7737
  }
@@ -7731,13 +7745,179 @@ var linkLength = (buffer, offset) => {
7731
7745
  if (parenClose === -1) {
7732
7746
  return 0;
7733
7747
  }
7734
- return parenClose + 1;
7748
+ return parenClose + 1 - start;
7749
+ };
7750
+
7751
+ // src/extensions/tags/xml-block-decoration.ts
7752
+ import { xmlLanguage as xmlLanguage2 } from "@codemirror/lang-xml";
7753
+ import { Decoration as Decoration16, ViewPlugin as ViewPlugin22 } from "@codemirror/view";
7754
+ var xmlBlockDecoration = ({ tag, lineClass, contentClass, hideTags }) => {
7755
+ const lineDecoration = lineClass ? Decoration16.line({
7756
+ class: lineClass
7757
+ }) : void 0;
7758
+ const contentDecoration = contentClass ? Decoration16.mark({
7759
+ class: contentClass
7760
+ }) : void 0;
7761
+ const hideDecoration = hideTags ? Decoration16.replace({}) : void 0;
7762
+ const buildDecorations5 = (view) => {
7763
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7764
+ if (!text.includes(`<${tag}`)) {
7765
+ return Decoration16.none;
7766
+ }
7767
+ const tree = xmlLanguage2.parser.parse(text);
7768
+ const ranges = [];
7769
+ tree.iterate({
7770
+ enter: (node) => {
7771
+ if (node.type.name !== "Element") {
7772
+ return;
7773
+ }
7774
+ const openTag = node.node.getChild("OpenTag");
7775
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7776
+ const tagNameNode = openTag?.getChild("TagName");
7777
+ if (!openTag || !tagNameNode) {
7778
+ return;
7779
+ }
7780
+ if (text.slice(tagNameNode.from, tagNameNode.to) !== tag) {
7781
+ return;
7782
+ }
7783
+ const contentFrom = openTag.to;
7784
+ const contentTo = closeTag?.from ?? node.node.to;
7785
+ if (hideDecoration) {
7786
+ ranges.push(hideDecoration.range(openTag.from, openTag.to));
7787
+ if (closeTag) {
7788
+ ranges.push(hideDecoration.range(closeTag.from, closeTag.to));
7789
+ }
7790
+ }
7791
+ if (contentDecoration && contentFrom < contentTo) {
7792
+ ranges.push(contentDecoration.range(contentFrom, contentTo));
7793
+ }
7794
+ if (lineDecoration && contentFrom <= view.state.doc.length) {
7795
+ let pos = contentFrom;
7796
+ while (pos <= contentTo && pos <= view.state.doc.length) {
7797
+ const line = view.state.doc.lineAt(pos);
7798
+ ranges.push(lineDecoration.range(line.from));
7799
+ if (line.to >= contentTo) {
7800
+ break;
7801
+ }
7802
+ pos = line.to + 1;
7803
+ }
7804
+ }
7805
+ }
7806
+ });
7807
+ return Decoration16.set(ranges, true);
7808
+ };
7809
+ return ViewPlugin22.fromClass(class {
7810
+ decorations;
7811
+ constructor(view) {
7812
+ this.decorations = buildDecorations5(view);
7813
+ }
7814
+ update(update2) {
7815
+ if (update2.docChanged) {
7816
+ this.decorations = buildDecorations5(update2.view);
7817
+ }
7818
+ }
7819
+ }, {
7820
+ decorations: (instance) => instance.decorations
7821
+ });
7822
+ };
7823
+
7824
+ // src/extensions/tags/xml-formatting.ts
7825
+ import { xmlLanguage as xmlLanguage3 } from "@codemirror/lang-xml";
7826
+ import { Decoration as Decoration17, EditorView as EditorView31, ViewPlugin as ViewPlugin23 } from "@codemirror/view";
7827
+ var XML_TAG_NODES = /* @__PURE__ */ new Set([
7828
+ "OpenTag",
7829
+ "CloseTag",
7830
+ "SelfClosingTag",
7831
+ "MismatchedCloseTag"
7832
+ ]);
7833
+ var xmlElementMark = Decoration17.mark({
7834
+ class: "cm-xml-element"
7835
+ });
7836
+ var xmlTagMark = Decoration17.mark({
7837
+ class: "cm-xml-tag"
7838
+ });
7839
+ var xmlContentMark = Decoration17.mark({
7840
+ class: "cm-xml-content"
7841
+ });
7842
+ var xmlFormatting = ({ skip } = {}) => {
7843
+ const skipSet = skip && skip.length > 0 ? new Set(skip) : void 0;
7844
+ const buildDecorations5 = (view) => {
7845
+ const text = view.state.sliceDoc(0, view.state.doc.length);
7846
+ if (!text.includes("<")) {
7847
+ return Decoration17.none;
7848
+ }
7849
+ const tagNameAt = (node) => text.slice(node.from, node.to);
7850
+ const tree = xmlLanguage3.parser.parse(text);
7851
+ const ranges = [];
7852
+ tree.iterate({
7853
+ enter: (node) => {
7854
+ const name = node.type.name;
7855
+ if (name === "SelfClosingTag" && node.from < node.to) {
7856
+ if (skipSet) {
7857
+ const tagNameNode = node.node.getChild("TagName");
7858
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7859
+ return false;
7860
+ }
7861
+ }
7862
+ ranges.push(xmlElementMark.range(node.from, node.to));
7863
+ ranges.push(xmlTagMark.range(node.from, node.to));
7864
+ return;
7865
+ }
7866
+ if (XML_TAG_NODES.has(name) && node.from < node.to) {
7867
+ ranges.push(xmlTagMark.range(node.from, node.to));
7868
+ return;
7869
+ }
7870
+ if (name === "Element" && node.from < node.to) {
7871
+ const openTag = node.node.getChild("OpenTag");
7872
+ if (openTag && skipSet) {
7873
+ const tagNameNode = openTag.getChild("TagName");
7874
+ if (tagNameNode && skipSet.has(tagNameAt(tagNameNode))) {
7875
+ return false;
7876
+ }
7877
+ }
7878
+ const closeTag = node.node.getChild("CloseTag") ?? node.node.getChild("MismatchedCloseTag");
7879
+ ranges.push(xmlElementMark.range(node.from, node.to));
7880
+ if (openTag && closeTag && openTag.to < closeTag.from) {
7881
+ ranges.push(xmlContentMark.range(openTag.to, closeTag.from));
7882
+ }
7883
+ }
7884
+ }
7885
+ });
7886
+ return Decoration17.set(ranges, true);
7887
+ };
7888
+ return [
7889
+ ViewPlugin23.fromClass(class {
7890
+ decorations;
7891
+ constructor(view) {
7892
+ this.decorations = buildDecorations5(view);
7893
+ }
7894
+ update(update2) {
7895
+ if (update2.docChanged) {
7896
+ this.decorations = buildDecorations5(update2.view);
7897
+ }
7898
+ }
7899
+ }, {
7900
+ decorations: (instance) => instance.decorations
7901
+ }),
7902
+ EditorView31.baseTheme({
7903
+ ".cm-xml-element": {
7904
+ backgroundColor: "var(--color-current-surface)",
7905
+ borderRadius: "0.25rem",
7906
+ padding: "0.25rem"
7907
+ },
7908
+ ".cm-xml-tag": {
7909
+ color: "var(--color-blue-500)",
7910
+ fontFamily: "var(--font-mono)"
7911
+ },
7912
+ ".cm-xml-content": {}
7913
+ })
7914
+ ];
7735
7915
  };
7736
7916
 
7737
7917
  // src/extensions/tags/xml-tags.ts
7738
7918
  import { syntaxTree as syntaxTree11 } from "@codemirror/language";
7739
7919
  import { Prec as Prec7, RangeSetBuilder as RangeSetBuilder7, StateEffect as StateEffect12, StateField as StateField14 } from "@codemirror/state";
7740
- import { Decoration as Decoration16, EditorView as EditorView31, ViewPlugin as ViewPlugin22, WidgetType as WidgetType10, keymap as keymap13 } from "@codemirror/view";
7920
+ import { Decoration as Decoration18, EditorView as EditorView32, ViewPlugin as ViewPlugin24, WidgetType as WidgetType10, keymap as keymap14 } from "@codemirror/view";
7741
7921
  import { invariant as invariant7 } from "@dxos/invariant";
7742
7922
  import { log as log11 } from "@dxos/log";
7743
7923
  import { Domino as Domino4 } from "@dxos/ui";
@@ -7746,15 +7926,7 @@ import { Domino as Domino4 } from "@dxos/ui";
7746
7926
  import { invariant as invariant6 } from "@dxos/invariant";
7747
7927
  var __dxlog_file16 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-util.ts";
7748
7928
  var nodeToJson = (state, node) => {
7749
- invariant6(node.type.name === "Element", "Node is not an Element", {
7750
- F: __dxlog_file16,
7751
- L: 18,
7752
- S: void 0,
7753
- A: [
7754
- "node.type.name === 'Element'",
7755
- "'Node is not an Element'"
7756
- ]
7757
- });
7929
+ 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'"] });
7758
7930
  const openTag = node.node.getChild("OpenTag") || node.node.getChild("SelfClosingTag");
7759
7931
  if (openTag) {
7760
7932
  const tagName = openTag.getChild("TagName");
@@ -7786,13 +7958,23 @@ var nodeToJson = (state, node) => {
7786
7958
  if (node.type.name === "Element" && openTag.type.name !== "SelfClosingTag") {
7787
7959
  const children = [];
7788
7960
  let child = node.node.firstChild;
7961
+ const appendText = (raw) => {
7962
+ if (raw.length === 0) {
7963
+ return;
7964
+ }
7965
+ const last = children[children.length - 1];
7966
+ if (typeof last === "string") {
7967
+ children[children.length - 1] = last + raw;
7968
+ } else {
7969
+ children.push(raw);
7970
+ }
7971
+ };
7789
7972
  while (child) {
7790
7973
  if (child.type.name !== "OpenTag" && child.type.name !== "CloseTag") {
7791
7974
  if (child.type.name === "Text") {
7792
- const text = state.doc.sliceString(child.from, child.to).trim();
7793
- if (text) {
7794
- children.push(text);
7795
- }
7975
+ appendText(state.doc.sliceString(child.from, child.to));
7976
+ } else if (child.type.name === "EntityReference" || child.type.name === "CharacterReference") {
7977
+ appendText(decodeXmlEntity(state.doc.sliceString(child.from, child.to)));
7796
7978
  } else if (child.type.name === "Element") {
7797
7979
  const data = nodeToJson(state, child);
7798
7980
  if (data) {
@@ -7802,13 +7984,48 @@ var nodeToJson = (state, node) => {
7802
7984
  }
7803
7985
  child = child.nextSibling;
7804
7986
  }
7987
+ if (children.length > 0 && typeof children[0] === "string") {
7988
+ children[0] = children[0].trimStart();
7989
+ }
7805
7990
  if (children.length > 0) {
7806
- tag.children = children;
7991
+ const lastIndex = children.length - 1;
7992
+ const last = children[lastIndex];
7993
+ if (typeof last === "string") {
7994
+ children[lastIndex] = last.trimEnd();
7995
+ }
7996
+ }
7997
+ const trimmed = children.filter((value) => typeof value !== "string" || value.length > 0);
7998
+ if (trimmed.length > 0) {
7999
+ tag.children = trimmed;
7807
8000
  }
7808
8001
  }
7809
8002
  return tag;
7810
8003
  }
7811
8004
  };
8005
+ var XML_NAMED_ENTITIES = {
8006
+ "&lt;": "<",
8007
+ "&gt;": ">",
8008
+ "&amp;": "&",
8009
+ "&quot;": '"',
8010
+ "&apos;": "'"
8011
+ };
8012
+ var decodeXmlEntity = (raw) => {
8013
+ const named = XML_NAMED_ENTITIES[raw];
8014
+ if (named !== void 0) {
8015
+ return named;
8016
+ }
8017
+ const numeric = /^&#(x?)([0-9a-fA-F]+);$/.exec(raw);
8018
+ if (numeric) {
8019
+ const code = parseInt(numeric[2], numeric[1] ? 16 : 10);
8020
+ if (Number.isFinite(code)) {
8021
+ try {
8022
+ return String.fromCodePoint(code);
8023
+ } catch {
8024
+ }
8025
+ }
8026
+ }
8027
+ return raw;
8028
+ };
7812
8029
 
7813
8030
  // src/extensions/tags/xml-tags.ts
7814
8031
  var __dxlog_file17 = "/__w/dxos/dxos/packages/ui/ui-editor/src/extensions/tags/xml-tags.ts";
@@ -7846,12 +8063,7 @@ var widgetStateMapStateField = StateField14.define({
7846
8063
  log11("widget updated", {
7847
8064
  id,
7848
8065
  value
7849
- }, {
7850
- F: __dxlog_file17,
7851
- L: 184,
7852
- S: void 0,
7853
- C: (f, a) => f(...a)
7854
- });
8066
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 59, S: void 0 });
7855
8067
  const state = typeof value === "function" ? value(map[id]) : value;
7856
8068
  return {
7857
8069
  ...map,
@@ -7862,25 +8074,16 @@ var widgetStateMapStateField = StateField14.define({
7862
8074
  return map;
7863
8075
  }
7864
8076
  });
7865
- var XML_WIDGET_DATA_ATTR = "data-xml-widget";
7866
- var xmlTags = ({ registry, setWidgets, bookmarks: bookmarks2, paragraphToWidgetGapRem } = {}) => {
8077
+ var xmlTags = ({ registry, setWidgets, bookmarks: bookmarks2 } = {}) => {
7867
8078
  const notifier = createWidgetMap(setWidgets);
7868
8079
  const widgetDecorationsField = createWidgetDecorationsField(registry, notifier);
7869
- const paragraphGapTheme = paragraphToWidgetGapRem != null && paragraphToWidgetGapRem > 0 ? EditorView31.baseTheme({
7870
- [`& .cm-content > .cm-line:not(:has([${XML_WIDGET_DATA_ATTR}])) + .cm-line:has([${XML_WIDGET_DATA_ATTR}])`]: {
7871
- marginTop: `${paragraphToWidgetGapRem}rem`
7872
- }
7873
- }) : null;
7874
8080
  return [
7875
8081
  widgetContextStateField,
7876
8082
  widgetStateMapStateField,
7877
8083
  widgetDecorationsField,
7878
8084
  createWidgetUpdatePlugin(widgetDecorationsField, notifier),
7879
8085
  createNavigationEffectPlugin(widgetDecorationsField, bookmarks2),
7880
- bookmarks2?.length ? Prec7.highest(keyHandlers) : [],
7881
- ...paragraphGapTheme ? [
7882
- paragraphGapTheme
7883
- ] : []
8086
+ bookmarks2?.length ? Prec7.highest(keyHandlers) : []
7884
8087
  ];
7885
8088
  };
7886
8089
  var createWidgetMap = (setWidgets) => {
@@ -7890,12 +8093,7 @@ var createWidgetMap = (setWidgets) => {
7890
8093
  log11("widget mounted", {
7891
8094
  id: state.id,
7892
8095
  tag: state.props._tag
7893
- }, {
7894
- F: __dxlog_file17,
7895
- L: 261,
7896
- S: void 0,
7897
- C: (f, a) => f(...a)
7898
- });
8096
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 101, S: void 0 });
7899
8097
  widgets.set(state.id, state);
7900
8098
  setWidgets?.([
7901
8099
  ...widgets.values()
@@ -7906,12 +8104,7 @@ var createWidgetMap = (setWidgets) => {
7906
8104
  log11("widget unmounted", {
7907
8105
  id,
7908
8106
  tag: state?.props._tag
7909
- }, {
7910
- F: __dxlog_file17,
7911
- L: 267,
7912
- S: void 0,
7913
- C: (f, a) => f(...a)
7914
- });
8107
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 112, S: void 0 });
7915
8108
  widgets.delete(id);
7916
8109
  setWidgets?.([
7917
8110
  ...widgets.values()
@@ -7920,7 +8113,7 @@ var createWidgetMap = (setWidgets) => {
7920
8113
  };
7921
8114
  return notifier;
7922
8115
  };
7923
- var keyHandlers = keymap13.of([
8116
+ var keyHandlers = keymap14.of([
7924
8117
  {
7925
8118
  key: "Mod-ArrowUp",
7926
8119
  run: (view) => {
@@ -7941,7 +8134,7 @@ var keyHandlers = keymap13.of([
7941
8134
  }
7942
8135
  ]);
7943
8136
  var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7944
- return EditorView31.updateListener.of((update2) => {
8137
+ return EditorView32.updateListener.of((update2) => {
7945
8138
  update2.transactions.forEach((transaction) => {
7946
8139
  for (const effect of transaction.effects) {
7947
8140
  if (effect.is(navigatePreviousEffect)) {
@@ -7969,7 +8162,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
7969
8162
  anchor: line.from,
7970
8163
  head: line.from
7971
8164
  },
7972
- effects: scrollerLineEffect.of({
8165
+ effects: crawlerLineEffect.of({
7973
8166
  line: line.number - 1,
7974
8167
  offset: -16
7975
8168
  })
@@ -8002,7 +8195,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
8002
8195
  anchor: line.to,
8003
8196
  head: line.to
8004
8197
  },
8005
- effects: scrollerLineEffect.of({
8198
+ effects: crawlerLineEffect.of({
8006
8199
  line: line.number - 1,
8007
8200
  offset: -16
8008
8201
  })
@@ -8014,7 +8207,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
8014
8207
  anchor: line.to,
8015
8208
  head: line.to
8016
8209
  },
8017
- effects: scrollerLineEffect.of({
8210
+ effects: crawlerLineEffect.of({
8018
8211
  line: line.number - 1,
8019
8212
  position: "end"
8020
8213
  })
@@ -8026,7 +8219,7 @@ var createNavigationEffectPlugin = (widgetDecorationsField, bookmarks2) => {
8026
8219
  });
8027
8220
  });
8028
8221
  };
8029
- var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin22.fromClass(class {
8222
+ var createWidgetUpdatePlugin = (widgetDecorationsField, notifier) => ViewPlugin24.fromClass(class {
8030
8223
  update(update2) {
8031
8224
  const widgetStateMap = update2.state.field(widgetStateMapStateField);
8032
8225
  const { decorations: decorations2 } = update2.state.field(widgetDecorationsField);
@@ -8071,7 +8264,7 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.def
8071
8264
  }
8072
8265
  return {
8073
8266
  from: 0,
8074
- decorations: Decoration16.none
8267
+ decorations: Decoration18.none
8075
8268
  };
8076
8269
  }
8077
8270
  }
@@ -8082,12 +8275,7 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.def
8082
8275
  log11("document reset", {
8083
8276
  from,
8084
8277
  to: state.doc.length
8085
- }, {
8086
- F: __dxlog_file17,
8087
- L: 429,
8088
- S: void 0,
8089
- C: (f, a) => f(...a)
8090
- });
8278
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 298, S: void 0 });
8091
8279
  return buildDecorations4(state, {
8092
8280
  from: 0,
8093
8281
  to: state.doc.length
@@ -8116,8 +8304,8 @@ var createWidgetDecorationsField = (registry = {}, notifier) => StateField14.def
8116
8304
  };
8117
8305
  },
8118
8306
  provide: (field) => [
8119
- EditorView31.decorations.from(field, (v) => v.decorations),
8120
- EditorView31.atomicRanges.of((view) => view.state.field(field).decorations || Decoration16.none)
8307
+ EditorView32.decorations.from(field, (v) => v.decorations),
8308
+ EditorView32.atomicRanges.of((view) => view.state.field(field).decorations || Decoration18.none)
8121
8309
  ]
8122
8310
  });
8123
8311
  var buildDecorations4 = (state, range, registry, notifier) => {
@@ -8128,7 +8316,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8128
8316
  if (!tree || tree.type.name === "Program" && tree.length === 0) {
8129
8317
  return {
8130
8318
  from: range.from,
8131
- decorations: Decoration16.none
8319
+ decorations: Decoration18.none
8132
8320
  };
8133
8321
  }
8134
8322
  let last = range.from;
@@ -8164,7 +8352,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8164
8352
  };
8165
8353
  const widget = factory ? factory(props) ?? void 0 : Component ? new PlaceholderWidget2(widgetId, Component, props, notifier) : void 0;
8166
8354
  if (widget) {
8167
- builder.add(nodeRange.from, nodeRange.to, Decoration16.replace({
8355
+ builder.add(nodeRange.from, nodeRange.to, Decoration18.replace({
8168
8356
  widget,
8169
8357
  block,
8170
8358
  atomic: true,
@@ -8176,12 +8364,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8176
8364
  }
8177
8365
  }
8178
8366
  } catch (err) {
8179
- log11.catch(err, void 0, {
8180
- F: __dxlog_file17,
8181
- L: 538,
8182
- S: void 0,
8183
- C: (f, a) => f(...a)
8184
- });
8367
+ log11.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 401, S: void 0 });
8185
8368
  }
8186
8369
  return false;
8187
8370
  }
@@ -8228,7 +8411,7 @@ var buildDecorations4 = (state, range, registry, notifier) => {
8228
8411
  };
8229
8412
  const widget = def.factory ? def.factory(mergedProps) ?? void 0 : def.Component ? new PlaceholderWidget2(widgetId, def.Component, mergedProps, notifier, true) : void 0;
8230
8413
  if (widget) {
8231
- builder.add(absoluteFrom, range.to, Decoration16.replace({
8414
+ builder.add(absoluteFrom, range.to, Decoration18.replace({
8232
8415
  widget,
8233
8416
  block: def.block,
8234
8417
  atomic: true,
@@ -8260,15 +8443,7 @@ var PlaceholderWidget2 = class extends WidgetType10 {
8260
8443
  #view;
8261
8444
  constructor(id, Component, props, notifier, streaming) {
8262
8445
  super(), this.id = id, this.Component = Component, this.props = props, this.notifier = notifier, this.streaming = streaming;
8263
- invariant7(id, void 0, {
8264
- F: __dxlog_file17,
8265
- L: 641,
8266
- S: this,
8267
- A: [
8268
- "id",
8269
- ""
8270
- ]
8271
- });
8446
+ invariant7(id, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file17, L: 488, S: this, A: ["id", ""] });
8272
8447
  }
8273
8448
  get root() {
8274
8449
  return this.#root;
@@ -8284,9 +8459,7 @@ var PlaceholderWidget2 = class extends WidgetType10 {
8284
8459
  }
8285
8460
  toDOM(view) {
8286
8461
  this.#view = view;
8287
- this.#root = Domino4.of("div").classNames("min-h-[24px]").attributes({
8288
- [XML_WIDGET_DATA_ATTR]: ""
8289
- }).root;
8462
+ this.#root = Domino4.of("div").classNames("min-h-[24px]").root;
8290
8463
  const props = Object.assign({}, this.props, {
8291
8464
  view
8292
8465
  });
@@ -8317,71 +8490,10 @@ var PlaceholderWidget2 = class extends WidgetType10 {
8317
8490
  this.#view = void 0;
8318
8491
  }
8319
8492
  };
8320
-
8321
- // src/extensions/typewriter.ts
8322
- import { keymap as keymap14 } from "@codemirror/view";
8323
- var defaultItems = [
8324
- "hello world!",
8325
- "this is a test.",
8326
- "this is [DXOS](https://dxos.org)"
8327
- ];
8328
- var typewriter = ({ delay = 75, items = defaultItems } = {}) => {
8329
- let t;
8330
- let idx = 0;
8331
- return [
8332
- keymap14.of([
8333
- {
8334
- // Reset.
8335
- key: "alt-meta-'",
8336
- run: () => {
8337
- clearTimeout(t);
8338
- idx = 0;
8339
- return true;
8340
- }
8341
- },
8342
- {
8343
- // Next prompt.
8344
- // TODO(burdon): Press 1-9 to select prompt?
8345
- key: "Shift-Meta-'",
8346
- run: (view) => {
8347
- clearTimeout(t);
8348
- const text = items[idx++];
8349
- if (idx === items?.length) {
8350
- idx = 0;
8351
- }
8352
- let i = 0;
8353
- const insert = (d = 0) => {
8354
- t = setTimeout(() => {
8355
- const pos = view.state.selection.main.head;
8356
- view.dispatch({
8357
- changes: {
8358
- from: pos,
8359
- insert: text[i++]
8360
- },
8361
- selection: {
8362
- anchor: pos + 1
8363
- }
8364
- });
8365
- if (i < text.length) {
8366
- insert(Math.random() * delay * (text[i] === " " ? 2 : 1));
8367
- }
8368
- }, d);
8369
- };
8370
- insert();
8371
- return true;
8372
- }
8373
- }
8374
- ])
8375
- ];
8376
- };
8377
8493
  export {
8378
8494
  Cursor,
8379
- EditorInputMode,
8380
- EditorInputModes,
8381
8495
  EditorState4 as EditorState,
8382
- EditorView32 as EditorView,
8383
- EditorViewMode,
8384
- EditorViewModes,
8496
+ EditorView33 as EditorView,
8385
8497
  Inline,
8386
8498
  InputModeExtensions,
8387
8499
  List,
@@ -8390,7 +8502,6 @@ export {
8390
8502
  SpaceAwarenessProvider,
8391
8503
  TextKind,
8392
8504
  Tree,
8393
- XML_WIDGET_DATA_ATTR,
8394
8505
  addBlockquote,
8395
8506
  addBookmark,
8396
8507
  addCodeblock,
@@ -8415,6 +8526,9 @@ export {
8415
8526
  commentsState,
8416
8527
  compactSlots,
8417
8528
  convertTreeToJson,
8529
+ crawler,
8530
+ crawlerActiveEffect,
8531
+ crawlerLineEffect,
8418
8532
  createBasicExtensions,
8419
8533
  createComment,
8420
8534
  createCrawler,
@@ -8478,6 +8592,7 @@ export {
8478
8592
  markdownTagsExtensions,
8479
8593
  matchCompletion,
8480
8594
  mention,
8595
+ mobileSlots,
8481
8596
  modalStateEffect,
8482
8597
  modalStateField,
8483
8598
  moveItemDown,
@@ -8501,8 +8616,6 @@ export {
8501
8616
  scrollThreadIntoView,
8502
8617
  scrollToLine,
8503
8618
  scroller,
8504
- scrollerCrawlEffect,
8505
- scrollerLineEffect,
8506
8619
  selectionState,
8507
8620
  setBlockquote,
8508
8621
  setComments,
@@ -8510,6 +8623,7 @@ export {
8510
8623
  setSelection,
8511
8624
  setStyle,
8512
8625
  singleValueFacet,
8626
+ snippets2 as snippets,
8513
8627
  staticCompletion,
8514
8628
  submit,
8515
8629
  tabbable,
@@ -8529,10 +8643,12 @@ export {
8529
8643
  treeFacet,
8530
8644
  typeahead,
8531
8645
  typewriter,
8532
- wire,
8533
- wireBypass,
8646
+ typewriterBypass,
8647
+ typewriterDrainingEffect,
8534
8648
  wrapWithCatch,
8649
+ xmlBlockDecoration,
8535
8650
  xmlElementLength,
8651
+ xmlFormatting,
8536
8652
  xmlTagContextEffect,
8537
8653
  xmlTagResetEffect,
8538
8654
  xmlTagUpdateEffect,