@dxos/react-ui-editor 0.3.11-main.008b7c6 → 0.3.11-main.0252f93

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 (187) hide show
  1. package/dist/lib/browser/index.mjs +1510 -1369
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +16 -14
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +20 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  11. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/Toolbar/Toolbar.d.ts +9 -0
  13. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -0
  14. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +14 -0
  15. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -0
  16. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  17. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  18. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +2 -0
  19. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +1 -0
  20. package/dist/types/src/extensions/automerge/automerge.test.d.ts +2 -0
  21. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -0
  22. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  23. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  24. package/dist/types/src/extensions/automerge/defs.d.ts +9 -11
  25. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  26. package/dist/types/src/extensions/automerge/semaphore.d.ts +8 -12
  27. package/dist/types/src/extensions/automerge/semaphore.d.ts.map +1 -1
  28. package/dist/types/src/extensions/automerge/update-automerge.d.ts +2 -2
  29. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  30. package/dist/types/src/extensions/awareness.d.ts +2 -2
  31. package/dist/types/src/extensions/awareness.d.ts.map +1 -1
  32. package/dist/types/src/extensions/basic.d.ts +4 -5
  33. package/dist/types/src/extensions/basic.d.ts.map +1 -1
  34. package/dist/types/src/extensions/comments.d.ts +37 -20
  35. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  36. package/dist/types/src/{util → extensions}/cursor.d.ts +10 -2
  37. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  38. package/dist/types/src/extensions/index.d.ts +2 -8
  39. package/dist/types/src/extensions/index.d.ts.map +1 -1
  40. package/dist/types/src/extensions/markdown/bundle.d.ts +3 -6
  41. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  42. package/dist/types/src/extensions/markdown/code.d.ts.map +1 -0
  43. package/dist/types/src/extensions/markdown/formatting.d.ts +11 -0
  44. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -0
  45. package/dist/types/src/extensions/markdown/heading.d.ts +6 -0
  46. package/dist/types/src/extensions/markdown/heading.d.ts.map +1 -0
  47. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  48. package/dist/types/src/extensions/markdown/hr.d.ts.map +1 -0
  49. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -0
  50. package/dist/types/src/extensions/markdown/index.d.ts +8 -0
  51. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -0
  53. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -0
  54. package/dist/types/src/extensions/markdown/tasklist.d.ts.map +1 -0
  55. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  56. package/dist/types/src/extensions/outliner.d.ts +4 -0
  57. package/dist/types/src/extensions/outliner.d.ts.map +1 -0
  58. package/dist/types/src/hooks/awareness-provider.d.ts +7 -4
  59. package/dist/types/src/hooks/awareness-provider.d.ts.map +1 -1
  60. package/dist/types/src/hooks/defs.d.ts +25 -0
  61. package/dist/types/src/hooks/defs.d.ts.map +1 -0
  62. package/dist/types/src/hooks/index.d.ts +3 -2
  63. package/dist/types/src/hooks/index.d.ts.map +1 -1
  64. package/dist/types/src/hooks/useActionHandler.d.ts +4 -0
  65. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -0
  66. package/dist/types/src/hooks/useTextEditor.d.ts +17 -0
  67. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -0
  68. package/dist/types/src/hooks/useTextModel.d.ts +2 -32
  69. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  70. package/dist/types/src/index.d.ts +3 -0
  71. package/dist/types/src/index.d.ts.map +1 -1
  72. package/dist/types/src/styles/markdown.d.ts +2 -2
  73. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  74. package/dist/types/src/styles/tokens.d.ts +1 -0
  75. package/dist/types/src/styles/tokens.d.ts.map +1 -1
  76. package/dist/types/src/themes/default.d.ts.map +1 -1
  77. package/dist/types/src/util.d.ts +7 -0
  78. package/dist/types/src/util.d.ts.map +1 -0
  79. package/package.json +47 -37
  80. package/src/components/TextEditor/MarkdownEditor.stories.tsx +84 -91
  81. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  82. package/src/components/TextEditor/TextEditor.tsx +131 -95
  83. package/src/components/TextEditor/comments.stories.tsx +357 -0
  84. package/src/components/Toolbar/Toolbar.stories.tsx +86 -0
  85. package/src/components/Toolbar/Toolbar.tsx +115 -0
  86. package/src/extensions/autocomplete.ts +23 -19
  87. package/src/extensions/automerge/automerge.spec.tsx +76 -0
  88. package/src/extensions/automerge/automerge.test.ts +13 -0
  89. package/src/extensions/automerge/automerge.ts +55 -46
  90. package/src/extensions/automerge/cursor.ts +5 -4
  91. package/src/extensions/automerge/defs.ts +13 -34
  92. package/src/extensions/automerge/semaphore.ts +33 -32
  93. package/src/extensions/automerge/update-automerge.ts +14 -8
  94. package/src/extensions/automerge/update-codemirror.ts +5 -5
  95. package/src/extensions/awareness.ts +43 -49
  96. package/src/extensions/basic.ts +22 -17
  97. package/src/extensions/comments.ts +323 -268
  98. package/src/extensions/cursor.ts +50 -0
  99. package/src/extensions/index.ts +2 -8
  100. package/src/extensions/markdown/bundle.ts +20 -30
  101. package/src/extensions/markdown/code.ts +142 -0
  102. package/src/extensions/markdown/formatting.ts +159 -0
  103. package/src/extensions/markdown/heading.ts +59 -0
  104. package/src/extensions/markdown/highlight.ts +11 -26
  105. package/src/extensions/markdown/hr.ts +71 -0
  106. package/src/extensions/markdown/image.ts +87 -0
  107. package/src/extensions/markdown/index.ts +8 -0
  108. package/src/extensions/{link.ts → markdown/link.ts} +87 -86
  109. package/src/extensions/{table.ts → markdown/table.ts} +10 -4
  110. package/src/extensions/markdown/tasklist.ts +110 -0
  111. package/src/extensions/mention.ts +9 -2
  112. package/src/extensions/outliner.ts +12 -0
  113. package/src/hooks/awareness-provider.ts +24 -20
  114. package/src/hooks/defs.ts +42 -0
  115. package/src/hooks/index.ts +4 -2
  116. package/src/hooks/useActionHandler.ts +45 -0
  117. package/src/hooks/useTextEditor.ts +29 -0
  118. package/src/hooks/useTextModel.ts +50 -59
  119. package/src/index.ts +3 -0
  120. package/src/styles/markdown.ts +9 -12
  121. package/src/styles/tokens.ts +3 -0
  122. package/src/themes/default.ts +50 -79
  123. package/src/util.ts +41 -0
  124. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts +0 -12
  125. package/dist/types/src/components/TextEditor/codemirror.stories.d.ts.map +0 -1
  126. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +0 -12
  127. package/dist/types/src/components/TextEditor/yjs.stories.d.ts.map +0 -1
  128. package/dist/types/src/extensions/code.d.ts.map +0 -1
  129. package/dist/types/src/extensions/hr.d.ts.map +0 -1
  130. package/dist/types/src/extensions/image.d.ts.map +0 -1
  131. package/dist/types/src/extensions/link.d.ts.map +0 -1
  132. package/dist/types/src/extensions/mermaid.d.ts +0 -3
  133. package/dist/types/src/extensions/mermaid.d.ts.map +0 -1
  134. package/dist/types/src/extensions/table.d.ts.map +0 -1
  135. package/dist/types/src/extensions/tasklist.d.ts.map +0 -1
  136. package/dist/types/src/extensions/yjs/cursor.d.ts +0 -4
  137. package/dist/types/src/extensions/yjs/cursor.d.ts.map +0 -1
  138. package/dist/types/src/extensions/yjs/index.d.ts +0 -2
  139. package/dist/types/src/extensions/yjs/index.d.ts.map +0 -1
  140. package/dist/types/src/extensions/yjs/yjs.d.ts +0 -4
  141. package/dist/types/src/extensions/yjs/yjs.d.ts.map +0 -1
  142. package/dist/types/src/extensions/yjs/yjs.test.d.ts +0 -2
  143. package/dist/types/src/extensions/yjs/yjs.test.d.ts.map +0 -1
  144. package/dist/types/src/hooks/automerge.d.ts +0 -3
  145. package/dist/types/src/hooks/automerge.d.ts.map +0 -1
  146. package/dist/types/src/hooks/yjs.d.ts +0 -27
  147. package/dist/types/src/hooks/yjs.d.ts.map +0 -1
  148. package/dist/types/src/testing/index.d.ts +0 -3
  149. package/dist/types/src/testing/index.d.ts.map +0 -1
  150. package/dist/types/src/testing/proto/gen/schema.d.ts +0 -15
  151. package/dist/types/src/testing/proto/gen/schema.d.ts.map +0 -1
  152. package/dist/types/src/testing/proto/index.d.ts +0 -2
  153. package/dist/types/src/testing/proto/index.d.ts.map +0 -1
  154. package/dist/types/src/testing/replicator.d.ts +0 -45
  155. package/dist/types/src/testing/replicator.d.ts.map +0 -1
  156. package/dist/types/src/util/cursor.d.ts.map +0 -1
  157. package/dist/types/src/util/index.d.ts +0 -3
  158. package/dist/types/src/util/index.d.ts.map +0 -1
  159. package/dist/types/src/util/util.d.ts +0 -5
  160. package/dist/types/src/util/util.d.ts.map +0 -1
  161. package/src/components/TextEditor/codemirror.stories.ts +0 -115
  162. package/src/components/TextEditor/yjs.stories.tsx +0 -56
  163. package/src/extensions/code.ts +0 -99
  164. package/src/extensions/hr.ts +0 -74
  165. package/src/extensions/image.ts +0 -69
  166. package/src/extensions/mermaid.ts +0 -11
  167. package/src/extensions/tasklist.ts +0 -121
  168. package/src/extensions/yjs/cursor.ts +0 -21
  169. package/src/extensions/yjs/index.ts +0 -5
  170. package/src/extensions/yjs/yjs.test.ts +0 -35
  171. package/src/extensions/yjs/yjs.ts +0 -16
  172. package/src/hooks/automerge.ts +0 -52
  173. package/src/hooks/yjs.ts +0 -178
  174. package/src/testing/index.ts +0 -6
  175. package/src/testing/proto/gen/schema.ts +0 -49
  176. package/src/testing/proto/index.ts +0 -5
  177. package/src/testing/proto/schema.proto +0 -15
  178. package/src/testing/replicator.ts +0 -184
  179. package/src/util/cursor.ts +0 -29
  180. package/src/util/index.ts +0 -6
  181. package/src/util/util.ts +0 -18
  182. /package/dist/types/src/extensions/{code.d.ts → markdown/code.d.ts} +0 -0
  183. /package/dist/types/src/extensions/{hr.d.ts → markdown/hr.d.ts} +0 -0
  184. /package/dist/types/src/extensions/{image.d.ts → markdown/image.d.ts} +0 -0
  185. /package/dist/types/src/extensions/{link.d.ts → markdown/link.d.ts} +0 -0
  186. /package/dist/types/src/extensions/{table.d.ts → markdown/table.d.ts} +0 -0
  187. /package/dist/types/src/extensions/{tasklist.d.ts → markdown/tasklist.d.ts} +0 -0
@@ -2,9 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { EditorState, type Extension } from '@codemirror/state';
5
+ import { EditorState, type Extension, type StateEffect } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
- import { useFocusableGroup } from '@fluentui/react-tabster';
7
+ // import { useFocusableGroup } from '@fluentui/react-tabster';
8
8
  import { vim } from '@replit/codemirror-vim';
9
9
  import defaultsDeep from 'lodash.defaultsdeep';
10
10
  import React, {
@@ -14,18 +14,22 @@ import React, {
14
14
  useCallback,
15
15
  useEffect,
16
16
  useImperativeHandle,
17
+ useRef,
17
18
  useState,
18
19
  } from 'react';
19
20
 
20
- import { generateName } from '@dxos/display-name';
21
+ import { log } from '@dxos/log';
21
22
  import { useThemeContext } from '@dxos/react-ui';
22
- import { getColorForValue, inputSurface, mx } from '@dxos/react-ui-theme';
23
+ import { attentionSurface, mx } from '@dxos/react-ui-theme';
24
+ import { isNotFalsy } from '@dxos/util';
23
25
 
24
- import { basicBundle, markdownBundle, setCommentRange } from '../../extensions';
25
- import { type CommentRange, type EditorModel } from '../../hooks';
26
+ import { basicBundle, markdownBundle } from '../../extensions';
27
+ import { type EditorModel } from '../../hooks';
26
28
  import { type ThemeStyles } from '../../styles';
27
29
  import { defaultTheme, markdownTheme, textTheme } from '../../themes';
30
+ import { logChanges } from '../../util';
28
31
 
32
+ // TODO(burdon): Change to enum?
29
33
  export const EditorModes = ['default', 'vim'] as const;
30
34
  export type EditorMode = (typeof EditorModes)[number];
31
35
 
@@ -34,133 +38,164 @@ export type CursorInfo = {
34
38
  to: number;
35
39
  line: number;
36
40
  lines: number;
41
+ length: number;
37
42
  after?: string;
38
43
  };
39
44
 
40
- export type TextEditorRef = {
41
- root: HTMLDivElement | null;
42
- state?: EditorState;
43
- view?: EditorView;
44
- };
45
-
46
45
  export type TextEditorSlots = {
47
46
  root?: Omit<ComponentProps<'div'>, 'ref'>;
48
47
  editor?: {
49
48
  className?: string;
50
- placeholder?: string;
51
- spellCheck?: boolean;
52
- tabIndex?: number;
53
- theme?: ThemeStyles;
49
+ };
50
+ content?: {
51
+ className?: string;
54
52
  };
55
53
  };
56
54
 
55
+ // TODO(burdon): Spellcheck?
57
56
  export type TextEditorProps = {
58
- model: EditorModel;
57
+ model: EditorModel; // TODO(burdon): Optional (e.g., just provide content if readonly).
59
58
  readonly?: boolean; // TODO(burdon): Move into model.
60
- comments?: CommentRange[]; // TODO(burdon): Move into extension.
61
- extensions?: Extension[];
62
- editorMode?: EditorMode;
59
+ autoFocus?: boolean;
60
+ lineWrapping?: boolean;
61
+ scrollTo?: StateEffect<any>; // TODO(burdon): Restore scroll position: scrollTo EditorView.scrollSnapshot().
62
+ selection?: { anchor: number; head?: number };
63
+ editorMode?: EditorMode; // TODO(burdon): Factor out.
64
+ placeholder?: string;
65
+ theme?: ThemeStyles;
63
66
  slots?: TextEditorSlots;
67
+ extensions?: Extension[];
68
+ debug?: boolean;
64
69
  };
65
70
 
66
71
  /**
67
72
  * Base text editor.
68
- * NOTE: Rather than adding properties, try to create extensions that can be reused.
69
73
  */
70
- export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
71
- ({ model, readonly, comments, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
74
+ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
75
+ (
76
+ {
77
+ model,
78
+ readonly,
79
+ autoFocus,
80
+ scrollTo,
81
+ selection,
82
+ editorMode,
83
+ theme,
84
+ slots = defaultSlots,
85
+ extensions = [],
86
+ debug,
87
+ },
88
+ forwardedRef,
89
+ ) => {
90
+ // TODO(burdon): Hook causes error even if properties are not spread into div.
91
+ // Uncaught TypeError: Cannot read properties of undefined (reading 'relatedTarget')
92
+ // Uses event.detail, which is deprecated (not event.details). At runtime the event has a property `details`.
93
+ // https://github.com/microsoft/tabster/blob/master/src/State/FocusedElement.ts#L348 (e.detail.relatedTarget)
94
+ // https://github.com/microsoft/keyborg/blob/49e49b2c3ba0a5f6cc518ac46825d7551def8109/src/FocusEvent.ts#L58
95
+ // const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
72
96
  const { themeMode } = useThemeContext();
73
- const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
74
- const [root, setRoot] = useState<HTMLDivElement | null>(null);
75
- const [{ state = undefined, view = undefined } = {}, setEditor] = useState<
76
- { state?: EditorState; view?: EditorView } | undefined
77
- >();
78
- useImperativeHandle(forwardedRef, () => ({ root, state, view }), [view, state, root]);
79
-
80
- // TODO(burdon): Factor out as extension.
81
- const { awareness, peer } = model;
82
- useEffect(() => {
83
- if (awareness && peer) {
84
- awareness.setLocalStateField('user', {
85
- name: peer.name ?? generateName(peer.id),
86
- color: getColorForValue({ value: peer.id, type: 'color' }),
87
- colorLight: getColorForValue({ value: peer.id, themeMode, type: 'highlight' }),
88
- });
89
- }
90
- }, [awareness, peer, themeMode]);
91
97
 
92
- // TODO(burdon): Factor out as extension.
98
+ const rootRef = useRef<HTMLDivElement>(null);
99
+ const [view, setView] = useState<EditorView | null>(null);
100
+
101
+ // The view ref can be used to focus the editor.
102
+ // NOTE: This does not cause the parent to re-render, so the ref is not available immediately.
103
+ useImperativeHandle<EditorView | null, EditorView | null>(forwardedRef, () => view, [view]);
104
+
105
+ // Set focus.
93
106
  useEffect(() => {
94
- if (view && comments?.length) {
95
- view.dispatch({
96
- effects: setCommentRange.of({ model, comments }),
97
- });
107
+ if (autoFocus) {
108
+ view?.focus();
98
109
  }
99
- }, [view, comments]);
110
+ }, [view, autoFocus]);
100
111
 
112
+ // Create editor state and view.
113
+ // The view is recreated if the model or extensions are changed.
101
114
  useEffect(() => {
102
- if (!root) {
115
+ if (!model || !rootRef.current) {
103
116
  return;
104
117
  }
105
118
 
106
- // TODO(burdon): Remember cursor position.
119
+ //
120
+ // EditorState
107
121
  // https://codemirror.net/docs/ref/#state.EditorStateConfig
122
+ //
108
123
  const state = EditorState.create({
109
124
  doc: model.text(),
125
+ selection,
110
126
  extensions: [
111
- readonly && EditorState.readOnly.of(readonly),
112
-
113
- // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
114
- editorMode === 'vim' && vim(),
127
+ // TODO(burdon): Doesn't catch errors in keymap functions.
128
+ EditorView.exceptionSink.of((err) => {
129
+ log.catch(err);
130
+ }),
115
131
 
116
132
  // Theme.
117
- // TODO(burdon): Make theme configurable.
133
+ // TODO(burdon): Make configurable.
118
134
  EditorView.baseTheme(defaultTheme),
119
- EditorView.theme(slots?.editor?.theme ?? {}),
135
+ EditorView.theme(theme ?? {}),
120
136
  EditorView.darkTheme.of(themeMode === 'dark'),
137
+ EditorView.editorAttributes.of({ class: slots.editor?.className ?? '' }),
138
+ EditorView.contentAttributes.of({ class: slots.content?.className ?? '' }),
139
+
140
+ // State.
141
+ EditorView.editable.of(!readonly),
142
+ EditorState.readOnly.of(!!readonly),
121
143
 
122
144
  // Storage and replication.
145
+ // NOTE: This must come before user extensions.
123
146
  model.extension,
124
147
 
148
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
149
+ editorMode === 'vim' && vim(),
150
+
125
151
  // Custom.
126
152
  ...extensions,
127
- ].filter(Boolean) as Extension[],
153
+ ].filter(isNotFalsy),
128
154
  });
129
155
 
130
- // NOTE: This repaints the editor.
131
- // If the new state is derived from the old state, it will likely not be visible other than the cursor resetting.
132
- // Ideally this should not happen except when changing between text objects.
133
- view?.destroy();
134
- setEditor({
156
+ //
157
+ // EditorView
158
+ // https://codemirror.net/docs/ref/#view.EditorViewConfig
159
+ //
160
+ const newView = new EditorView({
135
161
  state,
136
- view: new EditorView({
137
- state,
138
- parent: root,
139
- // NOTE: Uncomment to spy on all transactions.
140
- // https://codemirror.net/docs/ref/#view.EditorView.dispatch
141
- // dispatch: (transaction, view) => {
142
- // view.update([transaction]);
143
- // },
144
- }),
162
+ parent: rootRef.current,
163
+ scrollTo,
164
+ // NOTE: Uncomment to debug/monitor all transactions.
165
+ // https://codemirror.net/docs/ref/#view.EditorView.dispatch
166
+ dispatchTransactions: (trs, view) => {
167
+ if (debug) {
168
+ logChanges(trs);
169
+ }
170
+ view.update(trs);
171
+ },
145
172
  });
146
173
 
174
+ view?.destroy();
175
+ setView(newView);
176
+
147
177
  return () => {
148
- view?.destroy();
149
- setEditor(undefined);
178
+ newView?.destroy();
179
+ setView(null);
150
180
  };
151
- }, [root, model, readonly, editorMode, themeMode]);
181
+ }, [rootRef, model, readonly, editorMode, themeMode]);
152
182
 
183
+ // Handles tab/focus.
184
+ // Pressing Escape focuses the outer div (to support tab navigation); pressing Enter refocuses the editor.
185
+ // TODO(burdon): Convert to keymap?
153
186
  const handleKeyUp = useCallback(
154
187
  (event: KeyboardEvent) => {
155
188
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
156
189
  switch (key) {
157
190
  case 'Enter': {
158
- view?.contentDOM.focus();
191
+ view?.focus();
159
192
  break;
160
193
  }
161
194
 
162
195
  case 'Escape': {
163
- editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
196
+ if (editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey)) {
197
+ view?.focus();
198
+ }
164
199
  break;
165
200
  }
166
201
  }
@@ -171,42 +206,45 @@ export const BaseTextEditor = forwardRef<TextEditorRef, TextEditorProps>(
171
206
  return (
172
207
  <div
173
208
  key={model.id}
174
- ref={setRoot}
209
+ role='none'
175
210
  tabIndex={0}
176
- {...slots?.root}
177
- {...(editorMode !== 'vim' && tabsterDOMAttribute)}
211
+ {...slots.root}
212
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
178
213
  onKeyUp={handleKeyUp}
214
+ ref={rootRef}
179
215
  />
180
216
  );
181
217
  },
182
218
  );
183
219
 
184
- export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
185
- ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
220
+ export const TextEditor = forwardRef<EditorView, TextEditorProps>(
221
+ ({ readonly, placeholder, lineWrapping, theme = textTheme, slots, extensions = [], ...props }, forwardedRef) => {
186
222
  const { themeMode } = useThemeContext();
187
- const slots = defaultsDeep({}, _slots, defaultTextSlots);
223
+ const updatedSlots = defaultsDeep({}, slots, defaultTextSlots);
188
224
  return (
189
225
  <BaseTextEditor
190
226
  ref={forwardedRef}
191
227
  readonly={readonly}
192
- extensions={[basicBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
193
- slots={slots}
228
+ extensions={[basicBundle({ themeMode, placeholder, lineWrapping }), ...extensions]}
229
+ theme={theme}
230
+ slots={updatedSlots}
194
231
  {...props}
195
232
  />
196
233
  );
197
234
  },
198
235
  );
199
236
 
200
- export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
201
- ({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
237
+ export const MarkdownEditor = forwardRef<EditorView, TextEditorProps>(
238
+ ({ readonly, placeholder, theme = markdownTheme, slots, extensions = [], ...props }, forwardedRef) => {
202
239
  const { themeMode } = useThemeContext();
203
- const slots = defaultsDeep({}, _slots, defaultMarkdownSlots);
240
+ const updatedSlots = defaultsDeep({}, slots, defaultMarkdownSlots);
204
241
  return (
205
242
  <BaseTextEditor
206
243
  ref={forwardedRef}
207
244
  readonly={readonly}
208
- extensions={[markdownBundle({ readonly, themeMode, placeholder: slots?.editor?.placeholder }), ...extensions]}
209
- slots={slots}
245
+ extensions={[markdownBundle({ themeMode, placeholder }), ...extensions]}
246
+ theme={theme}
247
+ slots={updatedSlots}
210
248
  {...props}
211
249
  />
212
250
  );
@@ -215,20 +253,18 @@ export const MarkdownEditor = forwardRef<TextEditorRef, TextEditorProps>(
215
253
 
216
254
  export const defaultSlots: TextEditorSlots = {
217
255
  root: {
218
- className: mx('p-2', inputSurface),
256
+ // TODO(burdon): Add focusRing by default/as property?
257
+ className: mx('flex flex-col grow overflow-y-auto', attentionSurface),
258
+ },
259
+ editor: {
260
+ className: 'h-full p-2',
219
261
  },
220
262
  };
221
263
 
222
264
  export const defaultTextSlots: TextEditorSlots = {
223
265
  ...defaultSlots,
224
- editor: {
225
- theme: textTheme,
226
- },
227
266
  };
228
267
 
229
268
  export const defaultMarkdownSlots: TextEditorSlots = {
230
269
  ...defaultSlots,
231
- editor: {
232
- theme: markdownTheme,
233
- },
234
270
  };