@dxos/react-ui-editor 0.4.3-main.1ddf290 → 0.4.3-main.2a46fc0
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.
- package/dist/lib/browser/index.mjs +57 -27
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +3 -0
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +0 -3
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +1 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/modes.d.ts +11 -0
- package/dist/types/src/extensions/modes.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +10 -1
- package/src/components/TextEditor/TextEditor.tsx +29 -50
- package/src/extensions/index.ts +1 -0
- package/src/extensions/modes.ts +36 -0
- package/src/index.ts +3 -2
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import { EditorState, type Extension, type StateEffect } from '@codemirror/state';
|
|
6
6
|
import { EditorView } from '@codemirror/view';
|
|
7
|
-
|
|
8
|
-
import { vim } from '@replit/codemirror-vim';
|
|
7
|
+
import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
9
8
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
10
9
|
import React, {
|
|
11
10
|
type ComponentProps,
|
|
12
|
-
type KeyboardEvent,
|
|
13
11
|
forwardRef,
|
|
12
|
+
type KeyboardEventHandler,
|
|
14
13
|
useCallback,
|
|
15
14
|
useEffect,
|
|
16
15
|
useImperativeHandle,
|
|
@@ -23,16 +22,12 @@ import { useThemeContext } from '@dxos/react-ui';
|
|
|
23
22
|
import { focusRing } from '@dxos/react-ui-theme';
|
|
24
23
|
import { isNotFalsy } from '@dxos/util';
|
|
25
24
|
|
|
26
|
-
import { createBasicBundle, createMarkdownExtensions } from '../../extensions';
|
|
25
|
+
import { createBasicBundle, createMarkdownExtensions, editorMode } from '../../extensions';
|
|
27
26
|
import { type EditorModel } from '../../hooks';
|
|
28
27
|
import { type ThemeStyles } from '../../styles';
|
|
29
28
|
import { defaultTheme, markdownTheme, textTheme } from '../../themes';
|
|
30
29
|
import { logChanges } from '../../util';
|
|
31
30
|
|
|
32
|
-
// TODO(burdon): Change to enum?
|
|
33
|
-
export const EditorModes = ['default', 'vim'] as const;
|
|
34
|
-
export type EditorMode = (typeof EditorModes)[number];
|
|
35
|
-
|
|
36
31
|
export type CursorInfo = {
|
|
37
32
|
from: number;
|
|
38
33
|
to: number;
|
|
@@ -52,7 +47,6 @@ export type TextEditorSlots = {
|
|
|
52
47
|
};
|
|
53
48
|
};
|
|
54
49
|
|
|
55
|
-
// TODO(burdon): Spellcheck?
|
|
56
50
|
export type TextEditorProps = {
|
|
57
51
|
model: EditorModel; // TODO(burdon): Optional (e.g., just provide content if readonly).
|
|
58
52
|
readonly?: boolean; // TODO(burdon): Move into model.
|
|
@@ -60,7 +54,6 @@ export type TextEditorProps = {
|
|
|
60
54
|
lineWrapping?: boolean;
|
|
61
55
|
scrollTo?: StateEffect<any>; // TODO(burdon): Restore scroll position: scrollTo EditorView.scrollSnapshot().
|
|
62
56
|
selection?: { anchor: number; head?: number };
|
|
63
|
-
editorMode?: EditorMode; // TODO(burdon): Factor out.
|
|
64
57
|
placeholder?: string;
|
|
65
58
|
theme?: ThemeStyles;
|
|
66
59
|
slots?: TextEditorSlots;
|
|
@@ -74,26 +67,10 @@ export type TextEditorProps = {
|
|
|
74
67
|
// TODO(burdon): Replace with useTextEditor.
|
|
75
68
|
export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
76
69
|
(
|
|
77
|
-
{
|
|
78
|
-
model,
|
|
79
|
-
readonly,
|
|
80
|
-
autoFocus,
|
|
81
|
-
scrollTo,
|
|
82
|
-
selection,
|
|
83
|
-
editorMode,
|
|
84
|
-
theme,
|
|
85
|
-
slots = defaultSlots,
|
|
86
|
-
extensions = [],
|
|
87
|
-
debug,
|
|
88
|
-
},
|
|
70
|
+
{ model, readonly, autoFocus, scrollTo, selection, theme, slots = defaultSlots, extensions = [], debug },
|
|
89
71
|
forwardedRef,
|
|
90
72
|
) => {
|
|
91
|
-
|
|
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' });
|
|
73
|
+
const tabsterDOMAttribute = useFocusableGroup({ tabBehavior: 'limited' });
|
|
97
74
|
const { themeMode } = useThemeContext();
|
|
98
75
|
|
|
99
76
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
@@ -143,6 +120,15 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
|
143
120
|
EditorView.editorAttributes.of({ class: slots.editor?.className ?? '' }),
|
|
144
121
|
EditorView.contentAttributes.of({ class: slots.content?.className ?? '' }),
|
|
145
122
|
|
|
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
|
+
|
|
146
132
|
// State.
|
|
147
133
|
EditorView.editable.of(!readonly),
|
|
148
134
|
EditorState.readOnly.of(!!readonly),
|
|
@@ -151,9 +137,6 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
|
151
137
|
// NOTE: This must come before user extensions.
|
|
152
138
|
model.extension,
|
|
153
139
|
|
|
154
|
-
// TODO(burdon): Factor out? (Requires special handling for Escape/Enter below).
|
|
155
|
-
editorMode === 'vim' && vim(),
|
|
156
|
-
|
|
157
140
|
// Custom.
|
|
158
141
|
...extensions,
|
|
159
142
|
].filter(isNotFalsy),
|
|
@@ -180,44 +163,40 @@ export const BaseTextEditor = forwardRef<EditorView, TextEditorProps>(
|
|
|
180
163
|
view?.destroy();
|
|
181
164
|
setView(newView);
|
|
182
165
|
|
|
166
|
+
// Remove tabster attribute (rely on custom keymap).
|
|
167
|
+
if (state.facet(editorMode).noTabster) {
|
|
168
|
+
rootRef.current.removeAttribute('data-tabster');
|
|
169
|
+
}
|
|
170
|
+
|
|
183
171
|
return () => {
|
|
184
172
|
newView?.destroy();
|
|
185
173
|
setView(null);
|
|
186
174
|
};
|
|
187
|
-
}, [rootRef, model, readonly,
|
|
175
|
+
}, [rootRef, model, readonly, themeMode]);
|
|
188
176
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
(event: KeyboardEvent) => {
|
|
194
|
-
const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
|
|
177
|
+
// Focus editor on Enter (e.g., when tabbing to this component).
|
|
178
|
+
const handleKeyUp = useCallback<KeyboardEventHandler<HTMLDivElement>>(
|
|
179
|
+
(event) => {
|
|
180
|
+
const { key } = event;
|
|
195
181
|
switch (key) {
|
|
196
182
|
case 'Enter': {
|
|
197
183
|
view?.focus();
|
|
198
184
|
break;
|
|
199
185
|
}
|
|
200
|
-
|
|
201
|
-
case 'Escape': {
|
|
202
|
-
if (editorMode === 'vim' && (altKey || shiftKey || metaKey || ctrlKey)) {
|
|
203
|
-
view?.focus();
|
|
204
|
-
}
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
186
|
}
|
|
208
187
|
},
|
|
209
|
-
[view
|
|
188
|
+
[view],
|
|
210
189
|
);
|
|
211
190
|
|
|
212
191
|
return (
|
|
213
192
|
<div
|
|
214
|
-
key={model.id}
|
|
215
193
|
role='none'
|
|
194
|
+
ref={rootRef}
|
|
195
|
+
key={model.id}
|
|
216
196
|
tabIndex={0}
|
|
217
|
-
{...slots.root}
|
|
218
|
-
// {...(editorMode !== 'vim' && tabsterDOMAttribute)}
|
|
219
197
|
onKeyUp={handleKeyUp}
|
|
220
|
-
|
|
198
|
+
{...slots.root}
|
|
199
|
+
{...tabsterDOMAttribute}
|
|
221
200
|
/>
|
|
222
201
|
);
|
|
223
202
|
},
|
package/src/extensions/index.ts
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
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
|
+
|
|
5
8
|
export { TextKind } from '@dxos/protocols/proto/dxos/echo/model/text';
|
|
6
9
|
export { Doc, YText, YXmlFragment } from '@dxos/text-model';
|
|
7
|
-
export { type Extension } from '@codemirror/state';
|
|
8
|
-
export { type EditorView, keymap } from '@codemirror/view';
|
|
9
10
|
|
|
10
11
|
export * from './components';
|
|
11
12
|
export * from './extensions';
|