@dxos/react-ui-editor 0.8.1 → 0.8.2-main.2f9c567

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 (115) hide show
  1. package/dist/lib/browser/index.mjs +502 -374
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/testing/index.mjs +67 -0
  5. package/dist/lib/browser/testing/index.mjs.map +7 -0
  6. package/dist/lib/node/index.cjs +518 -382
  7. package/dist/lib/node/index.cjs.map +4 -4
  8. package/dist/lib/node/meta.json +1 -1
  9. package/dist/lib/node/testing/index.cjs +101 -0
  10. package/dist/lib/node/testing/index.cjs.map +7 -0
  11. package/dist/lib/node-esm/index.mjs +502 -374
  12. package/dist/lib/node-esm/index.mjs.map +4 -4
  13. package/dist/lib/node-esm/meta.json +1 -1
  14. package/dist/lib/node-esm/testing/index.mjs +69 -0
  15. package/dist/lib/node-esm/testing/index.mjs.map +7 -0
  16. package/dist/types/src/components/EditorToolbar/util.d.ts +3 -3
  17. package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
  18. package/dist/types/src/components/EditorToolbar/{viewMode.d.ts → view-mode.d.ts} +1 -1
  19. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -0
  20. package/dist/types/src/defaults.d.ts +1 -0
  21. package/dist/types/src/defaults.d.ts.map +1 -1
  22. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  23. package/dist/types/src/extensions/command/action.d.ts +17 -0
  24. package/dist/types/src/extensions/command/action.d.ts.map +1 -0
  25. package/dist/types/src/extensions/command/command.d.ts +5 -10
  26. package/dist/types/src/extensions/command/command.d.ts.map +1 -1
  27. package/dist/types/src/extensions/command/hint.d.ts +4 -2
  28. package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
  29. package/dist/types/src/extensions/command/index.d.ts +1 -0
  30. package/dist/types/src/extensions/command/index.d.ts.map +1 -1
  31. package/dist/types/src/extensions/command/menu.d.ts +7 -2
  32. package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
  33. package/dist/types/src/extensions/command/state.d.ts +9 -11
  34. package/dist/types/src/extensions/command/state.d.ts.map +1 -1
  35. package/dist/types/src/extensions/comments.d.ts +9 -7
  36. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  37. package/dist/types/src/extensions/index.d.ts +1 -0
  38. package/dist/types/src/extensions/index.d.ts.map +1 -1
  39. package/dist/types/src/extensions/markdown/decorate.d.ts +4 -1
  40. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  41. package/dist/types/src/extensions/markdown/formatting.d.ts +2 -2
  42. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/link.d.ts +4 -1
  44. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  45. package/dist/types/src/extensions/modes.d.ts +5 -5
  46. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  47. package/dist/types/src/extensions/preview/index.d.ts +2 -0
  48. package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
  49. package/dist/types/src/extensions/preview/preview.d.ts +39 -0
  50. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
  51. package/dist/types/src/hooks/useTextEditor.d.ts +2 -1
  52. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  53. package/dist/types/src/{InputMode.stories.d.ts → stories/InputMode.stories.d.ts} +1 -1
  54. package/dist/types/src/stories/InputMode.stories.d.ts.map +1 -0
  55. package/dist/types/src/{TextEditor.stories.d.ts → stories/TextEditorBasic.stories.d.ts} +2 -35
  56. package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +1 -0
  57. package/dist/types/src/stories/TextEditorComments.stories.d.ts +13 -0
  58. package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +1 -0
  59. package/dist/types/src/stories/TextEditorPreview.stories.d.ts +13 -0
  60. package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +1 -0
  61. package/dist/types/src/stories/TextEditorSpecial.stories.d.ts +19 -0
  62. package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +1 -0
  63. package/dist/types/src/stories/story-utils.d.ts +53 -0
  64. package/dist/types/src/stories/story-utils.d.ts.map +1 -0
  65. package/dist/types/src/testing/RefPopover.d.ts +21 -0
  66. package/dist/types/src/testing/RefPopover.d.ts.map +1 -0
  67. package/dist/types/src/testing/index.d.ts +2 -0
  68. package/dist/types/src/testing/index.d.ts.map +1 -0
  69. package/dist/types/src/types.d.ts +5 -0
  70. package/dist/types/src/types.d.ts.map +1 -1
  71. package/dist/types/src/util/react.d.ts +6 -1
  72. package/dist/types/src/util/react.d.ts.map +1 -1
  73. package/package.json +35 -27
  74. package/src/components/EditorToolbar/EditorToolbar.tsx +2 -2
  75. package/src/components/EditorToolbar/util.ts +3 -3
  76. package/src/defaults.ts +5 -3
  77. package/src/extensions/automerge/automerge.stories.tsx +3 -11
  78. package/src/extensions/command/action.ts +49 -0
  79. package/src/extensions/command/command.ts +9 -27
  80. package/src/extensions/command/hint.ts +33 -30
  81. package/src/extensions/command/index.ts +1 -0
  82. package/src/extensions/command/menu.ts +11 -8
  83. package/src/extensions/command/state.ts +41 -61
  84. package/src/extensions/comments.ts +9 -9
  85. package/src/extensions/folding.tsx +1 -1
  86. package/src/extensions/index.ts +1 -0
  87. package/src/extensions/markdown/decorate.ts +4 -3
  88. package/src/extensions/markdown/formatting.ts +2 -2
  89. package/src/extensions/markdown/image.ts +12 -11
  90. package/src/extensions/markdown/link.ts +33 -24
  91. package/src/extensions/modes.ts +5 -6
  92. package/src/extensions/preview/index.ts +5 -0
  93. package/src/extensions/preview/preview.ts +271 -0
  94. package/src/hooks/useTextEditor.ts +4 -3
  95. package/src/{InputMode.stories.tsx → stories/InputMode.stories.tsx} +4 -4
  96. package/src/stories/TextEditorBasic.stories.tsx +289 -0
  97. package/src/stories/TextEditorComments.stories.tsx +99 -0
  98. package/src/stories/TextEditorPreview.stories.tsx +239 -0
  99. package/src/stories/TextEditorSpecial.stories.tsx +107 -0
  100. package/src/stories/story-utils.tsx +329 -0
  101. package/src/testing/RefPopover.tsx +74 -0
  102. package/src/testing/index.ts +5 -0
  103. package/src/types.ts +7 -0
  104. package/src/util/react.tsx +20 -2
  105. package/dist/types/src/InputMode.stories.d.ts.map +0 -1
  106. package/dist/types/src/TextEditor.stories.d.ts.map +0 -1
  107. package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +0 -1
  108. package/dist/types/src/extensions/command/preview.d.ts +0 -12
  109. package/dist/types/src/extensions/command/preview.d.ts.map +0 -1
  110. package/dist/types/src/fragments.d.ts +0 -3
  111. package/dist/types/src/fragments.d.ts.map +0 -1
  112. package/src/TextEditor.stories.tsx +0 -856
  113. package/src/extensions/command/preview.ts +0 -79
  114. package/src/fragments.ts +0 -19
  115. /package/src/components/EditorToolbar/{viewMode.ts → view-mode.ts} +0 -0
@@ -15,6 +15,7 @@ import { image } from './image';
15
15
  import { formattingStyles, bulletListIndentationWidth, orderedListIndentationWidth } from './styles';
16
16
  import { table } from './table';
17
17
  import { theme, type HeadingLevel } from '../../styles';
18
+ import { type RenderCallback } from '../../types';
18
19
  import { wrapWithCatch } from '../../util';
19
20
 
20
21
  /**
@@ -45,7 +46,7 @@ class HorizontalRuleWidget extends WidgetType {
45
46
  class LinkButton extends WidgetType {
46
47
  constructor(
47
48
  private readonly url: string,
48
- private readonly render: (el: HTMLElement, url: string) => void,
49
+ private readonly render: RenderCallback<{ url: string }>,
49
50
  ) {
50
51
  super();
51
52
  }
@@ -57,7 +58,7 @@ class LinkButton extends WidgetType {
57
58
  // TODO(burdon): Create icon and link directly without react?
58
59
  override toDOM(view: EditorView) {
59
60
  const el = document.createElement('span');
60
- this.render(el, this.url);
61
+ this.render(el, { url: this.url }, view);
61
62
  return el;
62
63
  }
63
64
  }
@@ -519,7 +520,7 @@ export interface DecorateOptions {
519
520
  */
520
521
  selectionChangeDelay?: number;
521
522
  numberedHeadings?: { from: number; to?: number };
522
- renderLinkButton?: (el: Element, url: string) => void;
523
+ renderLinkButton?: RenderCallback<{ url: string }>;
523
524
  }
524
525
 
525
526
  export const decorateMarkdown = (options: DecorateOptions = {}) => {
@@ -17,7 +17,7 @@ import { EditorView, keymap } from '@codemirror/view';
17
17
  import { type SyntaxNodeRef, type SyntaxNode } from '@lezer/common';
18
18
  import { useMemo } from 'react';
19
19
 
20
- import { type ReactiveObject } from '@dxos/live-object';
20
+ import { type Live } from '@dxos/live-object';
21
21
 
22
22
  import { type EditorToolbarState } from '../../components';
23
23
 
@@ -1250,7 +1250,7 @@ export const getFormatting = (state: EditorState): Formatting => {
1250
1250
  /**
1251
1251
  * Hook provides an extension to compute the current formatting state.
1252
1252
  */
1253
- export const useFormattingState = (state: ReactiveObject<EditorToolbarState>): Extension => {
1253
+ export const useFormattingState = (state: Live<EditorToolbarState>): Extension => {
1254
1254
  return useMemo(
1255
1255
  () =>
1256
1256
  EditorView.updateListener.of((update) => {
@@ -51,16 +51,6 @@ export const image = (_options: ImageOptions = {}): Extension => {
51
51
  ];
52
52
  };
53
53
 
54
- const preloaded = new Set<string>();
55
-
56
- const preloadImage = (url: string) => {
57
- if (!preloaded.has(url)) {
58
- const img = document.createElement('img');
59
- img.src = url;
60
- preloaded.add(url);
61
- }
62
- };
63
-
64
54
  const buildDecorations = (from: number, to: number, state: EditorState) => {
65
55
  const decorations: Range<Decoration>[] = [];
66
56
  const cursor = state.selection.main.head;
@@ -94,13 +84,23 @@ const buildDecorations = (from: number, to: number, state: EditorState) => {
94
84
  return decorations;
95
85
  };
96
86
 
87
+ const preloaded = new Set<string>();
88
+
89
+ const preloadImage = (url: string) => {
90
+ if (!preloaded.has(url)) {
91
+ const img = document.createElement('img');
92
+ img.src = url;
93
+ preloaded.add(url);
94
+ }
95
+ };
96
+
97
97
  class ImageWidget extends WidgetType {
98
98
  constructor(readonly _url: string) {
99
99
  super();
100
100
  }
101
101
 
102
102
  override eq(other: this) {
103
- return this._url === (other as any as ImageWidget)._url;
103
+ return this._url === other._url;
104
104
  }
105
105
 
106
106
  override toDOM(view: EditorView) {
@@ -113,6 +113,7 @@ class ImageWidget extends WidgetType {
113
113
  } else {
114
114
  img.classList.add('cm-loaded-image');
115
115
  }
116
+
116
117
  return img;
117
118
  }
118
119
  }
@@ -9,30 +9,39 @@ import { type SyntaxNode } from '@lezer/common';
9
9
 
10
10
  import { tooltipContent } from '@dxos/react-ui-theme';
11
11
 
12
- export const linkTooltip = (render: (el: HTMLElement, url: string) => void) => {
13
- return hoverTooltip((view, pos, side) => {
14
- const syntax = syntaxTree(view.state).resolveInner(pos, side);
15
- let link = null;
16
- for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
17
- link = node.name === 'Link' ? node : null;
18
- }
12
+ import { type RenderCallback } from '../../types';
19
13
 
20
- const url = link && link.getChild('URL');
21
- if (!url || !link) {
22
- return null;
23
- }
14
+ export const linkTooltip = (renderTooltip: RenderCallback<{ url: string }>) => {
15
+ return hoverTooltip(
16
+ (view, pos, side) => {
17
+ const syntax = syntaxTree(view.state).resolveInner(pos, side);
18
+ let link = null;
19
+ for (let i = 0, node: SyntaxNode | null = syntax; !link && node && i < 5; node = node.parent, i++) {
20
+ link = node.name === 'Link' ? node : null;
21
+ }
24
22
 
25
- const urlText = view.state.sliceDoc(url.from, url.to);
26
- return {
27
- pos: link.from,
28
- end: link.to,
29
- above: true,
30
- create: () => {
31
- const el = document.createElement('div');
32
- el.className = tooltipContent({}, 'pli-2 plb-1');
33
- render(el, urlText);
34
- return { dom: el, offset: { x: 0, y: 4 } };
35
- },
36
- };
37
- });
23
+ const url = link && link.getChild('URL');
24
+ if (!url || !link) {
25
+ return null;
26
+ }
27
+
28
+ const urlText = view.state.sliceDoc(url.from, url.to);
29
+ return {
30
+ pos: link.from,
31
+ end: link.to,
32
+ // NOTE: Forcing above causes the tooltip to flicker.
33
+ // above: true,
34
+ create: () => {
35
+ const el = document.createElement('div');
36
+ el.className = tooltipContent({});
37
+ renderTooltip(el, { url: urlText }, view);
38
+ return { dom: el, offset: { x: 0, y: 4 } };
39
+ },
40
+ };
41
+ },
42
+ {
43
+ // NOTE: 0 = default of 300ms.
44
+ hoverTime: 1,
45
+ },
46
+ );
38
47
  };
@@ -6,18 +6,17 @@ import { type Extension } from '@codemirror/state';
6
6
  import { keymap } from '@codemirror/view';
7
7
  import { vim } from '@replit/codemirror-vim';
8
8
  import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
9
-
10
- import { S } from '@dxos/echo-schema';
9
+ import { Schema } from 'effect';
11
10
 
12
11
  import { singleValueFacet } from '../util';
13
12
 
14
13
  export const EditorViewModes = ['preview', 'readonly', 'source'] as const;
15
- export const EditorViewMode = S.Union(...EditorViewModes.map((mode) => S.Literal(mode)));
16
- export type EditorViewMode = S.Schema.Type<typeof EditorViewMode>;
14
+ export const EditorViewMode = Schema.Union(...EditorViewModes.map((mode) => Schema.Literal(mode)));
15
+ export type EditorViewMode = Schema.Schema.Type<typeof EditorViewMode>;
17
16
 
18
17
  export const EditorInputModes = ['default', 'vim', 'vscode'] as const;
19
- export const EditorInputMode = S.Union(...EditorInputModes.map((mode) => S.Literal(mode)));
20
- export type EditorInputMode = S.Schema.Type<typeof EditorInputMode>;
18
+ export const EditorInputMode = Schema.Union(...EditorInputModes.map((mode) => Schema.Literal(mode)));
19
+ export type EditorInputMode = Schema.Schema.Type<typeof EditorInputMode>;
21
20
 
22
21
  export type EditorInputConfig = {
23
22
  type?: string;
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './preview';
@@ -0,0 +1,271 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos/lit-ui/dx-ref-tag.pcss';
6
+
7
+ import { syntaxTree } from '@codemirror/language';
8
+ import {
9
+ type EditorState,
10
+ type Extension,
11
+ type RangeSet,
12
+ RangeSetBuilder,
13
+ StateField,
14
+ type Transaction,
15
+ } from '@codemirror/state';
16
+ import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
17
+ import { type SyntaxNode } from '@lezer/common';
18
+
19
+ import { type RenderCallback } from '../../types';
20
+
21
+ export type PreviewLinkRef = {
22
+ suggest?: boolean;
23
+ block?: boolean;
24
+ label: string;
25
+ ref: string;
26
+ };
27
+
28
+ export type PreviewLinkTarget = {
29
+ label: string;
30
+ text?: string;
31
+ object?: any;
32
+ };
33
+
34
+ export type PreviewAction =
35
+ | {
36
+ type: 'insert';
37
+ link: PreviewLinkRef;
38
+ target: PreviewLinkTarget;
39
+ }
40
+ | {
41
+ type: 'delete';
42
+ link: PreviewLinkRef;
43
+ };
44
+
45
+ // TODO(burdon): Handle error.
46
+ export type PreviewLookup = (link: PreviewLinkRef) => Promise<PreviewLinkTarget | null | undefined>;
47
+
48
+ export type PreviewActionHandler = (action: PreviewAction) => void;
49
+
50
+ export type PreviewRenderProps = {
51
+ readonly: boolean;
52
+ link: PreviewLinkRef;
53
+ onAction: PreviewActionHandler;
54
+ onLookup?: PreviewLookup;
55
+ };
56
+
57
+ export type PreviewOptions = {
58
+ renderBlock?: RenderCallback<PreviewRenderProps>;
59
+ onLookup?: PreviewLookup;
60
+ };
61
+
62
+ /**
63
+ * Create preview decorations.
64
+ */
65
+ export const preview = (options: PreviewOptions = {}): Extension => {
66
+ return [
67
+ // NOTE: Atomic block decorations must be created from a state field, now a widget, otherwise it results in the following error:
68
+ // "Block decorations may not be specified via plugins"
69
+ StateField.define<DecorationSet>({
70
+ create: (state) => buildDecorations(state, options),
71
+ update: (_: RangeSet<Decoration>, tr: Transaction) => buildDecorations(tr.state, options),
72
+ provide: (field) => [
73
+ EditorView.decorations.from(field),
74
+ EditorView.atomicRanges.of((view) => view.state.field(field)),
75
+ ],
76
+ }),
77
+
78
+ EditorView.theme({
79
+ '.cm-preview-block': {
80
+ marginLeft: '-1rem',
81
+ marginRight: '-1rem',
82
+ padding: '1rem',
83
+ borderRadius: '0.5rem',
84
+ background: 'var(--dx-modalSurface)',
85
+ border: '1px solid var(--dx-separator)',
86
+ },
87
+ }),
88
+ ];
89
+ };
90
+
91
+ /**
92
+ * Link references.
93
+ *
94
+ * [Label][dxn:echo:123] Inline reference
95
+ * ![Label][dxn:echo:123] Block reference
96
+ * ![Label][?dxn:echo:123] Suggestion
97
+ */
98
+ const getLinkRef = (state: EditorState, node: SyntaxNode): PreviewLinkRef | undefined => {
99
+ const mark = node.getChild('LinkMark');
100
+ const label = node.getChild('LinkLabel');
101
+ if (mark && label) {
102
+ const ref = state.sliceDoc(label.from + 1, label.to - 1);
103
+ return {
104
+ suggest: ref.startsWith('?'),
105
+ block: state.sliceDoc(mark.from, mark.from + 1) === '!',
106
+ label: state.sliceDoc(mark.to, label.from - 1),
107
+ ref: ref.startsWith('?') ? ref.slice(1) : ref,
108
+ };
109
+ }
110
+ };
111
+
112
+ /**
113
+ * Echo references are represented as markdown reference links.
114
+ * https://www.markdownguide.org/basic-syntax/#reference-style-links
115
+ * [Label|block][dxn:echo:123]
116
+ * [Label|inline][dxn:echo:123]
117
+ */
118
+ const buildDecorations = (state: EditorState, options: PreviewOptions) => {
119
+ const builder = new RangeSetBuilder<Decoration>();
120
+
121
+ syntaxTree(state).iterate({
122
+ enter: (node) => {
123
+ switch (node.name) {
124
+ //
125
+ // Decoration.
126
+ // [Label][dxn:echo:123]
127
+ //
128
+ case 'Link': {
129
+ const link = getLinkRef(state, node.node);
130
+ if (link) {
131
+ builder.add(
132
+ node.from,
133
+ node.to,
134
+ Decoration.replace({
135
+ widget: new PreviewInlineWidget(options, link),
136
+ }),
137
+ );
138
+ }
139
+ break;
140
+ }
141
+ //
142
+ // Block widget.
143
+ // ![Label][dxn:echo:123]
144
+ //
145
+ case 'Image': {
146
+ const link = getLinkRef(state, node.node);
147
+ if (options.renderBlock && link) {
148
+ builder.add(
149
+ node.from,
150
+ node.to,
151
+ Decoration.replace({
152
+ block: true,
153
+ // atomic: true,
154
+ widget: new PreviewBlockWidget(options, link),
155
+ }),
156
+ );
157
+ }
158
+ break;
159
+ }
160
+ }
161
+ },
162
+ });
163
+
164
+ return builder.finish();
165
+ };
166
+
167
+ /**
168
+ * Inline widget.
169
+ * [Label][dxn:echo:123]
170
+ */
171
+ class PreviewInlineWidget extends WidgetType {
172
+ constructor(
173
+ readonly _options: PreviewOptions,
174
+ readonly _link: PreviewLinkRef,
175
+ ) {
176
+ super();
177
+ }
178
+
179
+ // override ignoreEvent() {
180
+ // return false;
181
+ // }
182
+
183
+ override eq(other: this) {
184
+ return this._link.ref === other._link.ref && this._link.label === other._link.label;
185
+ }
186
+
187
+ override toDOM(view: EditorView) {
188
+ const root = document.createElement('dx-ref-tag');
189
+ root.textContent = this._link.label;
190
+ root.setAttribute('ref', this._link.ref);
191
+ return root;
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Block widget.
197
+ * ![Label][dxn:echo:123]
198
+ */
199
+ class PreviewBlockWidget extends WidgetType {
200
+ constructor(
201
+ readonly _options: PreviewOptions,
202
+ readonly _link: PreviewLinkRef,
203
+ ) {
204
+ super();
205
+ }
206
+
207
+ // override ignoreEvent() {
208
+ // return true;
209
+ // }
210
+
211
+ override eq(other: this) {
212
+ return this._link.ref === other._link.ref;
213
+ }
214
+
215
+ override toDOM(view: EditorView) {
216
+ const root = document.createElement('div');
217
+ root.classList.add('cm-preview-block');
218
+
219
+ // TODO(burdon): Inject handler.
220
+ const handleAction: PreviewActionHandler = (action) => {
221
+ const pos = view.posAtDOM(root);
222
+ const node = syntaxTree(view.state).resolve(pos + 1).node.parent;
223
+ if (!node) {
224
+ return;
225
+ }
226
+
227
+ const link = getLinkRef(view.state, node);
228
+ if (link?.ref !== action.link.ref) {
229
+ return;
230
+ }
231
+
232
+ switch (action.type) {
233
+ // TODO(burdon): Should we dispatch to the view or mutate the document? (i.e., handle externally?)
234
+ // Insert ref text.
235
+ case 'insert': {
236
+ view.dispatch({
237
+ changes: {
238
+ from: node.from,
239
+ to: node.to,
240
+ insert: action.target.text,
241
+ },
242
+ });
243
+ break;
244
+ }
245
+ // Remove ref.
246
+ case 'delete': {
247
+ view.dispatch({
248
+ changes: {
249
+ from: node.from,
250
+ to: node.to,
251
+ },
252
+ });
253
+ break;
254
+ }
255
+ }
256
+ };
257
+
258
+ this._options.renderBlock!(
259
+ root,
260
+ {
261
+ readonly: view.state.readOnly,
262
+ link: this._link,
263
+ onAction: handleAction,
264
+ onLookup: this._options.onLookup,
265
+ },
266
+ view,
267
+ );
268
+
269
+ return root;
270
+ }
271
+ }
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { EditorState, type EditorStateConfig } from '@codemirror/state';
5
+ import { EditorState, type EditorStateConfig, type Text } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
7
  import { useFocusableGroup, type TabsterTypes } from '@fluentui/react-tabster';
8
8
  import {
@@ -43,6 +43,7 @@ export type CursorInfo = {
43
43
 
44
44
  export type UseTextEditorProps = Pick<EditorStateConfig, 'extensions'> & {
45
45
  id?: string;
46
+ doc?: Text;
46
47
  initialValue?: string;
47
48
  className?: string;
48
49
  autoFocus?: boolean;
@@ -61,7 +62,7 @@ export const useTextEditor = (
61
62
  props: MaybeProvider<UseTextEditorProps> = {},
62
63
  deps: DependencyList = [],
63
64
  ): UseTextEditor => {
64
- const { id, initialValue, extensions, autoFocus, scrollTo, selection, moveToEndOfLine, debug } =
65
+ const { id, doc, initialValue, extensions, autoFocus, scrollTo, selection, moveToEndOfLine, debug } =
65
66
  useMemo<UseTextEditorProps>(() => getProviderValue(props), deps ?? []);
66
67
 
67
68
  // NOTE: Increments by 2 in strict mode.
@@ -87,7 +88,7 @@ export const useTextEditor = (
87
88
 
88
89
  // https://codemirror.net/docs/ref/#state.EditorStateConfig
89
90
  const state = EditorState.create({
90
- doc: initialValue,
91
+ doc: doc ?? initialValue,
91
92
  // selection: initialSelection,
92
93
  extensions: [
93
94
  id && documentId.of(id),
@@ -10,7 +10,7 @@ import { Toolbar as NaturalToolbar, Select, useThemeContext } from '@dxos/react-
10
10
  import { attentionSurface, mx } from '@dxos/react-ui-theme';
11
11
  import { withLayout, withTheme } from '@dxos/storybook-utils';
12
12
 
13
- import { EditorToolbar, useEditorToolbarState } from './components';
13
+ import { EditorToolbar, useEditorToolbarState } from '../components';
14
14
  import {
15
15
  type EditorInputMode,
16
16
  decorateMarkdown,
@@ -20,9 +20,9 @@ import {
20
20
  createBasicExtensions,
21
21
  createThemeExtensions,
22
22
  InputModeExtensions,
23
- } from './extensions';
24
- import { useActionHandler, useTextEditor, type UseTextEditorProps } from './hooks';
25
- import translations from './translations';
23
+ } from '../extensions';
24
+ import { useActionHandler, useTextEditor, type UseTextEditorProps } from '../hooks';
25
+ import translations from '../translations';
26
26
 
27
27
  type StoryProps = { placeholder?: string; readOnly?: boolean } & UseTextEditorProps;
28
28