@dxos/ui-editor 0.0.0 → 0.8.4-main.1c7ec43d41

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 (255) hide show
  1. package/dist/lib/browser/index.mjs +8633 -0
  2. package/dist/lib/browser/index.mjs.map +7 -0
  3. package/dist/lib/browser/meta.json +1 -0
  4. package/dist/lib/browser/types/index.mjs +33 -0
  5. package/dist/lib/browser/types/index.mjs.map +7 -0
  6. package/dist/lib/node-esm/index.mjs +8635 -0
  7. package/dist/lib/node-esm/index.mjs.map +7 -0
  8. package/dist/lib/node-esm/meta.json +1 -0
  9. package/dist/lib/node-esm/types/index.mjs +35 -0
  10. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  11. package/dist/types/src/defaults.d.ts +6 -0
  12. package/dist/types/src/defaults.d.ts.map +1 -0
  13. package/dist/types/src/extensions/annotations.d.ts +9 -0
  14. package/dist/types/src/extensions/annotations.d.ts.map +1 -0
  15. package/dist/types/src/extensions/auto-scroll.d.ts +18 -0
  16. package/dist/types/src/extensions/auto-scroll.d.ts.map +1 -0
  17. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts +17 -0
  18. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
  19. package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
  20. package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
  21. package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
  22. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
  23. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +23 -0
  24. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
  25. package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
  26. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
  27. package/dist/types/src/extensions/automerge/automerge.d.ts +4 -0
  28. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -0
  29. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  30. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  31. package/dist/types/src/extensions/automerge/cursor.d.ts +4 -0
  32. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/defs.d.ts +17 -0
  34. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -0
  35. package/dist/types/src/extensions/automerge/index.d.ts +2 -0
  36. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -0
  37. package/dist/types/src/extensions/automerge/sync.d.ts +17 -0
  38. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -0
  39. package/dist/types/src/extensions/automerge/update-automerge.d.ts +6 -0
  40. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -0
  41. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +5 -0
  42. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -0
  43. package/dist/types/src/extensions/awareness/awareness-provider.d.ts +31 -0
  44. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -0
  45. package/dist/types/src/extensions/awareness/awareness.d.ts +46 -0
  46. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -0
  47. package/dist/types/src/extensions/awareness/index.d.ts +3 -0
  48. package/dist/types/src/extensions/awareness/index.d.ts.map +1 -0
  49. package/dist/types/src/extensions/blast.d.ts +25 -0
  50. package/dist/types/src/extensions/blast.d.ts.map +1 -0
  51. package/dist/types/src/extensions/blocks.d.ts +2 -0
  52. package/dist/types/src/extensions/blocks.d.ts.map +1 -0
  53. package/dist/types/src/extensions/bookmarks.d.ts +12 -0
  54. package/dist/types/src/extensions/bookmarks.d.ts.map +1 -0
  55. package/dist/types/src/extensions/comments.d.ts +90 -0
  56. package/dist/types/src/extensions/comments.d.ts.map +1 -0
  57. package/dist/types/src/extensions/debug.d.ts +3 -0
  58. package/dist/types/src/extensions/debug.d.ts.map +1 -0
  59. package/dist/types/src/extensions/dnd.d.ts +9 -0
  60. package/dist/types/src/extensions/dnd.d.ts.map +1 -0
  61. package/dist/types/src/extensions/factories.d.ts +88 -0
  62. package/dist/types/src/extensions/factories.d.ts.map +1 -0
  63. package/dist/types/src/extensions/factories.test.d.ts +2 -0
  64. package/dist/types/src/extensions/factories.test.d.ts.map +1 -0
  65. package/dist/types/src/extensions/focus.d.ts +7 -0
  66. package/dist/types/src/extensions/focus.d.ts.map +1 -0
  67. package/dist/types/src/extensions/folding.d.ts +6 -0
  68. package/dist/types/src/extensions/folding.d.ts.map +1 -0
  69. package/dist/types/src/extensions/hashtag.d.ts +3 -0
  70. package/dist/types/src/extensions/hashtag.d.ts.map +1 -0
  71. package/dist/types/src/extensions/index.d.ts +32 -0
  72. package/dist/types/src/extensions/index.d.ts.map +1 -0
  73. package/dist/types/src/extensions/json.d.ts +7 -0
  74. package/dist/types/src/extensions/json.d.ts.map +1 -0
  75. package/dist/types/src/extensions/listener.d.ts +13 -0
  76. package/dist/types/src/extensions/listener.d.ts.map +1 -0
  77. package/dist/types/src/extensions/markdown/action.d.ts +12 -0
  78. package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
  79. package/dist/types/src/extensions/markdown/bundle.d.ts +25 -0
  80. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -0
  81. package/dist/types/src/extensions/markdown/changes.d.ts +10 -0
  82. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -0
  83. package/dist/types/src/extensions/markdown/changes.test.d.ts +2 -0
  84. package/dist/types/src/extensions/markdown/changes.test.d.ts.map +1 -0
  85. package/dist/types/src/extensions/markdown/debug.d.ts +11 -0
  86. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -0
  87. package/dist/types/src/extensions/markdown/decorate.d.ts +25 -0
  88. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -0
  89. package/dist/types/src/extensions/markdown/formatting.d.ts +63 -0
  90. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  91. package/dist/types/src/extensions/markdown/formatting.test.d.ts +3 -0
  92. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -0
  93. package/dist/types/src/extensions/markdown/highlight.d.ts +37 -0
  94. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -0
  95. package/dist/types/src/extensions/markdown/image.d.ts +7 -0
  96. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  97. package/dist/types/src/extensions/markdown/index.d.ts +10 -0
  98. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -0
  99. package/dist/types/src/extensions/markdown/link.d.ts +7 -0
  100. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  101. package/dist/types/src/extensions/markdown/parser.test.d.ts +2 -0
  102. package/dist/types/src/extensions/markdown/parser.test.d.ts.map +1 -0
  103. package/dist/types/src/extensions/markdown/styles.d.ts +4 -0
  104. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -0
  105. package/dist/types/src/extensions/markdown/table.d.ts +8 -0
  106. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  107. package/dist/types/src/extensions/mention.d.ts +7 -0
  108. package/dist/types/src/extensions/mention.d.ts.map +1 -0
  109. package/dist/types/src/extensions/modal.d.ts +7 -0
  110. package/dist/types/src/extensions/modal.d.ts.map +1 -0
  111. package/dist/types/src/extensions/modes.d.ts +10 -0
  112. package/dist/types/src/extensions/modes.d.ts.map +1 -0
  113. package/dist/types/src/extensions/outliner/commands.d.ts +10 -0
  114. package/dist/types/src/extensions/outliner/commands.d.ts.map +1 -0
  115. package/dist/types/src/extensions/outliner/editor.d.ts +5 -0
  116. package/dist/types/src/extensions/outliner/editor.d.ts.map +1 -0
  117. package/dist/types/src/extensions/outliner/editor.test.d.ts +2 -0
  118. package/dist/types/src/extensions/outliner/editor.test.d.ts.map +1 -0
  119. package/dist/types/src/extensions/outliner/index.d.ts +4 -0
  120. package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
  121. package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
  122. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
  123. package/dist/types/src/extensions/outliner/outliner.d.ts +11 -0
  124. package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -0
  125. package/dist/types/src/extensions/outliner/outliner.test.d.ts +2 -0
  126. package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +1 -0
  127. package/dist/types/src/extensions/outliner/selection.d.ts +12 -0
  128. package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -0
  129. package/dist/types/src/extensions/outliner/tree.d.ts +79 -0
  130. package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -0
  131. package/dist/types/src/extensions/outliner/tree.test.d.ts +2 -0
  132. package/dist/types/src/extensions/outliner/tree.test.d.ts.map +1 -0
  133. package/dist/types/src/extensions/preview/index.d.ts +2 -0
  134. package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
  135. package/dist/types/src/extensions/preview/preview.d.ts +34 -0
  136. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
  137. package/dist/types/src/extensions/replacer.d.ts +21 -0
  138. package/dist/types/src/extensions/replacer.d.ts.map +1 -0
  139. package/dist/types/src/extensions/replacer.test.d.ts +2 -0
  140. package/dist/types/src/extensions/replacer.test.d.ts.map +1 -0
  141. package/dist/types/src/extensions/scroll-past-end.d.ts +3 -0
  142. package/dist/types/src/extensions/scroll-past-end.d.ts.map +1 -0
  143. package/dist/types/src/extensions/scroller.d.ts +68 -0
  144. package/dist/types/src/extensions/scroller.d.ts.map +1 -0
  145. package/dist/types/src/extensions/selection.d.ts +24 -0
  146. package/dist/types/src/extensions/selection.d.ts.map +1 -0
  147. package/dist/types/src/extensions/snippets.d.ts +10 -0
  148. package/dist/types/src/extensions/snippets.d.ts.map +1 -0
  149. package/dist/types/src/extensions/state.d.ts +2 -0
  150. package/dist/types/src/extensions/state.d.ts.map +1 -0
  151. package/dist/types/src/extensions/submit.d.ts +10 -0
  152. package/dist/types/src/extensions/submit.d.ts.map +1 -0
  153. package/dist/types/src/extensions/tags/extended-markdown.d.ts +10 -0
  154. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +1 -0
  155. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts +2 -0
  156. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts.map +1 -0
  157. package/dist/types/src/extensions/tags/fader.d.ts +12 -0
  158. package/dist/types/src/extensions/tags/fader.d.ts.map +1 -0
  159. package/dist/types/src/extensions/tags/index.d.ts +7 -0
  160. package/dist/types/src/extensions/tags/index.d.ts.map +1 -0
  161. package/dist/types/src/extensions/tags/typewriter.d.ts +43 -0
  162. package/dist/types/src/extensions/tags/typewriter.d.ts.map +1 -0
  163. package/dist/types/src/extensions/tags/typewriter.test.d.ts +2 -0
  164. package/dist/types/src/extensions/tags/typewriter.test.d.ts.map +1 -0
  165. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +31 -0
  166. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +1 -0
  167. package/dist/types/src/extensions/tags/xml-formatting.d.ts +24 -0
  168. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +1 -0
  169. package/dist/types/src/extensions/tags/xml-tags.d.ts +117 -0
  170. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -0
  171. package/dist/types/src/extensions/tags/xml-util.d.ts +10 -0
  172. package/dist/types/src/extensions/tags/xml-util.d.ts.map +1 -0
  173. package/dist/types/src/extensions/tags/xml-util.test.d.ts +2 -0
  174. package/dist/types/src/extensions/tags/xml-util.test.d.ts.map +1 -0
  175. package/dist/types/src/index.d.ts +8 -0
  176. package/dist/types/src/index.d.ts.map +1 -0
  177. package/dist/types/src/styles/index.d.ts +2 -0
  178. package/dist/types/src/styles/index.d.ts.map +1 -0
  179. package/dist/types/src/styles/theme.d.ts +58 -0
  180. package/dist/types/src/styles/theme.d.ts.map +1 -0
  181. package/dist/types/src/types/index.d.ts +2 -0
  182. package/dist/types/src/types/index.d.ts.map +1 -0
  183. package/dist/types/src/types/types.d.ts +21 -0
  184. package/dist/types/src/types/types.d.ts.map +1 -0
  185. package/dist/types/src/util/cursor.d.ts +31 -0
  186. package/dist/types/src/util/cursor.d.ts.map +1 -0
  187. package/dist/types/src/util/debug.d.ts +17 -0
  188. package/dist/types/src/util/debug.d.ts.map +1 -0
  189. package/dist/types/src/util/decorations.d.ts +4 -0
  190. package/dist/types/src/util/decorations.d.ts.map +1 -0
  191. package/dist/types/src/util/dom.d.ts +10 -0
  192. package/dist/types/src/util/dom.d.ts.map +1 -0
  193. package/dist/types/src/util/facet.d.ts +3 -0
  194. package/dist/types/src/util/facet.d.ts.map +1 -0
  195. package/dist/types/src/util/index.d.ts +7 -0
  196. package/dist/types/src/util/index.d.ts.map +1 -0
  197. package/dist/types/src/util/util.d.ts +8 -0
  198. package/dist/types/src/util/util.d.ts.map +1 -0
  199. package/dist/types/tsconfig.tsbuildinfo +1 -0
  200. package/package.json +42 -43
  201. package/src/defaults.ts +33 -20
  202. package/src/extensions/annotations.ts +1 -1
  203. package/src/extensions/auto-scroll.ts +234 -0
  204. package/src/extensions/autocomplete/placeholder.ts +37 -18
  205. package/src/extensions/automerge/automerge.test.tsx +37 -11
  206. package/src/extensions/automerge/automerge.ts +5 -7
  207. package/src/extensions/blocks.ts +5 -5
  208. package/src/extensions/comments.ts +5 -6
  209. package/src/extensions/dnd.ts +2 -2
  210. package/src/extensions/factories.test.ts +88 -0
  211. package/src/extensions/factories.ts +32 -15
  212. package/src/extensions/folding.ts +5 -22
  213. package/src/extensions/index.ts +4 -3
  214. package/src/extensions/markdown/action.ts +0 -1
  215. package/src/extensions/markdown/bundle.ts +23 -9
  216. package/src/extensions/markdown/decorate.ts +15 -12
  217. package/src/extensions/markdown/formatting.ts +5 -10
  218. package/src/extensions/markdown/highlight.ts +15 -7
  219. package/src/extensions/markdown/link.ts +27 -33
  220. package/src/extensions/markdown/parser.test.ts +0 -1
  221. package/src/extensions/markdown/styles.ts +42 -9
  222. package/src/extensions/markdown/table.ts +24 -2
  223. package/src/extensions/outliner/outliner.test.ts +0 -1
  224. package/src/extensions/outliner/outliner.ts +3 -4
  225. package/src/extensions/outliner/tree.test.ts +0 -1
  226. package/src/extensions/preview/preview.ts +62 -15
  227. package/src/extensions/scroll-past-end.ts +32 -0
  228. package/src/extensions/scroller.ts +256 -0
  229. package/src/extensions/selection.ts +1 -1
  230. package/src/extensions/snippets.ts +67 -0
  231. package/src/extensions/tags/extended-markdown.test.ts +120 -2
  232. package/src/extensions/tags/extended-markdown.ts +80 -1
  233. package/src/extensions/tags/fader.ts +195 -0
  234. package/src/extensions/tags/index.ts +4 -1
  235. package/src/extensions/tags/testing/text.md +36 -0
  236. package/src/extensions/tags/testing/text.txt +35 -0
  237. package/src/extensions/tags/typewriter.test.ts +65 -0
  238. package/src/extensions/tags/typewriter.ts +594 -0
  239. package/src/extensions/tags/xml-block-decoration.ts +123 -0
  240. package/src/extensions/tags/xml-formatting.ts +125 -0
  241. package/src/extensions/tags/xml-tags.ts +186 -35
  242. package/src/extensions/tags/xml-util.test.ts +199 -24
  243. package/src/extensions/tags/xml-util.ts +62 -5
  244. package/src/index.ts +0 -1
  245. package/src/styles/index.ts +0 -2
  246. package/src/styles/theme.ts +124 -33
  247. package/src/types/types.ts +10 -2
  248. package/src/typings.d.ts +8 -0
  249. package/src/util/cursor.ts +1 -2
  250. package/src/extensions/autoscroll.ts +0 -165
  251. package/src/extensions/scrolling.ts +0 -189
  252. package/src/extensions/tags/streamer.ts +0 -243
  253. package/src/extensions/typewriter.ts +0 -68
  254. package/src/styles/markdown.ts +0 -26
  255. package/src/styles/tokens.ts +0 -17
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ declare module '*.md?raw' {
6
+ const content: string;
7
+ export default content;
8
+ }
@@ -5,7 +5,6 @@
5
5
  import { type EditorState } from '@codemirror/state';
6
6
 
7
7
  import { type Range } from '../types';
8
-
9
8
  import { singleValueFacet } from './facet';
10
9
 
11
10
  /**
@@ -26,7 +25,7 @@ export const overlap = (a: Range, b: Range): boolean => a.from <= b.to && a.to >
26
25
  * while positive - with the next one.
27
26
  */
28
27
  export interface CursorConverter {
29
- toCursor(position: number, assoc?: -1 | 1 | undefined): string;
28
+ toCursor(position: number, assoc?: -1 | 1): string;
30
29
  fromCursor(cursor: string): number;
31
30
  }
32
31
 
@@ -1,165 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { StateEffect } from '@codemirror/state';
6
- import { EditorView, ViewPlugin } from '@codemirror/view';
7
-
8
- import { debounce } from '@dxos/async';
9
- import { Domino } from '@dxos/ui';
10
-
11
- import { scrollToLineEffect } from './scrolling';
12
-
13
- // TODO(burdon): Reconcile with scrollToLineEffect (scrolling).
14
- export const scrollToBottomEffect = StateEffect.define<ScrollBehavior | undefined>();
15
-
16
- export type AutoScrollOptions = {
17
- /** Auto-scroll when reaches the bottom. */
18
- autoScroll?: boolean;
19
- /** Threshold in px to trigger scroll from bottom. */
20
- threshold?: number;
21
- /** Throttle time in ms. */
22
- throttleDelay?: number;
23
- /** Callback when auto-scrolling. */
24
- onAutoScroll?: (props: { view: EditorView; distanceFromBottom: number }) => boolean | void;
25
- };
26
-
27
- /**
28
- * Extension that supports pinning the scroll position and automatically scrolls to the bottom when content is added.
29
- */
30
- // TODO(burdon): Reconcile with transcript-extension.
31
- export const autoScroll = ({
32
- autoScroll = true,
33
- threshold = 100,
34
- throttleDelay = 1_000,
35
- onAutoScroll,
36
- }: Partial<AutoScrollOptions> = {}) => {
37
- let buttonContainer: HTMLDivElement | undefined;
38
- let hideTimeout: NodeJS.Timeout | undefined;
39
- let lastScrollTop = 0;
40
- let isPinned = true;
41
-
42
- const setPinned = (pin: boolean) => {
43
- isPinned = pin;
44
- buttonContainer?.classList.toggle('opacity-0', pin);
45
- };
46
-
47
- // Temporarily hide the scrollbar while auto-scrolling.
48
- const hideScrollbar = (view: EditorView) => {
49
- view.scrollDOM.classList.add('cm-hide-scrollbar');
50
- clearTimeout(hideTimeout);
51
- hideTimeout = setTimeout(() => {
52
- view.scrollDOM.classList.remove('cm-hide-scrollbar');
53
- }, 1_000);
54
- };
55
-
56
- // Throttled scroll to bottom.
57
- const scrollToBottom = (view: EditorView, behavior?: ScrollBehavior) => {
58
- setPinned(true);
59
- hideScrollbar(view);
60
- const line = view.state.doc.lineAt(view.state.doc.length);
61
- view.dispatch({
62
- selection: { anchor: line.to, head: line.to },
63
- effects: scrollToLineEffect.of({
64
- line: line.number,
65
- options: { position: 'end', offset: threshold, behavior },
66
- }),
67
- });
68
- };
69
-
70
- // Throttled check for distance from bottom (for downward scrolls only).
71
- const checkDistance = debounce((view: EditorView) => {
72
- const scrollerRect = view.scrollDOM.getBoundingClientRect();
73
- const coords = view.coordsAtPos(view.state.doc.length);
74
- const distanceFromBottom = coords ? coords.bottom - scrollerRect.bottom : 0;
75
- setPinned(distanceFromBottom < 0);
76
- }, 1_000);
77
-
78
- // Debounce scroll updates so rapid edits don't cause clunky scrolling.
79
- const triggerUpdate = debounce((view: EditorView) => scrollToBottom(view), throttleDelay);
80
-
81
- return [
82
- // Update listener for logging when scrolling is needed.
83
- EditorView.updateListener.of(({ view, transactions, heightChanged }) => {
84
- // TODO(burdon): Remove and use scrollToLineEffect instead.
85
- transactions.forEach((transaction) => {
86
- for (const effect of transaction.effects) {
87
- if (effect.is(scrollToBottomEffect)) {
88
- scrollToBottom(view, effect.value);
89
- }
90
- }
91
- });
92
-
93
- // Maybe scroll if doc changed and pinned.
94
- // NOTE: Geometry changed is triggered when widgets change height (e.g., toggle tool block).
95
- if (heightChanged && isPinned) {
96
- const coords = view.coordsAtPos(view.state.doc.length);
97
- const scrollerRect = view.scrollDOM.getBoundingClientRect();
98
- const distanceFromBottom = coords ? scrollerRect.bottom - coords.bottom : 0;
99
- if (autoScroll && distanceFromBottom < threshold) {
100
- const shouldScroll = onAutoScroll?.({ view, distanceFromBottom }) ?? true;
101
- if (shouldScroll) {
102
- triggerUpdate(view);
103
- }
104
- } else if (distanceFromBottom < 0) {
105
- setPinned(false);
106
- }
107
- }
108
- }),
109
-
110
- // Detect user scroll.
111
- EditorView.domEventHandlers({
112
- scroll: (event, view) => {
113
- const currentScrollTop = view.scrollDOM.scrollTop;
114
- const scrollingUp = currentScrollTop < lastScrollTop;
115
- lastScrollTop = currentScrollTop;
116
-
117
- // If user scrolls up, immediately unpin auto-scroll.
118
- if (scrollingUp) {
119
- setPinned(false);
120
- } else {
121
- checkDistance(view);
122
- }
123
- },
124
- }),
125
-
126
- // Scroll button.
127
- ViewPlugin.fromClass(
128
- class {
129
- constructor(view: EditorView) {
130
- const icon = Domino.of('dx-icon' as any).attributes({ icon: 'ph--arrow-down--regular' });
131
- const button = Domino.of('button')
132
- .classNames('dx-button bg-accentSurface')
133
- .attributes({ 'data-density': 'fine' })
134
- .children(icon)
135
- .on('click', () => {
136
- scrollToBottom(view);
137
- });
138
- buttonContainer = Domino.of('div')
139
- .classNames('cm-scroll-button transition-opacity duration-300 opacity-0')
140
- .children(button).root as HTMLDivElement;
141
-
142
- view.scrollDOM.parentElement!.appendChild(buttonContainer);
143
- }
144
- },
145
- ),
146
-
147
- // Styles.
148
- EditorView.theme({
149
- '.cm-scroller': {
150
- scrollbarWidth: 'thin',
151
- },
152
- '.cm-scroller.cm-hide-scrollbar': {
153
- scrollbarWidth: 'none',
154
- },
155
- '.cm-scroller.cm-hide-scrollbar::-webkit-scrollbar': {
156
- display: 'none',
157
- },
158
- '.cm-scroll-button': {
159
- position: 'absolute',
160
- bottom: '0.5rem',
161
- right: '1rem',
162
- },
163
- }),
164
- ];
165
- };
@@ -1,189 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { StateEffect } from '@codemirror/state';
6
- import { EditorView, ViewPlugin } from '@codemirror/view';
7
-
8
- /**
9
- * Configuration options for smooth scrolling behavior.
10
- */
11
- export type SmoothScrollOptions = {
12
- /**
13
- * Additional offset from the target line in pixels.
14
- * Positive values scroll past the line, negative values stop before it.
15
- * @default 0
16
- */
17
- offset?: number;
18
- /**
19
- * Position of the target line in the viewport.
20
- * - 'start': Line appears at the start (top) of the screen
21
- * - 'end': Line appears at the end (bottom) of the screen
22
- * @default 'start'
23
- */
24
- position?: 'start' | 'end';
25
- /**
26
- * Whether to use smooth scrolling.
27
- * @default 'smooth'
28
- */
29
- behavior?: ScrollBehavior;
30
- };
31
-
32
- /**
33
- * Parameters for the scroll to line effect.
34
- */
35
- export type ScrollToLineProps = {
36
- /**
37
- * The line number to scroll to (1-based).
38
- */
39
- line: number;
40
- /**
41
- * Optional configuration to override default scroll behavior.
42
- */
43
- options?: SmoothScrollOptions;
44
- };
45
-
46
- /**
47
- * StateEffect for triggering smooth scroll to a specific line.
48
- */
49
- export const scrollToLineEffect = StateEffect.define<ScrollToLineProps>();
50
-
51
- /**
52
- * Extension that provides smooth scrolling to specific lines in the editor.
53
- *
54
- * @example
55
- * ```typescript
56
- * // Add to editor extensions.
57
- * const extensions = [
58
- * smoothScroll()
59
- * ];
60
- *
61
- * // Trigger scroll to line 42.
62
- * view.dispatch({
63
- * effects: scrollToLineEffect.of({ line: 42 })
64
- * });
65
- *
66
- * // Scroll with custom options.
67
- * view.dispatch({
68
- * effects: scrollToLineEffect.of({ line: 100, options: { offset: -50 } })
69
- * });
70
- *
71
- * // Scroll so line appears at end (bottom) of screen.
72
- * view.dispatch({
73
- * effects: scrollToLineEffect.of({ line: 50, options: { position: 'end' } })
74
- * });
75
- * ```
76
- */
77
- export const smoothScroll = ({ offset = 0, position = 'start' }: Partial<SmoothScrollOptions> = {}) => {
78
- // ViewPlugin to manage scroll animations.
79
- const scrollPlugin = ViewPlugin.fromClass(
80
- class SmoothScrollPlugin {
81
- constructor(private readonly view: EditorView) {}
82
-
83
- // No-op.
84
- destroy() {}
85
-
86
- /**
87
- * Perform smooth scroll to the specified line.
88
- */
89
- scrollToLine(lineNumber: number, options: SmoothScrollOptions) {
90
- const { offset: animOffset = 0, position: animPosition, behavior } = options;
91
- const doc = this.view.state.doc;
92
- const scroller = this.view.scrollDOM;
93
-
94
- // Convert 1-based line number to 0-based.
95
- const targetLine = Math.max(0, lineNumber - 1);
96
- if (behavior === 'instant') {
97
- requestAnimationFrame(() => {
98
- this.view.dispatch({
99
- selection: { anchor: doc.line(targetLine + 1).from },
100
- scrollIntoView: true,
101
- });
102
- });
103
- return;
104
- }
105
-
106
- // Get the position of the target line.
107
- if (targetLine >= doc.lines) {
108
- // Line doesn't exist, scroll to end.
109
- const targetScrollTop = scroller.scrollHeight - scroller.clientHeight + (animOffset || 0);
110
- this.animateScroll(scroller, targetScrollTop);
111
- return;
112
- }
113
-
114
- const lineStart = doc.line(targetLine + 1).from;
115
- const coords = this.view.coordsAtPos(lineStart);
116
- if (!coords) {
117
- return;
118
- }
119
-
120
- // Calculate target scroll position based on position option.
121
- const currentScrollTop = scroller.scrollTop;
122
- const scrollerRect = scroller.getBoundingClientRect();
123
- const maxScrollTop = scroller.scrollHeight - scroller.clientHeight;
124
-
125
- let targetScrollTop: number;
126
- if (animPosition === 'end') {
127
- // Position line at end (bottom) of viewport.
128
- // Calculate how far down we need to scroll so the line's bottom aligns with viewport bottom.
129
- targetScrollTop = currentScrollTop + coords.bottom - scrollerRect.bottom + animOffset;
130
- } else {
131
- // Default: position line at start (top) of viewport.
132
- targetScrollTop = currentScrollTop + coords.top - scrollerRect.top + animOffset;
133
- }
134
-
135
- // Clamp to valid scroll range.
136
- const clampedScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop));
137
- this.animateScroll(scroller, clampedScrollTop);
138
- }
139
-
140
- /**
141
- * Animate scroll using browser's built-in smooth scrolling.
142
- */
143
- private animateScroll(element: HTMLElement, targetScrollTop: number) {
144
- if (Math.abs(targetScrollTop - element.scrollTop) < 1) {
145
- return;
146
- }
147
-
148
- // Use browser's built-in smooth scrolling.
149
- element.scrollTo({
150
- top: targetScrollTop,
151
- behavior: 'smooth',
152
- });
153
- }
154
- },
155
- );
156
-
157
- return [
158
- scrollPlugin,
159
-
160
- // Update listener to handle scroll effects.
161
- EditorView.updateListener.of((update) => {
162
- update.transactions.forEach((transaction) => {
163
- for (const effect of transaction.effects) {
164
- if (effect.is(scrollToLineEffect)) {
165
- const { line, options = {} } = effect.value;
166
- const plugin = update.view.plugin(scrollPlugin);
167
- if (plugin) {
168
- plugin.scrollToLine(line, { offset, position, ...options });
169
- }
170
- }
171
- }
172
- });
173
- }),
174
- ];
175
- };
176
-
177
- /**
178
- * Helper function to scroll to a specific line.
179
- * This is a convenience function that can be used directly with an EditorView.
180
- *
181
- * @param view - The CodeMirror EditorView instance
182
- * @param line - The line number to scroll to (1-based)
183
- * @param options - Optional scroll configuration
184
- */
185
- export const scrollToLine = (view: EditorView, line: number, options?: SmoothScrollOptions) => {
186
- view.dispatch({
187
- effects: scrollToLineEffect.of({ line, options }),
188
- });
189
- };
@@ -1,243 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { type Extension, StateEffect, StateField } from '@codemirror/state';
6
- import { Decoration, type DecorationSet, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
7
-
8
- import { Domino } from '@dxos/ui';
9
- import { isTruthy } from '@dxos/util';
10
-
11
- const BLINK_RATE = 2_000;
12
-
13
- export type StreamerOptions = {
14
- cursor?: boolean;
15
- // When true, uses defaults. When object, allows configuring removal delay.
16
- fadeIn?: boolean | { removalDelay?: number };
17
- };
18
-
19
- /**
20
- * Extension that adds a blinking cursor widget at the end of the document.
21
- */
22
- export const streamer = (options: StreamerOptions = {}): Extension => {
23
- return [
24
- options.cursor && cursor(),
25
- options.fadeIn && fadeIn(typeof options.fadeIn === 'object' ? options.fadeIn : {}),
26
- ].filter(isTruthy);
27
- };
28
-
29
- /**
30
- * State field to manage the cursor widget decoration.
31
- */
32
- const cursor = (): Extension => {
33
- const hideCursor = StateEffect.define();
34
-
35
- // State field to track if cursor should be shown.
36
- const showCursor = StateField.define<boolean>({
37
- create: () => true,
38
- update: (value, tr) => {
39
- for (const effect of tr.effects) {
40
- if (effect.is(hideCursor)) {
41
- return false;
42
- }
43
- }
44
- if (tr.docChanged) {
45
- return true;
46
- }
47
-
48
- return value;
49
- },
50
- });
51
-
52
- // View plugin to manage timer and dispatch effects.
53
- const timerPlugin = ViewPlugin.fromClass(
54
- class {
55
- timer: any;
56
-
57
- constructor(private view: EditorView) {}
58
-
59
- update(update: ViewUpdate) {
60
- if (update.docChanged) {
61
- clearTimeout(this.timer);
62
- this.timer = setTimeout(() => {
63
- this.view.dispatch({
64
- effects: hideCursor.of(null),
65
- });
66
- }, BLINK_RATE);
67
- }
68
- }
69
-
70
- destroy() {
71
- clearTimeout(this.timer);
72
- }
73
- },
74
- );
75
-
76
- // Decoration field that uses the showCursor state.
77
- const cursorDecoration = StateField.define<DecorationSet>({
78
- create: () => Decoration.none,
79
- update: (_decorations, tr) => {
80
- const show = tr.state.field(showCursor);
81
- if (!show) {
82
- return Decoration.none;
83
- }
84
-
85
- // Always place cursor at the end of the document.
86
- const endPos = tr.state.doc.length;
87
- return Decoration.set([
88
- Decoration.widget({
89
- widget: new CursorWidget(),
90
- side: 1, // Place after the position.
91
- }).range(endPos),
92
- ]);
93
- },
94
- provide: (f) => EditorView.decorations.from(f),
95
- });
96
-
97
- return [showCursor, timerPlugin, cursorDecoration];
98
- };
99
-
100
- /**
101
- * Widget class for the cursor at the end of the document.
102
- * Half
103
- */
104
- class CursorWidget extends WidgetType {
105
- toDOM() {
106
- const inner = Domino.of('span').text('\u258F').style({ animation: 'blink 2s infinite' });
107
- return Domino.of('span').style({ opacity: '0.8' }).children(inner).root;
108
- }
109
- }
110
-
111
- /**
112
- * State field to detect and decorate appended text with a fade-in effect.
113
- * Also schedules removal of the last appended decoration after a delay.
114
- */
115
- const fadeIn = (options: { removalDelay?: number } = {}): Extension => {
116
- const FADE_IN_DURATION = 1_000; // ms.
117
- const DEFAULT_REMOVAL_DELAY = 5_000; // ms.
118
- const removalDelay = options.removalDelay ?? DEFAULT_REMOVAL_DELAY;
119
-
120
- // Effect to remove a specific decoration by range.
121
- const removeDecoration = StateEffect.define<{ from: number; to: number }>();
122
-
123
- // Decoration field that adds fade-in marks for appended content and responds to removal effects.
124
- const fadeField = StateField.define<DecorationSet>({
125
- create: () => Decoration.none,
126
- update: (decorations, tr) => {
127
- let next = decorations;
128
-
129
- // Apply removals first, if any.
130
- for (const effect of tr.effects) {
131
- if (effect.is(removeDecoration)) {
132
- const target = effect.value;
133
- next = next.update({
134
- filter: (from, to) => !(from === target.from && to === target.to),
135
- });
136
- }
137
- }
138
-
139
- if (!tr.docChanged) {
140
- return next;
141
- }
142
-
143
- // Reset decorations if the entire content was replaced.
144
- let isReset = tr.state.doc.length === 0;
145
- if (!isReset) {
146
- tr.changes.iterChanges((fromA, toA) => {
147
- if (fromA === 0 && toA === tr.startState.doc.length) {
148
- isReset = true;
149
- }
150
- });
151
- }
152
- if (isReset) {
153
- return Decoration.none;
154
- }
155
-
156
- // Add fade-in decorations for appended content at the end only.
157
- const add: any[] = [];
158
- tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
159
- // Don't fade in initial content.
160
- if (fromB === 0 && toB === inserted.length) {
161
- return;
162
- }
163
- // At-the-end append.
164
- if (toA === tr.startState.doc.length && inserted.length > 0) {
165
- add.push(Decoration.mark({ class: 'cm-fade-in' }).range(fromB, toB));
166
- }
167
- });
168
-
169
- return next.update({ add });
170
- },
171
- provide: (f) => EditorView.decorations.from(f),
172
- });
173
-
174
- // View plugin that tracks appended ranges and schedules their removal.
175
- const timerPlugin = ViewPlugin.fromClass(
176
- class {
177
- // Map a simple key "from-to" to timer id.
178
- _timers = new Map<string, any>();
179
-
180
- constructor(private view: EditorView) {}
181
-
182
- update(update: ViewUpdate) {
183
- if (!update.docChanged) {
184
- return;
185
- }
186
-
187
- update.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
188
- // Only consider appends at the end.
189
- if (toA !== update.startState.doc.length || inserted.length === 0) {
190
- return;
191
- }
192
-
193
- const key = `${fromB}-${toB}`;
194
- // Clear any prior timer for this exact range.
195
- if (this._timers.has(key)) {
196
- clearTimeout(this._timers.get(key));
197
- }
198
-
199
- const totalDelay = FADE_IN_DURATION + removalDelay;
200
- const id = setTimeout(() => {
201
- this.view.dispatch({
202
- effects: removeDecoration.of({ from: fromB, to: toB }),
203
- });
204
- this._timers.delete(key);
205
- }, totalDelay);
206
-
207
- this._timers.set(key, id);
208
- });
209
- }
210
-
211
- destroy() {
212
- for (const id of this._timers.values()) {
213
- clearTimeout(id);
214
- }
215
- this._timers.clear();
216
- }
217
- },
218
- );
219
-
220
- return [
221
- fadeField,
222
- timerPlugin,
223
- EditorView.theme({
224
- '.cm-line > span': {
225
- opacity: '0.8',
226
- },
227
- '.cm-fade-in': {
228
- animation: 'fade-in 3s ease-out forwards',
229
- },
230
- '@keyframes fade-in': {
231
- '0%': {
232
- opacity: '0',
233
- },
234
- '80%': {
235
- opacity: '1',
236
- },
237
- '100%': {
238
- opacity: '0.8',
239
- },
240
- },
241
- }),
242
- ];
243
- };
@@ -1,68 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { type Extension } from '@codemirror/state';
6
- import { keymap } from '@codemirror/view';
7
-
8
- // TODO(burdon): Review https://github.com/sergeche/codemirror-movie?tab=readme-ov-file
9
-
10
- export type DemoOptions = {
11
- delay?: number;
12
- items?: string[];
13
- };
14
-
15
- const defaultItems = ['hello world!', 'this is a test.', 'this is [DXOS](https://dxos.org)'];
16
-
17
- /**
18
- * Configurable plugin that let's user cycle through pre-configured input script.
19
- */
20
- export const typewriter = ({ delay = 75, items = defaultItems }: DemoOptions = {}): Extension => {
21
- let t: any;
22
- let idx = 0; // TODO(burdon): Make global.
23
-
24
- return [
25
- keymap.of([
26
- {
27
- // Reset.
28
- key: "alt-meta-'",
29
- run: () => {
30
- clearTimeout(t);
31
- idx = 0;
32
- return true;
33
- },
34
- },
35
- {
36
- // Next prompt.
37
- // TODO(burdon): Press 1-9 to select prompt?
38
- key: "Shift-Meta-'",
39
- run: (view) => {
40
- clearTimeout(t);
41
- // TODO(burdon): Add space if needed.
42
- const text = items[idx++];
43
- if (idx === items?.length) {
44
- idx = 0;
45
- }
46
-
47
- let i = 0;
48
- const insert = (d = 0) => {
49
- t = setTimeout(() => {
50
- const pos = view.state.selection.main.head;
51
- view.dispatch({
52
- changes: { from: pos, insert: text[i++] },
53
- selection: { anchor: pos + 1 },
54
- });
55
-
56
- if (i < text.length) {
57
- insert(Math.random() * delay * (text[i] === ' ' ? 2 : 1));
58
- }
59
- }, d);
60
- };
61
-
62
- insert();
63
- return true;
64
- },
65
- },
66
- ]),
67
- ];
68
- };