@dxos/react-ui-editor 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6

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 (90) hide show
  1. package/dist/lib/browser/index.mjs +298 -377
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +298 -377
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  8. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts +1 -3
  9. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts.map +1 -1
  10. package/dist/types/src/components/EditorMenuProvider/menu-presets.d.ts.map +1 -1
  11. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
  12. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts.map +1 -1
  13. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  14. package/dist/types/src/components/EditorToolbar/blocks.d.ts +3 -17
  15. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  16. package/dist/types/src/components/EditorToolbar/formatting.d.ts +3 -17
  17. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  18. package/dist/types/src/components/EditorToolbar/headings.d.ts +3 -17
  19. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorToolbar/image.d.ts +3 -8
  21. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorToolbar/index.d.ts +0 -1
  23. package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
  24. package/dist/types/src/components/EditorToolbar/lists.d.ts +6 -0
  25. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -0
  26. package/dist/types/src/components/EditorToolbar/search.d.ts +3 -8
  27. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -1
  28. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +3 -17
  29. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  30. package/dist/types/src/stories/Automerge.stories.d.ts +25 -24
  31. package/dist/types/src/stories/Automerge.stories.d.ts.map +1 -1
  32. package/dist/types/src/stories/Comments.stories.d.ts +1 -1
  33. package/dist/types/src/stories/EditorToolbar.stories.d.ts +26 -26
  34. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  35. package/dist/types/src/stories/Experimental.stories.d.ts +1 -1
  36. package/dist/types/src/stories/Markdown.stories.d.ts +1 -1
  37. package/dist/types/src/stories/Outliner.stories.d.ts +2 -2
  38. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  39. package/dist/types/src/stories/Popover.stories.d.ts +2 -2
  40. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -1
  41. package/dist/types/src/stories/Preview.stories.d.ts +1 -1
  42. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  43. package/dist/types/src/stories/TextEditor.stories.d.ts +1 -1
  44. package/dist/types/src/stories/components/EditorStory.d.ts +3 -3
  45. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  46. package/dist/types/src/stories/components/util.d.ts +3 -3
  47. package/dist/types/src/stories/components/util.d.ts.map +1 -1
  48. package/dist/types/src/translations.d.ts +24 -24
  49. package/dist/types/src/translations.d.ts.map +1 -1
  50. package/dist/types/src/util/react.d.ts +1 -4
  51. package/dist/types/src/util/react.d.ts.map +1 -1
  52. package/dist/types/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +45 -45
  54. package/src/components/Editor/Editor.stories.tsx +2 -2
  55. package/src/components/Editor/Editor.tsx +12 -6
  56. package/src/components/EditorContent/EditorContent.tsx +1 -1
  57. package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +21 -28
  58. package/src/components/EditorMenuProvider/menu-presets.ts +1 -0
  59. package/src/components/EditorMenuProvider/useEditorMenu.ts +8 -1
  60. package/src/components/EditorPreviewProvider/EditorPreviewProvider.tsx +5 -7
  61. package/src/components/EditorToolbar/EditorToolbar.tsx +24 -61
  62. package/src/components/EditorToolbar/blocks.ts +54 -46
  63. package/src/components/EditorToolbar/formatting.ts +43 -44
  64. package/src/components/EditorToolbar/headings.ts +42 -48
  65. package/src/components/EditorToolbar/image.ts +16 -21
  66. package/src/components/EditorToolbar/index.ts +0 -1
  67. package/src/components/EditorToolbar/lists.ts +57 -0
  68. package/src/components/EditorToolbar/search.ts +16 -21
  69. package/src/components/EditorToolbar/view-mode.ts +34 -40
  70. package/src/stories/Automerge.stories.tsx +8 -10
  71. package/src/stories/Comments.stories.tsx +4 -4
  72. package/src/stories/EditorToolbar.stories.tsx +32 -17
  73. package/src/stories/Experimental.stories.tsx +3 -3
  74. package/src/stories/Markdown.stories.tsx +2 -2
  75. package/src/stories/Outliner.stories.tsx +4 -4
  76. package/src/stories/Popover.stories.tsx +6 -6
  77. package/src/stories/Preview.stories.tsx +58 -48
  78. package/src/stories/Tags.stories.tsx +5 -5
  79. package/src/stories/TextEditor.stories.tsx +2 -2
  80. package/src/stories/Theme.stories.tsx +2 -2
  81. package/src/stories/components/EditorStory.tsx +17 -12
  82. package/src/stories/components/util.tsx +19 -22
  83. package/src/translations.ts +29 -24
  84. package/src/util/react.tsx +2 -11
  85. package/dist/types/src/components/EditorToolbar/actions.d.ts +0 -24
  86. package/dist/types/src/components/EditorToolbar/actions.d.ts.map +0 -1
  87. package/dist/types/src/stories/CommandDialog.stories.d.ts +0 -14
  88. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +0 -1
  89. package/src/components/EditorToolbar/actions.ts +0 -87
  90. package/src/stories/CommandDialog.stories.tsx +0 -81
@@ -8,7 +8,7 @@ import React from 'react';
8
8
 
9
9
  import { log } from '@dxos/log';
10
10
  import { faker } from '@dxos/random';
11
- import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { blast, defaultOptions, dropFile, join, typewriter } from '@dxos/ui-editor';
13
13
 
14
14
  import { EditorStory, content } from './components';
@@ -16,7 +16,7 @@ import { EditorStory, content } from './components';
16
16
  const meta = {
17
17
  title: 'ui/react-ui-editor/Experimental',
18
18
  component: EditorStory,
19
- decorators: [withTheme],
19
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
20
20
  parameters: {
21
21
  layout: 'fullscreen',
22
22
  },
@@ -30,7 +30,7 @@ type Story = StoryObj<typeof meta>;
30
30
  // Typewriter
31
31
  //
32
32
 
33
- const typewriterItems = localStorage.getItem('dxos.org/testing/typewriter')?.split(',');
33
+ const typewriterItems = localStorage.getItem('org.dxos.testing.typewriter')?.split(',');
34
34
 
35
35
  export const Typewriter: Story = {
36
36
  render: () => (
@@ -6,7 +6,7 @@ import { markdown } from '@codemirror/lang-markdown';
6
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
7
7
  import React from 'react';
8
8
 
9
- import { withTheme } from '@dxos/react-ui/testing';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
10
  import { decorateMarkdown, image, join, linkTooltip, table } from '@dxos/ui-editor';
11
11
 
12
12
  import { EditorStory, content, defaultExtensions, headings, renderLinkTooltip, text } from './components';
@@ -14,7 +14,7 @@ import { EditorStory, content, defaultExtensions, headings, renderLinkTooltip, t
14
14
  const meta = {
15
15
  title: 'ui/react-ui-editor/Markdown',
16
16
  component: EditorStory,
17
- decorators: [withTheme],
17
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
18
18
  parameters: {
19
19
  layout: 'fullscreen',
20
20
  },
@@ -5,7 +5,7 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useMemo, useState } from 'react';
7
7
 
8
- import { withTheme } from '@dxos/react-ui/testing';
8
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
9
9
  import { withAttention } from '@dxos/react-ui-attention/testing';
10
10
  import { deleteItem, hashtag, join, listItemToString, outliner, treeFacet } from '@dxos/ui-editor';
11
11
 
@@ -13,11 +13,11 @@ import { type EditorController, type EditorMenuGroup, EditorMenuProvider } from
13
13
 
14
14
  import { EditorStory } from './components';
15
15
 
16
- type StoryProps = {
16
+ type DefaultStoryProps = {
17
17
  text?: string;
18
18
  };
19
19
 
20
- const DefaultStory = ({ text }: StoryProps) => {
20
+ const DefaultStory = ({ text }: DefaultStoryProps) => {
21
21
  const [controller, setController] = useState<EditorController | null>(null);
22
22
 
23
23
  const extensions = useMemo(() => [outliner(), hashtag()], []);
@@ -68,7 +68,7 @@ const DefaultStory = ({ text }: StoryProps) => {
68
68
  const meta = {
69
69
  title: 'ui/react-ui-editor/Outliner',
70
70
  render: DefaultStory,
71
- decorators: [withTheme, withAttention],
71
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withAttention()],
72
72
  parameters: {
73
73
  layout: 'fullscreen',
74
74
  },
@@ -8,7 +8,7 @@ import React, { useCallback, useState } from 'react';
8
8
  import { Obj, Query } from '@dxos/echo';
9
9
  import { faker } from '@dxos/random';
10
10
  import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
11
- import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import { TestSchema, type ValueGenerator, createObjectFactory } from '@dxos/schema/testing';
13
13
  import { Domino, mx } from '@dxos/ui';
14
14
  import { insertAtCursor, insertAtLineStart, join } from '@dxos/ui-editor';
@@ -38,15 +38,15 @@ const customCompletions: EditorMenuGroup = createMenuGroup({
38
38
  const placeholder = (trigger: string[]) => {
39
39
  const pressEl = Domino.of('span').text('Press');
40
40
  const triggerEls = trigger.map((trigger) =>
41
- Domino.of('span').classNames(mx('border border-separator rounded-sm mx-1 pli-1 pbs-[2px] pbe-[3px]')).text(trigger),
41
+ Domino.of('span').classNames(mx('border border-separator rounded-xs mx-1 px-1 py-[2px] pb-[3px]')).text(trigger),
42
42
  );
43
43
  const forCommandsEl = Domino.of('span').text('for commands');
44
44
  return Domino.of('div').children(pressEl, ...triggerEls, forCommandsEl).root;
45
45
  };
46
46
 
47
- type StoryProps = Omit<UseEditorMenuProps, 'viewRef'> & { text: string };
47
+ type DefaultStoryProps = Omit<UseEditorMenuProps, 'viewRef'> & { text: string };
48
48
 
49
- const DefaultStory = ({ text, ...props }: StoryProps) => {
49
+ const DefaultStory = ({ text, ...props }: DefaultStoryProps) => {
50
50
  const [controller, setController] = useState<EditorController | null>(null);
51
51
  const { groupsRef, extension, ...menuProps } = useEditorMenu(props);
52
52
 
@@ -57,7 +57,7 @@ const DefaultStory = ({ text, ...props }: StoryProps) => {
57
57
  );
58
58
  };
59
59
 
60
- const LinkStory = (args: StoryProps) => {
60
+ const LinkStory = (args: DefaultStoryProps) => {
61
61
  const { space } = useClientStory();
62
62
 
63
63
  const getMenu = useCallback<NonNullable<UseEditorMenuProps['getMenu']>>(
@@ -103,7 +103,7 @@ const LinkStory = (args: StoryProps) => {
103
103
  const meta = {
104
104
  title: 'ui/react-ui-editor/Popover',
105
105
  render: DefaultStory,
106
- decorators: [withTheme],
106
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
107
107
  parameters: {
108
108
  layout: 'fullscreen',
109
109
  },
@@ -10,9 +10,9 @@ import { createPortal } from 'react-dom';
10
10
 
11
11
  import { invariant } from '@dxos/invariant';
12
12
  import { faker } from '@dxos/random';
13
- import { Popover } from '@dxos/react-ui';
14
- import { withTheme } from '@dxos/react-ui/testing';
15
- import { Card } from '@dxos/react-ui-mosaic';
13
+ import { Card, Popover, Toolbar } from '@dxos/react-ui';
14
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
+ import { Menu, createMenuAction } from '@dxos/react-ui-menu';
16
16
  import {
17
17
  type PreviewBlock,
18
18
  type PreviewLinkRef,
@@ -22,15 +22,15 @@ import {
22
22
  preview,
23
23
  } from '@dxos/ui-editor';
24
24
  import { hoverableControls } from '@dxos/ui-theme';
25
- import { isTruthy, trim } from '@dxos/util';
25
+ import { trim } from '@dxos/util';
26
26
 
27
27
  import { type EditorController, EditorPreviewProvider, useEditorPreview } from '../components';
28
28
 
29
29
  import { EditorStory } from './components';
30
30
 
31
- const handlePreviewLookup = async ({ label, ref }: PreviewLinkRef): Promise<PreviewLinkTarget> => {
31
+ const handlePreviewLookup = async ({ dxn, label }: PreviewLinkRef): Promise<PreviewLinkTarget> => {
32
32
  // Random text.
33
- faker.seed(ref.split('').reduce((acc: number, char: string) => acc + char.charCodeAt(0), 1));
33
+ faker.seed(dxn.split('').reduce((acc: number, char: string) => acc + char.charCodeAt(0), 1));
34
34
  const text = Array.from({ length: 2 }, () => faker.lorem.paragraphs()).join('\n\n');
35
35
  return {
36
36
  label,
@@ -51,24 +51,25 @@ const useRefTarget = (link: PreviewLinkRef): PreviewLinkTarget | undefined => {
51
51
 
52
52
  const PreviewCard = () => {
53
53
  const { target } = useEditorPreview('PreviewCard');
54
+ if (!target) {
55
+ return null;
56
+ }
54
57
 
55
58
  return (
56
59
  <Popover.Portal>
57
60
  <Popover.Content onOpenAutoFocus={(event) => event.preventDefault()}>
58
- <Popover.Viewport classNames='popover-card-width'>
61
+ <Popover.Viewport classNames='dx-card-popover-width'>
59
62
  <Card.Root border={false}>
60
63
  <Card.Toolbar>
61
- <Card.Icon toolbar icon='ph--file-text--regular' />
62
- <Card.Title>{target?.label}</Card.Title>
64
+ <Card.Icon icon='ph--file-text--regular' />
65
+ <Card.Title>{target.label}</Card.Title>
63
66
  <Popover.Close asChild>
64
- <Card.Close />
67
+ <Card.CloseIconButton />
65
68
  </Popover.Close>
66
69
  </Card.Toolbar>
67
- {target && (
68
- <Card.Row>
69
- <Card.Text variant='description'>{target.text}</Card.Text>
70
- </Card.Row>
71
- )}
70
+ <Card.Row>
71
+ <Card.Text variant='description'>{target.text}</Card.Text>
72
+ </Card.Row>
72
73
  </Card.Root>
73
74
  </Popover.Viewport>
74
75
  <Popover.Arrow />
@@ -101,7 +102,7 @@ const PreviewBlockComponent = ({ link, el, view }: { link: PreviewLinkRef; el: H
101
102
  }
102
103
 
103
104
  const link = getLinkRef(view.state, node);
104
- if (link?.ref !== action.link.ref) {
105
+ if (link?.dxn !== action.link.dxn) {
105
106
  return;
106
107
  }
107
108
 
@@ -143,36 +144,45 @@ const PreviewBlockComponent = ({ link, el, view }: { link: PreviewLinkRef; el: H
143
144
  }
144
145
  }, [handleAction, link, target]);
145
146
 
147
+ const menuItems = useMemo(
148
+ () => [
149
+ createMenuAction('delete', handleDelete, {
150
+ label: link.suggest ? 'Discard' : 'Delete',
151
+ icon: 'ph--x--regular',
152
+ }),
153
+ ...(target
154
+ ? [
155
+ createMenuAction('apply', handleInsert, {
156
+ label: 'Apply',
157
+ icon: 'ph--check--regular',
158
+ }),
159
+ ]
160
+ : []),
161
+ ],
162
+ [handleDelete, handleInsert, link.suggest, target],
163
+ );
164
+
146
165
  return createPortal(
147
- <Card.Root classNames={hoverableControls}>
148
- {!view?.state.readOnly && (
149
- <Card.Toolbar>
150
- <Card.Icon toolbar icon='ph--bookmark--regular' />
151
- <Card.Title>{link.label}</Card.Title>
152
- <Card.Menu
153
- items={[
154
- {
155
- id: 'delete',
156
- label: link.suggest ? 'Discard' : 'Delete',
157
- icon: 'ph--x--regular',
158
- onClick: handleDelete,
159
- },
160
- target && {
161
- id: 'apply',
162
- label: 'Apply',
163
- icon: 'ph--check--regular',
164
- onClick: handleInsert,
165
- },
166
- ].filter(isTruthy)}
167
- />
168
- </Card.Toolbar>
169
- )}
170
- {target && (
171
- <Card.Row>
172
- <Card.Text className='text-description'>{target.text}</Card.Text>
173
- </Card.Row>
174
- )}
175
- </Card.Root>,
166
+ <Menu.Root>
167
+ <Card.Root classNames={hoverableControls}>
168
+ {!view?.state.readOnly && (
169
+ <Card.Toolbar>
170
+ <Card.Icon icon='ph--bookmark--regular' />
171
+ <Card.Title>{link.label}</Card.Title>
172
+ {/* TODO(wittjosiah): Reconcile with Card.Menu. */}
173
+ <Menu.Trigger asChild disabled={!menuItems?.length}>
174
+ <Toolbar.IconButton iconOnly variant='ghost' icon='ph--dots-three-vertical--regular' label='Menu' />
175
+ </Menu.Trigger>
176
+ <Menu.Content items={menuItems} />
177
+ </Card.Toolbar>
178
+ )}
179
+ {target && (
180
+ <Card.Row>
181
+ <Card.Text className='text-description'>{target.text}</Card.Text>
182
+ </Card.Row>
183
+ )}
184
+ </Card.Root>
185
+ </Menu.Root>,
176
186
  el,
177
187
  );
178
188
  };
@@ -180,7 +190,7 @@ const PreviewBlockComponent = ({ link, el, view }: { link: PreviewLinkRef; el: H
180
190
  const meta = {
181
191
  title: 'ui/react-ui-editor/Preview',
182
192
  component: EditorStory,
183
- decorators: [withTheme],
193
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
184
194
  parameters: {
185
195
  layout: 'fullscreen',
186
196
  },
@@ -217,7 +227,7 @@ export const Default: Story = {
217
227
  setPreviewBlocks((prev) => [...prev, block]);
218
228
  },
219
229
  removeBlockContainer: (block) => {
220
- setPreviewBlocks((prev) => prev.filter(({ link: prevLink }) => prevLink.ref !== block.link.ref));
230
+ setPreviewBlocks((prev) => prev.filter(({ link: prevLink }) => prevLink.dxn !== block.link.dxn));
221
231
  },
222
232
  }),
223
233
  ];
@@ -230,7 +240,7 @@ export const Default: Story = {
230
240
  <PreviewCard />
231
241
  {controller?.view &&
232
242
  previewBlocks.map(({ link, el }) => (
233
- <PreviewBlockComponent key={link.ref} link={link} el={el} view={controller.view!} />
243
+ <PreviewBlockComponent key={link.dxn} link={link} el={el} view={controller.view!} />
234
244
  ))}
235
245
  </EditorPreviewProvider>
236
246
  );
@@ -7,7 +7,7 @@ import React, { useEffect, useState } from 'react';
7
7
  import { createPortal } from 'react-dom';
8
8
 
9
9
  import { useThemeContext } from '@dxos/react-ui';
10
- import { withTheme } from '@dxos/react-ui/testing';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
11
  import {
12
12
  type XmlWidgetRegistry,
13
13
  type XmlWidgetState,
@@ -25,7 +25,7 @@ const registry = {
25
25
  /**
26
26
  * Custom tag: <test/>
27
27
  */
28
- ['test' as const]: {
28
+ test: {
29
29
  block: true,
30
30
  Component: ({ start = '0' }) => {
31
31
  const [count, setCount] = useState<number>(safeParseInt(start, 0));
@@ -43,7 +43,7 @@ const registry = {
43
43
  return () => clearInterval(interval);
44
44
  }, []);
45
45
 
46
- return <div className='p-2 border border-separator rounded'>Test {count}</div>;
46
+ return <div className='p-2 border border-separator rounded-sm'>Test {count}</div>;
47
47
  },
48
48
  },
49
49
  } satisfies XmlWidgetRegistry;
@@ -64,7 +64,7 @@ const DefaultStory = ({ text }: { text?: string }) => {
64
64
 
65
65
  return (
66
66
  <>
67
- <div ref={parentRef} className='is-full p-4' />
67
+ <div ref={parentRef} className='w-full p-4' />
68
68
  {widgets.map(({ id, root, Component, props }) => (
69
69
  <div key={id}>{createPortal(<Component {...props} />, root)}</div>
70
70
  ))}
@@ -87,7 +87,7 @@ const text = trim`
87
87
  const meta = {
88
88
  title: 'ui/react-ui-editor/Tags',
89
89
  render: DefaultStory,
90
- decorators: [withTheme],
90
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
91
91
  parameters: {
92
92
  layout: 'fullscreen',
93
93
  },
@@ -8,7 +8,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
8
  import React from 'react';
9
9
 
10
10
  import { log } from '@dxos/log';
11
- import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
12
  import {
13
13
  InputModeExtensions,
14
14
  decorateMarkdown,
@@ -37,7 +37,7 @@ import {
37
37
  const meta = {
38
38
  title: 'ui/react-ui-editor/TextEditor',
39
39
  component: EditorStory,
40
- decorators: [withTheme],
40
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
41
41
  parameters: {
42
42
  layout: 'fullscreen',
43
43
  controls: { disable: true },
@@ -32,7 +32,7 @@ const DefaultStory = () => {
32
32
  );
33
33
 
34
34
  return (
35
- <div className='is-full grid grid-cols-2 gap-2'>
35
+ <div className='w-full grid grid-cols-2 gap-2'>
36
36
  <Editor.Root>
37
37
  <Editor.Content classNames='p-2' extensions={ext1} initialValue={createText(false)} />
38
38
  </Editor.Root>
@@ -46,7 +46,7 @@ const DefaultStory = () => {
46
46
  const meta: Meta<typeof DefaultStory> = {
47
47
  title: 'ui/react-ui-editor/Theme',
48
48
  component: DefaultStory,
49
- decorators: [withTheme, withLayout()],
49
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
50
50
  parameters: {
51
51
  layout: 'fullscreen',
52
52
  },
@@ -12,7 +12,7 @@ import { PublicKey } from '@dxos/keys';
12
12
  import { log } from '@dxos/log';
13
13
  import { useMergeRefs, useThemeContext } from '@dxos/react-ui';
14
14
  import { useAttentionAttributes } from '@dxos/react-ui-attention';
15
- import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
15
+ import { Json } from '@dxos/react-ui-syntax-highlighter';
16
16
  import {
17
17
  type DebugNode,
18
18
  type ThemeExtensionsOptions,
@@ -20,8 +20,7 @@ import {
20
20
  createMarkdownExtensions,
21
21
  createThemeExtensions,
22
22
  debugTree,
23
- decorateMarkdown,
24
- editorSlots,
23
+ documentSlots,
25
24
  } from '@dxos/ui-editor';
26
25
  import { mx } from '@dxos/ui-theme';
27
26
  import { isNonNullable } from '@dxos/util';
@@ -34,12 +33,12 @@ export type DebugMode = 'raw' | 'tree' | 'raw+tree';
34
33
 
35
34
  const defaultId = 'editor-' + PublicKey.random().toHex().slice(0, 8);
36
35
 
37
- export type StoryProps = Pick<UseTextEditorProps, 'id' | 'scrollTo' | 'selection' | 'extensions'> &
36
+ export type EditorStoryProps = Pick<UseTextEditorProps, 'id' | 'scrollTo' | 'selection' | 'extensions'> &
38
37
  Pick<ThemeExtensionsOptions, 'slots'> & {
39
38
  debug?: DebugMode;
40
39
  debugCustom?: (view: EditorView) => ReactNode;
41
40
  text?: string;
42
- object?: Obj.Obj<TestSchema.Expando>;
41
+ object?: Obj.OfShape<TestSchema.Expando>;
43
42
  readOnly?: boolean;
44
43
  placeholder?: string;
45
44
  lineNumbers?: boolean;
@@ -47,7 +46,7 @@ export type StoryProps = Pick<UseTextEditorProps, 'id' | 'scrollTo' | 'selection
47
46
  onReady?: (view: EditorView) => void;
48
47
  };
49
48
 
50
- export const EditorStory = forwardRef<EditorController, StoryProps>(
49
+ export const EditorStory = forwardRef<EditorController, EditorStoryProps>(
51
50
  ({ debug, debugCustom, text, extensions: extensionsProp, ...props }, forwardedRef) => {
52
51
  const controllerRef = useRef<EditorController>(null);
53
52
  const mergedRef = useMergeRefs([controllerRef, forwardedRef]);
@@ -63,12 +62,12 @@ export const EditorStory = forwardRef<EditorController, StoryProps>(
63
62
 
64
63
  const view = controllerRef.current?.view;
65
64
  return (
66
- <div className={mx('is-full bs-full grid overflow-hidden', debug && 'grid-cols-2 lg:grid-cols-[1fr_600px]')}>
65
+ <div className={mx('dx-container grid', debug && 'grid-cols-2 lg:grid-cols-[1fr_600px]')}>
67
66
  <EditorComponent ref={mergedRef} object={object} text={text} extensions={extensions} {...props} />
68
67
 
69
68
  {debug && (
70
69
  <div
71
- className='grid bs-full auto-rows-fr border-l border-separator divide-y divide-separator overflow-hidden'
70
+ className='grid h-full auto-rows-fr border-l border-separator divide-y divide-separator overflow-hidden'
72
71
  {...attentionAttrs}
73
72
  >
74
73
  {view && debugCustom?.(view)}
@@ -77,7 +76,14 @@ export const EditorStory = forwardRef<EditorController, StoryProps>(
77
76
  {view?.state.doc.toString()}
78
77
  </pre>
79
78
  )}
80
- {(debug === 'tree' || debug === 'raw+tree') && <JsonFilter data={tree} classNames='p-1 text-xs' />}
79
+ {(debug === 'tree' || debug === 'raw+tree') && (
80
+ <Json.Root data={tree}>
81
+ <Json.Content>
82
+ <Json.Filter />
83
+ <Json.Data classNames='p-1 text-xs' />
84
+ </Json.Content>
85
+ </Json.Root>
86
+ )}
81
87
  </div>
82
88
  )}
83
89
  </div>
@@ -88,7 +94,7 @@ export const EditorStory = forwardRef<EditorController, StoryProps>(
88
94
  /**
89
95
  * Default story component.
90
96
  */
91
- const EditorComponent = forwardRef<EditorController, StoryProps>(
97
+ const EditorComponent = forwardRef<EditorController, EditorStoryProps>(
92
98
  (
93
99
  {
94
100
  id = defaultId,
@@ -101,7 +107,7 @@ const EditorComponent = forwardRef<EditorController, StoryProps>(
101
107
  scrollTo,
102
108
  selection,
103
109
  extensions,
104
- slots = editorSlots,
110
+ slots = documentSlots,
105
111
  onReady,
106
112
  },
107
113
  forwardedRef,
@@ -119,7 +125,6 @@ const EditorComponent = forwardRef<EditorController, StoryProps>(
119
125
  createBasicExtensions({ readOnly, placeholder, lineNumbers, scrollPastEnd: true, search: true }),
120
126
  createThemeExtensions({ monospace, themeMode, syntaxHighlighting: true, slots }),
121
127
  createMarkdownExtensions(),
122
- decorateMarkdown(),
123
128
  extensions || [],
124
129
  ],
125
130
  }),
@@ -4,12 +4,12 @@
4
4
 
5
5
  import { type Completion } from '@codemirror/autocomplete';
6
6
  import { type Extension } from '@codemirror/state';
7
- import React, { type FC } from 'react';
8
7
 
9
8
  import { faker } from '@dxos/random';
10
- import { Icon } from '@dxos/react-ui';
9
+ import { Domino } from '@dxos/ui';
11
10
  import {
12
11
  type EditorSelectionState,
12
+ type RenderCallback,
13
13
  decorateMarkdown,
14
14
  folding,
15
15
  formattingKeymap,
@@ -17,9 +17,9 @@ import {
17
17
  linkTooltip,
18
18
  table,
19
19
  } from '@dxos/ui-editor';
20
- import { mx } from '@dxos/ui-theme';
20
+ import { safeUrl } from '@dxos/util';
21
21
 
22
- import { createRenderer, str } from '../../util';
22
+ import { str } from '../../util';
23
23
 
24
24
  export const num = () => faker.number.int({ min: 0, max: 9999 }).toLocaleString();
25
25
 
@@ -204,30 +204,27 @@ export const links: Completion[] = [
204
204
  export const names = ['adam', 'alice', 'alison', 'bob', 'carol', 'charlie', 'sayuri', 'shoko'];
205
205
 
206
206
  const hover =
207
- 'rounded-sm text-baseText text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
208
-
209
- const LinkTooltip: FC<{ url: string }> = ({ url }) => {
210
- const web = new URL(url);
211
- return (
212
- <a href={url} target='_blank' rel='noreferrer' className={mx(hover, 'flex items-center gap-2')}>
213
- {web.origin}
214
- <Icon icon='ph--arrow-square-out--regular' size={4} />
215
- </a>
207
+ 'rounded-xs text-base-surface-text text-primary-600 hover:text-primary-500 dark:text-primary-300 hover:dark:text-primary-200';
208
+
209
+ export const renderLinkTooltip: RenderCallback<{ url: string }> = (el, { url }) => {
210
+ el.appendChild(
211
+ Domino.of('a')
212
+ .attributes({ href: url, target: '_blank', rel: 'noreferrer', 'aria-label': 'Open link' })
213
+ .classNames(hover, 'flex items-center gap-2')
214
+ .text(safeUrl(url)?.toString() ?? url)
215
+ .children(Domino.svg('ph--arrow-square-out--regular')).root,
216
216
  );
217
217
  };
218
218
 
219
- export const renderLinkTooltip = createRenderer(LinkTooltip);
220
-
221
- const LinkButton: FC<{ url: string }> = ({ url }) => {
222
- return (
223
- <a href={url} target='_blank' rel='noreferrer' className={mx(hover)}>
224
- <Icon icon='ph--arrow-square-out--regular' size={4} classNames='inline-block mis-1 mb-[3px]' />
225
- </a>
219
+ export const renderLinkButton: RenderCallback<{ url: string }> = (el, { url }) => {
220
+ el.appendChild(
221
+ Domino.of('span')
222
+ .attributes({ 'aria-hidden': 'true' })
223
+ .classNames(hover, 'ms-1 inline-block align-[-0.125em]')
224
+ .children(Domino.svg('ph--arrow-square-out--regular')).root,
226
225
  );
227
226
  };
228
227
 
229
- export const renderLinkButton = createRenderer(LinkButton);
230
-
231
228
  // Shared extensions.
232
229
  export const defaultExtensions: Extension[] = [
233
230
  decorateMarkdown({ renderLinkButton, selectionChangeDelay: 100 }),
@@ -10,30 +10,35 @@ export const translations = [
10
10
  {
11
11
  'en-US': {
12
12
  [translationKey]: {
13
- 'strong label': 'Bold',
14
- 'emphasis label': 'Italics',
15
- 'strikethrough label': 'Strikethrough',
16
- 'code label': 'Code',
17
- 'link label': 'Link',
18
- 'list-bullet label': 'Bullet list',
19
- 'list-ordered label': 'Numbered list',
20
- 'list-task label': 'Task list',
21
- 'blockquote label': 'Block quote',
22
- 'codeblock label': 'Code block',
23
- 'comment label': 'Create comment',
24
- 'selection overlaps existing comment label': 'Selection overlaps existing comment',
25
- 'select text to comment label': 'Select text to comment',
26
- 'image label': 'Insert image',
27
- 'table label': 'Create table',
28
- 'heading label': 'Heading level',
29
- 'heading level label_zero': 'Paragraph',
30
- 'heading level label_one': 'Heading level {{count}}',
31
- 'heading level label_other': 'Heading level {{count}}',
32
- 'search label': 'Search',
33
- 'view mode label': 'Editor view',
34
- 'preview mode label': 'Markdown',
35
- 'readonly mode label': 'Read only',
36
- 'source mode label': 'Plain text',
13
+ 'comment.label': 'Create comment',
14
+ 'image.label': 'Insert image',
15
+ 'search.label': 'Search',
16
+
17
+ 'block.label': 'Block',
18
+ 'block.blockquote.label': 'Block quote',
19
+ 'block.codeblock.label': 'Code block',
20
+ 'block.table.label': 'Create table',
21
+
22
+ 'formatting.label': 'Formatting',
23
+ 'formatting.strong.label': 'Bold',
24
+ 'formatting.emphasis.label': 'Italics',
25
+ 'formatting.strikethrough.label': 'Strikethrough',
26
+ 'formatting.code.label': 'Code',
27
+ 'formatting.link.label': 'Link',
28
+
29
+ 'list.bullet.label': 'Bullet list',
30
+ 'list.ordered.label': 'Numbered list',
31
+ 'list.task.label': 'Task list',
32
+
33
+ 'heading.label': 'Heading level',
34
+ 'heading-level.label_zero': 'Paragraph',
35
+ 'heading-level.label_one': 'Heading level {{count}}',
36
+ 'heading-level.label_other': 'Heading level {{count}}',
37
+
38
+ 'view-mode.label': 'Editor view',
39
+ 'view-mode.preview.label': 'Markdown',
40
+ 'view-mode.source.label': 'Plain text',
41
+ 'view-mode.readonly.label': 'Read only',
37
42
  },
38
43
  },
39
44
  },
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type FC, type ReactNode } from 'react';
5
+ import React, { type FC } from 'react';
6
6
  import { createRoot } from 'react-dom/client';
7
7
 
8
8
  import { ThemeProvider, Tooltip } from '@dxos/react-ui';
@@ -14,22 +14,13 @@ import { defaultTx } from '@dxos/ui-theme';
14
14
  */
15
15
  export const str = (...lines: string[]) => lines.join('\n');
16
16
 
17
- /** @deprecated */
18
- // TODO(wittjosiah): Replace with portals which are lighter weight and inherit context from the main react tree.
19
- export const renderRoot = <T extends Element>(root: T, node: ReactNode): T => {
20
- createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
21
- return root;
22
- };
23
-
24
17
  /**
25
- * Utility to create a renderer for a React component.
26
18
  * @deprecated
27
19
  */
28
20
  export const createRenderer =
29
21
  <TProps extends object>(Component: FC<TProps>): RenderCallback<TProps> =>
30
22
  (el, props) => {
31
- renderRoot(
32
- el,
23
+ createRoot(el).render(
33
24
  <ThemeProvider tx={defaultTx}>
34
25
  <Tooltip.Provider>
35
26
  <Component {...props} />