@dxos/react-ui-editor 0.8.4-main.e098934 → 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 (205) 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 +3555 -3484
  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.map +2 -2
  7. package/dist/lib/browser/types/index.mjs +1 -1
  8. package/dist/lib/node-esm/{chunk-YXYQPV6R.mjs → chunk-YJZGD3LY.mjs} +2 -2
  9. package/dist/lib/node-esm/chunk-YJZGD3LY.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +3555 -3484
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  14. package/dist/lib/node-esm/types/index.mjs +1 -1
  15. package/dist/types/src/components/Editor/Editor.d.ts +24 -9
  16. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  17. package/dist/types/src/components/Editor/Editor.stories.d.ts +27 -0
  18. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -0
  19. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  20. package/dist/types/src/components/EditorToolbar/util.d.ts +1 -1
  21. package/dist/types/src/components/index.d.ts +0 -1
  22. package/dist/types/src/components/index.d.ts.map +1 -1
  23. package/dist/types/src/extensions/{autocomplete.d.ts → autocomplete/autocomplete.d.ts} +1 -1
  24. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +1 -0
  25. package/dist/types/src/extensions/autocomplete/index.d.ts +5 -0
  26. package/dist/types/src/extensions/autocomplete/index.d.ts.map +1 -0
  27. package/dist/types/src/extensions/autocomplete/match.d.ts +13 -0
  28. package/dist/types/src/extensions/autocomplete/match.d.ts.map +1 -0
  29. package/dist/types/src/extensions/autocomplete/placeholder.d.ts +20 -0
  30. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +1 -0
  31. package/dist/types/src/extensions/autocomplete/typeahead.d.ts +10 -0
  32. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +1 -0
  33. package/dist/types/src/extensions/automerge/automerge.d.ts +1 -1
  34. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  35. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +1 -1
  36. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  37. package/dist/types/src/extensions/automerge/sync.d.ts +2 -2
  38. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  39. package/dist/types/src/extensions/autoscroll.d.ts +2 -2
  40. package/dist/types/src/extensions/autoscroll.d.ts.map +1 -1
  41. package/dist/types/src/extensions/factories.d.ts +7 -2
  42. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  43. package/dist/types/src/extensions/focus.d.ts.map +1 -1
  44. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  45. package/dist/types/src/extensions/index.d.ts +2 -1
  46. package/dist/types/src/extensions/index.d.ts.map +1 -1
  47. package/dist/types/src/extensions/json.d.ts +1 -1
  48. package/dist/types/src/extensions/json.d.ts.map +1 -1
  49. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  50. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  51. package/dist/types/src/extensions/modes.d.ts +1 -1
  52. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  53. package/dist/types/src/extensions/outliner/menu.d.ts +8 -0
  54. package/dist/types/src/extensions/outliner/menu.d.ts.map +1 -0
  55. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts +36 -0
  56. package/dist/types/src/extensions/popover/PopoverMenuProvider.d.ts.map +1 -0
  57. package/dist/types/src/extensions/popover/index.d.ts +8 -0
  58. package/dist/types/src/extensions/popover/index.d.ts.map +1 -0
  59. package/dist/types/src/extensions/popover/menu-presets.d.ts +4 -0
  60. package/dist/types/src/extensions/popover/menu-presets.d.ts.map +1 -0
  61. package/dist/types/src/extensions/popover/menu.d.ts +24 -0
  62. package/dist/types/src/extensions/popover/menu.d.ts.map +1 -0
  63. package/dist/types/src/extensions/popover/modal.d.ts +7 -0
  64. package/dist/types/src/extensions/popover/modal.d.ts.map +1 -0
  65. package/dist/types/src/extensions/popover/popover.d.ts +47 -0
  66. package/dist/types/src/extensions/popover/popover.d.ts.map +1 -0
  67. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts +34 -0
  68. package/dist/types/src/extensions/popover/usePopoverMenu.d.ts.map +1 -0
  69. package/dist/types/src/extensions/popover/util.d.ts +8 -0
  70. package/dist/types/src/extensions/popover/util.d.ts.map +1 -0
  71. package/dist/types/src/extensions/preview/preview.d.ts +0 -1
  72. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  73. package/dist/types/src/extensions/state.d.ts +2 -0
  74. package/dist/types/src/extensions/state.d.ts.map +1 -0
  75. package/dist/types/src/extensions/tags/streamer.d.ts.map +1 -1
  76. package/dist/types/src/extensions/tags/xml-tags.d.ts +1 -0
  77. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +1 -1
  78. package/dist/types/src/hooks/useTextEditor.d.ts +4 -8
  79. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  80. package/dist/types/src/stories/{Command.stories.d.ts → CommandDialog.stories.d.ts} +2 -3
  81. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +1 -0
  82. package/dist/types/src/stories/Comments.stories.d.ts +3 -4
  83. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  84. package/dist/types/src/stories/EditorToolbar.stories.d.ts +1 -2
  85. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  86. package/dist/types/src/stories/Experimental.stories.d.ts +3 -4
  87. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  88. package/dist/types/src/stories/Markdown.stories.d.ts +3 -4
  89. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
  90. package/dist/types/src/stories/Outliner.stories.d.ts +0 -1
  91. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  92. package/dist/types/src/stories/{CommandMenu.stories.d.ts → Popover.stories.d.ts} +6 -6
  93. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -0
  94. package/dist/types/src/stories/Preview.stories.d.ts +3 -4
  95. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  96. package/dist/types/src/stories/Tags.stories.d.ts +0 -1
  97. package/dist/types/src/stories/Tags.stories.d.ts.map +1 -1
  98. package/dist/types/src/stories/TextEditor.stories.d.ts +3 -5
  99. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  100. package/dist/types/src/stories/components/EditorStory.d.ts +5 -5
  101. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  102. package/dist/types/src/styles/theme.d.ts.map +1 -1
  103. package/dist/types/src/testing/PreviewPopover.d.ts.map +1 -1
  104. package/dist/types/src/types/types.d.ts +1 -1
  105. package/dist/types/src/types/types.d.ts.map +1 -1
  106. package/dist/types/src/util/index.d.ts +0 -1
  107. package/dist/types/src/util/index.d.ts.map +1 -1
  108. package/dist/types/tsconfig.tsbuildinfo +1 -1
  109. package/package.json +54 -51
  110. package/src/components/Editor/Editor.stories.tsx +69 -0
  111. package/src/components/Editor/Editor.tsx +57 -14
  112. package/src/components/EditorToolbar/EditorToolbar.tsx +1 -0
  113. package/src/components/index.ts +0 -1
  114. package/src/extensions/{autocomplete.ts → autocomplete/autocomplete.ts} +2 -1
  115. package/src/extensions/autocomplete/index.ts +8 -0
  116. package/src/extensions/autocomplete/match.ts +46 -0
  117. package/src/extensions/{command → autocomplete}/placeholder.ts +21 -17
  118. package/src/extensions/{command → autocomplete}/typeahead.ts +6 -48
  119. package/src/extensions/automerge/automerge.stories.tsx +8 -8
  120. package/src/extensions/automerge/automerge.ts +28 -9
  121. package/src/extensions/automerge/sync.ts +7 -3
  122. package/src/extensions/autoscroll.ts +29 -29
  123. package/src/extensions/factories.ts +41 -12
  124. package/src/extensions/focus.ts +5 -4
  125. package/src/extensions/folding.tsx +4 -6
  126. package/src/extensions/hashtag.tsx +2 -2
  127. package/src/extensions/index.ts +2 -1
  128. package/src/extensions/json.ts +1 -1
  129. package/src/extensions/markdown/bundle.ts +16 -4
  130. package/src/extensions/markdown/decorate.ts +1 -0
  131. package/src/extensions/modes.ts +2 -2
  132. package/src/extensions/{command/floating-menu.ts → outliner/menu.ts} +9 -9
  133. package/src/extensions/outliner/outliner.ts +2 -2
  134. package/src/extensions/popover/PopoverMenuProvider.tsx +221 -0
  135. package/src/extensions/popover/index.ts +12 -0
  136. package/src/extensions/popover/menu-presets.ts +124 -0
  137. package/src/extensions/popover/menu.ts +67 -0
  138. package/src/extensions/popover/modal.ts +24 -0
  139. package/src/extensions/popover/popover.ts +291 -0
  140. package/src/extensions/popover/usePopoverMenu.ts +173 -0
  141. package/src/extensions/popover/util.ts +29 -0
  142. package/src/extensions/preview/index.ts +1 -1
  143. package/src/extensions/preview/preview.ts +0 -2
  144. package/src/extensions/selection.ts +2 -2
  145. package/src/extensions/state.ts +7 -0
  146. package/src/extensions/tags/streamer.ts +4 -5
  147. package/src/extensions/tags/xml-tags.ts +59 -1
  148. package/src/hooks/useTextEditor.ts +27 -27
  149. package/src/stories/{Command.stories.tsx → CommandDialog.stories.tsx} +10 -22
  150. package/src/stories/Comments.stories.tsx +5 -5
  151. package/src/stories/EditorToolbar.stories.tsx +6 -5
  152. package/src/stories/Experimental.stories.tsx +6 -6
  153. package/src/stories/Markdown.stories.tsx +5 -5
  154. package/src/stories/Outliner.stories.tsx +21 -14
  155. package/src/stories/Popover.stories.tsx +163 -0
  156. package/src/stories/Preview.stories.tsx +5 -5
  157. package/src/stories/Tags.stories.tsx +5 -5
  158. package/src/stories/TextEditor.stories.tsx +7 -32
  159. package/src/stories/components/EditorStory.tsx +7 -5
  160. package/src/styles/theme.ts +12 -10
  161. package/src/testing/PreviewPopover.tsx +2 -0
  162. package/src/types/types.ts +1 -1
  163. package/src/util/index.ts +0 -1
  164. package/dist/lib/browser/chunk-22UMM3QJ.mjs.map +0 -7
  165. package/dist/lib/node-esm/chunk-YXYQPV6R.mjs.map +0 -7
  166. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts +0 -38
  167. package/dist/types/src/components/CommandMenu/CommandMenu.d.ts.map +0 -1
  168. package/dist/types/src/components/CommandMenu/index.d.ts +0 -2
  169. package/dist/types/src/components/CommandMenu/index.d.ts.map +0 -1
  170. package/dist/types/src/extensions/autocomplete.d.ts.map +0 -1
  171. package/dist/types/src/extensions/command/action.d.ts +0 -17
  172. package/dist/types/src/extensions/command/action.d.ts.map +0 -1
  173. package/dist/types/src/extensions/command/command-menu.d.ts +0 -20
  174. package/dist/types/src/extensions/command/command-menu.d.ts.map +0 -1
  175. package/dist/types/src/extensions/command/command.d.ts +0 -6
  176. package/dist/types/src/extensions/command/command.d.ts.map +0 -1
  177. package/dist/types/src/extensions/command/floating-menu.d.ts +0 -7
  178. package/dist/types/src/extensions/command/floating-menu.d.ts.map +0 -1
  179. package/dist/types/src/extensions/command/hint.d.ts +0 -19
  180. package/dist/types/src/extensions/command/hint.d.ts.map +0 -1
  181. package/dist/types/src/extensions/command/index.d.ts +0 -7
  182. package/dist/types/src/extensions/command/index.d.ts.map +0 -1
  183. package/dist/types/src/extensions/command/placeholder.d.ts +0 -10
  184. package/dist/types/src/extensions/command/placeholder.d.ts.map +0 -1
  185. package/dist/types/src/extensions/command/state.d.ts +0 -16
  186. package/dist/types/src/extensions/command/state.d.ts.map +0 -1
  187. package/dist/types/src/extensions/command/typeahead.d.ts +0 -22
  188. package/dist/types/src/extensions/command/typeahead.d.ts.map +0 -1
  189. package/dist/types/src/extensions/command/useCommandMenu.d.ts +0 -25
  190. package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +0 -1
  191. package/dist/types/src/stories/Command.stories.d.ts.map +0 -1
  192. package/dist/types/src/stories/CommandMenu.stories.d.ts.map +0 -1
  193. package/dist/types/src/util/domino.d.ts +0 -18
  194. package/dist/types/src/util/domino.d.ts.map +0 -1
  195. package/src/components/CommandMenu/CommandMenu.tsx +0 -346
  196. package/src/components/CommandMenu/index.ts +0 -5
  197. package/src/extensions/command/action.ts +0 -55
  198. package/src/extensions/command/command-menu.ts +0 -211
  199. package/src/extensions/command/command.ts +0 -34
  200. package/src/extensions/command/hint.ts +0 -103
  201. package/src/extensions/command/index.ts +0 -10
  202. package/src/extensions/command/state.ts +0 -90
  203. package/src/extensions/command/useCommandMenu.ts +0 -115
  204. package/src/stories/CommandMenu.stories.tsx +0 -158
  205. package/src/util/domino.ts +0 -51
@@ -1,103 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- // Based on https://github.com/codemirror/view/blob/main/src/placeholder.ts
4
- //
5
-
6
- import { RangeSetBuilder } from '@codemirror/state';
7
- import { Decoration, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from '@codemirror/view';
8
-
9
- import { clientRectsFor, flattenRect } from '../../util';
10
-
11
- import { commandState } from './state';
12
-
13
- export type HintOptions = {
14
- delay?: number;
15
- onHint?: () => string | undefined;
16
- };
17
-
18
- export const hint = ({ delay = 3_000, onHint }: HintOptions) => {
19
- return ViewPlugin.fromClass(
20
- class {
21
- decorations = Decoration.none;
22
- timeout: ReturnType<typeof setTimeout> | undefined;
23
-
24
- update(update: ViewUpdate) {
25
- if (this.timeout) {
26
- clearTimeout(this.timeout);
27
- this.timeout = undefined;
28
- }
29
-
30
- const builder = new RangeSetBuilder<Decoration>();
31
- const cState = update.view.state.field(commandState, false);
32
- if (!cState?.tooltip) {
33
- const selection = update.view.state.selection.main;
34
- const line = update.view.state.doc.lineAt(selection.from);
35
- // Only show if blank line.
36
- if (selection.from === selection.to && line.from === line.to) {
37
- // Set timeout to add decoration after delay.
38
- this.timeout = setTimeout(() => {
39
- const hint = onHint?.();
40
- if (hint) {
41
- const builder = new RangeSetBuilder<Decoration>();
42
- builder.add(selection.from, selection.to, Decoration.widget({ widget: new Hint(hint) }));
43
- this.decorations = builder.finish();
44
- update.view.update([]);
45
- }
46
- }, delay);
47
- }
48
- }
49
-
50
- this.decorations = builder.finish();
51
- }
52
-
53
- destroy() {
54
- if (this.timeout) {
55
- clearTimeout(this.timeout);
56
- }
57
- }
58
- },
59
- {
60
- provide: (plugin) => [EditorView.decorations.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none)],
61
- },
62
- );
63
- };
64
-
65
- export class Hint extends WidgetType {
66
- constructor(readonly content: string | HTMLElement) {
67
- super();
68
- }
69
-
70
- toDOM(): HTMLSpanElement {
71
- const wrap = document.createElement('span');
72
- wrap.className = 'cm-placeholder';
73
- wrap.style.pointerEvents = 'none';
74
- wrap.appendChild(typeof this.content === 'string' ? document.createTextNode(this.content) : this.content);
75
- if (typeof this.content === 'string') {
76
- wrap.setAttribute('aria-label', 'placeholder ' + this.content);
77
- } else {
78
- wrap.setAttribute('aria-hidden', 'true');
79
- }
80
-
81
- return wrap;
82
- }
83
-
84
- override coordsAt(dom: HTMLElement) {
85
- const rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
86
- if (!rects.length) {
87
- return null;
88
- }
89
-
90
- const style = window.getComputedStyle(dom.parentNode as HTMLElement);
91
- const rect = flattenRect(rects[0], style.direction !== 'rtl');
92
- const lineHeight = parseInt(style.lineHeight);
93
- if (rect.bottom - rect.top > lineHeight * 1.5) {
94
- return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.top + lineHeight };
95
- }
96
-
97
- return rect;
98
- }
99
-
100
- override ignoreEvent(): boolean {
101
- return false;
102
- }
103
- }
@@ -1,10 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export * from './action';
6
- export * from './command';
7
- export * from './command-menu';
8
- export * from './floating-menu';
9
- export * from './typeahead';
10
- export * from './useCommandMenu';
@@ -1,90 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { StateField } from '@codemirror/state';
6
- import { type EditorView, type Tooltip, type TooltipView, showTooltip } from '@codemirror/view';
7
-
8
- import { type RenderCallback } from '../../types';
9
- import { singleValueFacet } from '../../util';
10
-
11
- import { type Action, closeEffect, openEffect } from './action';
12
- import { type CommandOptions } from './command';
13
-
14
- export const commandConfig = singleValueFacet<CommandOptions>();
15
-
16
- export type PopupOptions = {
17
- renderDialog: RenderCallback<{ onAction: (action?: Action) => void }>;
18
- };
19
-
20
- type CommandState = {
21
- tooltip?: Tooltip;
22
- };
23
-
24
- export const commandState = StateField.define<CommandState>({
25
- create: () => ({}),
26
- update: (state, tr) => {
27
- for (const effect of tr.effects) {
28
- if (effect.is(closeEffect)) {
29
- return {};
30
- }
31
-
32
- const { renderDialog } = tr.state.facet(commandConfig);
33
- if (effect.is(openEffect) && renderDialog) {
34
- const { pos, fullWidth } = effect.value;
35
- const tooltip: Tooltip = {
36
- pos,
37
- above: false,
38
- arrow: false,
39
- strictSide: true,
40
- create: (view: EditorView) => {
41
- const root = document.createElement('div');
42
- const tooltipView: TooltipView = {
43
- dom: root,
44
- mount: (view: EditorView) => {
45
- if (fullWidth) {
46
- const parent = root.parentElement!;
47
- const { paddingLeft, paddingRight } = window.getComputedStyle(parent);
48
- const widthWithoutPadding = parent.clientWidth - parseFloat(paddingLeft) - parseFloat(paddingRight);
49
- root.style.width = `${widthWithoutPadding}px`;
50
- }
51
-
52
- // Render react component.
53
- renderDialog(
54
- root,
55
- {
56
- onAction: (action) => {
57
- view.dispatch({ effects: closeEffect.of(null) });
58
- switch (action?.type) {
59
- case 'insert': {
60
- // Insert into editor.
61
- const text = action.text + '\n';
62
- view.dispatch({
63
- changes: { from: pos, insert: text },
64
- selection: { anchor: pos + text.length },
65
- });
66
- break;
67
- }
68
- }
69
-
70
- // NOTE: Truncates text if set focus immediately.
71
- requestAnimationFrame(() => view.focus());
72
- },
73
- },
74
- view,
75
- );
76
- },
77
- };
78
-
79
- return tooltipView;
80
- },
81
- };
82
-
83
- return { tooltip };
84
- }
85
- }
86
-
87
- return state;
88
- },
89
- provide: (field) => [showTooltip.from(field, (value) => value.tooltip ?? null)],
90
- });
@@ -1,115 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type EditorView } from '@codemirror/view';
6
- import { type RefObject, useCallback, useMemo, useRef, useState } from 'react';
7
-
8
- import { type DxAnchorActivate } from '@dxos/react-ui';
9
- import { type MaybePromise } from '@dxos/util';
10
-
11
- import { type CommandMenuGroup, type CommandMenuItem, getItem, getNextItem, getPreviousItem } from '../../components';
12
-
13
- import { commandMenu, commandRangeEffect } from './command-menu';
14
- import { type PlaceholderOptions } from './placeholder';
15
-
16
- export type UseCommandMenuOptions = {
17
- viewRef: RefObject<EditorView | undefined>;
18
- trigger: string | string[];
19
- placeholder?: Partial<PlaceholderOptions>;
20
- getMenu: (trigger: string, query?: string) => MaybePromise<CommandMenuGroup[]>;
21
- };
22
-
23
- export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCommandMenuOptions) => {
24
- const currentRef = useRef<CommandMenuItem | null>(null);
25
- const groupsRef = useRef<CommandMenuGroup[]>([]);
26
- const [currentItem, setCurrentItem] = useState<string>();
27
- const [open, setOpen] = useState(false);
28
- const [_, refresh] = useState({});
29
-
30
- const handleOpenChange = useCallback(
31
- async (open: boolean, trigger?: string) => {
32
- if (open && trigger) {
33
- groupsRef.current = await getMenu(trigger);
34
- }
35
- setOpen(open);
36
- if (!open) {
37
- setCurrentItem(undefined);
38
- viewRef.current?.dispatch({ effects: [commandRangeEffect.of(null)] });
39
- }
40
- },
41
- [getMenu],
42
- );
43
-
44
- const handleActivate = useCallback(
45
- async (event: DxAnchorActivate) => {
46
- const item = getItem(groupsRef.current, currentItem);
47
- if (item) {
48
- currentRef.current = item;
49
- }
50
-
51
- const triggerKey = event.trigger.getAttribute('data-trigger');
52
- if (!open && triggerKey) {
53
- await handleOpenChange(true, triggerKey);
54
- }
55
- },
56
- [open, handleOpenChange],
57
- );
58
-
59
- const handleSelect = useCallback((item: CommandMenuItem) => {
60
- const view = viewRef.current;
61
- if (!view) {
62
- return;
63
- }
64
-
65
- const selection = view.state.selection.main;
66
- void item.onSelect?.(view, selection.head);
67
- }, []);
68
-
69
- const serializedTrigger = Array.isArray(trigger) ? trigger.join(',') : trigger;
70
- const memoizedCommandMenu = useMemo(() => {
71
- return commandMenu({
72
- trigger,
73
- placeholder,
74
- onClose: () => handleOpenChange(false),
75
- onArrowDown: () => {
76
- setCurrentItem((currentItem) => {
77
- const next = getNextItem(groupsRef.current, currentItem);
78
- currentRef.current = next;
79
- return next.id;
80
- });
81
- },
82
- onArrowUp: () => {
83
- setCurrentItem((currentItem) => {
84
- const previous = getPreviousItem(groupsRef.current, currentItem);
85
- currentRef.current = previous;
86
- return previous.id;
87
- });
88
- },
89
- onEnter: () => {
90
- if (currentRef.current) {
91
- handleSelect(currentRef.current);
92
- }
93
- },
94
- onTextChange: async (trigger, text) => {
95
- groupsRef.current = await getMenu(trigger, text);
96
- const firstItem = groupsRef.current.filter((group) => group.items.length > 0)[0]?.items[0];
97
- if (firstItem) {
98
- setCurrentItem(firstItem.id);
99
- currentRef.current = firstItem;
100
- }
101
- refresh({});
102
- },
103
- });
104
- }, [handleOpenChange, getMenu, serializedTrigger, placeholder]);
105
-
106
- return {
107
- commandMenu: memoizedCommandMenu,
108
- currentItem,
109
- groupsRef,
110
- open,
111
- onActivate: handleActivate,
112
- onOpenChange: setOpen,
113
- onSelect: handleSelect,
114
- };
115
- };
@@ -1,158 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import '@dxos-theme';
6
-
7
- import { type EditorView } from '@codemirror/view';
8
- import { type Meta, type StoryObj } from '@storybook/react-vite';
9
- import React, { useCallback, useRef } from 'react';
10
-
11
- import { Obj, Query } from '@dxos/echo';
12
- import { faker } from '@dxos/random';
13
- import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
14
- import { Testing, type ValueGenerator, createObjectFactory } from '@dxos/schema/testing';
15
- import { withLayout, withTheme } from '@dxos/storybook-utils';
16
-
17
- import {
18
- type CommandMenuGroup,
19
- type CommandMenuItem,
20
- CommandMenuProvider,
21
- coreSlashCommands,
22
- filterItems,
23
- insertAtCursor,
24
- insertAtLineStart,
25
- linkSlashCommands,
26
- } from '../components';
27
- import { type UseCommandMenuOptions, useCommandMenu } from '../extensions';
28
- import { str } from '../testing';
29
- import { Domino } from '../util';
30
-
31
- import { EditorStory, names } from './components';
32
-
33
- const generator: ValueGenerator = faker as any;
34
-
35
- type StoryProps = Omit<UseCommandMenuOptions, 'viewRef'> & { text: string };
36
-
37
- const DefaultStory = ({ text, ...options }: StoryProps) => {
38
- const viewRef = useRef<EditorView>();
39
- const { commandMenu, groupsRef, ...commandMenuProps } = useCommandMenu({ viewRef, ...options });
40
-
41
- return (
42
- <CommandMenuProvider groups={groupsRef.current} {...commandMenuProps}>
43
- <EditorStory ref={viewRef} text={text} placeholder={''} extensions={commandMenu} />
44
- </CommandMenuProvider>
45
- );
46
- };
47
-
48
- const groups: CommandMenuGroup[] = [
49
- coreSlashCommands,
50
- linkSlashCommands,
51
- {
52
- id: 'custom',
53
- label: 'Custom',
54
- items: [
55
- {
56
- id: 'custom-1',
57
- label: 'Log',
58
- icon: 'ph--log--regular',
59
- onSelect: console.log,
60
- },
61
- ],
62
- },
63
- ];
64
-
65
- const meta = {
66
- title: 'ui/react-ui-editor/CommandMenu',
67
- render: DefaultStory,
68
- decorators: [withTheme, withLayout({ fullscreen: true })],
69
- parameters: {
70
- layout: 'fullscreen',
71
- },
72
- } satisfies Meta<typeof DefaultStory>;
73
-
74
- export default meta;
75
-
76
- type Story = StoryObj<typeof meta>;
77
-
78
- // TODO(burdon): Not working.
79
- export const Slash: Story = {
80
- args: {
81
- text: str('# Slash', '', names.join(' '), ''),
82
- trigger: '/',
83
- placeholder: {
84
- content: () =>
85
- Domino.of('div')
86
- .child(Domino.of('span').text('Press'))
87
- .child(Domino.of('span').text('/').classNames('border border-separator rounded-sm mx-1 px-1'))
88
- .child(Domino.of('span').text('for commands'))
89
- .build(),
90
- },
91
- getMenu: (text) => {
92
- return filterItems(groups, (item) =>
93
- text ? (item.label as string).toLowerCase().includes(text.toLowerCase()) : true,
94
- );
95
- },
96
- },
97
- };
98
-
99
- export const Link: Story = {
100
- render: (args: StoryProps) => {
101
- const { space } = useClientProvider();
102
- const getMenu = useCallback(
103
- async (trigger: string, query?: string): Promise<CommandMenuGroup[]> => {
104
- if (trigger === '/') {
105
- return filterItems(groups, (item) =>
106
- query ? (item.label as string).toLowerCase().includes(query.toLowerCase()) : true,
107
- );
108
- }
109
-
110
- if (!space) {
111
- return [];
112
- }
113
-
114
- const name = query?.startsWith('@') ? query.slice(1).toLowerCase() : (query?.toLowerCase() ?? '');
115
- const result = await space?.db.query(Query.type(Testing.Contact)).run();
116
- const items = result.objects
117
- .filter((object) => object.name.toLowerCase().includes(name))
118
- .map(
119
- (object): CommandMenuItem => ({
120
- id: object.id,
121
- label: object.name,
122
- icon: 'ph--user--regular',
123
- onSelect: (view, head) => {
124
- const link = `[${object.name}](${Obj.getDXN(object)})`;
125
- if (query?.startsWith('@')) {
126
- insertAtLineStart(view, head, `!${link}\n`);
127
- } else {
128
- insertAtCursor(view, head, `${link} `);
129
- }
130
- },
131
- }),
132
- );
133
- return [{ id: 'echo', items }];
134
- },
135
- [space],
136
- );
137
-
138
- return <DefaultStory {...args} getMenu={getMenu} />;
139
- },
140
- decorators: [
141
- withClientProvider({
142
- createSpace: true,
143
- onInitialized: async (client) => {
144
- client.addTypes([Testing.Contact]);
145
- },
146
- onSpaceCreated: async ({ space }) => {
147
- const createObjects = createObjectFactory(space.db, generator);
148
- await createObjects([{ type: Testing.Contact, count: 10 }]);
149
- await space.db.flush({ indexes: true });
150
- },
151
- }),
152
- ],
153
- args: {
154
- text: str('# Link', '', names.join(' '), ''),
155
- trigger: ['/', '@'],
156
- getMenu: () => [],
157
- },
158
- };
@@ -1,51 +0,0 @@
1
- //
2
- // Copyright 2025 DXOS.org
3
- //
4
-
5
- import { mx } from '@dxos/react-ui-theme';
6
- import { type ClassNameValue } from '@dxos/react-ui-types';
7
-
8
- /**
9
- * Super lightweight chainable DOM builder.
10
- */
11
- export class Domino<T extends HTMLElement> {
12
- static of<K extends keyof HTMLElementTagNameMap>(tag: K): Domino<HTMLElementTagNameMap[K]> {
13
- return new Domino<HTMLElementTagNameMap[K]>(tag);
14
- }
15
-
16
- private readonly _el: T;
17
- constructor(tag: keyof HTMLElementTagNameMap) {
18
- this._el = document.createElement(tag) as T;
19
- }
20
- classNames(...classNames: ClassNameValue[]): this {
21
- this._el.className = mx(classNames);
22
- return this;
23
- }
24
- text(value: string): this {
25
- this._el.textContent = value;
26
- return this;
27
- }
28
- data(key: string, value: string): this {
29
- this._el.dataset[key] = value;
30
- return this;
31
- }
32
- style(styles: Partial<CSSStyleDeclaration>): this {
33
- Object.assign(this._el.style, styles);
34
- return this;
35
- }
36
- attr<K extends keyof T>(key: K, value: T[K]): this {
37
- (this._el as any)[key] = value;
38
- return this;
39
- }
40
- child<C extends HTMLElement>(...children: Domino<C>[]): this {
41
- children.forEach((child) => this._el.appendChild(child.build()));
42
- return this;
43
- }
44
- on(event: string, handler: (e: Event) => void): this {
45
- this._el.addEventListener(event, handler);
46
- return this;
47
- }
48
- build(): T {
49
- return this._el;
50
- }
51
- }