@dxos/react-ui-editor 0.8.4-main.a4bbb77 → 0.8.4-main.ae835ea

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 (194) hide show
  1. package/dist/lib/browser/{chunk-22UMM3QJ.mjs → chunk-HL3YF6WC.mjs} +2 -2
  2. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +4577 -4832
  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.map +2 -2
  7. package/dist/lib/browser/types/index.mjs +1 -1
  8. package/dist/lib/node-esm/{chunk-YXYQPV6R.mjs → chunk-YJZGD3LY.mjs} +2 -2
  9. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +4577 -4832
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  14. package/dist/lib/node-esm/types/index.mjs +1 -1
  15. package/dist/types/src/components/Editor/Editor.d.ts +13 -4
  16. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  17. package/dist/types/src/components/Editor/Editor.stories.d.ts +27 -0
  18. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +17 -2
  20. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  21. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorToolbar/util.d.ts +5 -19
  23. package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
  24. package/dist/types/src/components/index.d.ts +0 -1
  25. package/dist/types/src/components/index.d.ts.map +1 -1
  26. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
  27. package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
  28. package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
  29. package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
  30. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
  31. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +20 -0
  32. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
  33. package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
  34. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
  35. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  36. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  37. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  38. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  39. package/dist/types/src/extensions/automerge/sync.d.ts +3 -3
  40. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  41. package/dist/types/src/extensions/automerge/update-automerge.d.ts +1 -1
  42. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  43. package/dist/types/src/extensions/autoscroll.d.ts +2 -2
  44. package/dist/types/src/extensions/autoscroll.d.ts.map +1 -1
  45. package/dist/types/src/extensions/awareness/awareness-provider.d.ts +1 -1
  46. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
  47. package/dist/types/src/extensions/factories.d.ts +9 -4
  48. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  49. package/dist/types/src/extensions/index.d.ts +2 -3
  50. package/dist/types/src/extensions/index.d.ts.map +1 -1
  51. package/dist/types/src/extensions/json.d.ts +1 -1
  52. package/dist/types/src/extensions/json.d.ts.map +1 -1
  53. package/dist/types/src/extensions/listener.d.ts +8 -6
  54. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  55. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  56. package/dist/types/src/extensions/markdown/formatting.d.ts +1 -2
  57. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  58. package/dist/types/src/extensions/modes.d.ts +1 -1
  59. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  60. package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
  61. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
  62. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts +36 -0
  63. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts.map +1 -0
  64. package/dist/types/src/extensions/popover/index.d.ts +8 -0
  65. package/dist/types/src/extensions/popover/index.d.ts.map +1 -0
  66. package/dist/types/src/extensions/popover/menu-presets.d.ts +4 -0
  67. package/dist/types/src/extensions/popover/menu-presets.d.ts.map +1 -0
  68. package/dist/types/src/extensions/popover/menu.d.ts +24 -0
  69. package/dist/types/src/extensions/popover/menu.d.ts.map +1 -0
  70. package/dist/types/src/extensions/popover/modal.d.ts +7 -0
  71. package/dist/types/src/extensions/popover/modal.d.ts.map +1 -0
  72. package/dist/types/src/extensions/popover/popover.d.ts +47 -0
  73. package/dist/types/src/extensions/popover/popover.d.ts.map +1 -0
  74. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts +34 -0
  75. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts.map +1 -0
  76. package/dist/types/src/extensions/popover/util.d.ts +8 -0
  77. package/dist/types/src/extensions/popover/util.d.ts.map +1 -0
  78. package/dist/types/src/extensions/preview/preview.d.ts +6 -3
  79. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  80. package/dist/types/src/extensions/state.d.ts +2 -0
  81. package/dist/types/src/extensions/state.d.ts.map +1 -0
  82. package/dist/types/src/hooks/useTextEditor.d.ts +2 -6
  83. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  84. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +1 -1
  85. package/dist/types/src/stories/Comments.stories.d.ts +2 -2
  86. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  87. package/dist/types/src/stories/Experimental.stories.d.ts +2 -2
  88. package/dist/types/src/stories/Markdown.stories.d.ts +2 -2
  89. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  90. package/dist/types/src/stories/{CommandMenu.stories.d.ts → Popover.stories.d.ts} +6 -5
  91. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -0
  92. package/dist/types/src/stories/Preview.stories.d.ts +2 -2
  93. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  94. package/dist/types/src/stories/TextEditor.stories.d.ts +2 -3
  95. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  96. package/dist/types/src/stories/components/EditorStory.d.ts +3 -3
  97. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  98. package/dist/types/src/styles/theme.d.ts.map +1 -1
  99. package/dist/types/src/testing/PreviewPopover.d.ts.map +1 -1
  100. package/dist/types/src/types/types.d.ts +1 -1
  101. package/dist/types/src/types/types.d.ts.map +1 -1
  102. package/dist/types/tsconfig.tsbuildinfo +1 -1
  103. package/package.json +41 -38
  104. package/src/components/Editor/Editor.stories.tsx +69 -0
  105. package/src/components/Editor/Editor.tsx +25 -17
  106. package/src/components/EditorToolbar/EditorToolbar.tsx +88 -87
  107. package/src/components/EditorToolbar/headings.ts +6 -4
  108. package/src/components/EditorToolbar/util.ts +2 -18
  109. package/src/components/index.ts +0 -1
  110. package/src/extensions/{autocomplete.ts → autocomplete/autocomplete.ts} +1 -0
  111. package/src/extensions/autocomplete/index.ts +8 -0
  112. package/src/extensions/autocomplete/match.ts +46 -0
  113. package/src/extensions/{command-menu → autocomplete}/placeholder.ts +21 -17
  114. package/src/extensions/{command-dialog → autocomplete}/typeahead.ts +6 -48
  115. package/src/extensions/automerge/automerge.ts +28 -9
  116. package/src/extensions/automerge/cursor.ts +1 -1
  117. package/src/extensions/automerge/sync.ts +8 -4
  118. package/src/extensions/automerge/update-automerge.ts +1 -1
  119. package/src/extensions/autoscroll.ts +3 -3
  120. package/src/extensions/awareness/awareness-provider.ts +2 -2
  121. package/src/extensions/factories.ts +18 -10
  122. package/src/extensions/hashtag.tsx +2 -2
  123. package/src/extensions/index.ts +2 -3
  124. package/src/extensions/json.ts +1 -1
  125. package/src/extensions/listener.ts +14 -20
  126. package/src/extensions/markdown/bundle.ts +14 -2
  127. package/src/extensions/markdown/formatting.ts +8 -8
  128. package/src/extensions/modes.ts +2 -2
  129. package/src/extensions/{floating-menu.ts → outliner/menu.ts} +7 -5
  130. package/src/extensions/outliner/outliner.ts +2 -2
  131. package/src/extensions/popover/PopoverMenuProvider.tsx +220 -0
  132. package/src/extensions/popover/index.ts +12 -0
  133. package/src/extensions/popover/menu-presets.ts +124 -0
  134. package/src/extensions/popover/menu.ts +67 -0
  135. package/src/extensions/popover/modal.ts +24 -0
  136. package/src/extensions/popover/popover.ts +289 -0
  137. package/src/extensions/popover/usePopoverMenu.ts +173 -0
  138. package/src/extensions/popover/util.ts +29 -0
  139. package/src/extensions/preview/index.ts +1 -1
  140. package/src/extensions/preview/preview.ts +10 -7
  141. package/src/extensions/state.ts +7 -0
  142. package/src/hooks/useTextEditor.ts +21 -21
  143. package/src/stories/CommandDialog.stories.tsx +3 -14
  144. package/src/stories/EditorToolbar.stories.tsx +4 -5
  145. package/src/stories/Outliner.stories.tsx +16 -9
  146. package/src/stories/Popover.stories.tsx +163 -0
  147. package/src/stories/Preview.stories.tsx +15 -8
  148. package/src/stories/TextEditor.stories.tsx +3 -29
  149. package/src/stories/components/EditorStory.tsx +5 -3
  150. package/src/styles/theme.ts +2 -1
  151. package/src/testing/PreviewPopover.tsx +2 -0
  152. package/src/types/types.ts +1 -1
  153. package/dist/lib/browser/chunk-22UMM3QJ.mjs.map +0 -7
  154. package/dist/lib/node-esm/chunk-YXYQPV6R.mjs.map +0 -7
  155. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts +0 -38
  156. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts.map +0 -1
  157. package/dist/types/src/components/CommandMenu/index.d.ts +0 -2
  158. package/dist/types/src/components/CommandMenu/index.d.ts.map +0 -1
  159. package/dist/types/src/extensions/autocomplete.d.ts.map +0 -1
  160. package/dist/types/src/extensions/command-dialog/action.d.ts +0 -17
  161. package/dist/types/src/extensions/command-dialog/action.d.ts.map +0 -1
  162. package/dist/types/src/extensions/command-dialog/command-dialog.d.ts +0 -6
  163. package/dist/types/src/extensions/command-dialog/command-dialog.d.ts.map +0 -1
  164. package/dist/types/src/extensions/command-dialog/hint.d.ts +0 -19
  165. package/dist/types/src/extensions/command-dialog/hint.d.ts.map +0 -1
  166. package/dist/types/src/extensions/command-dialog/index.d.ts +0 -4
  167. package/dist/types/src/extensions/command-dialog/index.d.ts.map +0 -1
  168. package/dist/types/src/extensions/command-dialog/state.d.ts +0 -16
  169. package/dist/types/src/extensions/command-dialog/state.d.ts.map +0 -1
  170. package/dist/types/src/extensions/command-dialog/typeahead.d.ts +0 -22
  171. package/dist/types/src/extensions/command-dialog/typeahead.d.ts.map +0 -1
  172. package/dist/types/src/extensions/command-menu/command-menu.d.ts +0 -20
  173. package/dist/types/src/extensions/command-menu/command-menu.d.ts.map +0 -1
  174. package/dist/types/src/extensions/command-menu/index.d.ts +0 -3
  175. package/dist/types/src/extensions/command-menu/index.d.ts.map +0 -1
  176. package/dist/types/src/extensions/command-menu/placeholder.d.ts +0 -10
  177. package/dist/types/src/extensions/command-menu/placeholder.d.ts.map +0 -1
  178. package/dist/types/src/extensions/command-menu/useCommandMenu.d.ts +0 -24
  179. package/dist/types/src/extensions/command-menu/useCommandMenu.d.ts.map +0 -1
  180. package/dist/types/src/extensions/floating-menu.d.ts +0 -7
  181. package/dist/types/src/extensions/floating-menu.d.ts.map +0 -1
  182. package/dist/types/src/stories/CommandMenu.stories.d.ts.map +0 -1
  183. package/src/components/CommandMenu/CommandMenu.tsx +0 -348
  184. package/src/components/CommandMenu/index.ts +0 -5
  185. package/src/extensions/command-dialog/action.ts +0 -55
  186. package/src/extensions/command-dialog/command-dialog.ts +0 -34
  187. package/src/extensions/command-dialog/hint.ts +0 -103
  188. package/src/extensions/command-dialog/index.ts +0 -7
  189. package/src/extensions/command-dialog/state.ts +0 -90
  190. package/src/extensions/command-menu/command-menu.ts +0 -210
  191. package/src/extensions/command-menu/index.ts +0 -6
  192. package/src/extensions/command-menu/useCommandMenu.ts +0 -134
  193. package/src/stories/CommandMenu.stories.tsx +0 -158
  194. /package/dist/types/src/extensions/{autocomplete.d.ts → autocomplete/autocomplete.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.8.4-main.a4bbb77",
3
+ "version": "0.8.4-main.ae835ea",
4
4
  "description": "Document editing experience within a DXOS shell.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -40,6 +40,7 @@
40
40
  "@automerge/automerge": "3.1.2",
41
41
  "@codemirror/autocomplete": "^6.19.0",
42
42
  "@codemirror/commands": "^6.8.1",
43
+ "@codemirror/lang-html": "^6.4.11",
43
44
  "@codemirror/lang-javascript": "^6.2.4",
44
45
  "@codemirror/lang-json": "^6.0.2",
45
46
  "@codemirror/lang-markdown": "^6.3.4",
@@ -62,6 +63,7 @@
62
63
  "@radix-ui/react-use-controllable-state": "1.1.0",
63
64
  "@replit/codemirror-vim": "^6.2.1",
64
65
  "@replit/codemirror-vscode-keymap": "^6.0.2",
66
+ "@uiw/codemirror-theme-vscode": "^4.25.2",
65
67
  "ajv": "^8.17.1",
66
68
  "codemirror": "^6.0.1",
67
69
  "lib0": "^0.2.65",
@@ -69,27 +71,28 @@
69
71
  "lodash.merge": "^4.6.2",
70
72
  "lodash.sortby": "^4.7.0",
71
73
  "style-mod": "^4.1.0",
72
- "@dxos/app-graph": "0.8.4-main.a4bbb77",
73
- "@dxos/async": "0.8.4-main.a4bbb77",
74
- "@dxos/context": "0.8.4-main.a4bbb77",
75
- "@dxos/debug": "0.8.4-main.a4bbb77",
76
- "@dxos/display-name": "0.8.4-main.a4bbb77",
77
- "@dxos/echo-schema": "0.8.4-main.a4bbb77",
78
- "@dxos/invariant": "0.8.4-main.a4bbb77",
79
- "@dxos/lit-ui": "0.8.4-main.a4bbb77",
80
- "@dxos/live-object": "0.8.4-main.a4bbb77",
81
- "@dxos/log": "0.8.4-main.a4bbb77",
82
- "@dxos/protocols": "0.8.4-main.a4bbb77",
83
- "@dxos/react-ui-menu": "0.8.4-main.a4bbb77",
84
- "@dxos/react-hooks": "0.8.4-main.a4bbb77",
85
- "@dxos/react-ui-types": "0.8.4-main.a4bbb77",
86
- "@dxos/util": "0.8.4-main.a4bbb77",
87
- "@dxos/react-ui-stack": "0.8.4-main.a4bbb77"
74
+ "@dxos/app-graph": "0.8.4-main.ae835ea",
75
+ "@dxos/client": "0.8.4-main.ae835ea",
76
+ "@dxos/async": "0.8.4-main.ae835ea",
77
+ "@dxos/context": "0.8.4-main.ae835ea",
78
+ "@dxos/debug": "0.8.4-main.ae835ea",
79
+ "@dxos/display-name": "0.8.4-main.ae835ea",
80
+ "@dxos/invariant": "0.8.4-main.ae835ea",
81
+ "@dxos/live-object": "0.8.4-main.ae835ea",
82
+ "@dxos/lit-ui": "0.8.4-main.ae835ea",
83
+ "@dxos/log": "0.8.4-main.ae835ea",
84
+ "@dxos/react-hooks": "0.8.4-main.ae835ea",
85
+ "@dxos/react-ui-menu": "0.8.4-main.ae835ea",
86
+ "@dxos/protocols": "0.8.4-main.ae835ea",
87
+ "@dxos/echo": "0.8.4-main.ae835ea",
88
+ "@dxos/react-ui-stack": "0.8.4-main.ae835ea",
89
+ "@dxos/util": "0.8.4-main.ae835ea",
90
+ "@dxos/react-ui-types": "0.8.4-main.ae835ea"
88
91
  },
89
92
  "devDependencies": {
90
93
  "@automerge/automerge": "3.1.2",
91
- "@automerge/automerge-repo": "2.3.1",
92
- "@automerge/automerge-repo-network-broadcastchannel": "2.3.1",
94
+ "@automerge/automerge-repo": "2.4.0",
95
+ "@automerge/automerge-repo-network-broadcastchannel": "2.4.0",
93
96
  "@effect-rx/rx-react": "0.42.4",
94
97
  "@effect/platform": "0.92.1",
95
98
  "@types/chai": "^4.2.15",
@@ -97,14 +100,14 @@
97
100
  "@types/lodash.defaultsdeep": "^4.6.6",
98
101
  "@types/lodash.merge": "^4.6.6",
99
102
  "@types/lodash.sortby": "^4.7.7",
100
- "@types/react": "~19.2.0",
101
- "@types/react-dom": "~19.2.0",
103
+ "@types/react": "~19.2.2",
104
+ "@types/react-dom": "~19.2.2",
102
105
  "@types/react-test-renderer": "^17.0.2",
103
106
  "chai": "^4.4.1",
104
107
  "chai-dom": "^1.11.0",
105
108
  "effect": "3.18.3",
106
109
  "happy-dom": "^13.3.1",
107
- "jsdom": "^24.0.0",
110
+ "jsdom": "^27.0.0",
108
111
  "mocha": "^10.6.0",
109
112
  "react": "~19.2.0",
110
113
  "react-dom": "~19.2.0",
@@ -112,19 +115,19 @@
112
115
  "vite": "7.1.9",
113
116
  "vite-plugin-top-level-await": "^1.6.0",
114
117
  "vite-plugin-wasm": "^3.5.0",
115
- "@dxos/echo": "0.8.4-main.a4bbb77",
116
- "@dxos/echo-signals": "0.8.4-main.a4bbb77",
117
- "@dxos/config": "0.8.4-main.a4bbb77",
118
- "@dxos/react-client": "0.8.4-main.a4bbb77",
119
- "@dxos/random": "0.8.4-main.a4bbb77",
120
- "@dxos/react-ui": "0.8.4-main.a4bbb77",
121
- "@dxos/keyboard": "0.8.4-main.a4bbb77",
122
- "@dxos/react-ui-attention": "0.8.4-main.a4bbb77",
123
- "@dxos/react-ui-stack": "0.8.4-main.a4bbb77",
124
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.a4bbb77",
125
- "@dxos/react-ui-theme": "0.8.4-main.a4bbb77",
126
- "@dxos/schema": "0.8.4-main.a4bbb77",
127
- "@dxos/storybook-utils": "0.8.4-main.a4bbb77"
118
+ "@dxos/config": "0.8.4-main.ae835ea",
119
+ "@dxos/echo": "0.8.4-main.ae835ea",
120
+ "@dxos/echo-signals": "0.8.4-main.ae835ea",
121
+ "@dxos/random": "0.8.4-main.ae835ea",
122
+ "@dxos/keyboard": "0.8.4-main.ae835ea",
123
+ "@dxos/react-client": "0.8.4-main.ae835ea",
124
+ "@dxos/react-ui": "0.8.4-main.ae835ea",
125
+ "@dxos/react-ui-attention": "0.8.4-main.ae835ea",
126
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.ae835ea",
127
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
128
+ "@dxos/schema": "0.8.4-main.ae835ea",
129
+ "@dxos/react-ui-stack": "0.8.4-main.ae835ea",
130
+ "@dxos/storybook-utils": "0.8.4-main.ae835ea"
128
131
  },
129
132
  "peerDependencies": {
130
133
  "@effect-rx/rx-react": "^0.34.1",
@@ -132,9 +135,9 @@
132
135
  "effect": "^3.13.3",
133
136
  "react": "^19.0.0",
134
137
  "react-dom": "^19.0.0",
135
- "@dxos/react-client": "0.8.4-main.a4bbb77",
136
- "@dxos/react-ui": "0.8.4-main.a4bbb77",
137
- "@dxos/react-ui-theme": "0.8.4-main.a4bbb77"
138
+ "@dxos/react-client": "0.8.4-main.ae835ea",
139
+ "@dxos/react-ui-theme": "0.8.4-main.ae835ea",
140
+ "@dxos/react-ui": "0.8.4-main.ae835ea"
138
141
  },
139
142
  "publishConfig": {
140
143
  "access": "public"
@@ -0,0 +1,69 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React from 'react';
7
+ import { useMemo } from 'react';
8
+
9
+ import { createDocAccessor, createObject } from '@dxos/client/echo';
10
+ import { useThemeContext } from '@dxos/react-ui';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
+ import { DataType } from '@dxos/schema';
13
+
14
+ import { automerge, createBasicExtensions, createThemeExtensions } from '../../extensions';
15
+ import { Editor } from '../Editor';
16
+
17
+ const meta = {
18
+ title: 'ui/react-ui-editor/Editor',
19
+ component: Editor,
20
+ decorators: [withTheme, withLayout({ container: 'column' })],
21
+ } satisfies Meta<typeof Editor>;
22
+
23
+ export default meta;
24
+
25
+ type Story = StoryObj<typeof meta>;
26
+
27
+ export const Default: Story = {
28
+ render: (args) => {
29
+ const { themeMode } = useThemeContext();
30
+ const extensions = useMemo(
31
+ () => [
32
+ // Basic extensions.
33
+ createBasicExtensions(),
34
+ createThemeExtensions({ themeMode }),
35
+ ],
36
+ [],
37
+ );
38
+
39
+ return <Editor classNames='p-2' {...args} extensions={extensions} />;
40
+ },
41
+ args: {
42
+ moveToEnd: true,
43
+ value: 'Hello world!',
44
+ onChange: (value) => console.log(value),
45
+ },
46
+ };
47
+
48
+ export const Automerge: Story = {
49
+ render: ({ value, ...props }) => {
50
+ const { themeMode } = useThemeContext();
51
+ const extensions = useMemo(
52
+ () => [
53
+ // Basic extensions.
54
+ createBasicExtensions(),
55
+ createThemeExtensions({ themeMode }),
56
+ automerge(createDocAccessor(createObject(DataType.makeText(value)), ['content'])),
57
+ ],
58
+ [],
59
+ );
60
+
61
+ // TODO(burdon): Remove the need for initialValue.
62
+ return <Editor classNames='p-2' {...props} initialValue={value} extensions={extensions} />;
63
+ },
64
+ args: {
65
+ moveToEnd: true,
66
+ value: 'Hello world!',
67
+ onChange: (value) => console.log(value),
68
+ },
69
+ };
@@ -2,14 +2,22 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { Transaction } from '@codemirror/state';
5
6
  import { EditorView } from '@codemirror/view';
6
7
  import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
7
8
 
8
9
  import { type ThemedClassName } from '@dxos/react-ui';
9
10
  import { mx } from '@dxos/react-ui-theme';
10
11
 
12
+ import { initialSync } from '../../extensions';
11
13
  import { type UseTextEditorProps, useTextEditor } from '../../hooks';
12
14
 
15
+ // TODO(burdon): Convert to radix-style (support hooks inside).
16
+ // <Editor.Root>
17
+ // <Editor.Toolbar />
18
+ // <Editor.TextEditor />
19
+ // </Editor.Root>
20
+
13
21
  export type EditorController = {
14
22
  view: EditorView | null;
15
23
  focus: () => void;
@@ -17,10 +25,10 @@ export type EditorController = {
17
25
 
18
26
  export type EditorProps = ThemedClassName<
19
27
  {
20
- value?: string;
21
28
  moveToEnd?: boolean;
29
+ value?: string;
22
30
  onChange?: (value: string) => void;
23
- } & Omit<UseTextEditorProps, 'initialValue'>
31
+ } & UseTextEditorProps
24
32
  >;
25
33
 
26
34
  /**
@@ -28,17 +36,17 @@ export type EditorProps = ThemedClassName<
28
36
  * NOTE: This shouold not be used with the automerge extension.
29
37
  */
30
38
  export const Editor = forwardRef<EditorController, EditorProps>(
31
- ({ classNames, id, extensions = [], value, moveToEnd, onChange, ...props }, forwardedRef) => {
39
+ ({ classNames, id, extensions, moveToEnd, value, onChange, ...props }, forwardedRef) => {
32
40
  const { parentRef, focusAttributes, view } = useTextEditor(
33
41
  () => ({
34
42
  id,
43
+ initialValue: value,
35
44
  extensions: [
36
- extensions,
37
- EditorView.updateListener.of((update) => {
38
- const startValue = update.startState.doc.toString();
39
- const value = update.state.doc.toString();
40
- if (startValue !== value) {
41
- onChange?.(value);
45
+ extensions ?? [],
46
+ EditorView.updateListener.of(({ view, docChanged, transactions }) => {
47
+ const isInitialSync = transactions.some((tr) => tr.annotation(Transaction.userEvent) === initialSync.value);
48
+ if (!isInitialSync && docChanged) {
49
+ onChange?.(view.state.doc.toString());
42
50
  }
43
51
  }),
44
52
  ],
@@ -57,16 +65,16 @@ export const Editor = forwardRef<EditorController, EditorProps>(
57
65
  [view],
58
66
  );
59
67
 
60
- // Update content.
68
+ // Set initial value and cursor position.
61
69
  useEffect(() => {
62
- if (value !== view?.state.doc.toString()) {
63
- requestAnimationFrame(() => {
64
- view?.dispatch({
65
- changes: { from: 0, to: view.state.doc.length, insert: value },
66
- selection: moveToEnd ? { anchor: value?.length ?? 0 } : undefined,
67
- });
70
+ requestAnimationFrame(() => {
71
+ view?.dispatch({
72
+ annotations: initialSync,
73
+ changes: value ? [{ from: 0, to: view?.state.doc.length ?? 0, insert: value ?? '' }] : [],
74
+ selection: moveToEnd ? { anchor: view?.state.doc.length ?? 0 } : undefined,
68
75
  });
69
- }
76
+ view?.focus();
77
+ });
70
78
  }, [view, value, moveToEnd]);
71
79
 
72
80
  return <div role='none' className={mx('is-full', classNames)} {...focusAttributes} ref={parentRef} />;
@@ -6,7 +6,7 @@ import { Rx } from '@effect-rx/rx-react';
6
6
  import React, { memo, useMemo } from 'react';
7
7
 
8
8
  import { rxFromSignal } from '@dxos/app-graph';
9
- import { ElevationProvider } from '@dxos/react-ui';
9
+ import { ElevationProvider, type ThemedClassName } from '@dxos/react-ui';
10
10
  import {
11
11
  type ActionGraphProps,
12
12
  MenuProvider,
@@ -15,120 +15,121 @@ import {
15
15
  useMenuActions,
16
16
  } from '@dxos/react-ui-menu';
17
17
 
18
+ import { type EditorViewMode } from '../../types';
19
+
18
20
  import { createBlocks } from './blocks';
19
21
  import { createFormatting } from './formatting';
20
22
  import { createHeadings } from './headings';
21
23
  import { createImageUpload } from './image';
22
24
  import { createLists } from './lists';
23
25
  import { createSearch } from './search';
24
- import { type EditorToolbarActionGraphProps, type EditorToolbarFeatureFlags, type EditorToolbarProps } from './util';
26
+ import { type EditorToolbarActionGraphProps } from './util';
25
27
  import { createViewMode } from './view-mode';
26
28
 
29
+ export type EditorToolbarFeatureFlags = Partial<{
30
+ showHeadings: boolean;
31
+ showFormatting: boolean;
32
+ showLists: boolean;
33
+ showBlocks: boolean;
34
+ showSearch: boolean;
35
+
36
+ // TODO(wittjosiah): Factor out (depends on plugin-level capabilities.)
37
+ onImageUpload: () => void;
38
+ onViewModeChange: (mode: EditorViewMode) => void;
39
+ }>;
40
+
41
+ export type EditorToolbarProps = ThemedClassName<
42
+ {
43
+ role?: string;
44
+ attendableId?: string;
45
+ } & (EditorToolbarActionGraphProps & EditorToolbarFeatureFlags)
46
+ >;
47
+
48
+ // TODO(burdon): Remove role dependency.
49
+ export const EditorToolbar = memo(({ classNames, role, attendableId, ...props }: EditorToolbarProps) => {
50
+ const menuProps = useEditorToolbarActionGraph(props);
51
+
52
+ return (
53
+ <ElevationProvider elevation={role === 'section' ? 'positioned' : 'base'}>
54
+ <MenuProvider {...menuProps} attendableId={attendableId}>
55
+ <ToolbarMenu classNames={classNames} textBlockWidth />
56
+ </MenuProvider>
57
+ </ElevationProvider>
58
+ );
59
+ });
60
+
61
+ type ToolbarActionsProps = Pick<EditorToolbarActionGraphProps, 'state' | 'getView' | 'customActions'> &
62
+ EditorToolbarFeatureFlags;
63
+
64
+ // TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.
65
+ // E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.
66
+ // This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.
67
+ const useEditorToolbarActionGraph = ({ state, getView, customActions, ...features }: ToolbarActionsProps) => {
68
+ const menuCreator = useMemo(
69
+ () => createToolbarActions({ state, getView, customActions, ...features }),
70
+ [
71
+ state,
72
+ getView,
73
+ customActions,
74
+ features?.showHeadings,
75
+ features?.showFormatting,
76
+ features?.showLists,
77
+ features?.showBlocks,
78
+ features?.showSearch,
79
+ features?.onImageUpload,
80
+ features?.onViewModeChange,
81
+ ],
82
+ );
83
+
84
+ return useMenuActions(menuCreator);
85
+ };
86
+
27
87
  const createToolbarActions = ({
28
- getView,
29
88
  state,
89
+ getView,
30
90
  customActions,
31
91
  ...features
32
- }: EditorToolbarFeatureFlags &
33
- Pick<EditorToolbarActionGraphProps, 'getView' | 'state' | 'customActions'>): Rx.Rx<ActionGraphProps> => {
92
+ }: ToolbarActionsProps): Rx.Rx<ActionGraphProps> => {
34
93
  return Rx.make((get) => {
35
94
  const graph: ActionGraphProps = {
36
95
  nodes: [],
37
96
  edges: [],
38
97
  };
39
98
 
40
- if (features.headings ?? true) {
41
- const headings = get(rxFromSignal(() => createHeadings(state, getView)));
42
- graph.nodes.push(...headings.nodes);
43
- graph.edges.push(...headings.edges);
44
- }
45
- if (features.formatting ?? true) {
46
- const formatting = get(rxFromSignal(() => createFormatting(state, getView)));
47
- graph.nodes.push(...formatting.nodes);
48
- graph.edges.push(...formatting.edges);
99
+ // TODO(burdon): Builder pattern?
100
+ const addSubGraph = (graph: ActionGraphProps, subGraph: ActionGraphProps) => {
101
+ graph.nodes.push(...subGraph.nodes);
102
+ graph.edges.push(...subGraph.edges);
103
+ };
104
+
105
+ if (features?.showHeadings ?? true) {
106
+ addSubGraph(graph, get(rxFromSignal(() => createHeadings(state, getView))));
49
107
  }
50
- if (features.lists ?? true) {
51
- const lists = get(rxFromSignal(() => createLists(state, getView)));
52
- graph.nodes.push(...lists.nodes);
53
- graph.edges.push(...lists.edges);
108
+ if (features?.showFormatting ?? true) {
109
+ addSubGraph(graph, get(rxFromSignal(() => createFormatting(state, getView))));
54
110
  }
55
- if (features.blocks ?? true) {
56
- const blocks = get(rxFromSignal(() => createBlocks(state, getView)));
57
- graph.nodes.push(...blocks.nodes);
58
- graph.edges.push(...blocks.edges);
111
+ if (features?.showLists ?? true) {
112
+ addSubGraph(graph, get(rxFromSignal(() => createLists(state, getView))));
59
113
  }
60
- if (features.image) {
61
- const image = get(rxFromSignal(() => createImageUpload(features.image!)));
62
- graph.nodes.push(...image.nodes);
63
- graph.edges.push(...image.edges);
114
+ if (features?.showBlocks ?? true) {
115
+ addSubGraph(graph, get(rxFromSignal(() => createBlocks(state, getView))));
64
116
  }
65
- {
66
- const gap = createGapSeparator();
67
- graph.nodes.push(...gap.nodes);
68
- graph.edges.push(...gap.edges);
117
+ if (features?.onImageUpload) {
118
+ addSubGraph(graph, get(rxFromSignal(() => createImageUpload(features.onImageUpload!))));
69
119
  }
120
+
121
+ addSubGraph(graph, createGapSeparator());
122
+
70
123
  if (customActions) {
71
- const custom = get(customActions);
72
- graph.nodes.push(...custom.nodes);
73
- graph.edges.push(...custom.edges);
124
+ addSubGraph(graph, get(customActions));
74
125
  }
75
- if (features.search ?? true) {
76
- const search = get(rxFromSignal(() => createSearch(getView)));
77
- graph.nodes.push(...search.nodes);
78
- graph.edges.push(...search.edges);
126
+ if (features?.showSearch ?? true) {
127
+ addSubGraph(graph, get(rxFromSignal(() => createSearch(getView))));
79
128
  }
80
- if (features.viewMode) {
81
- const viewMode = get(rxFromSignal(() => createViewMode(state, features.viewMode!)));
82
- graph.nodes.push(...viewMode.nodes);
83
- graph.edges.push(...viewMode.edges);
129
+ if (features?.onViewModeChange) {
130
+ addSubGraph(graph, get(rxFromSignal(() => createViewMode(state, features.onViewModeChange!))));
84
131
  }
85
132
 
86
133
  return graph;
87
134
  });
88
135
  };
89
-
90
- // TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.
91
- // E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.
92
- // This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.
93
- const useEditorToolbarActionGraph = (props: EditorToolbarProps) => {
94
- const menuCreator = useMemo(
95
- () =>
96
- createToolbarActions({
97
- getView: props.getView,
98
- state: props.state,
99
- customActions: props.customActions,
100
- headings: props.headings,
101
- formatting: props.formatting,
102
- lists: props.lists,
103
- blocks: props.blocks,
104
- image: props.image,
105
- search: props.search,
106
- viewMode: props.viewMode,
107
- }),
108
- [
109
- props.getView,
110
- props.state,
111
- props.customActions,
112
- props.headings,
113
- props.formatting,
114
- props.lists,
115
- props.blocks,
116
- props.image,
117
- props.search,
118
- props.viewMode,
119
- ],
120
- );
121
-
122
- return useMenuActions(menuCreator);
123
- };
124
-
125
- export const EditorToolbar = memo(({ classNames, attendableId, role, ...props }: EditorToolbarProps) => {
126
- const menuProps = useEditorToolbarActionGraph(props);
127
- return (
128
- <ElevationProvider elevation={role === 'section' ? 'positioned' : 'base'}>
129
- <MenuProvider {...menuProps} attendableId={attendableId}>
130
- <ToolbarMenu classNames={classNames} textBlockWidth />
131
- </MenuProvider>
132
- </ElevationProvider>
133
- );
134
- });
@@ -19,12 +19,13 @@ const createHeadingGroupAction = (value: string) =>
19
19
  variant: 'dropdownMenu',
20
20
  applyActive: true,
21
21
  selectCardinality: 'single',
22
+ // TODO(wittjosiah): Remove? Not sure this does anything.
22
23
  value,
23
24
  } as ToolbarMenuActionGroupProperties,
24
25
  'ph--text-h--regular',
25
26
  );
26
27
 
27
- const createHeadingActions = (getView: () => EditorView) =>
28
+ const createHeadingActions = (currentLevel: string, getView: () => EditorView) =>
28
29
  Object.entries({
29
30
  '0': 'ph--paragraph--regular',
30
31
  '1': 'ph--text-h-one--regular',
@@ -40,6 +41,7 @@ const createHeadingActions = (getView: () => EditorView) =>
40
41
  {
41
42
  label: ['heading level label', { count: level, ns: translationKey }],
42
43
  icon,
44
+ checked: levelStr === currentLevel,
43
45
  },
44
46
  () => setHeading(level)(getView()),
45
47
  );
@@ -47,14 +49,14 @@ const createHeadingActions = (getView: () => EditorView) =>
47
49
 
48
50
  const computeHeadingValue = (state: EditorToolbarState) => {
49
51
  const blockType = state ? state.blockType : 'paragraph';
50
- const header = blockType && /heading(\d)/.exec(blockType);
51
- return header ? header[1] : blockType === 'paragraph' || !blockType ? '0' : '';
52
+ const heading = blockType && /heading(\d)/.exec(blockType);
53
+ return heading ? heading[1] : blockType === 'paragraph' || !blockType ? '0' : '';
52
54
  };
53
55
 
54
56
  export const createHeadings = (state: EditorToolbarState, getView: () => EditorView) => {
55
57
  const headingValue = computeHeadingValue(state);
56
58
  const headingGroupAction = createHeadingGroupAction(headingValue);
57
- const headingActions = createHeadingActions(getView);
59
+ const headingActions = createHeadingActions(headingValue, getView);
58
60
  return {
59
61
  nodes: [headingGroupAction as NodeArg<any>, ...headingActions],
60
62
  edges: [
@@ -8,7 +8,6 @@ import { useMemo } from 'react';
8
8
 
9
9
  import { type Action } from '@dxos/app-graph';
10
10
  import { type Live, live } from '@dxos/live-object';
11
- import { type ThemedClassName } from '@dxos/react-ui';
12
11
  import {
13
12
  type ActionGraphProps,
14
13
  type MenuActionProperties,
@@ -23,23 +22,12 @@ import type { EditorAction, Formatting } from '../../extensions';
23
22
  import { translationKey } from '../../translations';
24
23
  import { type EditorViewMode } from '../../types';
25
24
 
26
- export type EditorToolbarState = Formatting & Partial<{ viewMode: EditorViewMode }>;
25
+ export type EditorToolbarState = { viewMode?: EditorViewMode } & Formatting;
27
26
 
28
- export const useEditorToolbarState = (initialState: Partial<EditorToolbarState> = {}) => {
27
+ export const useEditorToolbarState = (initialState: Partial<EditorToolbarState> = {}): Live<EditorToolbarState> => {
29
28
  return useMemo(() => live<EditorToolbarState>(initialState), []);
30
29
  };
31
30
 
32
- export type EditorToolbarFeatureFlags = Partial<{
33
- headings: boolean;
34
- formatting: boolean;
35
- lists: boolean;
36
- blocks: boolean;
37
- search: boolean;
38
- // TODO(wittjosiah): Factor out. Depend on plugin-level capabilities.
39
- image: () => void;
40
- viewMode: (mode: EditorViewMode) => void;
41
- }>;
42
-
43
31
  export type EditorToolbarActionGraphProps = {
44
32
  state: Live<EditorToolbarState>;
45
33
  getView: () => EditorView;
@@ -47,10 +35,6 @@ export type EditorToolbarActionGraphProps = {
47
35
  customActions?: Rx.Rx<ActionGraphProps>;
48
36
  };
49
37
 
50
- export type EditorToolbarProps = ThemedClassName<
51
- EditorToolbarActionGraphProps & EditorToolbarFeatureFlags & { attendableId?: string; role?: string }
52
- >;
53
-
54
38
  export type EditorToolbarItem = EditorAction | MenuItemGroup | MenuSeparator;
55
39
 
56
40
  export const createEditorAction = (id: string, props: Partial<MenuActionProperties>, invoke: () => void) => {
@@ -2,6 +2,5 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export * from './CommandMenu';
6
5
  export * from './Editor';
7
6
  export * from './EditorToolbar';
@@ -40,6 +40,7 @@ export type AutocompleteOptions = {
40
40
  * Creates an autocomplete extension that shows inline suggestions.
41
41
  * Pressing Tab will complete the suggestion.
42
42
  */
43
+ // TODO(burdon): Reconcile with typeahead.
43
44
  export const autocomplete = ({ fireIfEmpty, onSubmit, onSuggest, onCancel }: AutocompleteOptions = {}): Extension => {
44
45
  const suggest = ViewPlugin.fromClass(
45
46
  class {
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './autocomplete';
6
+ export * from './match';
7
+ export * from './placeholder';
8
+ export * from './typeahead';
@@ -0,0 +1,46 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export type CompoetionContext = { line: string };
6
+
7
+ export type CompletionOptions = {
8
+ default?: string;
9
+ minLength?: number;
10
+ };
11
+
12
+ /**
13
+ * Util to match current line to a static list of completions.
14
+ */
15
+ export const staticCompletion =
16
+ (completions: string[], options: CompletionOptions = {}) =>
17
+ ({ line }: CompoetionContext) => {
18
+ if (line.length === 0 && options.default) {
19
+ return options.default;
20
+ }
21
+
22
+ const parts = line.split(/\s+/).filter(Boolean);
23
+ if (parts.length) {
24
+ const str = parts.at(-1)!;
25
+ if (str.length >= (options.minLength ?? 0)) {
26
+ for (const completion of completions) {
27
+ const match = matchCompletion(completion, str);
28
+ if (match) {
29
+ return match;
30
+ }
31
+ }
32
+ }
33
+ }
34
+ };
35
+
36
+ export const matchCompletion = (completion: string, str: string, minLength = 0): string | undefined => {
37
+ if (
38
+ str.length >= minLength &&
39
+ completion.length > str.length &&
40
+ completion.startsWith(str)
41
+ // TODO(burdon): If case insensitive, need to replace existing chars.
42
+ // completion.toLowerCase().startsWith(str.toLowerCase())
43
+ ) {
44
+ return completion.slice(str.length);
45
+ }
46
+ };