@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.8.4-main.bc674ce",
3
+ "version": "0.8.4-main.bcb3aa67d6",
4
4
  "description": "Text editor components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -55,7 +55,7 @@
55
55
  "@replit/codemirror-vim": "^6.2.1",
56
56
  "@replit/codemirror-vscode-keymap": "^6.0.2",
57
57
  "@uiw/codemirror-theme-vscode": "^4.25.2",
58
- "ajv": "^8.17.1",
58
+ "ajv": "^8.18.0",
59
59
  "bind-event-listener": "^3.0.0",
60
60
  "codemirror": "^6.0.1",
61
61
  "lib0": "^0.2.65",
@@ -63,32 +63,32 @@
63
63
  "lodash.merge": "^4.6.2",
64
64
  "lodash.sortby": "^4.7.0",
65
65
  "style-mod": "^4.1.0",
66
- "@dxos/async": "0.8.4-main.bc674ce",
67
- "@dxos/client": "0.8.4-main.bc674ce",
68
- "@dxos/context": "0.8.4-main.bc674ce",
69
- "@dxos/debug": "0.8.4-main.bc674ce",
70
- "@dxos/display-name": "0.8.4-main.bc674ce",
71
- "@dxos/invariant": "0.8.4-main.bc674ce",
72
- "@dxos/echo-db": "0.8.4-main.bc674ce",
73
- "@dxos/lit-ui": "0.8.4-main.bc674ce",
74
- "@dxos/log": "0.8.4-main.bc674ce",
75
- "@dxos/react-hooks": "0.8.4-main.bc674ce",
76
- "@dxos/protocols": "0.8.4-main.bc674ce",
77
- "@dxos/react-ui-menu": "0.8.4-main.bc674ce",
78
- "@dxos/react-ui-mosaic": "0.8.4-main.bc674ce",
79
- "@dxos/echo": "0.8.4-main.bc674ce",
80
- "@dxos/ui": "0.8.4-main.bc674ce",
81
- "@dxos/ui-editor": "0.8.4-main.bc674ce",
82
- "@dxos/ui-theme": "0.8.4-main.bc674ce",
83
- "@dxos/util": "0.8.4-main.bc674ce",
84
- "@dxos/app-graph": "0.8.4-main.bc674ce"
66
+ "@dxos/async": "0.8.4-main.bcb3aa67d6",
67
+ "@dxos/app-graph": "0.8.4-main.bcb3aa67d6",
68
+ "@dxos/context": "0.8.4-main.bcb3aa67d6",
69
+ "@dxos/echo": "0.8.4-main.bcb3aa67d6",
70
+ "@dxos/display-name": "0.8.4-main.bcb3aa67d6",
71
+ "@dxos/debug": "0.8.4-main.bcb3aa67d6",
72
+ "@dxos/echo-db": "0.8.4-main.bcb3aa67d6",
73
+ "@dxos/client": "0.8.4-main.bcb3aa67d6",
74
+ "@dxos/lit-ui": "0.8.4-main.bcb3aa67d6",
75
+ "@dxos/invariant": "0.8.4-main.bcb3aa67d6",
76
+ "@dxos/protocols": "0.8.4-main.bcb3aa67d6",
77
+ "@dxos/log": "0.8.4-main.bcb3aa67d6",
78
+ "@dxos/react-hooks": "0.8.4-main.bcb3aa67d6",
79
+ "@dxos/ui": "0.8.4-main.bcb3aa67d6",
80
+ "@dxos/react-ui-menu": "0.8.4-main.bcb3aa67d6",
81
+ "@dxos/react-ui-mosaic": "0.8.4-main.bcb3aa67d6",
82
+ "@dxos/ui-editor": "0.8.4-main.bcb3aa67d6",
83
+ "@dxos/ui-theme": "0.8.4-main.bcb3aa67d6",
84
+ "@dxos/util": "0.8.4-main.bcb3aa67d6"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@automerge/automerge": "3.2.3",
88
88
  "@automerge/automerge-repo": "2.5.1",
89
89
  "@automerge/automerge-repo-network-broadcastchannel": "2.5.1",
90
- "@effect-atom/atom-react": "^0.4.6",
91
- "@effect/platform": "0.93.6",
90
+ "@effect-atom/atom-react": "^0.5.0",
91
+ "@effect/platform": "0.94.4",
92
92
  "@types/chai": "^4.2.15",
93
93
  "@types/chai-dom": "^1.11.0",
94
94
  "@types/lodash.defaultsdeep": "^4.6.6",
@@ -99,39 +99,39 @@
99
99
  "@types/react-test-renderer": "^17.0.2",
100
100
  "chai": "^4.4.1",
101
101
  "chai-dom": "^1.11.0",
102
- "effect": "3.19.11",
103
- "happy-dom": "^13.3.1",
102
+ "effect": "3.20.0",
103
+ "happy-dom": "^20.0.0",
104
104
  "jsdom": "^27.0.0",
105
105
  "mocha": "^10.6.0",
106
106
  "react": "~19.2.3",
107
107
  "react-dom": "~19.2.3",
108
108
  "react-test-renderer": "~19.2.0",
109
- "vite": "7.1.9",
109
+ "vite": "^7.1.11",
110
110
  "vite-plugin-top-level-await": "^1.6.0",
111
111
  "vite-plugin-wasm": "^3.5.0",
112
- "@dxos/config": "0.8.4-main.bc674ce",
113
- "@dxos/echo": "0.8.4-main.bc674ce",
114
- "@dxos/echo-atom": "0.8.4-main.bc674ce",
115
- "@dxos/keyboard": "0.8.4-main.bc674ce",
116
- "@dxos/random": "0.8.4-main.bc674ce",
117
- "@dxos/react-client": "0.8.4-main.bc674ce",
118
- "@dxos/react-ui": "0.8.4-main.bc674ce",
119
- "@dxos/react-ui-attention": "0.8.4-main.bc674ce",
120
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.bc674ce",
121
- "@dxos/storybook-utils": "0.8.4-main.bc674ce",
122
- "@dxos/ui-theme": "0.8.4-main.bc674ce",
123
- "@dxos/ui-types": "0.8.4-main.bc674ce",
124
- "@dxos/schema": "0.8.4-main.bc674ce"
112
+ "@dxos/echo": "0.8.4-main.bcb3aa67d6",
113
+ "@dxos/echo-atom": "0.8.4-main.bcb3aa67d6",
114
+ "@dxos/config": "0.8.4-main.bcb3aa67d6",
115
+ "@dxos/random": "0.8.4-main.bcb3aa67d6",
116
+ "@dxos/keyboard": "0.8.4-main.bcb3aa67d6",
117
+ "@dxos/react-client": "0.8.4-main.bcb3aa67d6",
118
+ "@dxos/react-ui": "0.8.4-main.bcb3aa67d6",
119
+ "@dxos/react-ui-attention": "0.8.4-main.bcb3aa67d6",
120
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.bcb3aa67d6",
121
+ "@dxos/ui-types": "0.8.4-main.bcb3aa67d6",
122
+ "@dxos/storybook-utils": "0.8.4-main.bcb3aa67d6",
123
+ "@dxos/schema": "0.8.4-main.bcb3aa67d6",
124
+ "@dxos/ui-theme": "0.8.4-main.bcb3aa67d6"
125
125
  },
126
126
  "peerDependencies": {
127
- "@effect-atom/atom-react": "^0.4.6",
128
- "@effect/platform": "0.93.6",
129
- "effect": "3.19.11",
127
+ "@effect-atom/atom-react": "^0.5.0",
128
+ "@effect/platform": "0.94.4",
129
+ "effect": "3.20.0",
130
130
  "react": "~19.2.3",
131
131
  "react-dom": "~19.2.3",
132
- "@dxos/react-client": "0.8.4-main.bc674ce",
133
- "@dxos/react-ui": "0.8.4-main.bc674ce",
134
- "@dxos/ui-theme": "0.8.4-main.bc674ce"
132
+ "@dxos/react-ui": "0.8.4-main.bcb3aa67d6",
133
+ "@dxos/ui-theme": "0.8.4-main.bcb3aa67d6",
134
+ "@dxos/react-client": "0.8.4-main.bcb3aa67d6"
135
135
  },
136
136
  "publishConfig": {
137
137
  "access": "public"
@@ -42,7 +42,7 @@ const withExtensions: Decorator<EditorContentProps> = (Story, { args }) => {
42
42
  createThemeExtensions({ themeMode }),
43
43
  createMarkdownExtensions(),
44
44
  decorateMarkdown(),
45
- automerge(createDocAccessor(createObject(Text.make(args.initialValue)), ['content'])),
45
+ automerge(createDocAccessor(createObject(Text.make({ content: args.initialValue })), ['content'])),
46
46
  ],
47
47
  [themeMode],
48
48
  );
@@ -53,7 +53,7 @@ const withExtensions: Decorator<EditorContentProps> = (Story, { args }) => {
53
53
  const meta = {
54
54
  title: 'ui/react-ui-editor/Editor',
55
55
  component: Editor.Content,
56
- decorators: [withExtensions, withTheme, withLayout({ layout: 'column' }), withAttention],
56
+ decorators: [withExtensions, withTheme(), withLayout({ layout: 'column' }), withAttention()],
57
57
  parameters: {
58
58
  layout: 'fullscreen',
59
59
  },
@@ -84,6 +84,8 @@ EditorRoot.displayName = 'Editor.Root';
84
84
  // Viewport
85
85
  //
86
86
 
87
+ const EDITOR_VIEWPORT_NAME = 'Editor.Viewport';
88
+
87
89
  type EditorViewportProps = ThemedClassName<PropsWithChildren<{}>>;
88
90
 
89
91
  /**
@@ -91,18 +93,20 @@ type EditorViewportProps = ThemedClassName<PropsWithChildren<{}>>;
91
93
  */
92
94
  const EditorViewport = ({ classNames, children }: EditorViewportProps) => {
93
95
  return (
94
- <div role='none' className={mx('grid grid-rows-[min-content_1fr] bs-full overflow-hidden', classNames)}>
96
+ <div role='none' className={mx('grid grid-rows-[min-content_1fr] h-full overflow-hidden', classNames)}>
95
97
  {children}
96
98
  </div>
97
99
  );
98
100
  };
99
101
 
100
- EditorViewport.displayName = 'Editor.Viewport';
102
+ EditorViewport.displayName = EDITOR_VIEWPORT_NAME;
101
103
 
102
104
  //
103
105
  // Content
104
106
  //
105
107
 
108
+ const EDITOR_CONTENT_NAME = 'Editor.Content';
109
+
106
110
  type EditorContentProps = Omit<NaturalEditorContentProps, 'ref'>;
107
111
 
108
112
  /**
@@ -110,7 +114,7 @@ type EditorContentProps = Omit<NaturalEditorContentProps, 'ref'>;
110
114
  * Automatically registers the editor controller with the context.
111
115
  */
112
116
  const EditorContent = ({ extensions: providedExtensions, ...props }: EditorContentProps) => {
113
- const { extensions: additionalExtensions = [], setController } = useEditorContext(EditorContent.displayName);
117
+ const { extensions: additionalExtensions = [], setController } = useEditorContext(EDITOR_CONTENT_NAME);
114
118
 
115
119
  const extensions = useMemo(
116
120
  () => [additionalExtensions, providedExtensions].filter(isNonNullable).flat(),
@@ -120,12 +124,14 @@ const EditorContent = ({ extensions: providedExtensions, ...props }: EditorConte
120
124
  return <NaturalEditorContent {...props} extensions={extensions} ref={setController} />;
121
125
  };
122
126
 
123
- EditorContent.displayName = 'Editor.Content';
127
+ EditorContent.displayName = EDITOR_CONTENT_NAME;
124
128
 
125
129
  //
126
130
  // Toolbar
127
131
  //
128
132
 
133
+ const EDITOR_TOOLBAR_NAME = 'Editor.Toolbar';
134
+
129
135
  type EditorToolbarProps = Omit<NaturalEditorToolbarProps, 'getView' | 'state'>;
130
136
 
131
137
  /**
@@ -133,7 +139,7 @@ type EditorToolbarProps = Omit<NaturalEditorToolbarProps, 'getView' | 'state'>;
133
139
  * Automatically connects to the editor view through context.
134
140
  */
135
141
  const EditorToolbar = (props: EditorToolbarProps) => {
136
- const { controller, state } = useEditorContext(EditorToolbar.displayName);
142
+ const { controller, state } = useEditorContext(EDITOR_TOOLBAR_NAME);
137
143
 
138
144
  // TODO(burdon): Fix invariant.
139
145
  const getView = useCallback(() => {
@@ -144,7 +150,7 @@ const EditorToolbar = (props: EditorToolbarProps) => {
144
150
  return <NaturalEditorToolbar {...props} getView={getView} state={state} />;
145
151
  };
146
152
 
147
- EditorToolbar.displayName = 'Editor.Toolbar';
153
+ EditorToolbar.displayName = EDITOR_TOOLBAR_NAME;
148
154
 
149
155
  //
150
156
  // Editor
@@ -72,7 +72,7 @@ export const EditorContent = forwardRef<EditorController, EditorContentProps>(
72
72
  <div
73
73
  role='none'
74
74
  className={mx(
75
- 'is-full outline-none focus:border-accentSurface focus-within:border-neutralFocusIndicator',
75
+ 'w-full outline-hidden focus:border-accent-surface focus-within:border-neutral-focus-indicator',
76
76
  classNames,
77
77
  )}
78
78
  ref={parentRef}
@@ -13,6 +13,7 @@ import {
13
13
  type DxAnchorActivate,
14
14
  Icon,
15
15
  Popover,
16
+ ScrollArea,
16
17
  toLocalizedString,
17
18
  useDynamicRef,
18
19
  useThemeContext,
@@ -35,9 +36,7 @@ export type EditorMenuProviderProps = PropsWithChildren<{
35
36
  }>;
36
37
 
37
38
  /**
38
- * Implements the Popover and listens for the `dx-anchor-activate` event from the
39
- * `popover` extension's decoration.
40
- *
39
+ * Implements the Popover and listens for the `dx-anchor-activate` event from the `popover` extension's decoration.
41
40
  * NOTE: We don't use DropdownMenu because the command menu needs to manage focus explicitly.
42
41
  * I.e., focus must remain in the editor while displaying the menu (for type-ahead).
43
42
  */
@@ -78,10 +77,8 @@ export const EditorMenuProvider = ({
78
77
  root,
79
78
  DX_ANCHOR_ACTIVATE as any,
80
79
  (event: DxAnchorActivate) => {
81
- const { trigger, refId } = event;
82
-
83
- // If this has a `refId`, then it’s probably a URL or DXN and out of scope for this component.
84
- if (!refId) {
80
+ const { trigger, dxn } = event;
81
+ if (!dxn) {
85
82
  triggerRef.current = trigger as HTMLButtonElement;
86
83
  if (onActivate) {
87
84
  onActivate({ view: viewRef.current!, trigger: trigger.getAttribute('data-trigger') ?? undefined });
@@ -115,32 +112,32 @@ export const EditorMenuProvider = ({
115
112
  <Popover.Portal>
116
113
  <Popover.Content
117
114
  align='start'
118
- classNames={tx('menu.content', 'menu--exotic-unfocusable', { elevation: 'positioned' }, [
119
- 'overflow-y-auto',
120
- !menuGroups.length && 'hidden',
121
- ])}
115
+ classNames={['flex flex-col', !menuGroups.length && 'hidden']}
122
116
  style={{
123
117
  maxBlockSize: 36 * numItems + 10,
124
118
  }}
125
- /**
126
- * NOTE: We keep the focus in the editor, but Radix routes escape key.
127
- */
119
+ // NOTE: We keep the focus in the editor, but Radix routes escape key.
128
120
  onEscapeKeyDown={() => {
129
- // NOTE: Able to cancel if not in valid state.
130
- // event.preventDefault();
131
- onCancel?.({ view: view! });
121
+ const currentView = viewRef.current;
122
+ if (currentView) {
123
+ onCancel?.({ view: currentView });
124
+ }
132
125
  }}
133
126
  onOpenAutoFocus={(event) => event.preventDefault()}
134
127
  >
135
- <Popover.Viewport classNames={tx('menu.viewport', 'menu__viewport--exotic-unfocusable', {})}>
136
- <Menu groups={menuGroups} currentItem={currentItem} onSelect={handleSelect} />
128
+ <Popover.Viewport asChild classNames='dx-container'>
129
+ <ScrollArea.Root thin>
130
+ <ScrollArea.Viewport>
131
+ <Menu groups={menuGroups} currentItem={currentItem} onSelect={handleSelect} />
132
+ </ScrollArea.Viewport>
133
+ </ScrollArea.Root>
137
134
  </Popover.Viewport>
138
135
  <Popover.Arrow />
139
136
  </Popover.Content>
140
137
  </Popover.Portal>
141
138
 
142
139
  {/* Content */}
143
- <div ref={setRoot} role='none' className='contents'>
140
+ <div role='none' className='contents' ref={setRoot}>
144
141
  {children}
145
142
  </div>
146
143
  </Popover.Root>
@@ -162,7 +159,7 @@ const Menu = ({ groups, currentItem, onSelect }: MenuProps) => {
162
159
  {groups.map((group, index) => (
163
160
  <Fragment key={group.id}>
164
161
  <MenuGroup group={group} currentItem={currentItem} onSelect={onSelect} />
165
- {index < groups.length - 1 && <div className={tx('menu.separator', 'menu__item', {})} />}
162
+ {index < groups.length - 1 && <div className={tx('menu.separator', {})} />}
166
163
  </Fragment>
167
164
  ))}
168
165
  </ul>
@@ -185,7 +182,7 @@ const MenuGroup = ({ group, currentItem, onSelect }: MenuGroupProps) => {
185
182
  return (
186
183
  <>
187
184
  {group.label && (
188
- <div className={tx('menu.groupLabel', 'menu__group__label', {})}>
185
+ <div className={tx('menu.groupLabel', {})}>
189
186
  <span>{toLocalizedString(group.label, t)}</span>
190
187
  </div>
191
188
  )}
@@ -221,12 +218,8 @@ const MenuItem = ({ item, current, onSelect }: MenuItemProps) => {
221
218
  const handleSelect = useCallback(() => onSelect?.(item), [item, onSelect]);
222
219
 
223
220
  return (
224
- <li
225
- ref={listRef}
226
- className={tx('menu.item', 'menu__item--exotic-unfocusable', {}, [current && 'bg-hoverSurface'])}
227
- onClick={handleSelect}
228
- >
229
- {item.icon && <Icon icon={item.icon} size={5} />}
221
+ <li ref={listRef} className={tx('menu.item', {}, [current && 'bg-hover-surface'])} onClick={handleSelect}>
222
+ {item.icon && <Icon icon={item.icon} />}
230
223
  <span className='grow truncate'>{toLocalizedString(item.label, t)}</span>
231
224
  </li>
232
225
  );
@@ -110,6 +110,7 @@ export const linkSlashCommands: EditorMenuGroup = {
110
110
  label: 'Block embed',
111
111
  icon: 'ph--lego--regular',
112
112
  onSelect: ({ view, head }) => {
113
+ // Seed the same query shape as typing "@@" manually.
113
114
  view.dispatch({
114
115
  changes: { from: head, insert: '@@' },
115
116
  selection: { anchor: head + 2, head: head + 2 },
@@ -62,7 +62,8 @@ export const useEditorMenu = ({
62
62
  const getMenuOptions = useCallback<NonNullable<UseEditorMenuProps['getMenu']>>(
63
63
  async ({ text, trigger, ...props }) => {
64
64
  const groups = (await getMenu?.({ text, trigger, ...props })) ?? [];
65
- return filter
65
+ // The "@" menu can use "@@" as syntax for block embeds, so it owns its own query filtering.
66
+ return filter && trigger !== '@'
66
67
  ? filterMenuGroups(groups, (item) =>
67
68
  text ? (item.label as string).toLowerCase().startsWith(text.toLowerCase()) : true,
68
69
  )
@@ -108,7 +109,13 @@ export const useEditorMenu = ({
108
109
  );
109
110
 
110
111
  const handleSelect = useCallback<NonNullable<UseEditorMenu['onSelect']>>(({ view, item }) => {
112
+ // Delete trigger range (e.g., "/" and any typed filter text).
113
+ const { range } = view.state.field(popoverStateField) ?? {};
114
+ if (range) {
115
+ view.dispatch({ changes: { from: range.from, to: range.to, insert: '' } });
116
+ }
111
117
  void item.onSelect?.({ view, head: view.state.selection.main.head });
118
+ view.focus();
112
119
  }, []);
113
120
 
114
121
  const handleCancel = useCallback<NonNullable<UseEditorMenu['onCancel']>>(({ view }) => {
@@ -32,16 +32,16 @@ export const EditorPreviewProvider = ({ children, onLookup }: EditorPreviewProvi
32
32
 
33
33
  const handleActivate = useCallback(
34
34
  (event: DxAnchorActivate) => {
35
- const { refId, label, trigger: dxTrigger } = event;
35
+ const { dxn, label, trigger } = event;
36
36
  setValue((value) => ({
37
37
  ...value,
38
- link: { label, ref: refId },
38
+ link: { label, dxn },
39
39
  pending: true,
40
40
  }));
41
41
 
42
- triggerRef.current = dxTrigger;
42
+ triggerRef.current = trigger;
43
43
  queueMicrotask(() => setOpen(true));
44
- void onLookup?.({ label, ref: refId }).then((target) =>
44
+ void onLookup?.({ label, dxn }).then((target) =>
45
45
  setValue((value) => ({
46
46
  ...value,
47
47
  target: target ?? undefined,
@@ -68,9 +68,7 @@ export const EditorPreviewProvider = ({ children, onLookup }: EditorPreviewProvi
68
68
  <EditorPreviewContextProvider pending={value.pending} link={value.link} target={value.target}>
69
69
  <Popover.Root open={open} onOpenChange={setOpen}>
70
70
  <Popover.VirtualTrigger virtualRef={triggerRef as unknown as RefObject<HTMLButtonElement>} />
71
-
72
- {/* Content */}
73
- <div ref={setRoot} role='none' className='contents'>
71
+ <div role='none' className='contents' ref={setRoot}>
74
72
  {children}
75
73
  </div>
76
74
  </Popover.Root>
@@ -8,24 +8,17 @@ import React, { memo, useMemo } from 'react';
8
8
 
9
9
  import { type Node } from '@dxos/app-graph';
10
10
  import { ElevationProvider, type ThemedClassName } from '@dxos/react-ui';
11
- import {
12
- type ActionGraphProps,
13
- type MenuAction,
14
- MenuProvider,
15
- ToolbarMenu,
16
- createGapSeparator,
17
- useMenuActions,
18
- } from '@dxos/react-ui-menu';
11
+ import { type ActionGraphProps, Menu, type MenuAction, MenuBuilder, useMenuActions } from '@dxos/react-ui-menu';
19
12
  import { type EditorViewMode } from '@dxos/ui-editor';
20
13
 
21
- import { createLists } from './actions';
22
- import { createBlocks } from './blocks';
23
- import { createFormatting } from './formatting';
24
- import { createHeadings } from './headings';
25
- import { createImageUpload } from './image';
26
- import { createSearch } from './search';
14
+ import { addLists } from './lists';
15
+ import { addBlocks } from './blocks';
16
+ import { addFormatting } from './formatting';
17
+ import { addHeadings } from './headings';
18
+ import { addImageUpload } from './image';
19
+ import { addSearch } from './search';
27
20
  import { type EditorToolbarState } from './useEditorToolbar';
28
- import { createViewMode } from './view-mode';
21
+ import { addViewMode } from './view-mode';
29
22
 
30
23
  export type EditorToolbarFeatureFlags = Partial<{
31
24
  showHeadings: boolean;
@@ -55,15 +48,14 @@ export type EditorToolbarProps = ThemedClassName<
55
48
  } & (EditorToolbarActionGraphProps & EditorToolbarFeatureFlags)
56
49
  >;
57
50
 
58
- // TODO(burdon): Remove role dependency.
59
51
  export const EditorToolbar = memo(({ classNames, role, attendableId, onAction, ...props }: EditorToolbarProps) => {
60
- const menuProps = useEditorToolbarActionGraph(props);
52
+ const menuActions = useEditorToolbarActionGraph(props);
61
53
 
62
54
  return (
63
55
  <ElevationProvider elevation={role === 'section' ? 'positioned' : 'base'}>
64
- <MenuProvider {...menuProps} attendableId={attendableId} onAction={onAction}>
65
- <ToolbarMenu classNames={classNames} textBlockWidth />
66
- </MenuProvider>
56
+ <Menu.Root {...menuActions} attendableId={attendableId} onAction={onAction}>
57
+ <Menu.Toolbar classNames={classNames} />
58
+ </Menu.Root>
67
59
  </ElevationProvider>
68
60
  );
69
61
  });
@@ -74,6 +66,7 @@ type ToolbarActionsProps = Pick<EditorToolbarActionGraphProps, 'state' | 'getVie
74
66
  // TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.
75
67
  // E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.
76
68
  // This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.
69
+ // TODO(burdon): Some actions should toggle the state (e.g., toggle bullets on/off depending on the current state).
77
70
  const useEditorToolbarActionGraph = ({ state, getView, customActions, ...features }: ToolbarActionsProps) => {
78
71
  const menuCreator = useMemo(
79
72
  () => createToolbarActions({ state, getView, customActions, ...features }),
@@ -101,48 +94,18 @@ const createToolbarActions = ({
101
94
  ...features
102
95
  }: ToolbarActionsProps): Atom.Atom<ActionGraphProps> => {
103
96
  return Atom.make((get) => {
104
- const graph: ActionGraphProps = {
105
- nodes: [],
106
- edges: [],
107
- };
108
-
109
- // TODO(burdon): Builder pattern?
110
- const addSubGraph = (graph: ActionGraphProps, subGraph: ActionGraphProps) => {
111
- graph.nodes.push(...subGraph.nodes);
112
- graph.edges.push(...subGraph.edges);
113
- };
114
-
115
97
  // Subscribe to state changes.
116
98
  const stateSnapshot = get(state);
117
-
118
- if (features?.showHeadings ?? true) {
119
- addSubGraph(graph, createHeadings(stateSnapshot, getView));
120
- }
121
- if (features?.showFormatting ?? true) {
122
- addSubGraph(graph, createFormatting(stateSnapshot, getView));
123
- }
124
- if (features?.showLists ?? true) {
125
- addSubGraph(graph, createLists(stateSnapshot, getView));
126
- }
127
- if (features?.showBlocks ?? true) {
128
- addSubGraph(graph, createBlocks(stateSnapshot, getView));
129
- }
130
- if (features?.onImageUpload) {
131
- addSubGraph(graph, createImageUpload(features.onImageUpload!));
132
- }
133
-
134
- addSubGraph(graph, createGapSeparator());
135
-
136
- if (customActions) {
137
- addSubGraph(graph, get(customActions));
138
- }
139
- if (features?.showSearch ?? true) {
140
- addSubGraph(graph, createSearch(getView));
141
- }
142
- if (features?.onViewModeChange) {
143
- addSubGraph(graph, createViewMode(stateSnapshot, features.onViewModeChange!));
144
- }
145
-
146
- return graph;
99
+ return MenuBuilder.make()
100
+ .subgraph(features?.showHeadings !== false && addHeadings(stateSnapshot, getView))
101
+ .subgraph(features?.showFormatting !== false && addFormatting(stateSnapshot, getView))
102
+ .subgraph(features?.showLists !== false && addLists(stateSnapshot, getView))
103
+ .subgraph(features?.showBlocks !== false && addBlocks(stateSnapshot, getView))
104
+ .subgraph(features?.onImageUpload && addImageUpload(features.onImageUpload))
105
+ .separator('gap')
106
+ .subgraph(customActions && get(customActions))
107
+ .subgraph(features?.showSearch !== false && addSearch(getView))
108
+ .subgraph(features?.onViewModeChange && addViewMode(stateSnapshot, features.onViewModeChange))
109
+ .build();
147
110
  });
148
111
  };
@@ -4,56 +4,64 @@
4
4
 
5
5
  import { type EditorView } from '@codemirror/view';
6
6
 
7
- import { type Node } from '@dxos/app-graph';
8
- import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+ import { type ActionGroupBuilderFn, type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
9
8
  import { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from '@dxos/ui-editor';
10
9
 
11
- import { createEditorAction, createEditorActionGroup } from './actions';
12
- import { type EditorToolbarState } from './useEditorToolbar';
10
+ import { translationKey } from '../../translations';
13
11
 
14
- const createBlockGroupAction = (value: string) =>
15
- createEditorActionGroup('block', {
16
- variant: 'toggleGroup',
17
- selectCardinality: 'single',
18
- value,
19
- } as ToolbarMenuActionGroupProperties);
12
+ import { type EditorToolbarState } from './useEditorToolbar';
20
13
 
21
- const createBlockActions = (value: string, getView: () => EditorView, blankLine?: boolean) =>
22
- Object.entries({
23
- blockquote: 'ph--quotes--regular',
24
- codeblock: 'ph--code-block--regular',
25
- table: 'ph--table--regular',
26
- }).map(([type, icon]) => {
27
- const checked = type === value;
28
- return createEditorAction(type, { checked, ...(type === 'table' && { disabled: !!blankLine }), icon }, () => {
29
- const view = getView();
30
- if (!view) {
31
- return;
32
- }
14
+ const blockTypes = {
15
+ blockquote: 'ph--quotes--regular',
16
+ codeblock: 'ph--code-block--regular',
17
+ table: 'ph--table--regular',
18
+ };
33
19
 
34
- switch (type) {
35
- case 'blockquote':
36
- checked ? removeBlockquote(view) : addBlockquote(view);
37
- break;
38
- case 'codeblock':
39
- checked ? removeCodeblock(view) : addCodeblock(view);
40
- break;
41
- case 'table':
42
- insertTable(view);
43
- break;
44
- }
45
- });
46
- });
20
+ /** Add block actions to the builder. */
21
+ export const addBlocks =
22
+ (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>
23
+ (builder) => {
24
+ const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');
25
+ builder.group(
26
+ 'block',
27
+ {
28
+ label: ['block.label', { ns: translationKey }],
29
+ iconOnly: true,
30
+ variant: 'toggleGroup',
31
+ selectCardinality: 'single',
32
+ value,
33
+ } as ToolbarMenuActionGroupProperties,
34
+ (group) => {
35
+ for (const [type, icon] of Object.entries(blockTypes)) {
36
+ const checked = type === value;
37
+ group.action(
38
+ type,
39
+ {
40
+ label: [`block.${type}.label`, { ns: translationKey }],
41
+ checked,
42
+ ...(type === 'table' && { disabled: !!state.blankLine }),
43
+ icon,
44
+ },
45
+ () => {
46
+ const view = getView();
47
+ if (!view) {
48
+ return;
49
+ }
47
50
 
48
- export const createBlocks = (state: EditorToolbarState, getView: () => EditorView) => {
49
- const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');
50
- const blockGroupAction = createBlockGroupAction(value);
51
- const blockActions = createBlockActions(value, getView, state.blankLine);
52
- return {
53
- nodes: [blockGroupAction as Node.NodeArg<any>, ...blockActions],
54
- edges: [
55
- { source: 'root', target: 'block' },
56
- ...blockActions.map(({ id }) => ({ source: blockGroupAction.id, target: id })),
57
- ],
51
+ switch (type) {
52
+ case 'blockquote':
53
+ checked ? removeBlockquote(view) : addBlockquote(view);
54
+ break;
55
+ case 'codeblock':
56
+ checked ? removeCodeblock(view) : addCodeblock(view);
57
+ break;
58
+ case 'table':
59
+ insertTable(view);
60
+ break;
61
+ }
62
+ },
63
+ );
64
+ }
65
+ },
66
+ );
58
67
  };
59
- };