@dxos/react-ui-editor 0.8.1-staging.9eaf14f → 0.8.2-main.12df754

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 (112) hide show
  1. package/dist/lib/browser/index.mjs +499 -371
  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 +515 -379
  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 +499 -371
  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/preview/index.d.ts +2 -0
  46. package/dist/types/src/extensions/preview/index.d.ts.map +1 -0
  47. package/dist/types/src/extensions/preview/preview.d.ts +39 -0
  48. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -0
  49. package/dist/types/src/hooks/useTextEditor.d.ts +2 -1
  50. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  51. package/dist/types/src/{InputMode.stories.d.ts → stories/InputMode.stories.d.ts} +1 -1
  52. package/dist/types/src/stories/InputMode.stories.d.ts.map +1 -0
  53. package/dist/types/src/{TextEditor.stories.d.ts → stories/TextEditorBasic.stories.d.ts} +2 -35
  54. package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +1 -0
  55. package/dist/types/src/stories/TextEditorComments.stories.d.ts +13 -0
  56. package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +1 -0
  57. package/dist/types/src/stories/TextEditorPreview.stories.d.ts +13 -0
  58. package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +1 -0
  59. package/dist/types/src/stories/TextEditorSpecial.stories.d.ts +19 -0
  60. package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +1 -0
  61. package/dist/types/src/stories/story-utils.d.ts +53 -0
  62. package/dist/types/src/stories/story-utils.d.ts.map +1 -0
  63. package/dist/types/src/testing/RefPopover.d.ts +21 -0
  64. package/dist/types/src/testing/RefPopover.d.ts.map +1 -0
  65. package/dist/types/src/testing/index.d.ts +2 -0
  66. package/dist/types/src/testing/index.d.ts.map +1 -0
  67. package/dist/types/src/types.d.ts +5 -0
  68. package/dist/types/src/types.d.ts.map +1 -1
  69. package/dist/types/src/util/react.d.ts +6 -1
  70. package/dist/types/src/util/react.d.ts.map +1 -1
  71. package/package.json +33 -27
  72. package/src/components/EditorToolbar/EditorToolbar.tsx +2 -2
  73. package/src/components/EditorToolbar/util.ts +3 -3
  74. package/src/defaults.ts +5 -3
  75. package/src/extensions/automerge/automerge.stories.tsx +3 -11
  76. package/src/extensions/command/action.ts +49 -0
  77. package/src/extensions/command/command.ts +9 -27
  78. package/src/extensions/command/hint.ts +33 -30
  79. package/src/extensions/command/index.ts +1 -0
  80. package/src/extensions/command/menu.ts +11 -8
  81. package/src/extensions/command/state.ts +41 -61
  82. package/src/extensions/comments.ts +9 -9
  83. package/src/extensions/folding.tsx +1 -1
  84. package/src/extensions/index.ts +1 -0
  85. package/src/extensions/markdown/decorate.ts +4 -3
  86. package/src/extensions/markdown/formatting.ts +2 -2
  87. package/src/extensions/markdown/image.ts +12 -11
  88. package/src/extensions/markdown/link.ts +33 -24
  89. package/src/extensions/preview/index.ts +5 -0
  90. package/src/extensions/preview/preview.ts +271 -0
  91. package/src/hooks/useTextEditor.ts +4 -3
  92. package/src/{InputMode.stories.tsx → stories/InputMode.stories.tsx} +4 -4
  93. package/src/stories/TextEditorBasic.stories.tsx +289 -0
  94. package/src/stories/TextEditorComments.stories.tsx +99 -0
  95. package/src/stories/TextEditorPreview.stories.tsx +239 -0
  96. package/src/stories/TextEditorSpecial.stories.tsx +107 -0
  97. package/src/stories/story-utils.tsx +329 -0
  98. package/src/testing/RefPopover.tsx +74 -0
  99. package/src/testing/index.ts +5 -0
  100. package/src/types.ts +7 -0
  101. package/src/util/react.tsx +20 -2
  102. package/dist/types/src/InputMode.stories.d.ts.map +0 -1
  103. package/dist/types/src/TextEditor.stories.d.ts.map +0 -1
  104. package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +0 -1
  105. package/dist/types/src/extensions/command/preview.d.ts +0 -12
  106. package/dist/types/src/extensions/command/preview.d.ts.map +0 -1
  107. package/dist/types/src/fragments.d.ts +0 -3
  108. package/dist/types/src/fragments.d.ts.map +0 -1
  109. package/src/TextEditor.stories.tsx +0 -856
  110. package/src/extensions/command/preview.ts +0 -79
  111. package/src/fragments.ts +0 -19
  112. /package/src/components/EditorToolbar/{viewMode.ts → view-mode.ts} +0 -0
@@ -0,0 +1,107 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import defaultsDeep from 'lodash.defaultsdeep';
8
+ import React from 'react';
9
+
10
+ import { log } from '@dxos/log';
11
+ import { faker } from '@dxos/random';
12
+ import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
13
+
14
+ import { DefaultStory, str, content } from './story-utils';
15
+ import { typewriter, blast, defaultOptions, dropFile, listener } from '../extensions';
16
+
17
+ const meta: Meta<typeof DefaultStory> = {
18
+ title: 'ui/react-ui-editor/TextEditor',
19
+ decorators: [withTheme, withLayout({ fullscreen: true })],
20
+ render: DefaultStory,
21
+ parameters: { layout: 'fullscreen' },
22
+ };
23
+
24
+ export default meta;
25
+
26
+ //
27
+ // Listener
28
+ //
29
+
30
+ export const Listener = {
31
+ render: () => (
32
+ <DefaultStory
33
+ text={str('# Listener', '', content.footer)}
34
+ extensions={[
35
+ listener({
36
+ onFocus: (focusing) => {
37
+ console.log({ focusing });
38
+ },
39
+ onChange: (text) => {
40
+ console.log({ text });
41
+ },
42
+ }),
43
+ ]}
44
+ />
45
+ ),
46
+ };
47
+
48
+ //
49
+ // Typewriter
50
+ //
51
+
52
+ const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
53
+
54
+ export const Typewriter = {
55
+ render: () => (
56
+ <DefaultStory
57
+ text={str('# Typewriter', '', content.paragraphs, content.footer)}
58
+ extensions={[typewriter({ items: typewriterItems })]}
59
+ />
60
+ ),
61
+ };
62
+
63
+ //
64
+ // Blast
65
+ //
66
+
67
+ export const Blast = {
68
+ render: () => (
69
+ <DefaultStory
70
+ text={str('# Blast', '', content.paragraphs, content.codeblocks, content.paragraphs)}
71
+ extensions={[
72
+ typewriter({ items: typewriterItems }),
73
+ blast(
74
+ defaultsDeep(
75
+ {
76
+ effect: 2,
77
+ particleGravity: 0.2,
78
+ particleShrinkRate: 0.995,
79
+ color: () => [faker.number.int({ min: 100, max: 200 }), 0, 0],
80
+ // color: () => [faker.number.int(256), faker.number.int(256), faker.number.int(256)],
81
+ },
82
+ defaultOptions,
83
+ ),
84
+ ),
85
+ ]}
86
+ />
87
+ ),
88
+ };
89
+
90
+ //
91
+ // DND
92
+ //
93
+
94
+ export const DND = {
95
+ render: () => (
96
+ <DefaultStory
97
+ text={str('# DND', '')}
98
+ extensions={[
99
+ dropFile({
100
+ onDrop: (view, event) => {
101
+ log.info('drop', event);
102
+ },
103
+ }),
104
+ ]}
105
+ />
106
+ ),
107
+ };
@@ -0,0 +1,329 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type Completion } from '@codemirror/autocomplete';
6
+ import { type Extension } from '@codemirror/state';
7
+ import { type EditorView } from '@codemirror/view';
8
+ import React, { useEffect, useState, type FC } from 'react';
9
+
10
+ import { Expando } from '@dxos/echo-schema';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { live } from '@dxos/live-object';
13
+ import { faker } from '@dxos/random';
14
+ import { createDocAccessor, createObject } from '@dxos/react-client/echo';
15
+ import { useThemeContext, Icon } from '@dxos/react-ui';
16
+ import { mx } from '@dxos/react-ui-theme';
17
+
18
+ import { editorContent, editorGutter } from '../defaults';
19
+ import {
20
+ type EditorSelectionState,
21
+ type DebugNode,
22
+ decorateMarkdown,
23
+ formattingKeymap,
24
+ linkTooltip,
25
+ image,
26
+ table,
27
+ folding,
28
+ } from '../extensions';
29
+ import {
30
+ createDataExtensions,
31
+ createBasicExtensions,
32
+ createMarkdownExtensions,
33
+ createThemeExtensions,
34
+ debugTree,
35
+ } from '../extensions';
36
+ import { useTextEditor, type UseTextEditorProps } from '../hooks';
37
+ import { createRenderer } from '../util';
38
+
39
+ // Utility functions
40
+ export const str = (...lines: string[]) => lines.join('\n');
41
+
42
+ export const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
43
+
44
+ export const img = '![dxos](https://dxos.network/dxos-logotype-blue.png)';
45
+
46
+ export const code = str(
47
+ '// Code',
48
+ 'const Component = () => {',
49
+ ' const x = 100;',
50
+ '',
51
+ ' return () => <div>Test</div>;',
52
+ '};',
53
+ );
54
+
55
+ // Content blocks for stories
56
+ export const content = {
57
+ tasks: str(
58
+ //
59
+ '### TaskList',
60
+ '',
61
+ `- [x] ${faker.lorem.sentences()}`,
62
+ `- [ ] ${faker.lorem.sentences()}`,
63
+ ` - [ ] ${faker.lorem.sentences()}`,
64
+ ` - [ ] ${faker.lorem.sentences()}`,
65
+ ` - [x] ${faker.lorem.sentences()}`,
66
+ '',
67
+ ),
68
+
69
+ bullets: str(
70
+ //
71
+ '### BulletList',
72
+ '',
73
+ `- ${faker.lorem.sentences()}`,
74
+ `- ${faker.lorem.sentences()}`,
75
+ ` - ${faker.lorem.sentences()}`,
76
+ ` - ${faker.lorem.sentences()}`,
77
+ `- ${faker.lorem.sentences()}`,
78
+ '',
79
+ ),
80
+
81
+ numbered: str(
82
+ //
83
+ '### OrderedList (part 1)',
84
+ '',
85
+ `1. ${faker.lorem.sentences()}`,
86
+ `1. ${faker.lorem.sentences()}`,
87
+ `1. ${faker.lorem.sentences()}`,
88
+ ` 1. ${faker.lorem.sentences()}`,
89
+ ` 1. ${faker.lorem.sentences()}`,
90
+ ` 1. ${faker.lorem.sentences()}`,
91
+ `1. ${faker.lorem.sentences()}`,
92
+ '',
93
+ '### OrderedList (part 2)',
94
+ '',
95
+ `1. ${faker.lorem.sentences()}`,
96
+ '',
97
+ ),
98
+
99
+ typescript: code,
100
+
101
+ codeblocks: str('### Code', '', '```bash', '$ ls -las', '```', '', '```tsx', code, '```', ''),
102
+
103
+ comment: str('<!--', 'A comment', '-->', '', 'No comment.', 'Partial comment. <!-- comment. -->'),
104
+
105
+ links: str(
106
+ '### Links',
107
+ '',
108
+ 'This is a naked link https://dxos.org within a sentence.',
109
+ '',
110
+ 'Take a look at [DXOS](https://dxos.org) and how to [get started](https://docs.dxos.org/guide/getting-started.html).',
111
+ '',
112
+ 'This is all about https://dxos.org and related technologies.',
113
+ '',
114
+ ),
115
+
116
+ table: str(
117
+ '### Tables',
118
+ '',
119
+ `| ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} | ${faker.lorem.word().padStart(12)} |`,
120
+ `|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|-${''.padStart(12, '-')}-|`,
121
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
122
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
123
+ `| ${num().padStart(12)} | ${num().padStart(12)} | ${num().padStart(12)} |`,
124
+ '',
125
+ ),
126
+
127
+ image: str('### Image', '', img),
128
+
129
+ headings: str(
130
+ ...[1, 2, 3, 4, 5, 6].map((level) => ['#'.repeat(level) + ` Heading ${level}`, faker.lorem.sentences(), '']).flat(),
131
+ ),
132
+
133
+ formatting: str('### Formatting', '', 'This this is **bold**, ~~strikethrough~~, _italic_, and `f(INLINE)`.', ''),
134
+
135
+ blockquotes: str(
136
+ '### Blockquotes',
137
+ '',
138
+ '> This is a block quote.',
139
+ '',
140
+ '> This is a long wrapping block quote. Neque reiciendis ullam quae error labore sit, at, et, nulla, aut at nostrum omnis quas nostrum, at consectetur vitae eos asperiores non omnis ullam in beatae at vitae deserunt asperiores sapiente.',
141
+ '',
142
+ '> This is ...',
143
+ '... a multi-line ...',
144
+ 'block quote.',
145
+ '',
146
+ ),
147
+
148
+ paragraphs: str(...faker.helpers.multiple(() => [faker.lorem.paragraph(), ''], { count: 3 }).flat()),
149
+
150
+ footer: str('', '', '', '', ''),
151
+ };
152
+
153
+ // Combined text for stories
154
+ export const text = str(
155
+ '# Markdown',
156
+ 'Composer Markdown Editor',
157
+ '',
158
+
159
+ '---',
160
+ '## Basics',
161
+ content.blockquotes,
162
+ content.formatting,
163
+ content.links,
164
+
165
+ '---',
166
+ '## Lists',
167
+ content.bullets,
168
+ content.tasks,
169
+ content.numbered,
170
+
171
+ '---',
172
+ '## Misc',
173
+ content.codeblocks,
174
+ content.table,
175
+ content.image,
176
+ content.footer,
177
+ '=== LAST LINE ===',
178
+ );
179
+
180
+ // Shared links for autocomplete
181
+ export const links: Completion[] = [
182
+ { label: 'DXOS', apply: '[DXOS](https://dxos.org)' },
183
+ { label: 'GitHub', apply: '[DXOS GitHub](https://github.com/dxos)' },
184
+ { label: 'Automerge', apply: '[Automerge](https://automerge.org/)' },
185
+ { label: 'IPFS', apply: '[Protocol Labs](https://docs.ipfs.tech)' },
186
+ { label: 'StackEdit', apply: '[StackEdit](https://stackedit.io/app)' },
187
+ ];
188
+
189
+ export const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
190
+
191
+ const hover =
192
+ 'rounded-sm text-baseText text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
193
+
194
+ const LinkTooltip: FC<{ url: string }> = ({ url }) => {
195
+ const web = new URL(url);
196
+ return (
197
+ <a href={url} target='_blank' rel='noreferrer' className={mx(hover, 'flex items-center gap-2')}>
198
+ {web.origin}
199
+ <Icon icon='ph--arrow-square-out--regular' size={4} />
200
+ </a>
201
+ );
202
+ };
203
+
204
+ export const renderLinkTooltip = createRenderer(LinkTooltip);
205
+
206
+ const LinkButton: FC<{ url: string }> = ({ url }) => {
207
+ return (
208
+ <a href={url} target='_blank' rel='noreferrer' className={mx(hover)}>
209
+ <Icon icon='ph--arrow-square-out--regular' size={4} classNames='inline-block mis-1 mb-[3px]' />
210
+ </a>
211
+ );
212
+ };
213
+
214
+ export const renderLinkButton = createRenderer(LinkButton);
215
+
216
+ // Shared extensions
217
+ export const defaultExtensions: Extension[] = [
218
+ decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100 }),
219
+ formattingKeymap(),
220
+ linkTooltip(renderLinkTooltip),
221
+ ];
222
+
223
+ export const allExtensions: Extension[] = [
224
+ decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100, numberedHeadings: { from: 2, to: 4 } }),
225
+ formattingKeymap(),
226
+ linkTooltip(renderLinkTooltip),
227
+ image(),
228
+ table(),
229
+ folding(),
230
+ ];
231
+
232
+ // Long text for scrolling stories
233
+ export const longText = faker.helpers
234
+ .multiple(() => faker.lorem.paragraph({ min: 8, max: 16 }), { count: 20 })
235
+ .join('\n\n');
236
+
237
+ export const largeWithImages = faker.helpers
238
+ .multiple(() => [faker.lorem.paragraph({ min: 12, max: 16 }), img], { count: 20 })
239
+ .flatMap((x) => x)
240
+ .join('\n\n');
241
+
242
+ export const headings = str(
243
+ ...[1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 2, 3, 3, 2, 2, 6, 1]
244
+ .map((level) => ['#'.repeat(level) + ' ' + faker.lorem.sentence(3), faker.lorem.sentences(), ''])
245
+ .flat(),
246
+ );
247
+
248
+ export const global = new Map<string, EditorSelectionState>();
249
+
250
+ // Type definitions
251
+ export type DebugMode = 'raw' | 'tree' | 'raw+tree';
252
+
253
+ export type StoryProps = {
254
+ id?: string;
255
+ debug?: DebugMode;
256
+ text?: string;
257
+ readOnly?: boolean;
258
+ placeholder?: string;
259
+ lineNumbers?: boolean;
260
+ onReady?: (view: EditorView) => void;
261
+ } & Pick<UseTextEditorProps, 'scrollTo' | 'selection' | 'extensions'>;
262
+
263
+ // Default story component
264
+ export const DefaultStory = ({
265
+ id = 'editor-' + PublicKey.random().toHex().slice(0, 8),
266
+ debug,
267
+ text,
268
+ extensions,
269
+ readOnly,
270
+ placeholder = 'New document.',
271
+ scrollTo,
272
+ selection,
273
+ lineNumbers,
274
+ onReady,
275
+ }: StoryProps) => {
276
+ const [object] = useState(createObject(live(Expando, { content: text ?? '' })));
277
+ const { themeMode } = useThemeContext();
278
+ const [tree, setTree] = useState<DebugNode>();
279
+ const { parentRef, focusAttributes, view } = useTextEditor(
280
+ () => ({
281
+ id,
282
+ initialValue: text,
283
+ extensions: [
284
+ createDataExtensions({ id, text: createDocAccessor(object, ['content']) }),
285
+ createBasicExtensions({ readOnly, placeholder, lineNumbers, scrollPastEnd: true }),
286
+ createMarkdownExtensions({ themeMode }),
287
+ createThemeExtensions({
288
+ themeMode,
289
+ syntaxHighlighting: true,
290
+ slots: {
291
+ content: {
292
+ className: editorContent,
293
+ },
294
+ },
295
+ }),
296
+ editorGutter,
297
+ extensions || [],
298
+ debug ? debugTree(setTree) : [],
299
+ ],
300
+ scrollTo,
301
+ selection,
302
+ }),
303
+ [object, extensions, themeMode],
304
+ );
305
+
306
+ useEffect(() => {
307
+ if (view) {
308
+ onReady?.(view);
309
+ }
310
+ }, [view]);
311
+
312
+ return (
313
+ <div className='flex w-full'>
314
+ <div role='none' className='flex w-full overflow-hidden' ref={parentRef} {...focusAttributes} />
315
+ {debug && (
316
+ <div className='flex flex-col w-[800px] border-l border-separator divide-y divide-separator overflow-auto'>
317
+ {(debug === 'raw' || debug === 'raw+tree') && (
318
+ <pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>{view?.state.doc.toString()}</pre>
319
+ )}
320
+ {(debug === 'tree' || debug === 'raw+tree') && (
321
+ <pre className='p-1 font-mono text-xs text-green-800 dark:text-green-200'>
322
+ {JSON.stringify(tree, null, 2)}
323
+ </pre>
324
+ )}
325
+ </div>
326
+ )}
327
+ </div>
328
+ );
329
+ };
@@ -0,0 +1,74 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+ import React, { type PropsWithChildren, useRef, useState, useEffect, useCallback } from 'react';
7
+
8
+ import { addEventListener } from '@dxos/async';
9
+ import { type DxRefTagActivate } from '@dxos/lit-ui';
10
+ import { Popover } from '@dxos/react-ui';
11
+
12
+ import { type PreviewLinkRef, type PreviewLinkTarget, type PreviewLookup } from '../extensions';
13
+
14
+ const customEventOptions = { capture: true, passive: false };
15
+
16
+ // Create a context for the dxn value.
17
+ type RefPopoverValue = Partial<{ link: PreviewLinkRef; target: PreviewLinkTarget; pending: boolean }>;
18
+ const REF_POPOVER = 'RefPopover';
19
+ const [RefPopoverContextProvider, useRefPopover] = createContext<RefPopoverValue>(REF_POPOVER, {});
20
+
21
+ type RefPopoverProviderProps = PropsWithChildren<{ onLookup?: PreviewLookup }>;
22
+
23
+ const RefPopoverProvider = ({ children, onLookup }: RefPopoverProviderProps) => {
24
+ const trigger = useRef<HTMLButtonElement | null>(null);
25
+ const [value, setValue] = useState<RefPopoverValue>({});
26
+ const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
27
+ const [open, setOpen] = useState(false);
28
+
29
+ const handleDxRefTagActivate = useCallback(
30
+ (event: DxRefTagActivate) => {
31
+ const { ref, label, trigger: dxTrigger } = event;
32
+ setValue((value) => ({
33
+ ...value,
34
+ link: { label, ref },
35
+ pending: true,
36
+ }));
37
+ trigger.current = dxTrigger;
38
+ queueMicrotask(() => setOpen(true));
39
+ void onLookup?.({ label, ref }).then((target) =>
40
+ setValue((value) => ({
41
+ ...value,
42
+ target: target ?? undefined,
43
+ pending: false,
44
+ })),
45
+ );
46
+ },
47
+ [onLookup],
48
+ );
49
+
50
+ useEffect(() => {
51
+ return rootRef
52
+ ? addEventListener(rootRef, 'dx-ref-tag-activate', handleDxRefTagActivate, customEventOptions)
53
+ : undefined;
54
+ }, [rootRef]);
55
+
56
+ return (
57
+ <RefPopoverContextProvider pending={value.pending} link={value.link} target={value.target}>
58
+ <Popover.Root open={open} onOpenChange={setOpen}>
59
+ <Popover.VirtualTrigger virtualRef={trigger} />
60
+ <div role='none' className='contents' ref={setRootRef}>
61
+ {children}
62
+ </div>
63
+ </Popover.Root>
64
+ </RefPopoverContextProvider>
65
+ );
66
+ };
67
+
68
+ export const RefPopover = {
69
+ Provider: RefPopoverProvider,
70
+ };
71
+
72
+ export { useRefPopover };
73
+
74
+ export type { RefPopoverProviderProps, RefPopoverValue };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ export * from './RefPopover';
package/src/types.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ import { type EditorView } from '@codemirror/view';
6
+
5
7
  // Runtime data structure.
6
8
  export type Range = {
7
9
  from: number;
@@ -14,3 +16,8 @@ export type Comment = {
14
16
  id: string;
15
17
  cursor?: string;
16
18
  };
19
+
20
+ /**
21
+ * Callback that renders into a DOM element within the editor.
22
+ */
23
+ export type RenderCallback<Props extends object> = (el: HTMLElement, props: Props, view: EditorView) => void;
@@ -2,12 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type ReactNode } from 'react';
5
+ import React, { type FC, type ReactNode } from 'react';
6
6
  import { createRoot } from 'react-dom/client';
7
7
 
8
- import { ThemeProvider } from '@dxos/react-ui';
8
+ import { ThemeProvider, Tooltip } from '@dxos/react-ui';
9
9
  import { defaultTx } from '@dxos/react-ui-theme';
10
10
 
11
+ import { type RenderCallback } from '../types';
12
+
11
13
  // TODO(burdon): Factor out.
12
14
 
13
15
  export type ElementOptions = {
@@ -32,3 +34,19 @@ export const renderRoot = <T extends Element>(root: T, node: ReactNode): T => {
32
34
  createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
33
35
  return root;
34
36
  };
37
+
38
+ /**
39
+ * Utility to create a renderer for a React component.
40
+ */
41
+ export const createRenderer =
42
+ <Props extends object>(Component: FC<Props>): RenderCallback<Props> =>
43
+ (el, props) => {
44
+ renderRoot(
45
+ el,
46
+ <ThemeProvider tx={defaultTx}>
47
+ <Tooltip.Provider>
48
+ <Component {...props} />
49
+ </Tooltip.Provider>
50
+ </ThemeProvider>,
51
+ );
52
+ };
@@ -1 +0,0 @@
1
- {"version":3,"file":"InputMode.stories.d.ts","sourceRoot":"","sources":["../../../src/InputMode.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,KAAmB,MAAM,OAAO,CAAC;AAiBxC,OAAO,EAAmC,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGnF,KAAK,UAAU,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,kBAAkB,CAAC;AAqEpF,eAAO,MAAM,OAAO;;CAanB,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAC;;;iEAxFwE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FpF,wBAKE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"TextEditor.stories.d.ts","sourceRoot":"","sources":["../../../src/TextEditor.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAOrB,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAA2D,MAAM,OAAO,CAAC;AAYhF,OAAO,EAAE,KAAK,IAAI,EAAyB,MAAM,uBAAuB,CAAC;AAiCzE,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,SAAS,CAAC;AA2MjE,KAAK,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,CAAC;AAE7C,KAAK,UAAU,GAAG;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC,GAAG,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC;AAEtE,QAAA,MAAM,YAAY,uGAWf,UAAU,sBAsDZ,CAAC;AAEF,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAKnC,CAAC;AAEF,eAAe,IAAI,CAAC;AAqBpB,eAAO,MAAM,OAAO;;CAEnB,CAAC;AAMF,eAAO,MAAM,UAAU;;CAEtB,CAAC;AAMF,eAAO,MAAM,KAAK;;CAEjB,CAAC;AAMF,eAAO,MAAM,QAAQ;;CAEpB,CAAC;AAMF,eAAO,MAAM,YAAY;;CAExB,CAAC;AAMF,eAAO,MAAM,GAAG;;CAOf,CAAC;AAqBF,eAAO,MAAM,OAAO;;CAEnB,CAAC;AAEF,eAAO,MAAM,SAAS;;CAUrB,CAAC;AAEF,eAAO,MAAM,mBAAmB;;CAI/B,CAAC;AAEF,eAAO,MAAM,QAAQ;;CAepB,CAAC;AAMF,eAAO,MAAM,UAAU;;CAQtB,CAAC;AAEF,eAAO,MAAM,QAAQ;;CAIpB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAIjB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAEjB,CAAC;AAEF,eAAO,MAAM,IAAI;;CAEhB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAOjB,CAAC;AAEF,eAAO,MAAM,UAAU;;CAEtB,CAAC;AAEF,eAAO,MAAM,WAAW;;CAEvB,CAAC;AAEF,eAAO,MAAM,QAAQ;;CAIpB,CAAC;AAEF,eAAO,MAAM,KAAK;;CAEjB,CAAC;AAMF,eAAO,MAAM,YAAY;;CAWxB,CAAC;AAMF,eAAO,MAAM,UAAU;;CAQtB,CAAC;AAMF,eAAO,MAAM,YAAY;;CAcxB,CAAC;AAMF,eAAO,MAAM,OAAO;;CAWnB,CAAC;AAMF,eAAO,MAAM,MAAM;;CAQlB,CAAC;AAMF,eAAO,MAAM,OAAO;;CA8CnB,CAAC;AAsDF,eAAO,MAAM,QAAQ;;CAsCpB,CAAC;AAMF,eAAO,MAAM,WAAW;;CAIvB,CAAC;AAMF,eAAO,MAAM,GAAG;;CAaf,CAAC;AAMF,eAAO,MAAM,QAAQ;;CAgBpB,CAAC;AAQF,eAAO,MAAM,UAAU;;CAOtB,CAAC;AAMF,eAAO,MAAM,KAAK;;CAqBjB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"viewMode.d.ts","sourceRoot":"","sources":["../../../../../src/components/EditorToolbar/viewMode.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAA+C,KAAK,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AA6B9F,eAAO,MAAM,cAAc,UAAW,kBAAkB;;;;;;;;;;;;;;CAWvD,CAAC"}
@@ -1,12 +0,0 @@
1
- import { type Extension } from '@codemirror/state';
2
- export type PreviewOptions = {
3
- onRenderPreview: (el: HTMLElement, props: {
4
- url: string;
5
- text: string;
6
- }) => void;
7
- };
8
- /**
9
- * Create image decorations.
10
- */
11
- export declare const preview: (options: PreviewOptions) => Extension;
12
- //# sourceMappingURL=preview.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/command/preview.ts"],"names":[],"mappings":"AAKA,OAAO,EAEL,KAAK,SAAS,EAKf,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,YAAa,cAAc,KAAG,SASjD,CAAC"}
@@ -1,3 +0,0 @@
1
- export declare const stackItemContentEditorClassNames: (role?: string) => string;
2
- export declare const stackItemContentToolbarClassNames: (role?: string) => string;
3
- //# sourceMappingURL=fragments.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fragments.d.ts","sourceRoot":"","sources":["../../../src/fragments.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,gCAAgC,UAAW,MAAM,WAI3D,CAAC;AAEJ,eAAO,MAAM,iCAAiC,UAAW,MAAM,WAI5D,CAAC"}