@dxos/react-ui-editor 0.8.4-main.dedc0f3 → 0.8.4-main.ead640a

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 (218) hide show
  1. package/dist/lib/browser/{chunk-22UMM3QJ.mjs → chunk-HL3YF6WC.mjs} +2 -2
  2. package/dist/lib/browser/chunk-HL3YF6WC.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +5482 -5519
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +71 -1
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/browser/types/index.mjs +1 -1
  9. package/dist/lib/node-esm/{chunk-YXYQPV6R.mjs → chunk-YJZGD3LY.mjs} +2 -2
  10. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +7 -0
  11. package/dist/lib/node-esm/index.mjs +5482 -5519
  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 +71 -1
  15. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  16. package/dist/lib/node-esm/types/index.mjs +1 -1
  17. package/dist/types/src/components/Editor/Editor.d.ts +24 -9
  18. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  19. package/dist/types/src/components/Editor/Editor.stories.d.ts +27 -0
  20. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -0
  21. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorToolbar/util.d.ts +1 -1
  23. package/dist/types/src/components/index.d.ts +0 -1
  24. package/dist/types/src/components/index.d.ts.map +1 -1
  25. package/dist/types/src/extensions/{autocomplete.d.ts → autocomplete/autocomplete.d.ts} +1 -1
  26. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
  27. package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
  28. package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
  29. package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
  30. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
  31. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +20 -0
  32. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
  33. package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
  34. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
  35. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  36. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  37. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
  38. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  39. package/dist/types/src/extensions/automerge/sync.d.ts +2 -2
  40. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  41. package/dist/types/src/extensions/autoscroll.d.ts +2 -2
  42. package/dist/types/src/extensions/autoscroll.d.ts.map +1 -1
  43. package/dist/types/src/extensions/factories.d.ts +7 -2
  44. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  45. package/dist/types/src/extensions/focus.d.ts.map +1 -1
  46. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  47. package/dist/types/src/extensions/index.d.ts +2 -1
  48. package/dist/types/src/extensions/index.d.ts.map +1 -1
  49. package/dist/types/src/extensions/json.d.ts +1 -1
  50. package/dist/types/src/extensions/json.d.ts.map +1 -1
  51. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  52. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  53. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  54. package/dist/types/src/extensions/modes.d.ts +1 -1
  55. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  56. package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
  57. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
  58. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts +36 -0
  59. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts.map +1 -0
  60. package/dist/types/src/extensions/popover/index.d.ts +8 -0
  61. package/dist/types/src/extensions/popover/index.d.ts.map +1 -0
  62. package/dist/types/src/extensions/popover/menu-presets.d.ts +4 -0
  63. package/dist/types/src/extensions/popover/menu-presets.d.ts.map +1 -0
  64. package/dist/types/src/extensions/popover/menu.d.ts +24 -0
  65. package/dist/types/src/extensions/popover/menu.d.ts.map +1 -0
  66. package/dist/types/src/extensions/popover/modal.d.ts +7 -0
  67. package/dist/types/src/extensions/popover/modal.d.ts.map +1 -0
  68. package/dist/types/src/extensions/popover/popover.d.ts +47 -0
  69. package/dist/types/src/extensions/popover/popover.d.ts.map +1 -0
  70. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts +34 -0
  71. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts.map +1 -0
  72. package/dist/types/src/extensions/popover/util.d.ts +8 -0
  73. package/dist/types/src/extensions/popover/util.d.ts.map +1 -0
  74. package/dist/types/src/extensions/preview/preview.d.ts +0 -2
  75. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  76. package/dist/types/src/extensions/state.d.ts +2 -0
  77. package/dist/types/src/extensions/state.d.ts.map +1 -0
  78. package/dist/types/src/extensions/tags/streamer.d.ts.map +1 -1
  79. package/dist/types/src/extensions/tags/xml-tags.d.ts +1 -0
  80. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  81. package/dist/types/src/hooks/useTextEditor.d.ts +4 -8
  82. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  83. package/dist/types/src/stories/{Command.stories.d.ts → CommandDialog.stories.d.ts} +2 -3
  84. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +1 -0
  85. package/dist/types/src/stories/Comments.stories.d.ts +3 -4
  86. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  87. package/dist/types/src/stories/EditorToolbar.stories.d.ts +1 -2
  88. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  89. package/dist/types/src/stories/Experimental.stories.d.ts +3 -4
  90. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  91. package/dist/types/src/stories/Markdown.stories.d.ts +3 -4
  92. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
  93. package/dist/types/src/stories/Outliner.stories.d.ts +0 -1
  94. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  95. package/dist/types/src/stories/{CommandMenu.stories.d.ts → Popover.stories.d.ts} +6 -6
  96. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -0
  97. package/dist/types/src/stories/Preview.stories.d.ts +3 -4
  98. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  99. package/dist/types/src/stories/Tags.stories.d.ts +0 -1
  100. package/dist/types/src/stories/Tags.stories.d.ts.map +1 -1
  101. package/dist/types/src/stories/TextEditor.stories.d.ts +3 -5
  102. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  103. package/dist/types/src/stories/components/EditorStory.d.ts +5 -5
  104. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  105. package/dist/types/src/styles/theme.d.ts.map +1 -1
  106. package/dist/types/src/testing/PreviewPopover.d.ts +20 -0
  107. package/dist/types/src/testing/PreviewPopover.d.ts.map +1 -0
  108. package/dist/types/src/testing/index.d.ts +1 -0
  109. package/dist/types/src/testing/index.d.ts.map +1 -1
  110. package/dist/types/src/types/types.d.ts +1 -1
  111. package/dist/types/src/types/types.d.ts.map +1 -1
  112. package/dist/types/src/util/index.d.ts +0 -1
  113. package/dist/types/src/util/index.d.ts.map +1 -1
  114. package/dist/types/tsconfig.tsbuildinfo +1 -1
  115. package/package.json +55 -52
  116. package/src/components/Editor/Editor.stories.tsx +69 -0
  117. package/src/components/Editor/Editor.tsx +57 -14
  118. package/src/components/EditorToolbar/EditorToolbar.tsx +1 -0
  119. package/src/components/index.ts +0 -1
  120. package/src/extensions/{autocomplete.ts → autocomplete/autocomplete.ts} +2 -1
  121. package/src/extensions/autocomplete/index.ts +8 -0
  122. package/src/extensions/autocomplete/match.ts +46 -0
  123. package/src/extensions/{command → autocomplete}/placeholder.ts +21 -17
  124. package/src/extensions/{command → autocomplete}/typeahead.ts +6 -48
  125. package/src/extensions/automerge/automerge.stories.tsx +8 -8
  126. package/src/extensions/automerge/automerge.ts +28 -9
  127. package/src/extensions/automerge/sync.ts +7 -3
  128. package/src/extensions/autoscroll.ts +43 -37
  129. package/src/extensions/factories.ts +41 -12
  130. package/src/extensions/focus.ts +5 -4
  131. package/src/extensions/folding.tsx +4 -6
  132. package/src/extensions/hashtag.tsx +2 -2
  133. package/src/extensions/index.ts +2 -1
  134. package/src/extensions/json.ts +1 -1
  135. package/src/extensions/markdown/bundle.ts +16 -4
  136. package/src/extensions/markdown/decorate.ts +1 -0
  137. package/src/extensions/markdown/link.ts +3 -0
  138. package/src/extensions/modes.ts +2 -2
  139. package/src/extensions/{command/floating-menu.ts → outliner/menu.ts} +15 -20
  140. package/src/extensions/outliner/outliner.ts +3 -3
  141. package/src/extensions/popover/PopoverMenuProvider.tsx +221 -0
  142. package/src/extensions/popover/index.ts +12 -0
  143. package/src/extensions/popover/menu-presets.ts +124 -0
  144. package/src/extensions/popover/menu.ts +67 -0
  145. package/src/extensions/popover/modal.ts +24 -0
  146. package/src/extensions/popover/popover.ts +291 -0
  147. package/src/extensions/popover/usePopoverMenu.ts +173 -0
  148. package/src/extensions/popover/util.ts +29 -0
  149. package/src/extensions/preview/index.ts +1 -1
  150. package/src/extensions/preview/preview.ts +0 -5
  151. package/src/extensions/selection.ts +2 -2
  152. package/src/extensions/state.ts +7 -0
  153. package/src/extensions/tags/streamer.ts +4 -5
  154. package/src/extensions/tags/xml-tags.ts +59 -1
  155. package/src/hooks/useTextEditor.ts +27 -39
  156. package/src/stories/{Command.stories.tsx → CommandDialog.stories.tsx} +10 -22
  157. package/src/stories/Comments.stories.tsx +5 -5
  158. package/src/stories/EditorToolbar.stories.tsx +6 -5
  159. package/src/stories/Experimental.stories.tsx +6 -6
  160. package/src/stories/Markdown.stories.tsx +5 -5
  161. package/src/stories/Outliner.stories.tsx +42 -26
  162. package/src/stories/Popover.stories.tsx +163 -0
  163. package/src/stories/Preview.stories.tsx +9 -9
  164. package/src/stories/Tags.stories.tsx +5 -5
  165. package/src/stories/TextEditor.stories.tsx +7 -32
  166. package/src/stories/components/EditorStory.tsx +7 -5
  167. package/src/styles/theme.ts +12 -10
  168. package/src/{components/Popover/RefDropdownMenu.tsx → testing/PreviewPopover.tsx} +20 -29
  169. package/src/testing/index.ts +1 -0
  170. package/src/types/types.ts +1 -1
  171. package/src/util/index.ts +0 -1
  172. package/dist/lib/browser/chunk-22UMM3QJ.mjs.map +0 -7
  173. package/dist/lib/node-esm/chunk-YXYQPV6R.mjs.map +0 -7
  174. package/dist/types/src/components/Popover/CommandMenu.d.ts +0 -34
  175. package/dist/types/src/components/Popover/CommandMenu.d.ts.map +0 -1
  176. package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +0 -14
  177. package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +0 -1
  178. package/dist/types/src/components/Popover/RefPopover.d.ts +0 -37
  179. package/dist/types/src/components/Popover/RefPopover.d.ts.map +0 -1
  180. package/dist/types/src/components/Popover/index.d.ts +0 -4
  181. package/dist/types/src/components/Popover/index.d.ts.map +0 -1
  182. package/dist/types/src/extensions/autocomplete.d.ts.map +0 -1
  183. package/dist/types/src/extensions/command/action.d.ts +0 -17
  184. package/dist/types/src/extensions/command/action.d.ts.map +0 -1
  185. package/dist/types/src/extensions/command/command-menu.d.ts +0 -20
  186. package/dist/types/src/extensions/command/command-menu.d.ts.map +0 -1
  187. package/dist/types/src/extensions/command/command.d.ts +0 -6
  188. package/dist/types/src/extensions/command/command.d.ts.map +0 -1
  189. package/dist/types/src/extensions/command/floating-menu.d.ts +0 -7
  190. package/dist/types/src/extensions/command/floating-menu.d.ts.map +0 -1
  191. package/dist/types/src/extensions/command/hint.d.ts +0 -19
  192. package/dist/types/src/extensions/command/hint.d.ts.map +0 -1
  193. package/dist/types/src/extensions/command/index.d.ts +0 -7
  194. package/dist/types/src/extensions/command/index.d.ts.map +0 -1
  195. package/dist/types/src/extensions/command/placeholder.d.ts +0 -10
  196. package/dist/types/src/extensions/command/placeholder.d.ts.map +0 -1
  197. package/dist/types/src/extensions/command/state.d.ts +0 -16
  198. package/dist/types/src/extensions/command/state.d.ts.map +0 -1
  199. package/dist/types/src/extensions/command/typeahead.d.ts +0 -22
  200. package/dist/types/src/extensions/command/typeahead.d.ts.map +0 -1
  201. package/dist/types/src/extensions/command/useCommandMenu.d.ts +0 -26
  202. package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +0 -1
  203. package/dist/types/src/stories/Command.stories.d.ts.map +0 -1
  204. package/dist/types/src/stories/CommandMenu.stories.d.ts.map +0 -1
  205. package/dist/types/src/util/domino.d.ts +0 -18
  206. package/dist/types/src/util/domino.d.ts.map +0 -1
  207. package/src/components/Popover/CommandMenu.tsx +0 -279
  208. package/src/components/Popover/RefPopover.tsx +0 -117
  209. package/src/components/Popover/index.ts +0 -7
  210. package/src/extensions/command/action.ts +0 -56
  211. package/src/extensions/command/command-menu.ts +0 -211
  212. package/src/extensions/command/command.ts +0 -34
  213. package/src/extensions/command/hint.ts +0 -103
  214. package/src/extensions/command/index.ts +0 -10
  215. package/src/extensions/command/state.ts +0 -90
  216. package/src/extensions/command/useCommandMenu.ts +0 -119
  217. package/src/stories/CommandMenu.stories.tsx +0 -160
  218. package/src/util/domino.ts +0 -51
@@ -4,8 +4,8 @@
4
4
 
5
5
  import { EditorState, type EditorStateConfig, type Text } from '@codemirror/state';
6
6
  import { EditorView } from '@codemirror/view';
7
- import { type TabsterTypes, useFocusableGroup } from '@fluentui/react-tabster';
8
7
  import {
8
+ type ComponentPropsWithoutRef,
9
9
  type DependencyList,
10
10
  type KeyboardEventHandler,
11
11
  type RefObject,
@@ -17,9 +17,9 @@ import {
17
17
  } from 'react';
18
18
 
19
19
  import { log } from '@dxos/log';
20
- import { type MaybeProvider, getProviderValue, isNotFalsy } from '@dxos/util';
20
+ import { type MaybeProvider, getProviderValue, isTruthy } from '@dxos/util';
21
21
 
22
- import { type EditorSelection, createEditorStateTransaction, documentId, editorInputMode } from '../extensions';
22
+ import { type EditorSelection, createEditorStateTransaction, documentId, modalStateField } from '../extensions';
23
23
  import { debugDispatcher } from '../util';
24
24
 
25
25
  let instanceCount = 0;
@@ -34,13 +34,9 @@ export type CursorInfo = {
34
34
  };
35
35
 
36
36
  export type UseTextEditor = {
37
- // TODO(burdon): Rename.
38
- parentRef: RefObject<HTMLDivElement>;
39
- view?: EditorView;
40
- focusAttributes?: TabsterTypes.TabsterDOMAttribute & {
41
- tabIndex: 0;
42
- onKeyUp: KeyboardEventHandler<HTMLDivElement>;
43
- };
37
+ parentRef: RefObject<HTMLDivElement | null>;
38
+ view: EditorView | null;
39
+ focusAttributes?: ComponentPropsWithoutRef<'div'>;
44
40
  };
45
41
 
46
42
  export type UseTextEditorProps = Pick<EditorStateConfig, 'extensions'> & {
@@ -66,11 +62,11 @@ export const useTextEditor = (
66
62
 
67
63
  // NOTE: Increments by 2 in strict mode.
68
64
  const [instanceId] = useState(() => `text-editor-${++instanceCount}`);
69
- const [view, setView] = useState<EditorView>();
65
+ const [view, setView] = useState<EditorView | null>(null);
70
66
  const parentRef = useRef<HTMLDivElement>(null);
71
67
 
72
68
  useEffect(() => {
73
- let view: EditorView;
69
+ let view: EditorView | null = null;
74
70
  if (parentRef.current) {
75
71
  log('create', { id, instanceId, doc: initialValue?.length ?? 0 });
76
72
 
@@ -96,19 +92,7 @@ export const useTextEditor = (
96
92
  EditorView.exceptionSink.of((err) => {
97
93
  log.catch(err);
98
94
  }),
99
- // TODO(burdon): Factor out debug inspector.
100
- // ViewPlugin.fromClass(
101
- // class {
102
- // constructor(_view: EditorView) {
103
- // log('construct', { id });
104
- // }
105
- //
106
- // destroy() {
107
- // log('destroy', { id });
108
- // }
109
- // },
110
- // ),
111
- ].filter(isNotFalsy),
95
+ ].filter(isTruthy),
112
96
  });
113
97
 
114
98
  // https://codemirror.net/docs/ref/#view.EditorViewConfig
@@ -155,23 +139,28 @@ export const useTextEditor = (
155
139
  }
156
140
  }, [autoFocus, view]);
157
141
 
158
- const focusableGroupAttrs = useFocusableGroup({
159
- tabBehavior: 'limited',
160
- ignoreDefaultKeydown: {
161
- Escape: view?.state.facet(editorInputMode).noTabster,
162
- },
163
- });
164
-
165
142
  // Focus editor on Enter (e.g., when tabbing to this component).
166
- const handleKeyUp = useCallback<KeyboardEventHandler<HTMLDivElement>>(
143
+ const handleKeyDown = useCallback<KeyboardEventHandler<HTMLDivElement>>(
167
144
  (event) => {
168
145
  const { key, target, currentTarget } = event;
169
- if (target === currentTarget) {
170
- switch (key) {
171
- case 'Enter': {
146
+ switch (key) {
147
+ case 'Escape': {
148
+ // Check if popover is open.
149
+ const modal = view?.state.field(modalStateField, false);
150
+ if (modal) {
151
+ return;
152
+ }
153
+
154
+ // Focus the closest focusable parent.
155
+ const element = view?.contentDOM.closest('[tabindex="0"]') as HTMLDivElement | null;
156
+ element?.focus();
157
+ break;
158
+ }
159
+ case 'Enter': {
160
+ if (target === currentTarget) {
172
161
  view?.focus();
173
- break;
174
162
  }
163
+ break;
175
164
  }
176
165
  }
177
166
  },
@@ -183,8 +172,7 @@ export const useTextEditor = (
183
172
  view,
184
173
  focusAttributes: {
185
174
  tabIndex: 0 as const,
186
- ...focusableGroupAttrs,
187
- onKeyUp: handleKeyUp,
175
+ onKeyDown: handleKeyDown,
188
176
  },
189
177
  };
190
178
  };
@@ -2,28 +2,25 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { type KeyboardEvent, useState } from 'react';
9
7
 
10
8
  import { Button, Icon, Input } from '@dxos/react-ui';
9
+ import { withTheme } from '@dxos/react-ui/testing';
11
10
  import { mx } from '@dxos/react-ui-theme';
12
- import { withLayout, withTheme } from '@dxos/storybook-utils';
13
11
 
14
12
  import { editorWidth } from '../defaults';
15
- import { type Action, command, floatingMenu } from '../extensions';
16
13
  import { str } from '../testing';
17
- import { createRenderer } from '../util';
18
14
 
19
15
  import { EditorStory } from './components';
20
16
 
21
- const CommandDialog = ({ onAction }: { onAction: (action?: Action) => void }) => {
17
+ // TODO(burdon): Reimplement with Popover.
18
+ const CommandDialog = ({ onAction }: { onAction: (action?: any) => void }) => {
22
19
  const [text, setText] = useState('');
23
20
 
24
21
  const handleInsert = () => {
25
22
  // TODO(burdon): Use queue ref.
26
- const link = `[${text}](dxn:queue:data:123)`;
23
+ const link = `![${text}](dxn:queue:data:123)`;
27
24
  onAction(text.length ? { type: 'insert', text: link } : undefined);
28
25
  };
29
26
 
@@ -66,21 +63,12 @@ const CommandDialog = ({ onAction }: { onAction: (action?: Action) => void }) =>
66
63
  };
67
64
 
68
65
  const meta = {
69
- title: 'ui/react-ui-editor/Command',
70
- decorators: [withTheme, withLayout({ fullscreen: true })],
71
- render: () => (
72
- <EditorStory
73
- text={str('# Command', '', '', '')}
74
- extensions={[
75
- floatingMenu(),
76
- command({
77
- renderDialog: createRenderer(CommandDialog),
78
- onHint: () => "Press '/' for commands",
79
- }),
80
- ]}
81
- />
82
- ),
83
- parameters: { layout: 'fullscreen' },
66
+ title: 'ui/react-ui-editor/CommandDialog',
67
+ render: () => <EditorStory text={str('# Command', '', '')} extensions={[]} />,
68
+ decorators: [withTheme],
69
+ parameters: {
70
+ layout: 'fullscreen',
71
+ },
84
72
  } satisfies Meta<typeof Button>;
85
73
 
86
74
  export default meta;
@@ -2,8 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { effect, useSignal } from '@preact/signals-react';
8
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
9
7
  import React, { type FC } from 'react';
@@ -11,7 +9,7 @@ import React, { type FC } from 'react';
11
9
  import { keySymbols, parseShortcut } from '@dxos/keyboard';
12
10
  import { PublicKey } from '@dxos/keys';
13
11
  import { log } from '@dxos/log';
14
- import { withLayout, withTheme } from '@dxos/storybook-utils';
12
+ import { withTheme } from '@dxos/react-ui/testing';
15
13
 
16
14
  import { annotations, comments, createExternalCommentSync } from '../extensions';
17
15
  import { str } from '../testing';
@@ -23,8 +21,10 @@ import { EditorStory, content, longText } from './components';
23
21
  const meta = {
24
22
  title: 'ui/react-ui-editor/Comments',
25
23
  component: EditorStory,
26
- decorators: [withTheme, withLayout({ fullscreen: true })],
27
- parameters: { layout: 'fullscreen' },
24
+ decorators: [withTheme],
25
+ parameters: {
26
+ layout: 'fullscreen',
27
+ },
28
28
  } satisfies Meta<typeof EditorStory>;
29
29
 
30
30
  export default meta;
@@ -2,15 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useCallback, useState } from 'react';
9
7
 
10
8
  import { invariant } from '@dxos/invariant';
11
9
  import { useThemeContext } from '@dxos/react-ui';
10
+ import { withTheme } from '@dxos/react-ui/testing';
12
11
  import { attentionSurface, mx } from '@dxos/react-ui-theme';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
14
12
 
15
13
  import { EditorToolbar, useEditorToolbarState } from '../components';
16
14
  import { editorWidth } from '../defaults';
@@ -78,8 +76,11 @@ const DefaultStory = ({ autoFocus, initialValue, placeholder }: StoryProps) => {
78
76
  const meta = {
79
77
  title: 'ui/react-ui-editor/EditorToolbar',
80
78
  render: DefaultStory,
81
- decorators: [withTheme, withLayout({ fullscreen: true })],
82
- parameters: { translations, layout: 'fullscreen' },
79
+ decorators: [withTheme],
80
+ parameters: {
81
+ layout: 'fullscreen',
82
+ translations,
83
+ },
83
84
  } satisfies Meta<typeof DefaultStory>;
84
85
 
85
86
  export default meta;
@@ -2,15 +2,13 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import defaultsDeep from 'lodash.defaultsdeep';
9
7
  import React from 'react';
10
8
 
11
9
  import { log } from '@dxos/log';
12
10
  import { faker } from '@dxos/random';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
11
+ import { withTheme } from '@dxos/react-ui/testing';
14
12
 
15
13
  import { blast, defaultOptions, dropFile, typewriter } from '../extensions';
16
14
  import { str } from '../testing';
@@ -20,8 +18,10 @@ import { EditorStory, content } from './components';
20
18
  const meta = {
21
19
  title: 'ui/react-ui-editor/Experimental',
22
20
  component: EditorStory,
23
- decorators: [withTheme, withLayout({ fullscreen: true })],
24
- parameters: { layout: 'fullscreen' },
21
+ decorators: [withTheme],
22
+ parameters: {
23
+ layout: 'fullscreen',
24
+ },
25
25
  } satisfies Meta<typeof EditorStory>;
26
26
 
27
27
  export default meta;
@@ -32,7 +32,7 @@ type Story = StoryObj<typeof meta>;
32
32
  // Typewriter
33
33
  //
34
34
 
35
- const typewriterItems = localStorage.getItem('dxos.org/plugin/markdown/typewriter')?.split(',');
35
+ const typewriterItems = localStorage.getItem('dxos.org/testing/typewriter')?.split(',');
36
36
 
37
37
  export const Typewriter: Story = {
38
38
  render: () => (
@@ -2,13 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { markdown } from '@codemirror/lang-markdown';
8
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
9
7
  import React from 'react';
10
8
 
11
- import { withLayout, withTheme } from '@dxos/storybook-utils';
9
+ import { withTheme } from '@dxos/react-ui/testing';
12
10
 
13
11
  import { decorateMarkdown, image, linkTooltip, table } from '../extensions';
14
12
  import { str } from '../testing';
@@ -18,8 +16,10 @@ import { EditorStory, content, defaultExtensions, headings, renderLinkTooltip, t
18
16
  const meta = {
19
17
  title: 'ui/react-ui-editor/Markdown',
20
18
  component: EditorStory,
21
- decorators: [withTheme, withLayout({ fullscreen: true })],
22
- parameters: { layout: 'fullscreen' },
19
+ decorators: [withTheme],
20
+ parameters: {
21
+ layout: 'fullscreen',
22
+ },
23
23
  } satisfies Meta<typeof EditorStory>;
24
24
 
25
25
  export default meta;
@@ -2,18 +2,22 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type EditorView } from '@codemirror/view';
8
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
9
- import React, { useRef } from 'react';
7
+ import React, { useMemo, useRef } from 'react';
10
8
 
11
- import { DropdownMenu } from '@dxos/react-ui';
9
+ import { withTheme } from '@dxos/react-ui/testing';
12
10
  import { withAttention } from '@dxos/react-ui-attention/testing';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
14
11
 
15
- import { RefDropdownMenuProvider } from '../components';
16
- import { deleteItem, hashtag, listItemToString, outliner, treeFacet } from '../extensions';
12
+ import {
13
+ type PopoverMenuGroup,
14
+ PopoverMenuProvider,
15
+ deleteItem,
16
+ hashtag,
17
+ listItemToString,
18
+ outliner,
19
+ treeFacet,
20
+ } from '../extensions';
17
21
  import { str } from '../testing';
18
22
 
19
23
  import { EditorStory } from './components';
@@ -25,19 +29,38 @@ type StoryProps = {
25
29
  const DefaultStory = ({ text }: StoryProps) => {
26
30
  const viewRef = useRef<EditorView>(null);
27
31
 
28
- const handleDelete = () => {
29
- if (viewRef.current) {
30
- deleteItem(viewRef.current);
31
- }
32
- };
32
+ const commandGroups: PopoverMenuGroup[] = useMemo(
33
+ () => [
34
+ {
35
+ id: 'outliner-actions',
36
+ items: [
37
+ {
38
+ id: 'delete-row',
39
+ label: 'Delete',
40
+ onSelect: (view: EditorView) => {
41
+ deleteItem(view);
42
+ },
43
+ },
44
+ ],
45
+ },
46
+ ],
47
+ [],
48
+ );
33
49
 
34
50
  return (
35
- <RefDropdownMenuProvider>
51
+ <PopoverMenuProvider
52
+ view={viewRef.current}
53
+ groups={commandGroups}
54
+ onSelect={({ view, item }) => {
55
+ if (item.onSelect) {
56
+ return item.onSelect(view, view.state.selection.main.head);
57
+ }
58
+ }}
59
+ >
36
60
  <EditorStory
37
61
  ref={viewRef}
38
62
  text={text}
39
63
  extensions={[outliner(), hashtag()]}
40
- placeholder=''
41
64
  debug='raw+tree'
42
65
  debugCustom={(view) => {
43
66
  const tree = view.state.facet(treeFacet);
@@ -46,24 +69,17 @@ const DefaultStory = ({ text }: StoryProps) => {
46
69
  return <pre className='p-1 overflow-auto text-xs text-green-800 dark:text-green-200'>{lines.join('\n')}</pre>;
47
70
  }}
48
71
  />
49
-
50
- <DropdownMenu.Portal>
51
- <DropdownMenu.Content>
52
- <DropdownMenu.Viewport>
53
- <DropdownMenu.Item onClick={handleDelete}>Delete</DropdownMenu.Item>
54
- </DropdownMenu.Viewport>
55
- <DropdownMenu.Arrow />
56
- </DropdownMenu.Content>
57
- </DropdownMenu.Portal>
58
- </RefDropdownMenuProvider>
72
+ </PopoverMenuProvider>
59
73
  );
60
74
  };
61
75
 
62
76
  const meta = {
63
77
  title: 'ui/react-ui-editor/Outliner',
64
78
  render: DefaultStory,
65
- decorators: [withAttention, withTheme, withLayout({ fullscreen: true })],
66
- parameters: { layout: 'fullscreen' },
79
+ decorators: [withTheme, withAttention],
80
+ parameters: {
81
+ layout: 'fullscreen',
82
+ },
67
83
  } satisfies Meta<typeof DefaultStory>;
68
84
 
69
85
  export default meta;
@@ -0,0 +1,163 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type EditorView } from '@codemirror/view';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import React, { useCallback, useState } from 'react';
8
+
9
+ import { Obj, Query } from '@dxos/echo';
10
+ import { faker } from '@dxos/random';
11
+ import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
12
+ import { Domino } from '@dxos/react-ui';
13
+ import { withTheme } from '@dxos/react-ui/testing';
14
+ import { Testing, type ValueGenerator, createObjectFactory } from '@dxos/schema/testing';
15
+
16
+ import {
17
+ type PopoverMenuGroup,
18
+ type PopoverMenuItem,
19
+ PopoverMenuProvider,
20
+ type UsePopoverMenuProps,
21
+ createMenuGroup,
22
+ filterMenuGroups,
23
+ formattingCommands,
24
+ insertAtCursor,
25
+ insertAtLineStart,
26
+ linkSlashCommands,
27
+ usePopoverMenu,
28
+ } from '../extensions';
29
+ import { str } from '../testing';
30
+
31
+ import { EditorStory } from './components';
32
+
33
+ const generator: ValueGenerator = faker as any;
34
+
35
+ const customCompletions: PopoverMenuGroup = createMenuGroup({
36
+ id: 'test',
37
+ items: ['Hello world!', 'Hello DXOS', 'Hello Composer', 'https://dxos.org'],
38
+ });
39
+
40
+ const placeholder = (trigger: string[]) =>
41
+ Domino.of('div')
42
+ .children(
43
+ Domino.of('span').text('Press'),
44
+ ...trigger.map((trigger) =>
45
+ Domino.of('span')
46
+ .text(trigger)
47
+ .classNames('border border-separator rounded-sm mx-1 pis-1 pie-1 pbs-[2px] pbe-[3px]'),
48
+ ),
49
+ Domino.of('span').text('for commands'),
50
+ )
51
+ .build();
52
+
53
+ type StoryProps = Omit<UsePopoverMenuProps, 'viewRef'> & { text: string };
54
+
55
+ const DefaultStory = ({ text, ...props }: StoryProps) => {
56
+ const [view, setView] = useState<EditorView | null>(null);
57
+ const { groupsRef, extension, ...menuProps } = usePopoverMenu(props);
58
+
59
+ return (
60
+ <PopoverMenuProvider view={view} groups={groupsRef.current} {...menuProps}>
61
+ <EditorStory ref={setView} text={text} extensions={extension} />
62
+ </PopoverMenuProvider>
63
+ );
64
+ };
65
+
66
+ const LinkStory = (args: StoryProps) => {
67
+ const { space } = useClientProvider();
68
+ const getMenu = useCallback<NonNullable<UsePopoverMenuProps['getMenu']>>(
69
+ async ({ text, trigger }): Promise<PopoverMenuGroup[]> => {
70
+ if (trigger === '/') {
71
+ return filterMenuGroups([linkSlashCommands], (item) =>
72
+ text ? (item.label as string).toLowerCase().includes(text.toLowerCase()) : true,
73
+ );
74
+ }
75
+
76
+ if (!space) {
77
+ return [];
78
+ }
79
+
80
+ const name = text?.startsWith('@') ? text.slice(1).toLowerCase() : (text?.toLowerCase() ?? '');
81
+ const result = await space?.db.query(Query.type(Testing.Contact)).run();
82
+ const items = result.objects
83
+ .filter((object) => object.name.toLowerCase().includes(name))
84
+ .map(
85
+ (object): PopoverMenuItem => ({
86
+ id: object.id,
87
+ label: object.name,
88
+ icon: 'ph--user--regular',
89
+ onSelect: (view, head) => {
90
+ const link = `[${object.name}](${Obj.getDXN(object)})`;
91
+ if (text?.startsWith('@')) {
92
+ insertAtLineStart(view, head, `!${link}\n`);
93
+ } else {
94
+ insertAtCursor(view, head, `${link} `);
95
+ }
96
+ },
97
+ }),
98
+ );
99
+
100
+ return [{ id: 'test', items }];
101
+ },
102
+ [space],
103
+ );
104
+
105
+ return <DefaultStory {...args} getMenu={getMenu} />;
106
+ };
107
+
108
+ const meta = {
109
+ title: 'ui/react-ui-editor/Popover',
110
+ render: DefaultStory,
111
+ decorators: [withTheme],
112
+ parameters: {
113
+ layout: 'fullscreen',
114
+ },
115
+ } satisfies Meta<typeof DefaultStory>;
116
+
117
+ export default meta;
118
+
119
+ type Story = StoryObj<typeof meta>;
120
+
121
+ export const Default: Story = {
122
+ args: {
123
+ text: str('# Autocomplete', '', ''),
124
+ triggerKey: 'Ctrl-Space',
125
+ filter: true,
126
+ getMenu: () => [customCompletions],
127
+ },
128
+ };
129
+
130
+ export const Formatting: Story = {
131
+ args: {
132
+ text: str('# Slash command', '', ''),
133
+ trigger: '/',
134
+ placeholder: {
135
+ content: () => placeholder(['/']),
136
+ },
137
+ getMenu: () => [formattingCommands],
138
+ },
139
+ };
140
+
141
+ export const Link: Story = {
142
+ render: LinkStory,
143
+ decorators: [
144
+ withClientProvider({
145
+ createSpace: true,
146
+ onInitialized: async (client) => {
147
+ client.addTypes([Testing.Contact]);
148
+ },
149
+ onCreateSpace: async ({ space }) => {
150
+ const createObjects = createObjectFactory(space.db, generator);
151
+ await createObjects([{ type: Testing.Contact, count: 10 }]);
152
+ await space.db.flush({ indexes: true });
153
+ },
154
+ }),
155
+ ],
156
+ args: {
157
+ text: str('# Links', '', ''),
158
+ trigger: ['/', '@'],
159
+ placeholder: {
160
+ content: () => placeholder(['/', '@']),
161
+ },
162
+ },
163
+ };
@@ -2,8 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { syntaxTree } from '@codemirror/language';
8
6
  import { type EditorView } from '@codemirror/view';
9
7
  import { type Meta, type StoryObj } from '@storybook/react-vite';
@@ -13,13 +11,13 @@ import { createPortal } from 'react-dom';
13
11
  import { invariant } from '@dxos/invariant';
14
12
  import { faker } from '@dxos/random';
15
13
  import { Popover } from '@dxos/react-ui';
14
+ import { withTheme } from '@dxos/react-ui/testing';
16
15
  import { Card } from '@dxos/react-ui-stack';
17
16
  import { hoverableControlItem, hoverableControlItemTransition, hoverableControls } from '@dxos/react-ui-theme';
18
- import { withLayout, withTheme } from '@dxos/storybook-utils';
19
17
  import { trim } from '@dxos/util';
20
18
 
21
- import { PreviewProvider, useRefPopover } from '../components';
22
19
  import { type PreviewLinkRef, type PreviewLinkTarget, getLinkRef, image, preview } from '../extensions';
20
+ import { PreviewPopoverProvider, usePreviewPopover } from '../testing';
23
21
 
24
22
  import { EditorStory } from './components';
25
23
 
@@ -45,7 +43,7 @@ const useRefTarget = (link: PreviewLinkRef): PreviewLinkTarget | undefined => {
45
43
  };
46
44
 
47
45
  const PreviewCard = () => {
48
- const { target } = useRefPopover('PreviewCard');
46
+ const { target } = usePreviewPopover('PreviewCard');
49
47
  return (
50
48
  <Popover.Portal>
51
49
  <Popover.Content onOpenAutoFocus={(event) => event.preventDefault()}>
@@ -169,8 +167,10 @@ const PreviewBlock = ({ link, el, view }: { link: PreviewLinkRef; el: HTMLElemen
169
167
  const meta = {
170
168
  title: 'ui/react-ui-editor/Preview',
171
169
  component: EditorStory,
172
- decorators: [withTheme, withLayout({ fullscreen: true })],
173
- parameters: { layout: 'fullscreen' },
170
+ decorators: [withTheme],
171
+ parameters: {
172
+ layout: 'fullscreen',
173
+ },
174
174
  } satisfies Meta<typeof EditorStory>;
175
175
 
176
176
  export default meta;
@@ -200,7 +200,7 @@ export const Default: Story = {
200
200
  }, []);
201
201
 
202
202
  return (
203
- <PreviewProvider onLookup={handlePreviewLookup}>
203
+ <PreviewPopoverProvider onLookup={handlePreviewLookup}>
204
204
  <EditorStory
205
205
  ref={handleViewRef}
206
206
  text={trim`
@@ -223,7 +223,7 @@ export const Default: Story = {
223
223
  {previewBlocks.map(({ link, el }) => (
224
224
  <PreviewBlock key={link.ref} link={link} el={el} view={view} />
225
225
  ))}
226
- </PreviewProvider>
226
+ </PreviewPopoverProvider>
227
227
  );
228
228
  },
229
229
  };
@@ -2,14 +2,12 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useState } from 'react';
9
7
  import { createPortal } from 'react-dom';
10
8
 
11
9
  import { useThemeContext } from '@dxos/react-ui';
12
- import { withLayout, withTheme } from '@dxos/storybook-utils';
10
+ import { withTheme } from '@dxos/react-ui/testing';
13
11
  import { trim } from '@dxos/util';
14
12
 
15
13
  import {
@@ -66,8 +64,10 @@ const text = trim`
66
64
  const meta = {
67
65
  title: 'ui/react-ui-editor/Tags',
68
66
  render: DefaultStory,
69
- decorators: [withTheme, withLayout({ fullscreen: true })],
70
- parameters: { layout: 'fullscreen' },
67
+ decorators: [withTheme],
68
+ parameters: {
69
+ layout: 'fullscreen',
70
+ },
71
71
  } satisfies Meta<typeof DefaultStory>;
72
72
 
73
73
  export default meta;