@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.
- package/dist/lib/browser/index.mjs +27 -57
- 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 +0 -3
- package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TextEditor/TextEditor.d.ts +3 -0
- package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +0 -1
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/components/TextEditor/MarkdownEditor.stories.tsx +1 -10
- package/src/components/TextEditor/TextEditor.tsx +50 -29
- package/src/extensions/index.ts +0 -1
- package/src/index.ts +2 -3
- package/dist/types/src/extensions/modes.d.ts +0 -11
- package/dist/types/src/extensions/modes.d.ts.map +0 -1
- package/src/extensions/modes.ts +0 -36
|
@@ -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
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
},
|
package/src/extensions/index.ts
CHANGED
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"}
|
package/src/extensions/modes.ts
DELETED
|
@@ -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
|
-
};
|