@dxos/react-ui-editor 0.8.4-main.dedc0f3 → 0.8.4-main.e098934
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.
- package/dist/lib/browser/index.mjs +82 -190
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +71 -1
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +82 -190
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +71 -1
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/components/{Popover → CommandMenu}/CommandMenu.d.ts +10 -6
- package/dist/types/src/components/CommandMenu/CommandMenu.d.ts.map +1 -0
- package/dist/types/src/components/CommandMenu/index.d.ts +2 -0
- package/dist/types/src/components/CommandMenu/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/extensions/autoscroll.d.ts.map +1 -1
- package/dist/types/src/extensions/command/action.d.ts.map +1 -1
- package/dist/types/src/extensions/command/floating-menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/useCommandMenu.d.ts +1 -2
- package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
- package/dist/types/src/extensions/preview/preview.d.ts +0 -1
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/stories/CommandMenu.stories.d.ts.map +1 -1
- package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
- package/dist/types/src/testing/PreviewPopover.d.ts +20 -0
- package/dist/types/src/testing/PreviewPopover.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -33
- package/src/components/{Popover → CommandMenu}/CommandMenu.tsx +93 -26
- package/src/components/{Popover → CommandMenu}/index.ts +0 -2
- package/src/components/index.ts +1 -1
- package/src/extensions/autoscroll.ts +14 -8
- package/src/extensions/command/action.ts +0 -1
- package/src/extensions/command/command-menu.ts +1 -1
- package/src/extensions/command/floating-menu.ts +9 -14
- package/src/extensions/command/useCommandMenu.ts +3 -7
- package/src/extensions/markdown/link.ts +3 -0
- package/src/extensions/outliner/outliner.ts +1 -1
- package/src/extensions/preview/preview.ts +0 -3
- package/src/hooks/useTextEditor.ts +0 -12
- package/src/stories/CommandMenu.stories.tsx +5 -7
- package/src/stories/Outliner.stories.tsx +28 -19
- package/src/stories/Preview.stories.tsx +4 -4
- package/src/{components/Popover/RefDropdownMenu.tsx → testing/PreviewPopover.tsx} +19 -30
- package/src/testing/index.ts +1 -0
- package/dist/types/src/components/Popover/CommandMenu.d.ts.map +0 -1
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +0 -14
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +0 -1
- package/dist/types/src/components/Popover/RefPopover.d.ts +0 -37
- package/dist/types/src/components/Popover/RefPopover.d.ts.map +0 -1
- package/dist/types/src/components/Popover/index.d.ts +0 -4
- package/dist/types/src/components/Popover/index.d.ts.map +0 -1
- package/src/components/Popover/RefPopover.tsx +0 -117
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@dxos/react-ui-editor",
|
3
|
-
"version": "0.8.4-main.
|
3
|
+
"version": "0.8.4-main.e098934",
|
4
4
|
"description": "Document editing experience within a DXOS shell.",
|
5
5
|
"homepage": "https://dxos.org",
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
@@ -59,6 +59,7 @@
|
|
59
59
|
"@preact-signals/safe-react": "^0.9.0",
|
60
60
|
"@preact/signals-react": "^3.2.0",
|
61
61
|
"@radix-ui/react-context": "1.1.1",
|
62
|
+
"@radix-ui/react-use-controllable-state": "1.1.0",
|
62
63
|
"@replit/codemirror-vim": "^6.2.1",
|
63
64
|
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
64
65
|
"ajv": "^8.17.1",
|
@@ -68,22 +69,21 @@
|
|
68
69
|
"lodash.merge": "^4.6.2",
|
69
70
|
"lodash.sortby": "^4.7.0",
|
70
71
|
"style-mod": "^4.1.0",
|
71
|
-
"@dxos/app-graph": "0.8.4-main.
|
72
|
-
"@dxos/async": "0.8.4-main.
|
73
|
-
"@dxos/context": "0.8.4-main.
|
74
|
-
"@dxos/display-name": "0.8.4-main.
|
75
|
-
"@dxos/echo-schema": "0.8.4-main.
|
76
|
-
"@dxos/invariant": "0.8.4-main.
|
77
|
-
"@dxos/
|
78
|
-
"@dxos/
|
79
|
-
"@dxos/
|
80
|
-
"@dxos/
|
81
|
-
"@dxos/
|
82
|
-
"@dxos/react-
|
83
|
-
"@dxos/react-ui-
|
84
|
-
"@dxos/
|
85
|
-
"@dxos/
|
86
|
-
"@dxos/util": "0.8.4-main.dedc0f3"
|
72
|
+
"@dxos/app-graph": "0.8.4-main.e098934",
|
73
|
+
"@dxos/async": "0.8.4-main.e098934",
|
74
|
+
"@dxos/context": "0.8.4-main.e098934",
|
75
|
+
"@dxos/display-name": "0.8.4-main.e098934",
|
76
|
+
"@dxos/echo-schema": "0.8.4-main.e098934",
|
77
|
+
"@dxos/invariant": "0.8.4-main.e098934",
|
78
|
+
"@dxos/live-object": "0.8.4-main.e098934",
|
79
|
+
"@dxos/log": "0.8.4-main.e098934",
|
80
|
+
"@dxos/protocols": "0.8.4-main.e098934",
|
81
|
+
"@dxos/react-hooks": "0.8.4-main.e098934",
|
82
|
+
"@dxos/react-ui-stack": "0.8.4-main.e098934",
|
83
|
+
"@dxos/react-ui-menu": "0.8.4-main.e098934",
|
84
|
+
"@dxos/react-ui-types": "0.8.4-main.e098934",
|
85
|
+
"@dxos/debug": "0.8.4-main.e098934",
|
86
|
+
"@dxos/util": "0.8.4-main.e098934"
|
87
87
|
},
|
88
88
|
"devDependencies": {
|
89
89
|
"@automerge/automerge": "3.1.1",
|
@@ -111,19 +111,19 @@
|
|
111
111
|
"vite": "7.1.1",
|
112
112
|
"vite-plugin-top-level-await": "^1.6.0",
|
113
113
|
"vite-plugin-wasm": "^3.5.0",
|
114
|
-
"@dxos/
|
115
|
-
"@dxos/echo": "0.8.4-main.
|
116
|
-
"@dxos/
|
117
|
-
"@dxos/
|
118
|
-
"@dxos/
|
119
|
-
"@dxos/react-
|
120
|
-
"@dxos/react-
|
121
|
-
"@dxos/react-ui-stack": "0.8.4-main.
|
122
|
-
"@dxos/react-ui-
|
123
|
-
"@dxos/react-ui-
|
124
|
-
"@dxos/
|
125
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
126
|
-
"@dxos/
|
114
|
+
"@dxos/echo": "0.8.4-main.e098934",
|
115
|
+
"@dxos/echo-signals": "0.8.4-main.e098934",
|
116
|
+
"@dxos/keyboard": "0.8.4-main.e098934",
|
117
|
+
"@dxos/random": "0.8.4-main.e098934",
|
118
|
+
"@dxos/react-ui": "0.8.4-main.e098934",
|
119
|
+
"@dxos/react-ui-attention": "0.8.4-main.e098934",
|
120
|
+
"@dxos/react-client": "0.8.4-main.e098934",
|
121
|
+
"@dxos/react-ui-stack": "0.8.4-main.e098934",
|
122
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.e098934",
|
123
|
+
"@dxos/react-ui-theme": "0.8.4-main.e098934",
|
124
|
+
"@dxos/config": "0.8.4-main.e098934",
|
125
|
+
"@dxos/storybook-utils": "0.8.4-main.e098934",
|
126
|
+
"@dxos/schema": "0.8.4-main.e098934"
|
127
127
|
},
|
128
128
|
"peerDependencies": {
|
129
129
|
"@effect-rx/rx-react": "^0.34.1",
|
@@ -131,9 +131,9 @@
|
|
131
131
|
"effect": "^3.13.3",
|
132
132
|
"react": "~18.2.0",
|
133
133
|
"react-dom": "~18.2.0",
|
134
|
-
"@dxos/react-client": "0.8.4-main.
|
135
|
-
"@dxos/react-ui": "0.8.4-main.
|
136
|
-
"@dxos/react-ui
|
134
|
+
"@dxos/react-client": "0.8.4-main.e098934",
|
135
|
+
"@dxos/react-ui-theme": "0.8.4-main.e098934",
|
136
|
+
"@dxos/react-ui": "0.8.4-main.e098934"
|
137
137
|
},
|
138
138
|
"publishConfig": {
|
139
139
|
"access": "public"
|
@@ -3,9 +3,19 @@
|
|
3
3
|
//
|
4
4
|
|
5
5
|
import { type EditorView } from '@codemirror/view';
|
6
|
-
import
|
6
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
7
|
+
import React, { Fragment, type PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';
|
7
8
|
|
8
|
-
import {
|
9
|
+
import { addEventListener } from '@dxos/async';
|
10
|
+
import {
|
11
|
+
type DxAnchorActivate,
|
12
|
+
Icon,
|
13
|
+
type Label,
|
14
|
+
Popover,
|
15
|
+
toLocalizedString,
|
16
|
+
useThemeContext,
|
17
|
+
useTranslation,
|
18
|
+
} from '@dxos/react-ui';
|
9
19
|
import { type MaybePromise } from '@dxos/util';
|
10
20
|
|
11
21
|
import { commandRangeEffect } from '../../extensions';
|
@@ -23,37 +33,94 @@ export type CommandMenuItem = {
|
|
23
33
|
onSelect?: (view: EditorView, head: number) => MaybePromise<void>;
|
24
34
|
};
|
25
35
|
|
26
|
-
export type CommandMenuProps = {
|
36
|
+
export type CommandMenuProps = PropsWithChildren<{
|
27
37
|
groups: CommandMenuGroup[];
|
28
|
-
currentItem?: string;
|
29
38
|
onSelect: (item: CommandMenuItem) => void;
|
30
|
-
|
39
|
+
onActivate?: (event: DxAnchorActivate) => void;
|
40
|
+
currentItem?: string;
|
41
|
+
open?: boolean;
|
42
|
+
onOpenChange?: (nextOpen: boolean) => void;
|
43
|
+
defaultOpen?: boolean;
|
44
|
+
}>;
|
31
45
|
|
32
46
|
// NOTE: Not using DropdownMenu because the command menu needs to manage focus explicitly.
|
33
|
-
export const
|
47
|
+
export const CommandMenuProvider = ({
|
48
|
+
groups,
|
49
|
+
onSelect,
|
50
|
+
onActivate,
|
51
|
+
currentItem,
|
52
|
+
children,
|
53
|
+
open: propsOpen,
|
54
|
+
onOpenChange,
|
55
|
+
defaultOpen,
|
56
|
+
}: CommandMenuProps) => {
|
34
57
|
const { tx } = useThemeContext();
|
35
58
|
const groupsWithItems = groups.filter((group) => group.items.length > 0);
|
59
|
+
const trigger = useRef<HTMLButtonElement | null>(null);
|
60
|
+
|
61
|
+
const [open, setOpen] = useControllableState({
|
62
|
+
prop: propsOpen,
|
63
|
+
onChange: onOpenChange,
|
64
|
+
defaultProp: defaultOpen,
|
65
|
+
});
|
66
|
+
|
67
|
+
const handleDxAnchorActivate = useCallback(
|
68
|
+
(event: DxAnchorActivate) => {
|
69
|
+
const { trigger: dxTrigger, refId } = event;
|
70
|
+
// If this has a `refId`, then it’s probably a URL or DXN and out of scope for this component.
|
71
|
+
if (!refId) {
|
72
|
+
trigger.current = dxTrigger as HTMLButtonElement;
|
73
|
+
if (onActivate) {
|
74
|
+
onActivate(event);
|
75
|
+
} else {
|
76
|
+
queueMicrotask(() => setOpen(true));
|
77
|
+
}
|
78
|
+
}
|
79
|
+
},
|
80
|
+
[onActivate],
|
81
|
+
);
|
82
|
+
|
83
|
+
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
84
|
+
|
85
|
+
useEffect(() => {
|
86
|
+
if (!rootRef || !handleDxAnchorActivate) {
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
|
90
|
+
return addEventListener(rootRef, 'dx-anchor-activate' as any, handleDxAnchorActivate, {
|
91
|
+
capture: true,
|
92
|
+
passive: false,
|
93
|
+
});
|
94
|
+
}, [rootRef, handleDxAnchorActivate]);
|
95
|
+
|
36
96
|
return (
|
37
|
-
<Popover.
|
38
|
-
<Popover.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
'
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
<
|
47
|
-
|
48
|
-
|
49
|
-
<
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
97
|
+
<Popover.Root modal={false} open={open} onOpenChange={setOpen}>
|
98
|
+
<Popover.Portal>
|
99
|
+
<Popover.Content
|
100
|
+
align='start'
|
101
|
+
onOpenAutoFocus={(event) => event.preventDefault()}
|
102
|
+
classNames={tx('menu.content', 'menu--exotic-unfocusable', { elevation: 'positioned' }, [
|
103
|
+
'max-bs-80 overflow-y-auto',
|
104
|
+
])}
|
105
|
+
>
|
106
|
+
<Popover.Viewport classNames={tx('menu.viewport', 'menu__viewport--exotic-unfocusable', {})}>
|
107
|
+
<ul>
|
108
|
+
{groupsWithItems.map((group, index) => (
|
109
|
+
<Fragment key={group.id}>
|
110
|
+
<CommandGroup group={group} currentItem={currentItem} onSelect={onSelect} />
|
111
|
+
{index < groupsWithItems.length - 1 && <div className={tx('menu.separator', 'menu__item', {})} />}
|
112
|
+
</Fragment>
|
113
|
+
))}
|
114
|
+
</ul>
|
115
|
+
</Popover.Viewport>
|
116
|
+
<Popover.Arrow />
|
117
|
+
</Popover.Content>
|
118
|
+
</Popover.Portal>
|
119
|
+
<Popover.VirtualTrigger virtualRef={trigger} />
|
120
|
+
<div role='none' className='contents' ref={setRootRef}>
|
121
|
+
{children}
|
122
|
+
</div>
|
123
|
+
</Popover.Root>
|
57
124
|
);
|
58
125
|
};
|
59
126
|
|
package/src/components/index.ts
CHANGED
@@ -23,9 +23,10 @@ export type AutoScrollOptions = {
|
|
23
23
|
export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Partial<AutoScrollOptions> = {}) => {
|
24
24
|
let isThrottled = false;
|
25
25
|
let isPinned = true;
|
26
|
-
let lastScrollTop = 0;
|
27
26
|
let timeout: NodeJS.Timeout | undefined;
|
28
27
|
let buttonContainer: HTMLDivElement;
|
28
|
+
let lastScrollTop = 0;
|
29
|
+
let scrollCounter = 0;
|
29
30
|
|
30
31
|
const hideScrollbar = (view: EditorView) => {
|
31
32
|
view.scrollDOM.classList.add('cm-hide-scrollbar');
|
@@ -37,6 +38,7 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
|
|
37
38
|
|
38
39
|
const scrollToBottom = (view: EditorView) => {
|
39
40
|
isPinned = true;
|
41
|
+
scrollCounter = 0;
|
40
42
|
buttonContainer?.classList.add('opacity-0');
|
41
43
|
requestAnimationFrame(() => {
|
42
44
|
hideScrollbar(view);
|
@@ -84,11 +86,8 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
|
|
84
86
|
if (update.docChanged && isPinned && !isThrottled) {
|
85
87
|
const distanceFromBottom = calcDistance(update.view.scrollDOM);
|
86
88
|
|
87
|
-
// Hide scrollbar even if not scrolling to bottom.
|
88
|
-
// hideScrollbar(update.view);
|
89
|
-
|
90
89
|
// Keep pinned.
|
91
|
-
if (distanceFromBottom
|
90
|
+
if (distanceFromBottom >= overscroll) {
|
92
91
|
isThrottled = true;
|
93
92
|
requestAnimationFrame(() => {
|
94
93
|
scrollToBottom(update.view);
|
@@ -102,21 +101,28 @@ export const autoScroll = ({ overscroll = 4 * lineHeight, throttle = 2_000 }: Pa
|
|
102
101
|
}
|
103
102
|
}),
|
104
103
|
|
104
|
+
// Detect user scroll.
|
105
|
+
// NOTE: Multiple scroll events are triggered during programmatic smooth scrolling.
|
105
106
|
EditorView.domEventHandlers({
|
106
107
|
scroll: (event, view) => {
|
107
108
|
const scroller = view.scrollDOM;
|
109
|
+
// Suspect delta goes positive when rendering widgets, so count positive deltas.
|
110
|
+
// TODO(burdon): Detect user scroll directly (wheel, touch, keys, etc.)
|
111
|
+
if (lastScrollTop > scroller.scrollTop) {
|
112
|
+
scrollCounter++;
|
113
|
+
}
|
114
|
+
lastScrollTop = scroller.scrollTop;
|
108
115
|
const distanceFromBottom = calcDistance(scroller);
|
109
116
|
if (distanceFromBottom === 0) {
|
110
117
|
// Pin to bottom.
|
111
118
|
isPinned = true;
|
112
119
|
buttonContainer?.classList.add('opacity-0');
|
113
|
-
|
120
|
+
scrollCounter = 0;
|
121
|
+
} else if (scrollCounter > 3) {
|
114
122
|
// Break pin if user scrolls up.
|
115
123
|
isPinned = false;
|
116
124
|
buttonContainer?.classList.remove('opacity-0');
|
117
125
|
}
|
118
|
-
|
119
|
-
lastScrollTop = scroller.scrollTop;
|
120
126
|
},
|
121
127
|
}),
|
122
128
|
|
@@ -44,7 +44,7 @@ export const commandMenu = (options: CommandMenuOptions) => {
|
|
44
44
|
activeRange.to,
|
45
45
|
Decoration.mark({
|
46
46
|
tagName: 'dx-anchor',
|
47
|
-
class: 'cm-
|
47
|
+
class: 'cm-floating-menu-trigger',
|
48
48
|
attributes: {
|
49
49
|
'data-auto-trigger': 'true',
|
50
50
|
'data-trigger': trigger!,
|
@@ -34,12 +34,10 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
|
|
34
34
|
{
|
35
35
|
const icon = document.createElement('dx-icon');
|
36
36
|
icon.setAttribute('icon', options.icon ?? 'ph--dots-three-vertical--regular');
|
37
|
-
const button = document.createElement('button');
|
38
|
-
button.appendChild(icon);
|
39
37
|
|
40
38
|
this.tag = document.createElement('dx-anchor');
|
41
|
-
this.tag.classList.add('cm-
|
42
|
-
this.tag.appendChild(
|
39
|
+
this.tag.classList.add('cm-floating-menu-trigger');
|
40
|
+
this.tag.appendChild(icon);
|
43
41
|
}
|
44
42
|
|
45
43
|
container.appendChild(this.tag);
|
@@ -69,7 +67,7 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
|
|
69
67
|
this.tag.style.display = 'none';
|
70
68
|
this.tag.classList.add('opacity-10');
|
71
69
|
} else if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(closeEffect)))) {
|
72
|
-
this.tag.style.display = '
|
70
|
+
this.tag.style.display = '';
|
73
71
|
} else if (
|
74
72
|
update.docChanged ||
|
75
73
|
update.focusChanged ||
|
@@ -99,7 +97,7 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
|
|
99
97
|
|
100
98
|
this.tag.style.top = `${offsetTop}px`;
|
101
99
|
this.tag.style.left = `${offsetLeft}px`;
|
102
|
-
this.tag.style.display = '
|
100
|
+
this.tag.style.display = '';
|
103
101
|
}
|
104
102
|
|
105
103
|
scheduleUpdate() {
|
@@ -113,21 +111,18 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
|
|
113
111
|
),
|
114
112
|
|
115
113
|
EditorView.theme({
|
116
|
-
'.cm-
|
114
|
+
'.cm-floating-menu-trigger': {
|
117
115
|
position: 'fixed',
|
118
116
|
padding: '0',
|
119
117
|
border: 'none',
|
120
118
|
opacity: '0',
|
121
|
-
},
|
122
|
-
'[data-has-focus] & .cm-ref-tag': {
|
123
|
-
opacity: '1',
|
124
|
-
},
|
125
|
-
'.cm-ref-tag button': {
|
126
119
|
display: 'grid',
|
127
|
-
|
128
|
-
justifyContent: 'center',
|
120
|
+
placeContent: 'center',
|
129
121
|
width: '2rem',
|
130
122
|
height: '2rem',
|
131
123
|
},
|
124
|
+
'&:focus-within .cm-floating-menu-trigger': {
|
125
|
+
opacity: '1',
|
126
|
+
},
|
132
127
|
}),
|
133
128
|
];
|
@@ -5,7 +5,7 @@
|
|
5
5
|
import { type EditorView } from '@codemirror/view';
|
6
6
|
import { type RefObject, useCallback, useMemo, useRef, useState } from 'react';
|
7
7
|
|
8
|
-
import { type
|
8
|
+
import { type DxAnchorActivate } from '@dxos/react-ui';
|
9
9
|
import { type MaybePromise } from '@dxos/util';
|
10
10
|
|
11
11
|
import { type CommandMenuGroup, type CommandMenuItem, getItem, getNextItem, getPreviousItem } from '../../components';
|
@@ -21,7 +21,6 @@ export type UseCommandMenuOptions = {
|
|
21
21
|
};
|
22
22
|
|
23
23
|
export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCommandMenuOptions) => {
|
24
|
-
const triggerRef = useRef<DxAnchor | null>(null);
|
25
24
|
const currentRef = useRef<CommandMenuItem | null>(null);
|
26
25
|
const groupsRef = useRef<CommandMenuGroup[]>([]);
|
27
26
|
const [currentItem, setCurrentItem] = useState<string>();
|
@@ -35,7 +34,6 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
|
|
35
34
|
}
|
36
35
|
setOpen(open);
|
37
36
|
if (!open) {
|
38
|
-
triggerRef.current = null;
|
39
37
|
setCurrentItem(undefined);
|
40
38
|
viewRef.current?.dispatch({ effects: [commandRangeEffect.of(null)] });
|
41
39
|
}
|
@@ -50,7 +48,6 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
|
|
50
48
|
currentRef.current = item;
|
51
49
|
}
|
52
50
|
|
53
|
-
triggerRef.current = event.trigger;
|
54
51
|
const triggerKey = event.trigger.getAttribute('data-trigger');
|
55
52
|
if (!open && triggerKey) {
|
56
53
|
await handleOpenChange(true, triggerKey);
|
@@ -70,7 +67,7 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
|
|
70
67
|
}, []);
|
71
68
|
|
72
69
|
const serializedTrigger = Array.isArray(trigger) ? trigger.join(',') : trigger;
|
73
|
-
const
|
70
|
+
const memoizedCommandMenu = useMemo(() => {
|
74
71
|
return commandMenu({
|
75
72
|
trigger,
|
76
73
|
placeholder,
|
@@ -107,10 +104,9 @@ export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCo
|
|
107
104
|
}, [handleOpenChange, getMenu, serializedTrigger, placeholder]);
|
108
105
|
|
109
106
|
return {
|
110
|
-
commandMenu:
|
107
|
+
commandMenu: memoizedCommandMenu,
|
111
108
|
currentItem,
|
112
109
|
groupsRef,
|
113
|
-
ref: triggerRef,
|
114
110
|
open,
|
115
111
|
onActivate: handleActivate,
|
116
112
|
onOpenChange: setOpen,
|
@@ -159,7 +159,7 @@ const decorations = () => [
|
|
159
159
|
'.cm-list-item-focused': {
|
160
160
|
borderColor: 'var(--dx-accentFocusIndicator)',
|
161
161
|
},
|
162
|
-
'
|
162
|
+
'&:focus-within .cm-list-item-selected': {
|
163
163
|
borderColor: 'var(--dx-separator)',
|
164
164
|
},
|
165
165
|
}),
|
@@ -22,9 +22,6 @@ export type PreviewLinkTarget = {
|
|
22
22
|
object?: any;
|
23
23
|
};
|
24
24
|
|
25
|
-
// TODO(wittjosiah): Remove.
|
26
|
-
export type PreviewLookup = (link: PreviewLinkRef) => Promise<PreviewLinkTarget | null | undefined>;
|
27
|
-
|
28
25
|
export type PreviewOptions = {
|
29
26
|
addBlockContainer?: (link: PreviewLinkRef, el: HTMLElement) => void;
|
30
27
|
removeBlockContainer?: (link: PreviewLinkRef) => void;
|
@@ -96,18 +96,6 @@ export const useTextEditor = (
|
|
96
96
|
EditorView.exceptionSink.of((err) => {
|
97
97
|
log.catch(err);
|
98
98
|
}),
|
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
99
|
].filter(isNotFalsy),
|
112
100
|
});
|
113
101
|
|
@@ -15,10 +15,9 @@ import { Testing, type ValueGenerator, createObjectFactory } from '@dxos/schema/
|
|
15
15
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
16
16
|
|
17
17
|
import {
|
18
|
-
CommandMenu,
|
19
18
|
type CommandMenuGroup,
|
20
19
|
type CommandMenuItem,
|
21
|
-
|
20
|
+
CommandMenuProvider,
|
22
21
|
coreSlashCommands,
|
23
22
|
filterItems,
|
24
23
|
insertAtCursor,
|
@@ -37,13 +36,12 @@ type StoryProps = Omit<UseCommandMenuOptions, 'viewRef'> & { text: string };
|
|
37
36
|
|
38
37
|
const DefaultStory = ({ text, ...options }: StoryProps) => {
|
39
38
|
const viewRef = useRef<EditorView>();
|
40
|
-
const { commandMenu, groupsRef,
|
39
|
+
const { commandMenu, groupsRef, ...commandMenuProps } = useCommandMenu({ viewRef, ...options });
|
41
40
|
|
42
41
|
return (
|
43
|
-
<
|
42
|
+
<CommandMenuProvider groups={groupsRef.current} {...commandMenuProps}>
|
44
43
|
<EditorStory ref={viewRef} text={text} placeholder={''} extensions={commandMenu} />
|
45
|
-
|
46
|
-
</RefPopover>
|
44
|
+
</CommandMenuProvider>
|
47
45
|
);
|
48
46
|
};
|
49
47
|
|
@@ -123,7 +121,7 @@ export const Link: Story = {
|
|
123
121
|
label: object.name,
|
124
122
|
icon: 'ph--user--regular',
|
125
123
|
onSelect: (view, head) => {
|
126
|
-
const link = `[${object.name}]
|
124
|
+
const link = `[${object.name}](${Obj.getDXN(object)})`;
|
127
125
|
if (query?.startsWith('@')) {
|
128
126
|
insertAtLineStart(view, head, `!${link}\n`);
|
129
127
|
} else {
|
@@ -6,13 +6,12 @@ import '@dxos-theme';
|
|
6
6
|
|
7
7
|
import { type EditorView } from '@codemirror/view';
|
8
8
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
9
|
-
import React, { useRef } from 'react';
|
9
|
+
import React, { useMemo, useRef } from 'react';
|
10
10
|
|
11
|
-
import { DropdownMenu } from '@dxos/react-ui';
|
12
11
|
import { withAttention } from '@dxos/react-ui-attention/testing';
|
13
12
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
14
13
|
|
15
|
-
import {
|
14
|
+
import { type CommandMenuGroup, type CommandMenuItem, CommandMenuProvider } from '../components';
|
16
15
|
import { deleteItem, hashtag, listItemToString, outliner, treeFacet } from '../extensions';
|
17
16
|
import { str } from '../testing';
|
18
17
|
|
@@ -25,14 +24,33 @@ type StoryProps = {
|
|
25
24
|
const DefaultStory = ({ text }: StoryProps) => {
|
26
25
|
const viewRef = useRef<EditorView>(null);
|
27
26
|
|
28
|
-
const
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
const commandGroups: CommandMenuGroup[] = useMemo(
|
28
|
+
() => [
|
29
|
+
{
|
30
|
+
id: 'outliner-actions',
|
31
|
+
items: [
|
32
|
+
{
|
33
|
+
id: 'delete-row',
|
34
|
+
label: 'Delete',
|
35
|
+
onSelect: (view: EditorView) => {
|
36
|
+
deleteItem(view);
|
37
|
+
},
|
38
|
+
},
|
39
|
+
],
|
40
|
+
},
|
41
|
+
],
|
42
|
+
[],
|
43
|
+
);
|
33
44
|
|
34
45
|
return (
|
35
|
-
<
|
46
|
+
<CommandMenuProvider
|
47
|
+
groups={commandGroups}
|
48
|
+
onSelect={(item: CommandMenuItem) => {
|
49
|
+
if (viewRef.current && item.onSelect) {
|
50
|
+
return item.onSelect(viewRef.current, viewRef.current.state.selection.main.head);
|
51
|
+
}
|
52
|
+
}}
|
53
|
+
>
|
36
54
|
<EditorStory
|
37
55
|
ref={viewRef}
|
38
56
|
text={text}
|
@@ -46,16 +64,7 @@ const DefaultStory = ({ text }: StoryProps) => {
|
|
46
64
|
return <pre className='p-1 overflow-auto text-xs text-green-800 dark:text-green-200'>{lines.join('\n')}</pre>;
|
47
65
|
}}
|
48
66
|
/>
|
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>
|
67
|
+
</CommandMenuProvider>
|
59
68
|
);
|
60
69
|
};
|
61
70
|
|
@@ -18,8 +18,8 @@ import { hoverableControlItem, hoverableControlItemTransition, hoverableControls
|
|
18
18
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
19
19
|
import { trim } from '@dxos/util';
|
20
20
|
|
21
|
-
import { PreviewProvider, useRefPopover } from '../components';
|
22
21
|
import { type PreviewLinkRef, type PreviewLinkTarget, getLinkRef, image, preview } from '../extensions';
|
22
|
+
import { PreviewPopoverProvider, usePreviewPopover } from '../testing';
|
23
23
|
|
24
24
|
import { EditorStory } from './components';
|
25
25
|
|
@@ -45,7 +45,7 @@ const useRefTarget = (link: PreviewLinkRef): PreviewLinkTarget | undefined => {
|
|
45
45
|
};
|
46
46
|
|
47
47
|
const PreviewCard = () => {
|
48
|
-
const { target } =
|
48
|
+
const { target } = usePreviewPopover('PreviewCard');
|
49
49
|
return (
|
50
50
|
<Popover.Portal>
|
51
51
|
<Popover.Content onOpenAutoFocus={(event) => event.preventDefault()}>
|
@@ -200,7 +200,7 @@ export const Default: Story = {
|
|
200
200
|
}, []);
|
201
201
|
|
202
202
|
return (
|
203
|
-
<
|
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
|
-
</
|
226
|
+
</PreviewPopoverProvider>
|
227
227
|
);
|
228
228
|
},
|
229
229
|
};
|