@dxos/react-ui-editor 0.4.3-main.a51bbee → 0.4.3-main.c13b549

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.
@@ -4,12 +4,13 @@
4
4
 
5
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
+ import { vim } from '@replit/codemirror-vim';
8
9
  import defaultsDeep from 'lodash.defaultsdeep';
9
10
  import React, {
10
11
  type ComponentProps,
12
+ type KeyboardEvent,
11
13
  forwardRef,
12
- type KeyboardEventHandler,
13
14
  useCallback,
14
15
  useEffect,
15
16
  useImperativeHandle,
@@ -22,12 +23,16 @@ import { useThemeContext } from '@dxos/react-ui';
22
23
  import { focusRing } from '@dxos/react-ui-theme';
23
24
  import { isNotFalsy } from '@dxos/util';
24
25
 
25
- import { createBasicBundle, createMarkdownExtensions, editorMode } from '../../extensions';
26
+ import { createBasicBundle, createMarkdownExtensions } from '../../extensions';
26
27
  import { type EditorModel } from '../../hooks';
27
28
  import { type ThemeStyles } from '../../styles';
28
29
  import { defaultTheme, markdownTheme, textTheme } from '../../themes';
29
30
  import { logChanges } from '../../util';
30
31
 
32
+ // TODO(burdon): Change to enum?
33
+ export const EditorModes = ['default', 'vim'] as const;
34
+ export type EditorMode = (typeof EditorModes)[number];
35
+
31
36
  export type CursorInfo = {
32
37
  from: number;
33
38
  to: number;
@@ -47,6 +52,7 @@ export type TextEditorSlots = {
47
52
  };
48
53
  };
49
54
 
55
+ // TODO(burdon): Spellcheck?
50
56
  export type TextEditorProps = {
51
57
  model: EditorModel; // TODO(burdon): Optional (e.g., just provide content if readonly).
52
58
  readonly?: boolean; // TODO(burdon): Move into model.
@@ -54,6 +60,7 @@ export type TextEditorProps = {
54
60
  lineWrapping?: boolean;
55
61
  scrollTo?: StateEffect<any>; // TODO(burdon): Restore scroll position: scrollTo EditorView.scrollSnapshot().
56
62
  selection?: { anchor: number; head?: number };
63
+ editorMode?: EditorMode; // TODO(burdon): Factor out.
57
64
  placeholder?: string;
58
65
  theme?: ThemeStyles;
59
66
  slots?: TextEditorSlots;
@@ -67,10 +74,26 @@ export type TextEditorProps = {
67
74
  // TODO(burdon): Replace with useTextEditor.
68
75
  export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
69
76
  (
70
- { model, readonly, autoFocus, scrollTo, selection, theme, slots = defaultSlots, extensions = [], debug },
77
+ {
78
+ model,
79
+ readonly,
80
+ autoFocus,
81
+ scrollTo,
82
+ selection,
83
+ editorMode,
84
+ theme,
85
+ slots = defaultSlots,
86
+ extensions = [],
87
+ debug,
88
+ },
71
89
  forwardedRef,
72
90
  ) => {
73
- const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
91
+ // TODO(burdon): Hook causes error even if properties are not spread into div.
92
+ // Uncaught TypeError: Cannot read properties of undefined (reading 'relatedTarget')
93
+ // Uses event.detail, which is deprecated (not event.details). At runtime the event has a property `details`.
94
+ // https://github.com/microsoft/tabster/blob/master/src/State/FocusedElement.ts#L348 (e.detail.relatedTarget)
95
+ // https://github.com/microsoft/keyborg/blob/49e49b2c3ba0a5f6cc518ac46825d7551def8109/src/FocusEvent.ts#L58
96
+ // const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
74
97
  const { themeMode } = useThemeContext();
75
98
 
76
99
  const rootRef = useRef<HTMLDivElement>(null);
@@ -120,15 +143,6 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
120
143
  EditorView.editorAttributes.of({ class: slots.editor?.className ?? '' }),
121
144
  EditorView.contentAttributes.of({ class: slots.content?.className ?? '' }),
122
145
 
123
- // Focus.
124
- EditorView.updateListener.of((update) => {
125
- update.transactions.forEach((transaction) => {
126
- if (transaction.isUserEvent('focus.container')) {
127
- rootRef.current?.focus();
128
- }
129
- });
130
- }),
131
-
132
146
  // State.
133
147
  EditorView.editable.of(!readonly),
134
148
  EditorState.readOnly.of(!!readonly),
@@ -137,6 +151,9 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
137
151
  // NOTE: This must come before user extensions.
138
152
  model.extension,
139
153
 
154
+ // TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
155
+ editorMode === 'vim' && vim(),
156
+
140
157
  // Custom.
141
158
  ...extensions,
142
159
  ].filter(isNotFalsy),
@@ -163,40 +180,44 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
163
180
  view?.destroy();
164
181
  setView(newView);
165
182
 
166
- // Remove tabster attribute (rely on custom keymap).
167
- if (state.facet(editorMode).noTabster) {
168
- rootRef.current.removeAttribute('data-tabster');
169
- }
170
-
171
183
  return () => {
172
184
  newView?.destroy();
173
185
  setView(null);
174
186
  };
175
- }, [rootRef, model, readonly, themeMode]);
187
+ }, [rootRef, model, readonly, editorMode, themeMode]);
176
188
 
177
- // Focus editor on Enter (e.g., when tabbing to this component).
178
- const handleKeyUp = useCallback<KeyboardEventHandler<HTMLDivElement>>(
179
- (event) => {
180
- const { key } = event;
189
+ // Handles tab/focus.
190
+ // Pressing Escape focuses the outer div (to support tab navigation); pressing Enter refocuses the editor.
191
+ // TODO(burdon): Convert to keymap?
192
+ const handleKeyUp = useCallback(
193
+ (event: KeyboardEvent) => {
194
+ const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
181
195
  switch (key) {
182
196
  case 'Enter': {
183
197
  view?.focus();
184
198
  break;
185
199
  }
200
+
201
+ case 'Escape': {
202
+ if (editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey)) {
203
+ view?.focus();
204
+ }
205
+ break;
206
+ }
186
207
  }
187
208
  },
188
- [view],
209
+ [view, editorMode],
189
210
  );
190
211
 
191
212
  return (
192
213
  <div
193
- role='none'
194
- ref={rootRef}
195
214
  key={model.id}
215
+ role='none'
196
216
  tabIndex={0}
197
- onKeyUp={handleKeyUp}
198
217
  {...slots.root}
199
- {...tabsterDOMAttribute}
218
+ // {...(editorMode !== 'vim' && tabsterDOMAttribute)}
219
+ onKeyUp={handleKeyUp}
220
+ ref={rootRef}
200
221
  />
201
222
  );
202
223
  },
@@ -13,6 +13,5 @@ export * from './cursor';
13
13
  export * from './listener';
14
14
  export * from './markdown';
15
15
  export * from './mention';
16
- export * from './modes';
17
16
  export * from './outliner';
18
17
  export * from './typewriter';
package/src/index.ts CHANGED
@@ -2,11 +2,10 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export { type Extension, type EditorState } from '@codemirror/state';
6
- export { type EditorView, keymap } from '@codemirror/view';
7
-
8
5
  export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
9
6
  export { Doc, YText, YXmlFragment } from '@dxos/text-model';
7
+ export { type Extension } from '@codemirror/state';
8
+ export { type EditorView, keymap } from '@codemirror/view';
10
9
 
11
10
  export * from './components';
12
11
  export * from './extensions';
@@ -1,11 +0,0 @@
1
- import { type Extension, Facet } from '@codemirror/state';
2
- export type EditorMode = 'default' | 'vim' | undefined;
3
- export type EditorConfig = {
4
- type: string;
5
- noTabster?: boolean;
6
- };
7
- export declare const editorMode: Facet<EditorConfig, EditorConfig>;
8
- export declare const EditorModes: {
9
- [mode: string]: Extension;
10
- };
11
- //# sourceMappingURL=modes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"modes.d.ts","sourceRoot":"","sources":["../../../../src/extensions/modes.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1D,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAEvD,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,UAAU,mCAErB,CAAC;AAEH,eAAO,MAAM,WAAW,EAAE;IAAE,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAgBpD,CAAC"}
@@ -1,36 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type Extension, Facet } from '@codemirror/state';
6
- import { keymap } from '@codemirror/view';
7
- import { vim } from '@replit/codemirror-vim';
8
-
9
- export type EditorMode = 'default' | 'vim' | undefined;
10
-
11
- export type EditorConfig = {
12
- type: string;
13
- noTabster?: boolean;
14
- };
15
-
16
- export const editorMode = Facet.define<EditorConfig, EditorConfig>({
17
- combine: (modes) => modes[0] ?? {},
18
- });
19
-
20
- export const EditorModes: { [mode: string]: Extension } = {
21
- default: [],
22
- vim: [
23
- vim(),
24
- editorMode.of({ type: 'vim', noTabster: true }),
25
- keymap.of([
26
- {
27
- key: 'Alt-Escape',
28
- run: (view) => {
29
- // Focus container for tab navigation.
30
- view.dispatch({ userEvent: 'focus.container' });
31
- return true;
32
- },
33
- },
34
- ]),
35
- ],
36
- };