@dxos/plugin-deck 0.8.2-main.f081794 → 0.8.2-main.fbd8ed0
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/{app-graph-builder-VYZ4IWI3.mjs → app-graph-builder-R7COZ4A6.mjs} +16 -15
- package/dist/lib/browser/app-graph-builder-R7COZ4A6.mjs.map +7 -0
- package/dist/lib/browser/{check-app-scheme-SEYECDHI.mjs → check-app-scheme-7AXGR6UT.mjs} +2 -3
- package/dist/lib/browser/check-app-scheme-7AXGR6UT.mjs.map +7 -0
- package/dist/lib/browser/chunk-3O2UZVBA.mjs +121 -0
- package/dist/lib/browser/chunk-3O2UZVBA.mjs.map +7 -0
- package/dist/lib/browser/chunk-JAYQ5BTF.mjs +157 -0
- package/dist/lib/browser/chunk-JAYQ5BTF.mjs.map +7 -0
- package/dist/lib/browser/{chunk-VP6FCWFV.mjs → chunk-KIGMELV2.mjs} +257 -247
- package/dist/lib/browser/chunk-KIGMELV2.mjs.map +7 -0
- package/dist/lib/browser/{state-Z6UY2Z3M.mjs → chunk-OF5RIATN.mjs} +6 -4
- package/dist/lib/browser/chunk-OF5RIATN.mjs.map +7 -0
- package/dist/lib/browser/chunk-TRFYUEBA.mjs +145 -0
- package/dist/lib/browser/chunk-TRFYUEBA.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +4 -5
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/{intent-resolver-6AK45PT5.mjs → intent-resolver-MAKOS57L.mjs} +86 -63
- package/dist/lib/browser/intent-resolver-MAKOS57L.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-root-KA2IL5RA.mjs → react-root-DGQVIHXP.mjs} +6 -6
- package/dist/lib/browser/{react-surface-LIPGYEYN.mjs → react-surface-PXBXIOPU.mjs} +6 -6
- package/dist/lib/browser/{settings-6NU7CF2B.mjs → settings-UBWJF7J7.mjs} +4 -4
- package/dist/lib/browser/{settings-6NU7CF2B.mjs.map → settings-UBWJF7J7.mjs.map} +3 -3
- package/dist/lib/browser/state-4WFB4SDO.mjs +10 -0
- package/dist/lib/browser/state-4WFB4SDO.mjs.map +7 -0
- package/dist/lib/browser/{tools-VDVQTJMD.mjs → tools-IVPIPTVA.mjs} +7 -7
- package/dist/lib/browser/tools-IVPIPTVA.mjs.map +7 -0
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/browser/{url-handler-3CARFXQK.mjs → url-handler-JSYGSVSB.mjs} +4 -4
- package/dist/lib/browser/url-handler-JSYGSVSB.mjs.map +7 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts +2 -179
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/check-app-scheme.d.ts +2 -2
- package/dist/types/src/capabilities/check-app-scheme.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +5 -180
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/state.d.ts +2 -2
- package/dist/types/src/capabilities/state.d.ts.map +1 -1
- package/dist/types/src/capabilities/tools.d.ts.map +1 -1
- package/dist/types/src/capabilities/url-handler.d.ts +2 -2
- package/dist/types/src/capabilities/url-handler.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Popover.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
- package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -1
- package/dist/types/src/components/Plank/Plank.d.ts +18 -5
- package/dist/types/src/components/Plank/Plank.d.ts.map +1 -1
- package/dist/types/src/components/Plank/Plank.stories.d.ts +3 -3
- package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -1
- package/dist/types/src/components/Plank/PlankControls.d.ts +1 -0
- package/dist/types/src/components/Plank/PlankControls.d.ts.map +1 -1
- package/dist/types/src/components/Plank/PlankError.d.ts.map +1 -1
- package/dist/types/src/components/Plank/PlankHeading.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/Sidebar.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/SidebarButton.d.ts +2 -1
- package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +5 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useBreakpoints.d.ts.map +1 -0
- package/dist/types/src/hooks/useCompanions.d.ts.map +1 -0
- package/dist/types/src/hooks/useDeckCompanions.d.ts +13 -0
- package/dist/types/src/hooks/useDeckCompanions.d.ts.map +1 -0
- package/dist/types/src/hooks/useHoistStatusbar.d.ts.map +1 -0
- package/dist/types/src/hooks/useNodeActionExpander.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/layout.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +104 -104
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -4
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/layoutAppliesTopbar.d.ts.map +1 -1
- package/dist/types/src/util/overscroll.d.ts.map +1 -1
- package/dist/types/src/util/set-active.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +36 -29
- package/src/capabilities/app-graph-builder.ts +120 -92
- package/src/capabilities/check-app-scheme.ts +3 -7
- package/src/capabilities/index.ts +1 -0
- package/src/capabilities/intent-resolver.ts +140 -114
- package/src/capabilities/settings.ts +2 -2
- package/src/capabilities/state.ts +4 -2
- package/src/capabilities/tools.ts +4 -3
- package/src/capabilities/url-handler.ts +4 -4
- package/src/components/DeckLayout/ContentEmpty.tsx +3 -2
- package/src/components/DeckLayout/DeckLayout.tsx +12 -10
- package/src/components/DeckLayout/Popover.tsx +71 -43
- package/src/components/Plank/Plank.stories.tsx +20 -8
- package/src/components/Plank/Plank.tsx +101 -66
- package/src/components/Plank/PlankControls.tsx +26 -30
- package/src/components/Plank/PlankError.tsx +2 -6
- package/src/components/Plank/PlankHeading.tsx +23 -8
- package/src/components/Sidebar/ComplementarySidebar.tsx +4 -35
- package/src/components/Sidebar/Sidebar.tsx +2 -1
- package/src/components/Sidebar/SidebarButton.tsx +30 -7
- package/src/components/fragments.ts +1 -1
- package/src/hooks/index.ts +5 -1
- package/src/{util → hooks}/useCompanions.ts +3 -3
- package/src/hooks/useDeckCompanions.ts +33 -0
- package/src/hooks/useNodeActionExpander.ts +3 -8
- package/src/index.ts +1 -1
- package/src/types.ts +72 -71
- package/src/util/index.ts +1 -4
- package/dist/lib/browser/app-graph-builder-VYZ4IWI3.mjs.map +0 -7
- package/dist/lib/browser/check-app-scheme-SEYECDHI.mjs.map +0 -7
- package/dist/lib/browser/chunk-4QSEGMY3.mjs +0 -24
- package/dist/lib/browser/chunk-4QSEGMY3.mjs.map +0 -7
- package/dist/lib/browser/chunk-6HJZL3WT.mjs +0 -118
- package/dist/lib/browser/chunk-6HJZL3WT.mjs.map +0 -7
- package/dist/lib/browser/chunk-FLOVGNYB.mjs +0 -81
- package/dist/lib/browser/chunk-FLOVGNYB.mjs.map +0 -7
- package/dist/lib/browser/chunk-VP6FCWFV.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZMJMCN7O.mjs +0 -157
- package/dist/lib/browser/chunk-ZMJMCN7O.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-6AK45PT5.mjs.map +0 -7
- package/dist/lib/browser/state-Z6UY2Z3M.mjs.map +0 -7
- package/dist/lib/browser/tools-VDVQTJMD.mjs.map +0 -7
- package/dist/lib/browser/url-handler-3CARFXQK.mjs.map +0 -7
- package/dist/types/src/util/useBreakpoints.d.ts.map +0 -1
- package/dist/types/src/util/useCompanions.d.ts.map +0 -1
- package/dist/types/src/util/useHoistStatusbar.d.ts.map +0 -1
- /package/dist/lib/browser/{react-root-KA2IL5RA.mjs.map → react-root-DGQVIHXP.mjs.map} +0 -0
- /package/dist/lib/browser/{react-surface-LIPGYEYN.mjs.map → react-surface-PXBXIOPU.mjs.map} +0 -0
- /package/dist/types/src/{util → hooks}/useBreakpoints.d.ts +0 -0
- /package/dist/types/src/{util → hooks}/useCompanions.d.ts +0 -0
- /package/dist/types/src/{util → hooks}/useHoistStatusbar.d.ts +0 -0
- /package/src/{util → hooks}/useBreakpoints.ts +0 -0
- /package/src/{util → hooks}/useHoistStatusbar.ts +0 -0
|
@@ -2,72 +2,100 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import React, { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
7
|
|
|
7
8
|
import { Surface, useCapability } from '@dxos/app-framework';
|
|
8
|
-
import { Popover } from '@dxos/react-ui';
|
|
9
|
+
import { Popover, type PopoverContentInteractOutsideEvent } from '@dxos/react-ui';
|
|
9
10
|
|
|
10
11
|
import { DeckCapabilities } from '../../capabilities';
|
|
11
12
|
|
|
12
13
|
export type DeckPopoverRootProps = PropsWithChildren<{}>;
|
|
13
14
|
|
|
15
|
+
const DEBOUNCE_DELAY = 40;
|
|
16
|
+
|
|
17
|
+
type DeckPopoverContextValue = {
|
|
18
|
+
setOpen: (open: boolean) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const [DeckPopoverProvider, useDeckPopoverContext] = createContext<DeckPopoverContextValue>('DeckPopover');
|
|
22
|
+
|
|
14
23
|
export const PopoverRoot = ({ children }: DeckPopoverRootProps) => {
|
|
15
|
-
const
|
|
24
|
+
const layout = useCapability(DeckCapabilities.MutableDeckState);
|
|
16
25
|
const virtualRef = useRef<HTMLButtonElement | null>(null);
|
|
17
26
|
const [virtualIter, setVirtualIter] = useState(0);
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
|
18
29
|
|
|
19
|
-
// TODO(thure): This is a workaround for the
|
|
20
|
-
// the anchor further down the tree
|
|
21
|
-
const [delayedPopoverVisibility, setDelayedPopoverVisibility] = useState(false);
|
|
30
|
+
// TODO(thure): This is a workaround for the race condition between displaying a Popover and either rendering
|
|
31
|
+
// the anchor further down the tree or measuring the virtual trigger’s client rect.
|
|
22
32
|
useEffect(() => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
(nextOpen: boolean) => {
|
|
28
|
-
if (nextOpen && (context.popoverAnchor || context.popoverAnchorId)) {
|
|
29
|
-
context.popoverOpen = true;
|
|
30
|
-
} else {
|
|
31
|
-
context.popoverOpen = false;
|
|
32
|
-
context.popoverAnchor = undefined;
|
|
33
|
-
context.popoverAnchorId = undefined;
|
|
34
|
-
context.popoverSide = undefined;
|
|
33
|
+
setOpen(false);
|
|
34
|
+
if (layout.popoverOpen) {
|
|
35
|
+
if (debounceRef.current) {
|
|
36
|
+
clearTimeout(debounceRef.current);
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}, [context.popoverAnchor]);
|
|
38
|
+
if (layout.popoverAnchor && virtualRef.current !== layout.popoverAnchor) {
|
|
39
|
+
virtualRef.current = layout.popoverAnchor ?? null;
|
|
40
|
+
setVirtualIter((iter) => iter + 1);
|
|
41
|
+
}
|
|
42
|
+
debounceRef.current = setTimeout(() => setOpen(true), DEBOUNCE_DELAY);
|
|
43
|
+
}
|
|
44
|
+
}, [layout.popoverOpen, layout.popoverAnchorId, layout.popoverAnchor, layout.popoverContent]);
|
|
44
45
|
|
|
45
46
|
return (
|
|
46
|
-
<
|
|
47
|
-
modal
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
{children}
|
|
53
|
-
</Popover.Root>
|
|
47
|
+
<DeckPopoverProvider setOpen={setOpen}>
|
|
48
|
+
<Popover.Root modal={false} open={open}>
|
|
49
|
+
{layout.popoverAnchor && <Popover.VirtualTrigger key={virtualIter} virtualRef={virtualRef} />}
|
|
50
|
+
{children}
|
|
51
|
+
</Popover.Root>
|
|
52
|
+
</DeckPopoverProvider>
|
|
54
53
|
);
|
|
55
54
|
};
|
|
56
55
|
|
|
57
56
|
export const PopoverContent = () => {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
const layout = useCapability(DeckCapabilities.MutableDeckState);
|
|
58
|
+
const { setOpen } = useDeckPopoverContext('PopoverContent');
|
|
59
|
+
|
|
60
|
+
const handleClose = useCallback(
|
|
61
|
+
(event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
|
|
62
|
+
if (
|
|
63
|
+
// TODO(thure): CodeMirror should not focus itself when it updates.
|
|
64
|
+
event.type === 'dismissableLayer.focusOutside' &&
|
|
65
|
+
(event.currentTarget as HTMLElement | undefined)?.classList.contains('cm-content')
|
|
66
|
+
) {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
} else {
|
|
69
|
+
setOpen(false);
|
|
70
|
+
layout.popoverOpen = false;
|
|
71
|
+
layout.popoverAnchor = undefined;
|
|
72
|
+
layout.popoverAnchorId = undefined;
|
|
73
|
+
layout.popoverSide = undefined;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[setOpen],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const collisionBoundaries: HTMLElement[] = useMemo(() => {
|
|
80
|
+
const closest = layout.popoverAnchor?.closest('[data-popover-collision-boundary]') as
|
|
81
|
+
| HTMLElement
|
|
82
|
+
| null
|
|
83
|
+
| undefined;
|
|
84
|
+
return closest ? [closest] : [];
|
|
85
|
+
}, [layout.popoverAnchor]);
|
|
65
86
|
|
|
66
87
|
return (
|
|
67
88
|
<Popover.Portal>
|
|
68
|
-
<Popover.Content
|
|
89
|
+
<Popover.Content
|
|
90
|
+
side={layout.popoverSide}
|
|
91
|
+
onInteractOutside={handleClose}
|
|
92
|
+
onEscapeKeyDown={handleClose}
|
|
93
|
+
collisionBoundary={collisionBoundaries}
|
|
94
|
+
sticky='always'
|
|
95
|
+
hideWhenDetached
|
|
96
|
+
>
|
|
69
97
|
<Popover.Viewport>
|
|
70
|
-
<Surface role='popover' data={
|
|
98
|
+
<Surface role='popover' data={layout.popoverContent} limit={1} />
|
|
71
99
|
</Popover.Viewport>
|
|
72
100
|
<Popover.Arrow />
|
|
73
101
|
</Popover.Content>
|
|
@@ -5,39 +5,51 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
+
import React from 'react';
|
|
8
9
|
|
|
9
|
-
import { IntentPlugin } from '@dxos/app-framework';
|
|
10
|
+
import { IntentPlugin, SettingsPlugin } from '@dxos/app-framework';
|
|
10
11
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
12
|
+
import { AttentionPlugin } from '@dxos/plugin-attention';
|
|
11
13
|
import { GraphPlugin } from '@dxos/plugin-graph';
|
|
14
|
+
import { Stack } from '@dxos/react-ui-stack';
|
|
12
15
|
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
13
16
|
|
|
14
|
-
import { Plank } from './Plank';
|
|
17
|
+
import { Plank, type PlankProps } from './Plank';
|
|
18
|
+
import DeckStateFactory from '../../capabilities/state';
|
|
15
19
|
import translations from '../../translations';
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
const meta: Meta<typeof Plank> = {
|
|
21
|
+
const meta: Meta<PlankProps> = {
|
|
19
22
|
title: 'plugins/plugin-deck/Plank',
|
|
20
23
|
component: Plank,
|
|
24
|
+
render: (args) => {
|
|
25
|
+
return (
|
|
26
|
+
<Stack orientation='horizontal'>
|
|
27
|
+
<Plank {...args} />
|
|
28
|
+
</Stack>
|
|
29
|
+
);
|
|
30
|
+
},
|
|
21
31
|
decorators: [
|
|
22
32
|
withPluginManager({
|
|
23
|
-
plugins: [IntentPlugin(), GraphPlugin()],
|
|
33
|
+
plugins: [AttentionPlugin(), SettingsPlugin(), IntentPlugin(), GraphPlugin()],
|
|
34
|
+
capabilities: () => DeckStateFactory(),
|
|
24
35
|
}),
|
|
25
36
|
withTheme,
|
|
26
|
-
withLayout({ fullscreen: true
|
|
37
|
+
withLayout({ fullscreen: true }),
|
|
27
38
|
],
|
|
28
39
|
parameters: {
|
|
29
|
-
layout: 'centered',
|
|
30
40
|
translations,
|
|
31
41
|
},
|
|
32
42
|
};
|
|
33
43
|
|
|
34
44
|
export default meta;
|
|
35
45
|
|
|
36
|
-
type Story = StoryObj<
|
|
46
|
+
type Story = StoryObj<PlankProps>;
|
|
37
47
|
|
|
48
|
+
// TODO(burdon): Need to define surface provider?
|
|
38
49
|
export const Default: Story = {
|
|
39
50
|
args: {
|
|
40
51
|
id: 'plank-1',
|
|
41
52
|
part: 'solo',
|
|
53
|
+
layoutMode: 'deck',
|
|
42
54
|
},
|
|
43
55
|
};
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import React, {
|
|
6
|
-
Fragment,
|
|
7
6
|
type KeyboardEvent,
|
|
8
7
|
type PropsWithChildren,
|
|
9
8
|
memo,
|
|
@@ -31,40 +30,117 @@ import { PlankContentError, PlankError } from './PlankError';
|
|
|
31
30
|
import { PlankHeading } from './PlankHeading';
|
|
32
31
|
import { PlankLoading } from './PlankLoading';
|
|
33
32
|
import { DeckCapabilities } from '../../capabilities';
|
|
34
|
-
import { useMainSize } from '../../hooks';
|
|
33
|
+
import { useMainSize, useCompanions } from '../../hooks';
|
|
35
34
|
import { parseEntryId } from '../../layout';
|
|
36
|
-
import { DeckAction, type LayoutMode, type
|
|
37
|
-
import { useCompanions } from '../../util';
|
|
35
|
+
import { DeckAction, type LayoutMode, type ResolvedPart, type DeckSettingsProps } from '../../types';
|
|
38
36
|
|
|
39
37
|
const UNKNOWN_ID = 'unknown_id';
|
|
40
38
|
|
|
41
|
-
export type PlankProps = {
|
|
39
|
+
export type PlankProps = Pick<PlankComponentProps, 'layoutMode' | 'part' | 'path' | 'order' | 'active' | 'settings'> & {
|
|
42
40
|
id?: string;
|
|
43
41
|
companionId?: string;
|
|
44
|
-
part: Part;
|
|
45
|
-
path?: string[];
|
|
46
|
-
order?: number;
|
|
47
|
-
active?: string[];
|
|
48
|
-
layoutMode: LayoutMode;
|
|
49
|
-
settings?: DeckSettingsProps;
|
|
50
42
|
};
|
|
51
43
|
|
|
52
|
-
|
|
44
|
+
// TODO(burdon): Factor out conditional rendering.
|
|
45
|
+
// Remove this wrapper component and render the entire set of planks in the deck with conditional visibility
|
|
46
|
+
// to obviate mounting and unmounting when switching between solo and companion mode?
|
|
47
|
+
// NOTE(thure, in reply): Whether any surface should be rendered and hidden is a performance matter — remember that
|
|
48
|
+
// article surfaces contain full experiences, so being able to unmount them will yield relatively large performance
|
|
49
|
+
// benefits. I think where we anticipate users will definitely want to quickly switch between showing and hiding entire
|
|
50
|
+
// articles, over the (again probably large) performance benefit that unmounting them would confer, we can mount and
|
|
51
|
+
// hide them, but I think that scenario in its most unambiguous form is probably rare. You could extrapolate
|
|
52
|
+
// the scenario to include all “potential” planks such as companions, which we could keep mounted and hidden, but I
|
|
53
|
+
// don’t think the resulting performance would be acceptable. I think the real issue is “perceived performance” which
|
|
54
|
+
// has mitigations that are in between mounting and un-mounting since both of those have tradeoffs; we may need one or more
|
|
55
|
+
// “partially-mounted” experiences, like loading skeletons at the simple end, or screenshots of “sleeping” planks at
|
|
56
|
+
// the advanced end.
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A Plank is the main container for surfaces within a Deck.
|
|
60
|
+
* It may be paired with a companion plank that enables the user to select one of multiple companion surfaces.
|
|
61
|
+
*/
|
|
62
|
+
export const Plank = memo(({ id = UNKNOWN_ID, companionId, ...props }: PlankProps) => {
|
|
63
|
+
const { graph } = useAppGraph();
|
|
64
|
+
const node = useNode(graph, id);
|
|
65
|
+
const companions = useCompanions(id);
|
|
66
|
+
const currentCompanion = companions.find(({ id }) => id === companionId);
|
|
67
|
+
const hasCompanion = !!(companionId && currentCompanion);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<PlankContainer solo={props.part === 'solo'} companion={hasCompanion}>
|
|
71
|
+
<PlankComponent
|
|
72
|
+
id={id}
|
|
73
|
+
node={node}
|
|
74
|
+
companioned={hasCompanion ? 'primary' : undefined}
|
|
75
|
+
companions={hasCompanion ? [] : companions}
|
|
76
|
+
{...props}
|
|
77
|
+
{...(props.part === 'solo' ? { part: 'solo-primary' } : {})}
|
|
78
|
+
/>
|
|
79
|
+
{hasCompanion && (
|
|
80
|
+
<PlankComponent
|
|
81
|
+
id={companionId}
|
|
82
|
+
node={currentCompanion}
|
|
83
|
+
primary={node}
|
|
84
|
+
companions={companions}
|
|
85
|
+
companioned='companion'
|
|
86
|
+
{...props}
|
|
87
|
+
{...(props.part === 'solo' ? { part: 'solo-companion' } : { order: (props.order ?? 0) + 1 })}
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
</PlankContainer>
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const PlankContainer = ({ children, solo, companion }: PropsWithChildren<{ solo: boolean; companion: boolean }>) => {
|
|
95
|
+
const sizeAttrs = useMainSize();
|
|
96
|
+
if (!solo) {
|
|
97
|
+
return children;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// TODO(burdon): Make resizable.
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
role='none'
|
|
104
|
+
className={mx('absolute inset-0 grid', companion && 'grid-cols-[1fr_1fr]', railGridHorizontal, mainIntrinsicSize)}
|
|
105
|
+
{...sizeAttrs}
|
|
106
|
+
>
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
type PlankComponentProps = {
|
|
113
|
+
layoutMode: LayoutMode;
|
|
53
114
|
id: string;
|
|
54
115
|
part: ResolvedPart;
|
|
55
|
-
|
|
116
|
+
path?: string[];
|
|
117
|
+
order?: number;
|
|
118
|
+
active?: string[];
|
|
119
|
+
// TODO(burdon): Change to role?
|
|
56
120
|
companioned?: 'primary' | 'companion';
|
|
121
|
+
node?: Node;
|
|
57
122
|
primary?: Node;
|
|
58
123
|
companions?: Node[];
|
|
124
|
+
settings?: DeckSettingsProps;
|
|
59
125
|
};
|
|
60
126
|
|
|
61
|
-
const
|
|
62
|
-
({
|
|
127
|
+
const PlankComponent = memo(
|
|
128
|
+
({
|
|
129
|
+
layoutMode,
|
|
130
|
+
id,
|
|
131
|
+
part,
|
|
132
|
+
path,
|
|
133
|
+
order,
|
|
134
|
+
active,
|
|
135
|
+
companioned,
|
|
136
|
+
node,
|
|
137
|
+
primary,
|
|
138
|
+
companions,
|
|
139
|
+
settings,
|
|
140
|
+
}: PlankComponentProps) => {
|
|
63
141
|
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
64
142
|
const { deck, popoverAnchorId, scrollIntoView } = useCapability(DeckCapabilities.DeckState);
|
|
65
|
-
const rootElement = useRef<HTMLDivElement | null>(null);
|
|
66
143
|
const canResize = layoutMode === 'deck';
|
|
67
|
-
const Root = part.startsWith('solo') ? 'article' : StackItem.Root;
|
|
68
144
|
|
|
69
145
|
const attendableAttrs = useAttendableAttributes(primary?.id ?? id);
|
|
70
146
|
const index = active ? active.findIndex((entryId) => entryId === id) : 0;
|
|
@@ -72,10 +148,13 @@ const PlankImpl = memo(
|
|
|
72
148
|
const canIncrementStart = active && index !== undefined && index > 0 && length !== undefined && length > 1;
|
|
73
149
|
const canIncrementEnd = active && index !== undefined && index < length - 1 && length !== undefined;
|
|
74
150
|
|
|
151
|
+
const rootElement = useRef<HTMLDivElement | null>(null);
|
|
152
|
+
|
|
75
153
|
const { variant } = parseEntryId(id);
|
|
76
154
|
const sizeKey = `${id.split('+')[0]}${variant ? `${ATTENDABLE_PATH_SEPARATOR}${variant}` : ''}`;
|
|
77
155
|
const size = deck.plankSizing[sizeKey] as number | undefined;
|
|
78
|
-
|
|
156
|
+
|
|
157
|
+
const handleSizeChange = useCallback(
|
|
79
158
|
debounce((nextSize: number) => {
|
|
80
159
|
return dispatch(createIntent(DeckAction.UpdatePlankSize, { id: sizeKey, size: nextSize }));
|
|
81
160
|
}, 200),
|
|
@@ -116,12 +195,13 @@ const PlankImpl = memo(
|
|
|
116
195
|
path,
|
|
117
196
|
popoverAnchorId,
|
|
118
197
|
},
|
|
119
|
-
[node, node?.data, path, popoverAnchorId, primary?.data],
|
|
198
|
+
[node, node?.data, path, popoverAnchorId, primary?.data, variant],
|
|
120
199
|
);
|
|
121
200
|
|
|
122
201
|
// TODO(wittjosiah): Change prop to accept a component.
|
|
123
202
|
const placeholder = useMemo(() => <PlankLoading />, []);
|
|
124
203
|
|
|
204
|
+
const Root = part.startsWith('solo') ? 'article' : StackItem.Root;
|
|
125
205
|
const className = mx(
|
|
126
206
|
'attention-surface relative',
|
|
127
207
|
isSolo && mainIntrinsicSize,
|
|
@@ -143,7 +223,7 @@ const PlankImpl = memo(
|
|
|
143
223
|
: {
|
|
144
224
|
item: { id },
|
|
145
225
|
size,
|
|
146
|
-
onSizeChange:
|
|
226
|
+
onSizeChange: handleSizeChange,
|
|
147
227
|
classNames: className,
|
|
148
228
|
order,
|
|
149
229
|
role: 'article',
|
|
@@ -178,54 +258,9 @@ const PlankImpl = memo(
|
|
|
178
258
|
) : (
|
|
179
259
|
<PlankError id={id} part={part} />
|
|
180
260
|
)}
|
|
261
|
+
|
|
181
262
|
{canResize && <StackItem.ResizeHandle />}
|
|
182
263
|
</Root>
|
|
183
264
|
);
|
|
184
265
|
},
|
|
185
266
|
);
|
|
186
|
-
|
|
187
|
-
const SplitFrame = ({ children }: PropsWithChildren<{}>) => {
|
|
188
|
-
const sizeAttrs = useMainSize();
|
|
189
|
-
return (
|
|
190
|
-
<div
|
|
191
|
-
role='none'
|
|
192
|
-
className={mx('grid grid-cols-[1fr_1fr] absolute inset-0', railGridHorizontal, mainIntrinsicSize)}
|
|
193
|
-
{...sizeAttrs}
|
|
194
|
-
>
|
|
195
|
-
{children}
|
|
196
|
-
</div>
|
|
197
|
-
);
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
export const Plank = ({ id = UNKNOWN_ID, ...props }: PlankProps) => {
|
|
201
|
-
const { graph } = useAppGraph();
|
|
202
|
-
const node = useNode(graph, id);
|
|
203
|
-
const companions = useCompanions(id);
|
|
204
|
-
const currentCompanion = companions.find(({ id }) => id === props.companionId);
|
|
205
|
-
|
|
206
|
-
if (props.companionId) {
|
|
207
|
-
const Root = props.part === 'solo' ? SplitFrame : Fragment;
|
|
208
|
-
return (
|
|
209
|
-
<Root>
|
|
210
|
-
<PlankImpl
|
|
211
|
-
id={id}
|
|
212
|
-
node={node}
|
|
213
|
-
companioned='primary'
|
|
214
|
-
{...props}
|
|
215
|
-
{...(props.part === 'solo' ? { part: 'solo-primary' } : {})}
|
|
216
|
-
/>
|
|
217
|
-
<PlankImpl
|
|
218
|
-
id={props.companionId}
|
|
219
|
-
node={currentCompanion}
|
|
220
|
-
companioned='companion'
|
|
221
|
-
primary={node}
|
|
222
|
-
companions={companions}
|
|
223
|
-
{...props}
|
|
224
|
-
{...(props.part === 'solo' ? { part: 'solo-companion' } : { order: props.order! + 1 })}
|
|
225
|
-
/>
|
|
226
|
-
</Root>
|
|
227
|
-
);
|
|
228
|
-
} else {
|
|
229
|
-
return <PlankImpl id={id} node={node} companions={companions} {...props} />;
|
|
230
|
-
}
|
|
231
|
-
};
|
|
@@ -6,15 +6,7 @@ import React, { forwardRef, useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
|
|
8
8
|
import { invariant } from '@dxos/invariant';
|
|
9
|
-
import {
|
|
10
|
-
Button,
|
|
11
|
-
ButtonGroup,
|
|
12
|
-
type ButtonGroupProps,
|
|
13
|
-
type ButtonProps,
|
|
14
|
-
Icon,
|
|
15
|
-
Tooltip,
|
|
16
|
-
useTranslation,
|
|
17
|
-
} from '@dxos/react-ui';
|
|
9
|
+
import { ButtonGroup, type ButtonGroupProps, type ButtonProps, IconButton, useTranslation } from '@dxos/react-ui';
|
|
18
10
|
|
|
19
11
|
import { DECK_PLUGIN } from '../../meta';
|
|
20
12
|
import { DeckAction, type LayoutMode } from '../../types';
|
|
@@ -26,6 +18,7 @@ export type PlankCapabilities = {
|
|
|
26
18
|
incrementEnd?: boolean;
|
|
27
19
|
deck?: boolean;
|
|
28
20
|
solo?: boolean;
|
|
21
|
+
fullscreen?: boolean;
|
|
29
22
|
companion?: boolean;
|
|
30
23
|
};
|
|
31
24
|
|
|
@@ -39,22 +32,10 @@ export type PlankControlsProps = Omit<ButtonGroupProps, 'onClick'> & {
|
|
|
39
32
|
};
|
|
40
33
|
|
|
41
34
|
const PlankControl = ({ icon, label, ...props }: Omit<ButtonProps, 'children'> & { label: string; icon: string }) => {
|
|
42
|
-
return
|
|
43
|
-
<Tooltip.Root>
|
|
44
|
-
<Tooltip.Trigger asChild>
|
|
45
|
-
<Button variant='ghost' {...props}>
|
|
46
|
-
<span className='sr-only'>{label}</span>
|
|
47
|
-
<Icon icon={icon} size={5} />
|
|
48
|
-
</Button>
|
|
49
|
-
</Tooltip.Trigger>
|
|
50
|
-
<Tooltip.Portal>
|
|
51
|
-
<Tooltip.Content side='bottom'>{label}</Tooltip.Content>
|
|
52
|
-
</Tooltip.Portal>
|
|
53
|
-
</Tooltip.Root>
|
|
54
|
-
);
|
|
35
|
+
return <IconButton iconOnly label={label} icon={icon} size={5} variant='ghost' tooltipSide='bottom' {...props} />;
|
|
55
36
|
};
|
|
56
37
|
|
|
57
|
-
const plankControlSpacing = 'pli-2
|
|
38
|
+
const plankControlSpacing = 'pli-2';
|
|
58
39
|
|
|
59
40
|
type PlankComplimentControlsProps = {
|
|
60
41
|
primary?: string;
|
|
@@ -73,7 +54,7 @@ export const PlankCompanionControls = forwardRef<HTMLDivElement, PlankCompliment
|
|
|
73
54
|
<PlankControl
|
|
74
55
|
label={t('close companion label')}
|
|
75
56
|
variant='ghost'
|
|
76
|
-
icon='ph--
|
|
57
|
+
icon='ph--x--regular'
|
|
77
58
|
onClick={handleCloseCompanion}
|
|
78
59
|
classNames={plankControlSpacing}
|
|
79
60
|
/>
|
|
@@ -97,8 +78,8 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
|
|
|
97
78
|
const layoutIsAnySolo = !!layoutMode?.startsWith('solo');
|
|
98
79
|
|
|
99
80
|
return (
|
|
100
|
-
<ButtonGroup {...props} classNames={['app-no-drag', classNames]} ref={forwardedRef}>
|
|
101
|
-
{capabilities.deck
|
|
81
|
+
<ButtonGroup {...props} classNames={['app-no-drag !opacity-100', classNames]} ref={forwardedRef}>
|
|
82
|
+
{capabilities.deck ? (
|
|
102
83
|
<>
|
|
103
84
|
{capabilities.solo && (
|
|
104
85
|
<>
|
|
@@ -114,12 +95,18 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
|
|
|
114
95
|
label={t(
|
|
115
96
|
layoutMode === 'solo--fullscreen'
|
|
116
97
|
? 'exit fullscreen label'
|
|
117
|
-
:
|
|
118
|
-
? 'show
|
|
119
|
-
: 'show
|
|
98
|
+
: layoutIsAnySolo
|
|
99
|
+
? 'show deck plank label'
|
|
100
|
+
: 'show solo plank label',
|
|
120
101
|
)}
|
|
121
102
|
classNames={buttonClassNames}
|
|
122
|
-
icon={
|
|
103
|
+
icon={
|
|
104
|
+
layoutMode === 'solo--fullscreen'
|
|
105
|
+
? 'ph--corners-in--regular'
|
|
106
|
+
: layoutIsAnySolo
|
|
107
|
+
? 'ph--arrows-in-line-horizontal--regular'
|
|
108
|
+
: 'ph--arrows-out-line-horizontal--regular'
|
|
109
|
+
}
|
|
123
110
|
onClick={() => onClick?.(layoutMode === 'solo--fullscreen' ? 'solo--fullscreen' : 'solo')}
|
|
124
111
|
/>
|
|
125
112
|
</>
|
|
@@ -144,6 +131,15 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
|
|
|
144
131
|
</>
|
|
145
132
|
)}
|
|
146
133
|
</>
|
|
134
|
+
) : (
|
|
135
|
+
capabilities.fullscreen && (
|
|
136
|
+
<PlankControl
|
|
137
|
+
label={t(layoutMode === 'solo--fullscreen' ? 'exit fullscreen label' : 'show fullscreen plank label')}
|
|
138
|
+
classNames={buttonClassNames}
|
|
139
|
+
icon={layoutMode === 'solo--fullscreen' ? 'ph--corners-in--regular' : 'ph--corners-out--regular'}
|
|
140
|
+
onClick={() => onClick?.('solo--fullscreen')}
|
|
141
|
+
/>
|
|
142
|
+
)
|
|
147
143
|
)}
|
|
148
144
|
|
|
149
145
|
{close && !layoutIsAnySolo && (
|
|
@@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { type Node } from '@dxos/plugin-graph';
|
|
8
8
|
import { useTranslation } from '@dxos/react-ui';
|
|
9
|
-
import {
|
|
9
|
+
import { descriptionMessage, mx } from '@dxos/react-ui-theme';
|
|
10
10
|
|
|
11
11
|
import { PlankHeading, type PlankHeadingProps } from './PlankHeading';
|
|
12
12
|
import { PlankLoading } from './PlankLoading';
|
|
@@ -19,11 +19,7 @@ export const PlankContentError = ({ error }: { error?: Error }) => {
|
|
|
19
19
|
<div role='none' className='overflow-auto p-8 attention-surface grid place-items-center'>
|
|
20
20
|
<p
|
|
21
21
|
role='alert'
|
|
22
|
-
className={mx(
|
|
23
|
-
descriptionText,
|
|
24
|
-
'break-words border border-dashed border-separator rounded-lg p-8',
|
|
25
|
-
errorString.length < 256 && 'text-lg',
|
|
26
|
-
)}
|
|
22
|
+
className={mx(descriptionMessage, 'break-words rounded-lg p-8', errorString.length < 256 && 'text-lg')}
|
|
27
23
|
>
|
|
28
24
|
{error ? errorString : t('error fallback message')}
|
|
29
25
|
</p>
|
|
@@ -9,14 +9,17 @@ import { type Node } from '@dxos/plugin-graph';
|
|
|
9
9
|
import { Icon, IconButton, Popover, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { StackItem, type StackItemSigilAction } from '@dxos/react-ui-stack';
|
|
11
11
|
import { TextTooltip } from '@dxos/react-ui-text-tooltip';
|
|
12
|
+
import { hoverableControls, hoverableFocusedWithinControls } from '@dxos/react-ui-theme';
|
|
12
13
|
|
|
13
14
|
import { PlankCompanionControls, PlankControls } from './PlankControls';
|
|
15
|
+
import { useBreakpoints } from '../../hooks';
|
|
14
16
|
import { parseEntryId } from '../../layout';
|
|
15
17
|
import { DECK_PLUGIN } from '../../meta';
|
|
16
18
|
import { PLANK_COMPANION_TYPE, DeckAction, type ResolvedPart, type LayoutMode } from '../../types';
|
|
17
|
-
import { useBreakpoints } from '../../util';
|
|
18
19
|
import { soloInlinePadding } from '../fragments';
|
|
19
20
|
|
|
21
|
+
const MAX_COMPANIONS = 5;
|
|
22
|
+
|
|
20
23
|
export type PlankHeadingProps = {
|
|
21
24
|
id: string;
|
|
22
25
|
part: ResolvedPart;
|
|
@@ -63,7 +66,7 @@ export const PlankHeading = memo(
|
|
|
63
66
|
useEffect(() => {
|
|
64
67
|
const frame = requestAnimationFrame(() => {
|
|
65
68
|
// Load actions for the node.
|
|
66
|
-
node && graph.
|
|
69
|
+
node && graph.expand(node.id);
|
|
67
70
|
});
|
|
68
71
|
|
|
69
72
|
return () => cancelAnimationFrame(frame);
|
|
@@ -76,6 +79,7 @@ export const PlankHeading = memo(
|
|
|
76
79
|
solo: breakpoint !== 'mobile' && (part === 'solo' || part === 'deck'),
|
|
77
80
|
incrementStart: canIncrementStart,
|
|
78
81
|
incrementEnd: canIncrementEnd,
|
|
82
|
+
fullscreen: !isCompanionNode,
|
|
79
83
|
companion: !isCompanionNode && companions && companions.length > 0,
|
|
80
84
|
}),
|
|
81
85
|
[breakpoint, part, companions, canIncrementStart, canIncrementEnd, isCompanionNode, deckEnabled],
|
|
@@ -88,13 +92,16 @@ export const PlankHeading = memo(
|
|
|
88
92
|
} else if (variant) {
|
|
89
93
|
return [];
|
|
90
94
|
} else {
|
|
91
|
-
return [actions, graph.
|
|
95
|
+
return [actions, graph.getActions(node.id)].filter((a) => a.length > 0);
|
|
92
96
|
}
|
|
93
97
|
}, [actions, node, variant, graph]);
|
|
94
98
|
|
|
95
|
-
const handleAction = useCallback(
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
const handleAction = useCallback(
|
|
100
|
+
(action: StackItemSigilAction) => {
|
|
101
|
+
typeof action.data === 'function' && action.data?.({ parent: node, caller: DECK_PLUGIN });
|
|
102
|
+
},
|
|
103
|
+
[node],
|
|
104
|
+
);
|
|
98
105
|
|
|
99
106
|
const handlePlankAction = useCallback(
|
|
100
107
|
(eventType: DeckAction.PartAdjustment) => {
|
|
@@ -143,16 +150,24 @@ export const PlankHeading = memo(
|
|
|
143
150
|
classNames={[
|
|
144
151
|
'plb-1 border-be border-separator items-stretch gap-1 sticky inline-start-12 app-drag min-is-0 contain-layout',
|
|
145
152
|
part === 'solo' ? soloInlinePadding : 'pli-1',
|
|
153
|
+
...(layoutMode === 'solo--fullscreen'
|
|
154
|
+
? [
|
|
155
|
+
hoverableControls,
|
|
156
|
+
hoverableFocusedWithinControls,
|
|
157
|
+
'[&>*]:transition-opacity [&>*]:opacity-[--controls-opacity] bg-transparent border-transparent transition-[background-color,border-color] hover-hover:hover:bg-headerSurface focus-within:bg-headerSurface hover-hover:hover:border-separator focus-within:border-separator',
|
|
158
|
+
]
|
|
159
|
+
: []),
|
|
146
160
|
]}
|
|
161
|
+
data-plank-heading
|
|
147
162
|
>
|
|
148
|
-
{companions && isCompanionNode ? (
|
|
163
|
+
{companions && isCompanionNode /* TODO(thure): This is a tablist, it should be implemented as such. */ ? (
|
|
149
164
|
<div role='none' className='flex-1 min-is-0 overflow-x-auto scrollbar-thin flex gap-1'>
|
|
150
165
|
{companions.map(({ id, properties: { icon, label } }) => (
|
|
151
166
|
<IconButton
|
|
152
167
|
key={id}
|
|
153
168
|
data-id={id}
|
|
154
169
|
icon={icon}
|
|
155
|
-
iconOnly={node?.id !== id}
|
|
170
|
+
iconOnly={companions.length > MAX_COMPANIONS && node?.id !== id}
|
|
156
171
|
label={toLocalizedString(label, t)}
|
|
157
172
|
size={5}
|
|
158
173
|
variant={node?.id === id ? 'primary' : 'default'}
|