@dxos/react-ui-editor 0.8.3 → 0.8.4-main.1f223c7

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 (269) hide show
  1. package/dist/lib/browser/chunk-22UMM3QJ.mjs +22 -0
  2. package/dist/lib/browser/chunk-22UMM3QJ.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +2498 -1380
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +72 -2
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/browser/types/index.mjs +13 -0
  9. package/dist/lib/browser/types/index.mjs.map +7 -0
  10. package/dist/lib/node-esm/chunk-YXYQPV6R.mjs +24 -0
  11. package/dist/lib/node-esm/chunk-YXYQPV6R.mjs.map +7 -0
  12. package/dist/lib/node-esm/index.mjs +2500 -1383
  13. package/dist/lib/node-esm/index.mjs.map +4 -4
  14. package/dist/lib/node-esm/meta.json +1 -1
  15. package/dist/lib/node-esm/testing/index.mjs +72 -2
  16. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/types/index.mjs +14 -0
  18. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  19. package/dist/types/src/components/{Popover → CommandMenu}/CommandMenu.d.ts +10 -6
  20. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts.map +1 -0
  21. package/dist/types/src/components/CommandMenu/index.d.ts +2 -0
  22. package/dist/types/src/components/CommandMenu/index.d.ts.map +1 -0
  23. package/dist/types/src/components/Editor/Editor.d.ts +19 -0
  24. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -0
  25. package/dist/types/src/components/Editor/index.d.ts +2 -0
  26. package/dist/types/src/components/Editor/index.d.ts.map +1 -0
  27. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  28. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  29. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  30. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  31. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -1
  32. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -1
  33. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -1
  34. package/dist/types/src/components/EditorToolbar/util.d.ts +6 -5
  35. package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
  36. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +1 -1
  37. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  38. package/dist/types/src/components/index.d.ts +2 -1
  39. package/dist/types/src/components/index.d.ts.map +1 -1
  40. package/dist/types/src/defaults.d.ts.map +1 -1
  41. package/dist/types/src/extensions/autocomplete.d.ts +20 -7
  42. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  43. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  44. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +36 -45
  45. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  46. package/dist/types/src/extensions/automerge/defs.d.ts +1 -1
  47. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  48. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  49. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  50. package/dist/types/src/extensions/autoscroll.d.ts +10 -0
  51. package/dist/types/src/extensions/autoscroll.d.ts.map +1 -0
  52. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  53. package/dist/types/src/extensions/command/action.d.ts +1 -1
  54. package/dist/types/src/extensions/command/action.d.ts.map +1 -1
  55. package/dist/types/src/extensions/command/command-menu.d.ts +1 -1
  56. package/dist/types/src/extensions/command/command-menu.d.ts.map +1 -1
  57. package/dist/types/src/extensions/command/command.d.ts.map +1 -1
  58. package/dist/types/src/extensions/command/floating-menu.d.ts.map +1 -1
  59. package/dist/types/src/extensions/command/hint.d.ts +2 -7
  60. package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
  61. package/dist/types/src/extensions/command/index.d.ts +1 -1
  62. package/dist/types/src/extensions/command/index.d.ts.map +1 -1
  63. package/dist/types/src/extensions/command/state.d.ts +1 -1
  64. package/dist/types/src/extensions/command/state.d.ts.map +1 -1
  65. package/dist/types/src/extensions/command/typeahead.d.ts +7 -2
  66. package/dist/types/src/extensions/command/typeahead.d.ts.map +1 -1
  67. package/dist/types/src/extensions/command/useCommandMenu.d.ts +3 -4
  68. package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +1 -1
  69. package/dist/types/src/extensions/comments.d.ts +1 -1
  70. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  71. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  72. package/dist/types/src/extensions/factories.d.ts +15 -1
  73. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  74. package/dist/types/src/extensions/index.d.ts +2 -0
  75. package/dist/types/src/extensions/index.d.ts.map +1 -1
  76. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -1
  77. package/dist/types/src/extensions/markdown/bundle.d.ts +8 -2
  78. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  79. package/dist/types/src/extensions/markdown/changes.d.ts +1 -1
  80. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  81. package/dist/types/src/extensions/markdown/decorate.d.ts +9 -1
  82. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  83. package/dist/types/src/extensions/markdown/formatting.d.ts +1 -1
  84. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  85. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -1
  86. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  87. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  88. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  89. package/dist/types/src/extensions/modes.d.ts +0 -7
  90. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  91. package/dist/types/src/extensions/outliner/outliner.d.ts +1 -1
  92. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -1
  93. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -1
  94. package/dist/types/src/extensions/outliner/tree.d.ts +2 -2
  95. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
  96. package/dist/types/src/extensions/preview/preview.d.ts +3 -6
  97. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  98. package/dist/types/src/extensions/tags/extended-markdown.d.ts +10 -0
  99. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -0
  100. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts +2 -0
  101. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts.map +1 -0
  102. package/dist/types/src/extensions/tags/index.d.ts +4 -0
  103. package/dist/types/src/extensions/tags/index.d.ts.map +1 -0
  104. package/dist/types/src/extensions/tags/streamer.d.ts +12 -0
  105. package/dist/types/src/extensions/tags/streamer.d.ts.map +1 -0
  106. package/dist/types/src/extensions/tags/xml-tags.d.ts +71 -0
  107. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -0
  108. package/dist/types/src/extensions/tags/xml-util.d.ts +10 -0
  109. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -0
  110. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  111. package/dist/types/src/index.d.ts +1 -1
  112. package/dist/types/src/index.d.ts.map +1 -1
  113. package/dist/types/src/stories/Command.stories.d.ts +12 -4
  114. package/dist/types/src/stories/Command.stories.d.ts.map +1 -1
  115. package/dist/types/src/stories/CommandMenu.stories.d.ts +11 -4
  116. package/dist/types/src/stories/CommandMenu.stories.d.ts.map +1 -1
  117. package/dist/types/src/stories/Comments.stories.d.ts +21 -9
  118. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  119. package/dist/types/src/stories/EditorToolbar.stories.d.ts +40 -3
  120. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  121. package/dist/types/src/stories/Experimental.stories.d.ts +22 -12
  122. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  123. package/dist/types/src/stories/Markdown.stories.d.ts +32 -42
  124. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
  125. package/dist/types/src/stories/Outliner.stories.d.ts +15 -20
  126. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  127. package/dist/types/src/stories/Preview.stories.d.ts +21 -6
  128. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  129. package/dist/types/src/stories/Tags.stories.d.ts +17 -0
  130. package/dist/types/src/stories/Tags.stories.d.ts.map +1 -0
  131. package/dist/types/src/stories/TextEditor.stories.d.ts +38 -51
  132. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  133. package/dist/types/src/stories/components/EditorStory.d.ts +3 -6
  134. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  135. package/dist/types/src/styles/theme.d.ts.map +1 -1
  136. package/dist/types/src/testing/PreviewPopover.d.ts +20 -0
  137. package/dist/types/src/testing/PreviewPopover.d.ts.map +1 -0
  138. package/dist/types/src/testing/index.d.ts +1 -0
  139. package/dist/types/src/testing/index.d.ts.map +1 -1
  140. package/dist/types/src/testing/util.d.ts +1 -0
  141. package/dist/types/src/testing/util.d.ts.map +1 -1
  142. package/dist/types/src/translations.d.ts +28 -29
  143. package/dist/types/src/translations.d.ts.map +1 -1
  144. package/dist/types/src/types/index.d.ts +2 -0
  145. package/dist/types/src/types/index.d.ts.map +1 -0
  146. package/dist/types/src/types/types.d.ts +21 -0
  147. package/dist/types/src/types/types.d.ts.map +1 -0
  148. package/dist/types/src/util/cursor.d.ts.map +1 -1
  149. package/dist/types/src/util/debug.d.ts +1 -1
  150. package/dist/types/src/util/debug.d.ts.map +1 -1
  151. package/dist/types/src/util/decorations.d.ts +4 -0
  152. package/dist/types/src/util/decorations.d.ts.map +1 -0
  153. package/dist/types/src/util/dom.d.ts +2 -12
  154. package/dist/types/src/util/dom.d.ts.map +1 -1
  155. package/dist/types/src/util/domino.d.ts +18 -0
  156. package/dist/types/src/util/domino.d.ts.map +1 -0
  157. package/dist/types/src/util/index.d.ts +2 -0
  158. package/dist/types/src/util/index.d.ts.map +1 -1
  159. package/dist/types/src/util/react.d.ts +1 -1
  160. package/dist/types/src/util/react.d.ts.map +1 -1
  161. package/dist/types/tsconfig.tsbuildinfo +1 -1
  162. package/package.json +64 -55
  163. package/src/components/{Popover → CommandMenu}/CommandMenu.tsx +93 -26
  164. package/src/components/{Popover → CommandMenu}/index.ts +0 -2
  165. package/src/components/Editor/Editor.tsx +39 -0
  166. package/src/components/Editor/index.ts +5 -0
  167. package/src/components/EditorToolbar/EditorToolbar.tsx +40 -30
  168. package/src/components/EditorToolbar/blocks.ts +22 -25
  169. package/src/components/EditorToolbar/formatting.ts +22 -25
  170. package/src/components/EditorToolbar/headings.ts +10 -5
  171. package/src/components/EditorToolbar/image.ts +8 -4
  172. package/src/components/EditorToolbar/lists.ts +16 -19
  173. package/src/components/EditorToolbar/search.ts +8 -4
  174. package/src/components/EditorToolbar/util.ts +21 -9
  175. package/src/components/EditorToolbar/view-mode.ts +12 -7
  176. package/src/components/index.ts +2 -1
  177. package/src/defaults.ts +5 -2
  178. package/src/extensions/autocomplete.ts +204 -54
  179. package/src/extensions/automerge/automerge.stories.tsx +26 -17
  180. package/src/extensions/automerge/automerge.ts +4 -3
  181. package/src/extensions/automerge/defs.ts +1 -1
  182. package/src/extensions/automerge/sync.ts +1 -1
  183. package/src/extensions/automerge/update-automerge.ts +1 -1
  184. package/src/extensions/autoscroll.ts +157 -0
  185. package/src/extensions/awareness/awareness.ts +2 -2
  186. package/src/extensions/blast.ts +3 -16
  187. package/src/extensions/command/action.ts +1 -2
  188. package/src/extensions/command/command-menu.ts +7 -6
  189. package/src/extensions/command/command.ts +3 -3
  190. package/src/extensions/command/floating-menu.ts +10 -15
  191. package/src/extensions/command/hint.ts +2 -1
  192. package/src/extensions/command/index.ts +1 -1
  193. package/src/extensions/command/placeholder.ts +1 -1
  194. package/src/extensions/command/state.ts +4 -3
  195. package/src/extensions/command/typeahead.ts +28 -15
  196. package/src/extensions/command/useCommandMenu.ts +6 -9
  197. package/src/extensions/comments.ts +18 -13
  198. package/src/extensions/dnd.ts +1 -1
  199. package/src/extensions/factories.ts +22 -15
  200. package/src/extensions/folding.tsx +2 -2
  201. package/src/extensions/index.ts +2 -0
  202. package/src/extensions/markdown/action.ts +2 -1
  203. package/src/extensions/markdown/bundle.ts +25 -3
  204. package/src/extensions/markdown/changes.ts +1 -1
  205. package/src/extensions/markdown/decorate.ts +23 -14
  206. package/src/extensions/markdown/formatting.test.ts +7 -7
  207. package/src/extensions/markdown/formatting.ts +16 -14
  208. package/src/extensions/markdown/highlight.ts +1 -1
  209. package/src/extensions/markdown/image.ts +3 -4
  210. package/src/extensions/markdown/link.ts +3 -0
  211. package/src/extensions/markdown/table.ts +7 -1
  212. package/src/extensions/mention.ts +1 -1
  213. package/src/extensions/modes.ts +0 -9
  214. package/src/extensions/outliner/outliner.test.ts +3 -2
  215. package/src/extensions/outliner/outliner.ts +6 -5
  216. package/src/extensions/outliner/selection.ts +1 -1
  217. package/src/extensions/outliner/tree.test.ts +2 -1
  218. package/src/extensions/outliner/tree.ts +2 -2
  219. package/src/extensions/preview/preview.ts +59 -62
  220. package/src/extensions/tags/extended-markdown.test.ts +261 -0
  221. package/src/extensions/tags/extended-markdown.ts +78 -0
  222. package/src/extensions/tags/index.ts +7 -0
  223. package/src/extensions/tags/streamer.ts +244 -0
  224. package/src/extensions/tags/xml-tags.ts +335 -0
  225. package/src/extensions/tags/xml-util.ts +94 -0
  226. package/src/hooks/useTextEditor.ts +3 -15
  227. package/src/index.ts +1 -1
  228. package/src/stories/Command.stories.tsx +24 -31
  229. package/src/stories/CommandMenu.stories.tsx +29 -30
  230. package/src/stories/Comments.stories.tsx +10 -6
  231. package/src/stories/EditorToolbar.stories.tsx +10 -11
  232. package/src/stories/Experimental.stories.tsx +12 -8
  233. package/src/stories/Markdown.stories.tsx +21 -17
  234. package/src/stories/Outliner.stories.tsx +42 -30
  235. package/src/stories/Preview.stories.tsx +34 -33
  236. package/src/stories/Tags.stories.tsx +81 -0
  237. package/src/stories/TextEditor.stories.tsx +41 -35
  238. package/src/stories/components/EditorStory.tsx +9 -10
  239. package/src/styles/theme.ts +11 -10
  240. package/src/testing/PreviewPopover.tsx +78 -0
  241. package/src/testing/index.ts +1 -0
  242. package/src/testing/util.ts +2 -0
  243. package/src/translations.ts +5 -3
  244. package/src/types/index.ts +5 -0
  245. package/src/types/types.ts +32 -0
  246. package/src/util/cursor.ts +2 -1
  247. package/src/util/debug.ts +2 -2
  248. package/src/util/decorations.ts +21 -0
  249. package/src/util/dom.ts +5 -27
  250. package/src/util/domino.ts +51 -0
  251. package/src/util/index.ts +2 -0
  252. package/src/util/react.tsx +1 -1
  253. package/dist/lib/node/index.cjs +0 -7754
  254. package/dist/lib/node/index.cjs.map +0 -7
  255. package/dist/lib/node/meta.json +0 -1
  256. package/dist/lib/node/testing/index.cjs +0 -29
  257. package/dist/lib/node/testing/index.cjs.map +0 -7
  258. package/dist/types/src/components/Popover/CommandMenu.d.ts.map +0 -1
  259. package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +0 -21
  260. package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +0 -1
  261. package/dist/types/src/components/Popover/RefPopover.d.ts +0 -34
  262. package/dist/types/src/components/Popover/RefPopover.d.ts.map +0 -1
  263. package/dist/types/src/components/Popover/index.d.ts +0 -4
  264. package/dist/types/src/components/Popover/index.d.ts.map +0 -1
  265. package/dist/types/src/types.d.ts +0 -14
  266. package/dist/types/src/types.d.ts.map +0 -1
  267. package/src/components/Popover/RefDropdownMenu.tsx +0 -79
  268. package/src/components/Popover/RefPopover.tsx +0 -99
  269. package/src/types.ts +0 -23
@@ -0,0 +1,157 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { StateEffect } from '@codemirror/state';
6
+ import { EditorView, ViewPlugin } from '@codemirror/view';
7
+
8
+ import { Domino } from '../util';
9
+
10
+ const lineHeight = 24;
11
+
12
+ export const scrollToBottomEffect = StateEffect.define<any>();
13
+
14
+ export type AutoScrollOptions = {
15
+ overscroll: number;
16
+ throttle: number;
17
+ };
18
+
19
+ /**
20
+ * Extension that supports pinning the scroll position and automatically scrolls to the bottom when content is added.
21
+ */
22
+ // TODO(burdon): Reconcile with transcript-extension.
23
+ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Partial<AutoScrollOptions> = {}) => {
24
+ let isThrottled = false;
25
+ let isPinned = true;
26
+ let timeout: NodeJS.Timeout | undefined;
27
+ let buttonContainer: HTMLDivElement;
28
+ let lastScrollTop = 0;
29
+ let scrollCounter = 0;
30
+
31
+ const hideScrollbar = (view: EditorView) => {
32
+ view.scrollDOM.classList.add('cm-hide-scrollbar');
33
+ clearTimeout(timeout);
34
+ timeout = setTimeout(() => {
35
+ view.scrollDOM.classList.remove('cm-hide-scrollbar');
36
+ }, 1_000);
37
+ };
38
+
39
+ const scrollToBottom = (view: EditorView) => {
40
+ isPinned = true;
41
+ scrollCounter = 0;
42
+ buttonContainer?.classList.add('opacity-0');
43
+ requestAnimationFrame(() => {
44
+ hideScrollbar(view);
45
+ view.scrollDOM.scrollTo({
46
+ top: view.scrollDOM.scrollHeight,
47
+ behavior: 'smooth',
48
+ });
49
+ });
50
+ };
51
+
52
+ return [
53
+ // Scroll button.
54
+ ViewPlugin.fromClass(
55
+ class {
56
+ constructor(view: EditorView) {
57
+ const scroller = view.scrollDOM.parentElement;
58
+ buttonContainer = Domino.of('div')
59
+ .classNames(true && 'cm-scroll-button transition-opacity duration-300 opacity-0')
60
+ .child(
61
+ Domino.of('button')
62
+ .classNames('dx-button bg-accentSurface')
63
+ .data('density', 'fine')
64
+ .child(Domino.of<any>('dx-icon').attr('icon', 'ph--arrow-down--regular'))
65
+ .on('click', () => {
66
+ scrollToBottom(view);
67
+ }),
68
+ )
69
+ .build();
70
+ scroller?.appendChild(buttonContainer);
71
+ }
72
+ },
73
+ ),
74
+
75
+ // Update listener for logging when scrolling is needed.
76
+ EditorView.updateListener.of((update) => {
77
+ // Listen for effects.
78
+ update.transactions.forEach((transaction) => {
79
+ for (const effect of transaction.effects) {
80
+ if (effect.is(scrollToBottomEffect)) {
81
+ scrollToBottom(update.view);
82
+ }
83
+ }
84
+ });
85
+
86
+ if (update.docChanged && isPinned && !isThrottled) {
87
+ const distanceFromBottom = calcDistance(update.view.scrollDOM);
88
+
89
+ // Keep pinned.
90
+ if (distanceFromBottom >= overscroll) {
91
+ isThrottled = true;
92
+ requestAnimationFrame(() => {
93
+ scrollToBottom(update.view);
94
+ });
95
+
96
+ // Reset throttle.
97
+ setTimeout(() => {
98
+ isThrottled = false;
99
+ }, throttle);
100
+ }
101
+ }
102
+ }),
103
+
104
+ // Detect user scroll.
105
+ // NOTE: Multiple scroll events are triggered during programmatic smooth scrolling.
106
+ EditorView.domEventHandlers({
107
+ scroll: (event, view) => {
108
+ const scroller = view.scrollDOM;
109
+ // Suspect delta goes positive when rendering widgets, so count positive deltas.
110
+ // TODO(burdon): Detect user scroll directly (wheel, touch, keys, etc.)
111
+ if (lastScrollTop > scroller.scrollTop) {
112
+ scrollCounter++;
113
+ }
114
+ lastScrollTop = scroller.scrollTop;
115
+ const distanceFromBottom = calcDistance(scroller);
116
+ if (distanceFromBottom === 0) {
117
+ // Pin to bottom.
118
+ isPinned = true;
119
+ buttonContainer?.classList.add('opacity-0');
120
+ scrollCounter = 0;
121
+ } else if (scrollCounter > 3) {
122
+ // Break pin if user scrolls up.
123
+ isPinned = false;
124
+ buttonContainer?.classList.remove('opacity-0');
125
+ }
126
+ },
127
+ }),
128
+
129
+ EditorView.theme({
130
+ '.cm-scroller': {
131
+ paddingBottom: `${overscroll}px`,
132
+ scrollbarWidth: 'thin',
133
+ },
134
+ '.cm-scroller.cm-hide-scrollbar': {
135
+ scrollbarWidth: 'none',
136
+ },
137
+ '.cm-scroller.cm-hide-scrollbar::-webkit-scrollbar': {
138
+ display: 'none',
139
+ },
140
+
141
+ // TODO(burdon): IconButton.
142
+ '.cm-scroll-button': {
143
+ position: 'absolute',
144
+ bottom: '0.5rem',
145
+ right: '1rem',
146
+ },
147
+ }),
148
+ ];
149
+ };
150
+
151
+ const calcDistance = (scroller: HTMLElement) => {
152
+ const scrollTop = scroller.scrollTop;
153
+ const scrollHeight = scroller.scrollHeight;
154
+ const clientHeight = scroller.clientHeight;
155
+ const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);
156
+ return distanceFromBottom;
157
+ };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Annotation, type Extension, RangeSet, type Range } from '@codemirror/state';
5
+ import { Annotation, type Extension, type Range, RangeSet } from '@codemirror/state';
6
6
  import {
7
7
  Decoration,
8
8
  type DecorationSet,
@@ -16,7 +16,7 @@ import {
16
16
  import { Event } from '@dxos/async';
17
17
  import { Context } from '@dxos/context';
18
18
 
19
- import { singleValueFacet, Cursor, type CursorConverter } from '../../util';
19
+ import { Cursor, type CursorConverter, singleValueFacet } from '../../util';
20
20
 
21
21
  export interface AwarenessProvider {
22
22
  remoteStateChange: Event<void>;
@@ -9,6 +9,7 @@ import { type Extension } from '@codemirror/state';
9
9
  import { EditorView, keymap } from '@codemirror/view';
10
10
  import defaultsDeep from 'lodash.defaultsdeep';
11
11
 
12
+ import { throttle } from '@dxos/async';
12
13
  import { invariant } from '@dxos/invariant';
13
14
 
14
15
  export type BlastOptions = {
@@ -214,12 +215,12 @@ class Blaster {
214
215
  requestAnimationFrame(this.loop.bind(this));
215
216
  }
216
217
 
217
- shake = throttle<{ time: number }>(({ time }) => {
218
+ shake = throttle(({ time }: { time: number }) => {
218
219
  this._shakeTime = this._shakeTimeMax || time;
219
220
  this._shakeTimeMax = time;
220
221
  }, 100);
221
222
 
222
- spawn = throttle<{ element: Element; point: { x: number; y: number } }>(({ element, point }) => {
223
+ spawn = throttle(({ element, point }: { element: Element; point: { x: number; y: number } }) => {
223
224
  const color = getRGBComponents(element, this._options.color);
224
225
  const numParticles = random(this._options.particleNumRange.min, this._options.particleNumRange.max);
225
226
  const dir = this._lastPoint.x === point.x ? 0 : this._lastPoint.x < point.x ? 1 : -1;
@@ -336,20 +337,6 @@ class Effect2 extends Effect {
336
337
  // Utils
337
338
  //
338
339
 
339
- function throttle<T>(callback: (arg: T) => void, limit: number): (arg: T) => void {
340
- let wait = false;
341
- return function (...args: any[]) {
342
- if (!wait) {
343
- // @ts-ignore
344
- callback.apply(this, args);
345
- wait = true;
346
- setTimeout(() => {
347
- wait = false;
348
- }, limit);
349
- }
350
- };
351
- }
352
-
353
340
  const getRGBComponents = (node: Element, color: BlastOptions['color']): Particle['color'] => {
354
341
  if (typeof color === 'function') {
355
342
  return color();
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { StateEffect } from '@codemirror/state';
6
- import { type KeyBinding, type Command, type EditorView } from '@codemirror/view';
6
+ import { type Command, type EditorView, type KeyBinding } from '@codemirror/view';
7
7
 
8
8
  import { commandState } from './state';
9
9
 
@@ -46,7 +46,6 @@ export const closeCommand: Command = (view: EditorView) => {
46
46
  export const commandKeyBindings: readonly KeyBinding[] = [
47
47
  {
48
48
  key: '/',
49
- preventDefault: true,
50
49
  run: openCommand,
51
50
  },
52
51
  {
@@ -2,12 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { RangeSetBuilder, StateField, StateEffect, Prec } from '@codemirror/state';
6
- import { EditorView, ViewPlugin, type ViewUpdate, Decoration, keymap, type DecorationSet } from '@codemirror/view';
5
+ import { Prec, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';
6
+ import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, keymap } from '@codemirror/view';
7
7
 
8
- import { placeholder, type PlaceholderOptions } from './placeholder';
9
8
  import { type Range } from '../../types';
10
9
 
10
+ import { type PlaceholderOptions, placeholder } from './placeholder';
11
+
11
12
  export type CommandMenuOptions = {
12
13
  trigger: string | string[];
13
14
  placeholder?: Partial<PlaceholderOptions>;
@@ -37,13 +38,13 @@ export const commandMenu = (options: CommandMenuOptions) => {
37
38
  // Check if we should show the widget - only if cursor is within the active command range.
38
39
  const shouldShowWidget = activeRange && selection.head >= activeRange.from && selection.head <= activeRange.to;
39
40
  if (shouldShowWidget) {
40
- // Create mark decoration that wraps the entire line content in a dx-ref-tag.
41
+ // Create mark decoration that wraps the entire line content in a dx-anchor.
41
42
  builder.add(
42
43
  activeRange.from,
43
44
  activeRange.to,
44
45
  Decoration.mark({
45
- tagName: 'dx-ref-tag',
46
- class: 'cm-ref-tag',
46
+ tagName: 'dx-anchor',
47
+ class: 'cm-floating-menu-trigger',
47
48
  attributes: {
48
49
  'data-auto-trigger': 'true',
49
50
  'data-trigger': trigger!,
@@ -2,14 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Prec, type Extension } from '@codemirror/state';
5
+ import { type Extension, Prec } from '@codemirror/state';
6
6
  import { EditorView, keymap } from '@codemirror/view';
7
7
 
8
8
  import { isNonNullable } from '@dxos/util';
9
9
 
10
10
  import { closeEffect, commandKeyBindings } from './action';
11
- import { hint, type HintOptions } from './hint';
12
- import { commandConfig, commandState, type PopupOptions } from './state';
11
+ import { type HintOptions, hint } from './hint';
12
+ import { type PopupOptions, commandConfig, commandState } from './state';
13
13
 
14
14
  // TODO(burdon): Create knowledge base for CM notes and ideas.
15
15
  // https://discuss.codemirror.net/t/inline-code-hints-like-vscode/5533/4
@@ -34,12 +34,10 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
34
34
  {
35
35
  const icon = document.createElement('dx-icon');
36
36
  icon.setAttribute('icon', options.icon ?? 'ph--dots-three-vertical--regular');
37
- const button = document.createElement('button');
38
- button.appendChild(icon);
39
37
 
40
- this.tag = document.createElement('dx-ref-tag');
41
- this.tag.classList.add('cm-ref-tag');
42
- this.tag.appendChild(button);
38
+ this.tag = document.createElement('dx-anchor');
39
+ this.tag.classList.add('cm-floating-menu-trigger');
40
+ this.tag.appendChild(icon);
43
41
  }
44
42
 
45
43
  container.appendChild(this.tag);
@@ -69,7 +67,7 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
69
67
  this.tag.style.display = 'none';
70
68
  this.tag.classList.add('opacity-10');
71
69
  } else if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(closeEffect)))) {
72
- this.tag.style.display = 'block';
70
+ this.tag.style.display = '';
73
71
  } else if (
74
72
  update.docChanged ||
75
73
  update.focusChanged ||
@@ -99,7 +97,7 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
99
97
 
100
98
  this.tag.style.top = `${offsetTop}px`;
101
99
  this.tag.style.left = `${offsetLeft}px`;
102
- this.tag.style.display = 'block';
100
+ this.tag.style.display = '';
103
101
  }
104
102
 
105
103
  scheduleUpdate() {
@@ -113,21 +111,18 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
113
111
  ),
114
112
 
115
113
  EditorView.theme({
116
- '.cm-ref-tag': {
114
+ '.cm-floating-menu-trigger': {
117
115
  position: 'fixed',
118
116
  padding: '0',
119
117
  border: 'none',
120
118
  opacity: '0',
121
- },
122
- '[data-has-focus] & .cm-ref-tag': {
123
- opacity: '1',
124
- },
125
- '.cm-ref-tag button': {
126
119
  display: 'grid',
127
- alignItems: 'center',
128
- justifyContent: 'center',
120
+ placeContent: 'center',
129
121
  width: '2rem',
130
122
  height: '2rem',
131
123
  },
124
+ '&:focus-within .cm-floating-menu-trigger': {
125
+ opacity: '1',
126
+ },
132
127
  }),
133
128
  ];
@@ -6,9 +6,10 @@
6
6
  import { RangeSetBuilder } from '@codemirror/state';
7
7
  import { Decoration, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
8
8
 
9
- import { commandState } from './state';
10
9
  import { clientRectsFor, flattenRect } from '../../util';
11
10
 
11
+ import { commandState } from './state';
12
+
12
13
  export type HintOptions = {
13
14
  delay?: number;
14
15
  onHint?: () => string | undefined;
@@ -6,5 +6,5 @@ export * from './action';
6
6
  export * from './command';
7
7
  export * from './command-menu';
8
8
  export * from './floating-menu';
9
- export * from './useCommandMenu';
10
9
  export * from './typeahead';
10
+ export * from './useCommandMenu';
@@ -4,7 +4,7 @@
4
4
  //
5
5
 
6
6
  import { type Extension } from '@codemirror/state';
7
- import { Decoration, EditorView, WidgetType, ViewPlugin, type ViewUpdate } from '@codemirror/view';
7
+ import { Decoration, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
8
8
 
9
9
  import { clientRectsFor, flattenRect } from '../../util';
10
10
 
@@ -3,13 +3,14 @@
3
3
  //
4
4
 
5
5
  import { StateField } from '@codemirror/state';
6
- import { showTooltip, type EditorView, type Tooltip, type TooltipView } from '@codemirror/view';
6
+ import { type EditorView, type Tooltip, type TooltipView, showTooltip } from '@codemirror/view';
7
7
 
8
- import { closeEffect, type Action, openEffect } from './action';
9
- import { type CommandOptions } from './command';
10
8
  import { type RenderCallback } from '../../types';
11
9
  import { singleValueFacet } from '../../util';
12
10
 
11
+ import { type Action, closeEffect, openEffect } from './action';
12
+ import { type CommandOptions } from './command';
13
+
13
14
  export const commandConfig = singleValueFacet<CommandOptions>();
14
15
 
15
16
  export type PopupOptions = {
@@ -2,15 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { EditorSelection, Prec, RangeSetBuilder, type Extension } from '@codemirror/state';
5
+ import { EditorSelection, type Extension, Prec, RangeSetBuilder } from '@codemirror/state';
6
6
  import {
7
7
  type Command,
8
8
  Decoration,
9
9
  type DecorationSet,
10
10
  type EditorView,
11
- keymap,
12
11
  ViewPlugin,
13
12
  type ViewUpdate,
13
+ keymap,
14
14
  } from '@codemirror/view';
15
15
 
16
16
  import { Hint } from './hint';
@@ -87,30 +87,43 @@ export const typeahead = ({ onComplete }: TypeaheadOptions = {}): Extension => {
87
87
  ];
88
88
  };
89
89
 
90
+ type CompletionOptions = {
91
+ default?: string;
92
+ minLength?: number;
93
+ };
94
+
90
95
  /**
91
96
  * Util to match current line to a static list of completions.
92
97
  */
93
98
  export const staticCompletion =
94
- (completions: string[], defaultCompletion?: string) =>
99
+ (completions: string[], options: CompletionOptions = {}) =>
95
100
  ({ line }: TypeaheadContext) => {
96
- if (line.length === 0 && defaultCompletion) {
97
- return defaultCompletion;
101
+ if (line.length === 0 && options.default) {
102
+ return options.default;
98
103
  }
99
104
 
100
- const words = line.split(/\s+/).filter(Boolean);
101
- if (words.length) {
102
- const word = words.at(-1)!;
103
- for (const completion of completions) {
104
- const match = matchCompletion(completion, word);
105
- if (match) {
106
- return match;
105
+ const parts = line.split(/\s+/).filter(Boolean);
106
+ if (parts.length) {
107
+ const str = parts.at(-1)!;
108
+ if (str.length >= (options.minLength ?? 0)) {
109
+ for (const completion of completions) {
110
+ const match = matchCompletion(completion, str);
111
+ if (match) {
112
+ return match;
113
+ }
107
114
  }
108
115
  }
109
116
  }
110
117
  };
111
118
 
112
- export const matchCompletion = (completion: string, word: string): string | undefined => {
113
- if (completion.length > word.length && completion.startsWith(word)) {
114
- return completion.slice(word.length);
119
+ export const matchCompletion = (completion: string, str: string, minLength = 0): string | undefined => {
120
+ if (
121
+ str.length >= minLength &&
122
+ completion.length > str.length &&
123
+ completion.startsWith(str)
124
+ // TODO(burdon): If case insensitive, need to replace existing chars.
125
+ // completion.toLowerCase().startsWith(str.toLowerCase())
126
+ ) {
127
+ return completion.slice(str.length);
115
128
  }
116
129
  };
@@ -5,12 +5,13 @@
5
5
  import { type EditorView } from '@codemirror/view';
6
6
  import { type RefObject, useCallback, useMemo, useRef, useState } from 'react';
7
7
 
8
- import { type DxRefTag, type DxRefTagActivate } from '@dxos/lit-ui';
8
+ import { type DxAnchorActivate } from '@dxos/react-ui';
9
9
  import { type MaybePromise } from '@dxos/util';
10
10
 
11
+ import { type CommandMenuGroup, type CommandMenuItem, getItem, getNextItem, getPreviousItem } from '../../components';
12
+
11
13
  import { commandMenu, commandRangeEffect } from './command-menu';
12
14
  import { type PlaceholderOptions } from './placeholder';
13
- import { getItem, getNextItem, getPreviousItem, type CommandMenuGroup, type CommandMenuItem } from '../../components';
14
15
 
15
16
  export type UseCommandMenuOptions = {
16
17
  viewRef: RefObject<EditorView | undefined>;
@@ -20,7 +21,6 @@ export type UseCommandMenuOptions = {
20
21
  };
21
22
 
22
23
  export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCommandMenuOptions) => {
23
- const triggerRef = useRef<DxRefTag | null>(null);
24
24
  const currentRef = useRef<CommandMenuItem | null>(null);
25
25
  const groupsRef = useRef<CommandMenuGroup[]>([]);
26
26
  const [currentItem, setCurrentItem] = useState<string>();
@@ -34,7 +34,6 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
34
34
  }
35
35
  setOpen(open);
36
36
  if (!open) {
37
- triggerRef.current = null;
38
37
  setCurrentItem(undefined);
39
38
  viewRef.current?.dispatch({ effects: [commandRangeEffect.of(null)] });
40
39
  }
@@ -43,13 +42,12 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
43
42
  );
44
43
 
45
44
  const handleActivate = useCallback(
46
- async (event: DxRefTagActivate) => {
45
+ async (event: DxAnchorActivate) => {
47
46
  const item = getItem(groupsRef.current, currentItem);
48
47
  if (item) {
49
48
  currentRef.current = item;
50
49
  }
51
50
 
52
- triggerRef.current = event.trigger;
53
51
  const triggerKey = event.trigger.getAttribute('data-trigger');
54
52
  if (!open && triggerKey) {
55
53
  await handleOpenChange(true, triggerKey);
@@ -69,7 +67,7 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
69
67
  }, []);
70
68
 
71
69
  const serializedTrigger = Array.isArray(trigger) ? trigger.join(',') : trigger;
72
- const _commandMenu = useMemo(() => {
70
+ const memoizedCommandMenu = useMemo(() => {
73
71
  return commandMenu({
74
72
  trigger,
75
73
  placeholder,
@@ -106,10 +104,9 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
106
104
  }, [handleOpenChange, getMenu, serializedTrigger, placeholder]);
107
105
 
108
106
  return {
109
- commandMenu: _commandMenu,
107
+ commandMenu: memoizedCommandMenu,
110
108
  currentItem,
111
109
  groupsRef,
112
- ref: triggerRef,
113
110
  open,
114
111
  onActivate: handleActivate,
115
112
  onOpenChange: setOpen,
@@ -5,25 +5,26 @@
5
5
  import { invertedEffects } from '@codemirror/commands';
6
6
  import { type ChangeDesc, type Extension, StateEffect, StateField, type Text } from '@codemirror/state';
7
7
  import {
8
- hoverTooltip,
9
- keymap,
10
8
  type Command,
11
9
  Decoration,
12
10
  EditorView,
13
- type Rect,
14
11
  type PluginValue,
12
+ type Rect,
15
13
  ViewPlugin,
14
+ hoverTooltip,
15
+ keymap,
16
16
  } from '@codemirror/view';
17
17
  import sortBy from 'lodash.sortby';
18
18
  import { useEffect } from 'react';
19
19
 
20
- import { debounce, type CleanupFn } from '@dxos/async';
20
+ import { type CleanupFn, debounce } from '@dxos/async';
21
21
  import { log } from '@dxos/log';
22
22
  import { isNonNullable } from '@dxos/util';
23
23
 
24
+ import { type Comment, type Range, type RenderCallback } from '../types';
25
+ import { Cursor, callbackWrapper, singleValueFacet } from '../util';
26
+
24
27
  import { documentId } from './selection';
25
- import { type RenderCallback, type Comment, type Range } from '../types';
26
- import { Cursor, singleValueFacet, callbackWrapper } from '../util';
27
28
 
28
29
  //
29
30
  // State management.
@@ -57,7 +58,11 @@ const setCommentState = StateEffect.define<CommentsState>();
57
58
  * The ranges are tracked as Automerge cursors from which the absolute indexed ranges can be computed.
58
59
  */
59
60
  export const commentsState = StateField.define<CommentsState>({
60
- create: (state) => ({ id: state.facet(documentId), comments: [], selection: {} }),
61
+ create: (state) => ({
62
+ id: state.facet(documentId),
63
+ comments: [],
64
+ selection: {},
65
+ }),
61
66
  update: (value, tr) => {
62
67
  for (const effect of tr.effects) {
63
68
  // Update selection.
@@ -98,16 +103,16 @@ export const commentsState = StateField.define<CommentsState>({
98
103
  */
99
104
  const styles = EditorView.theme({
100
105
  '.cm-comment, .cm-comment-current': {
101
- margin: '0 -3px',
102
- padding: '3px',
103
- borderRadius: '3px',
106
+ padding: '3px 0',
107
+ backgroundColor: 'var(--dx-cmCommentSurface)',
108
+ },
109
+ '.cm-comment > span, .cm-comment-current > span': {
110
+ boxDecorationBreak: 'clone',
111
+ boxShadow: '0 0 1px 3px var(--dx-cmCommentSurface)',
104
112
  backgroundColor: 'var(--dx-cmCommentSurface)',
105
113
  color: 'var(--dx-cmComment)',
106
114
  cursor: 'pointer',
107
115
  },
108
- '.cm-comment:hover, .cm-comment-current': {
109
- textDecoration: 'underline',
110
- },
111
116
  });
112
117
 
113
118
  const createCommentMark = (id: string, isCurrent: boolean) =>
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import type { Extension } from '@codemirror/state';
6
- import { dropCursor, EditorView } from '@codemirror/view';
6
+ import { EditorView, dropCursor } from '@codemirror/view';
7
7
 
8
8
  export type DNDOptions = { onDrop?: (view: EditorView, event: { files: FileList }) => void };
9
9