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