@dxos/plugin-deck 0.8.1-staging.391c573 → 0.8.1-staging.9eaf14f

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 (176) hide show
  1. package/dist/lib/browser/{app-graph-builder-IYHAGFA3.mjs → app-graph-builder-VYZ4IWI3.mjs} +3 -3
  2. package/dist/lib/browser/{check-app-scheme-S3EYUPMF.mjs → check-app-scheme-SEYECDHI.mjs} +2 -2
  3. package/dist/lib/browser/{chunk-YCKJNTKG.mjs → chunk-6ZSOFCPP.mjs} +26 -6
  4. package/dist/lib/browser/chunk-6ZSOFCPP.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-B4LOJUWW.mjs +24 -0
  6. package/dist/lib/browser/{chunk-Z23S33X6.mjs → chunk-FJBMNSUC.mjs} +638 -483
  7. package/dist/lib/browser/chunk-FJBMNSUC.mjs.map +7 -0
  8. package/dist/lib/browser/chunk-FLOVGNYB.mjs +81 -0
  9. package/dist/lib/browser/chunk-FLOVGNYB.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-N7TEPFVR.mjs → chunk-NSATFAEE.mjs} +3 -3
  11. package/dist/lib/browser/{chunk-N7TEPFVR.mjs.map → chunk-NSATFAEE.mjs.map} +2 -2
  12. package/dist/lib/browser/{chunk-FYKBOM3C.mjs → chunk-RJNCG4ND.mjs} +66 -40
  13. package/dist/lib/browser/chunk-RJNCG4ND.mjs.map +7 -0
  14. package/dist/lib/browser/{chunk-22AQ5IVX.mjs → chunk-XMCG42ID.mjs} +2 -3
  15. package/dist/lib/browser/chunk-XMCG42ID.mjs.map +7 -0
  16. package/dist/lib/browser/index.mjs +14 -9
  17. package/dist/lib/browser/index.mjs.map +3 -3
  18. package/dist/lib/browser/{intent-resolver-P5BVUQKU.mjs → intent-resolver-UDYKO2QW.mjs} +78 -88
  19. package/dist/lib/browser/intent-resolver-UDYKO2QW.mjs.map +7 -0
  20. package/dist/lib/browser/meta.json +1 -1
  21. package/dist/lib/browser/{react-root-EP4UF3KA.mjs → react-root-XLXN2VEW.mjs} +8 -10
  22. package/dist/lib/browser/react-root-XLXN2VEW.mjs.map +7 -0
  23. package/dist/lib/browser/{react-surface-5B3RLJCD.mjs → react-surface-WNGMZL7I.mjs} +11 -10
  24. package/dist/lib/browser/react-surface-WNGMZL7I.mjs.map +7 -0
  25. package/dist/lib/browser/{settings-X3P2HKQJ.mjs → settings-HMDGSBGO.mjs} +5 -4
  26. package/dist/lib/browser/settings-HMDGSBGO.mjs.map +7 -0
  27. package/dist/lib/browser/{state-2MOTLKVR.mjs → state-7TN26M42.mjs} +7 -11
  28. package/dist/lib/browser/state-7TN26M42.mjs.map +7 -0
  29. package/dist/lib/browser/tools-SC6QEN7R.mjs +78 -0
  30. package/dist/lib/browser/tools-SC6QEN7R.mjs.map +7 -0
  31. package/dist/lib/browser/types.mjs +12 -6
  32. package/dist/lib/browser/{url-handler-MVHTKUYA.mjs → url-handler-ODG4B6NX.mjs} +7 -9
  33. package/dist/lib/browser/url-handler-ODG4B6NX.mjs.map +7 -0
  34. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  35. package/dist/types/src/capabilities/capabilities.d.ts +36 -14
  36. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  37. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  38. package/dist/types/src/capabilities/react-root.d.ts.map +1 -1
  39. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  40. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  41. package/dist/types/src/capabilities/state.d.ts +18 -6
  42. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  43. package/dist/types/src/capabilities/tools.d.ts +1 -0
  44. package/dist/types/src/capabilities/tools.d.ts.map +1 -1
  45. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -1
  46. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -1
  47. package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -1
  48. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +1 -4
  49. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  50. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  51. package/dist/types/src/components/DeckLayout/index.d.ts +1 -0
  52. package/dist/types/src/components/DeckLayout/index.d.ts.map +1 -1
  53. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts +6 -0
  54. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -0
  55. package/dist/types/src/components/DeckSettings/index.d.ts +2 -0
  56. package/dist/types/src/components/DeckSettings/index.d.ts.map +1 -0
  57. package/dist/types/src/components/Plank/Plank.d.ts +14 -0
  58. package/dist/types/src/components/Plank/Plank.d.ts.map +1 -0
  59. package/dist/types/src/components/Plank/Plank.stories.d.ts +8 -0
  60. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/{DeckLayout → Plank}/PlankControls.d.ts +8 -1
  62. package/dist/types/src/components/Plank/PlankControls.d.ts.map +1 -0
  63. package/dist/types/src/components/{DeckLayout → Plank}/PlankError.d.ts +2 -2
  64. package/dist/types/src/components/Plank/PlankError.d.ts.map +1 -0
  65. package/dist/types/src/components/Plank/PlankHeading.d.ts +20 -0
  66. package/dist/types/src/components/Plank/PlankHeading.d.ts.map +1 -0
  67. package/dist/types/src/components/Plank/PlankLoading.d.ts.map +1 -0
  68. package/dist/types/src/components/Plank/index.d.ts +6 -0
  69. package/dist/types/src/components/Plank/index.d.ts.map +1 -0
  70. package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +1 -0
  71. package/dist/types/src/components/Sidebar/Sidebar.d.ts.map +1 -0
  72. package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +1 -0
  73. package/dist/types/src/components/Sidebar/index.d.ts +4 -0
  74. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -0
  75. package/dist/types/src/components/index.d.ts +1 -1
  76. package/dist/types/src/components/index.d.ts.map +1 -1
  77. package/dist/types/src/events.d.ts +0 -1
  78. package/dist/types/src/events.d.ts.map +1 -1
  79. package/dist/types/src/hooks/index.d.ts +0 -1
  80. package/dist/types/src/hooks/index.d.ts.map +1 -1
  81. package/dist/types/src/index.d.ts +1 -0
  82. package/dist/types/src/index.d.ts.map +1 -1
  83. package/dist/types/src/layout.d.ts +7 -1
  84. package/dist/types/src/layout.d.ts.map +1 -1
  85. package/dist/types/src/meta.d.ts +2 -5
  86. package/dist/types/src/meta.d.ts.map +1 -1
  87. package/dist/types/src/translations.d.ts +4 -0
  88. package/dist/types/src/translations.d.ts.map +1 -1
  89. package/dist/types/src/types.d.ts +50 -48
  90. package/dist/types/src/types.d.ts.map +1 -1
  91. package/dist/types/src/util/index.d.ts +1 -0
  92. package/dist/types/src/util/index.d.ts.map +1 -1
  93. package/dist/types/src/util/set-active.d.ts +2 -2
  94. package/dist/types/src/util/set-active.d.ts.map +1 -1
  95. package/dist/types/src/util/useCompanions.d.ts +8 -0
  96. package/dist/types/src/util/useCompanions.d.ts.map +1 -0
  97. package/dist/types/src/util/useHoistStatusbar.d.ts.map +1 -1
  98. package/package.json +28 -29
  99. package/src/DeckPlugin.ts +0 -1
  100. package/src/capabilities/capabilities.ts +3 -4
  101. package/src/capabilities/intent-resolver.ts +63 -9
  102. package/src/capabilities/react-root.tsx +1 -9
  103. package/src/capabilities/react-surface.tsx +3 -4
  104. package/src/capabilities/settings.ts +7 -2
  105. package/src/capabilities/state.ts +4 -11
  106. package/src/capabilities/tools.ts +34 -22
  107. package/src/capabilities/url-handler.ts +2 -8
  108. package/src/components/DeckLayout/ActiveNode.tsx +2 -1
  109. package/src/components/DeckLayout/Banner.tsx +5 -3
  110. package/src/components/DeckLayout/ContentEmpty.tsx +1 -1
  111. package/src/components/DeckLayout/DeckLayout.tsx +58 -24
  112. package/src/components/DeckLayout/Fullscreen.tsx +1 -1
  113. package/src/components/DeckLayout/Toast.tsx +1 -1
  114. package/src/components/DeckLayout/index.ts +2 -0
  115. package/src/components/{LayoutSettings.tsx → DeckSettings/DeckSettings.tsx} +15 -10
  116. package/src/components/DeckSettings/index.ts +5 -0
  117. package/src/components/Plank/Plank.stories.tsx +43 -0
  118. package/src/components/Plank/Plank.tsx +230 -0
  119. package/src/components/{DeckLayout → Plank}/PlankControls.tsx +73 -27
  120. package/src/components/{DeckLayout → Plank}/PlankError.tsx +3 -3
  121. package/src/components/Plank/PlankHeading.tsx +207 -0
  122. package/src/components/Plank/index.ts +9 -0
  123. package/src/components/{DeckLayout → Sidebar}/ComplementarySidebar.tsx +65 -81
  124. package/src/components/Sidebar/index.ts +7 -0
  125. package/src/components/index.ts +1 -1
  126. package/src/events.ts +0 -1
  127. package/src/hooks/index.ts +0 -1
  128. package/src/index.ts +1 -0
  129. package/src/layout.ts +19 -2
  130. package/src/meta.ts +4 -4
  131. package/src/translations.ts +4 -0
  132. package/src/types.ts +81 -79
  133. package/src/util/index.ts +1 -0
  134. package/src/util/set-active.ts +2 -2
  135. package/src/util/useCompanions.ts +18 -0
  136. package/src/util/useHoistStatusbar.ts +2 -2
  137. package/dist/lib/browser/chunk-22AQ5IVX.mjs.map +0 -7
  138. package/dist/lib/browser/chunk-FYKBOM3C.mjs.map +0 -7
  139. package/dist/lib/browser/chunk-IZ5RPJ6T.mjs +0 -24
  140. package/dist/lib/browser/chunk-YCKJNTKG.mjs.map +0 -7
  141. package/dist/lib/browser/chunk-Z23S33X6.mjs.map +0 -7
  142. package/dist/lib/browser/intent-resolver-P5BVUQKU.mjs.map +0 -7
  143. package/dist/lib/browser/react-root-EP4UF3KA.mjs.map +0 -7
  144. package/dist/lib/browser/react-surface-5B3RLJCD.mjs.map +0 -7
  145. package/dist/lib/browser/settings-X3P2HKQJ.mjs.map +0 -7
  146. package/dist/lib/browser/state-2MOTLKVR.mjs.map +0 -7
  147. package/dist/lib/browser/tools-64LXGLYR.mjs +0 -59
  148. package/dist/lib/browser/tools-64LXGLYR.mjs.map +0 -7
  149. package/dist/lib/browser/url-handler-MVHTKUYA.mjs.map +0 -7
  150. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +0 -1
  151. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +0 -15
  152. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +0 -1
  153. package/dist/types/src/components/DeckLayout/Plank.d.ts +0 -13
  154. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +0 -1
  155. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +0 -1
  156. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +0 -1
  157. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts.map +0 -1
  158. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +0 -1
  159. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts.map +0 -1
  160. package/dist/types/src/components/LayoutSettings.d.ts +0 -6
  161. package/dist/types/src/components/LayoutSettings.d.ts.map +0 -1
  162. package/dist/types/src/hooks/useNode.d.ts +0 -11
  163. package/dist/types/src/hooks/useNode.d.ts.map +0 -1
  164. package/src/components/DeckLayout/NodePlankHeading.tsx +0 -148
  165. package/src/components/DeckLayout/Plank.tsx +0 -149
  166. package/src/hooks/useNode.ts +0 -46
  167. /package/dist/lib/browser/{app-graph-builder-IYHAGFA3.mjs.map → app-graph-builder-VYZ4IWI3.mjs.map} +0 -0
  168. /package/dist/lib/browser/{check-app-scheme-S3EYUPMF.mjs.map → check-app-scheme-SEYECDHI.mjs.map} +0 -0
  169. /package/dist/lib/browser/{chunk-IZ5RPJ6T.mjs.map → chunk-B4LOJUWW.mjs.map} +0 -0
  170. /package/dist/types/src/components/{DeckLayout → Plank}/PlankLoading.d.ts +0 -0
  171. /package/dist/types/src/components/{DeckLayout → Sidebar}/ComplementarySidebar.d.ts +0 -0
  172. /package/dist/types/src/components/{DeckLayout → Sidebar}/Sidebar.d.ts +0 -0
  173. /package/dist/types/src/components/{DeckLayout → Sidebar}/SidebarButton.d.ts +0 -0
  174. /package/src/components/{DeckLayout → Plank}/PlankLoading.tsx +0 -0
  175. /package/src/components/{DeckLayout → Sidebar}/Sidebar.tsx +0 -0
  176. /package/src/components/{DeckLayout → Sidebar}/SidebarButton.tsx +0 -0
@@ -7,24 +7,28 @@ import React from 'react';
7
7
  import { Input, Select, useTranslation } from '@dxos/react-ui';
8
8
  import { DeprecatedFormContainer, DeprecatedFormInput } from '@dxos/react-ui-form';
9
9
 
10
- import { DECK_PLUGIN } from '../meta';
10
+ import { DECK_PLUGIN } from '../../meta';
11
11
  import {
12
+ type DeckSettingsProps,
12
13
  type NewPlankPositioning,
13
14
  NewPlankPositions,
14
- type DeckSettingsProps,
15
15
  type Overscroll,
16
16
  OverscrollOptions,
17
- } from '../types';
17
+ } from '../../types';
18
18
 
19
19
  const isSocket = !!(globalThis as any).__args;
20
20
 
21
- export const LayoutSettings = ({ settings }: { settings: DeckSettingsProps }) => {
21
+ export const DeckSettings = ({ settings }: { settings: DeckSettingsProps }) => {
22
22
  const { t } = useTranslation(DECK_PLUGIN);
23
23
 
24
24
  return (
25
25
  <DeprecatedFormContainer>
26
+ <DeprecatedFormInput label={t('settings enable deck label')}>
27
+ <Input.Switch checked={settings.enableDeck} onCheckedChange={(checked) => (settings.enableDeck = checked)} />
28
+ </DeprecatedFormInput>
26
29
  <DeprecatedFormInput label={t('select new plank positioning label')}>
27
30
  <Select.Root
31
+ disabled={!settings.enableDeck}
28
32
  value={settings.newPlankPositioning ?? 'start'}
29
33
  onValueChange={(value) => (settings.newPlankPositioning = value as NewPlankPositioning)}
30
34
  >
@@ -44,6 +48,7 @@ export const LayoutSettings = ({ settings }: { settings: DeckSettingsProps }) =>
44
48
  </DeprecatedFormInput>
45
49
  <DeprecatedFormInput label={t('settings overscroll label')}>
46
50
  <Select.Root
51
+ disabled={!settings.enableDeck}
47
52
  value={settings.overscroll ?? 'none'}
48
53
  onValueChange={(value) => (settings.overscroll = value as Overscroll)}
49
54
  >
@@ -61,6 +66,12 @@ export const LayoutSettings = ({ settings }: { settings: DeckSettingsProps }) =>
61
66
  </Select.Portal>
62
67
  </Select.Root>
63
68
  </DeprecatedFormInput>
69
+ <DeprecatedFormInput label={t('settings enable statusbar label')}>
70
+ <Input.Switch
71
+ checked={settings.enableStatusbar}
72
+ onCheckedChange={(checked) => (settings.enableStatusbar = checked)}
73
+ />
74
+ </DeprecatedFormInput>
64
75
  <DeprecatedFormInput label={t('settings show hints label')}>
65
76
  <Input.Switch checked={settings.showHints} onCheckedChange={(checked) => (settings.showHints = checked)} />
66
77
  </DeprecatedFormInput>
@@ -72,12 +83,6 @@ export const LayoutSettings = ({ settings }: { settings: DeckSettingsProps }) =>
72
83
  />
73
84
  </DeprecatedFormInput>
74
85
  )}
75
- <DeprecatedFormInput label={t('settings enable statusbar label')}>
76
- <Input.Switch
77
- checked={settings.enableStatusbar}
78
- onCheckedChange={(checked) => (settings.enableStatusbar = checked)}
79
- />
80
- </DeprecatedFormInput>
81
86
  </DeprecatedFormContainer>
82
87
  );
83
88
  };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './DeckSettings';
@@ -0,0 +1,43 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+
9
+ import { IntentPlugin } from '@dxos/app-framework';
10
+ import { withPluginManager } from '@dxos/app-framework/testing';
11
+ import { GraphPlugin } from '@dxos/plugin-graph';
12
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
13
+
14
+ import { Plank } from './Plank';
15
+ import translations from '../../translations';
16
+
17
+ // TODO(burdon): invariant violation: No capability found for dxos.org/plugin/deck/capability/state
18
+ const meta: Meta<typeof Plank> = {
19
+ title: 'plugins/plugin-deck/Plank',
20
+ component: Plank,
21
+ decorators: [
22
+ withPluginManager({
23
+ plugins: [IntentPlugin(), GraphPlugin()],
24
+ }),
25
+ withTheme,
26
+ withLayout({ fullscreen: true, tooltips: true }),
27
+ ],
28
+ parameters: {
29
+ layout: 'centered',
30
+ translations,
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof meta>;
37
+
38
+ export const Default: Story = {
39
+ args: {
40
+ id: 'plank-1',
41
+ part: 'solo',
42
+ },
43
+ };
@@ -0,0 +1,230 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, {
6
+ Fragment,
7
+ type KeyboardEvent,
8
+ type PropsWithChildren,
9
+ memo,
10
+ useCallback,
11
+ useLayoutEffect,
12
+ useMemo,
13
+ useRef,
14
+ } from 'react';
15
+
16
+ import {
17
+ LayoutAction,
18
+ Surface,
19
+ createIntent,
20
+ useCapability,
21
+ useAppGraph,
22
+ useIntentDispatcher,
23
+ } from '@dxos/app-framework';
24
+ import { debounce } from '@dxos/async';
25
+ import { useNode, type Node } from '@dxos/plugin-graph';
26
+ import { ATTENDABLE_PATH_SEPARATOR, useAttendableAttributes } from '@dxos/react-ui-attention';
27
+ import { StackItem, railGridHorizontal } from '@dxos/react-ui-stack';
28
+ import { mainIntrinsicSize, mx } from '@dxos/react-ui-theme';
29
+
30
+ import { PlankContentError, PlankError } from './PlankError';
31
+ import { PlankHeading } from './PlankHeading';
32
+ import { PlankLoading } from './PlankLoading';
33
+ import { DeckCapabilities } from '../../capabilities';
34
+ import { useMainSize } from '../../hooks';
35
+ import { parseEntryId } from '../../layout';
36
+ import { DeckAction, type LayoutMode, type Part, type ResolvedPart, type DeckSettingsProps } from '../../types';
37
+ import { useCompanions } from '../../util';
38
+
39
+ const UNKNOWN_ID = 'unknown_id';
40
+
41
+ export type PlankProps = {
42
+ id?: string;
43
+ companionId?: string;
44
+ part: Part;
45
+ path?: string[];
46
+ order?: number;
47
+ active?: string[];
48
+ layoutMode: LayoutMode;
49
+ settings?: DeckSettingsProps;
50
+ };
51
+
52
+ type PlankImplProps = Omit<PlankProps, 'id' | 'companionId' | 'part'> & {
53
+ id: string;
54
+ part: ResolvedPart;
55
+ node?: Node;
56
+ companioned?: 'primary' | 'companion';
57
+ primary?: Node;
58
+ companions?: Node[];
59
+ };
60
+
61
+ const PlankImpl = memo(
62
+ ({ id, node, part, path, order, active, layoutMode, companioned, primary, companions, settings }: PlankImplProps) => {
63
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
64
+ const { deck, popoverAnchorId, scrollIntoView } = useCapability(DeckCapabilities.DeckState);
65
+ const rootElement = useRef<HTMLDivElement | null>(null);
66
+ const canResize = layoutMode === 'deck';
67
+ const Root = part.startsWith('solo') ? 'article' : StackItem.Root;
68
+
69
+ const attendableAttrs = useAttendableAttributes(primary?.id ?? id);
70
+ const index = active ? active.findIndex((entryId) => entryId === id) : 0;
71
+ const length = active?.length ?? 1;
72
+ const canIncrementStart = active && index !== undefined && index > 0 && length !== undefined && length > 1;
73
+ const canIncrementEnd = active && index !== undefined && index < length - 1 && length !== undefined;
74
+
75
+ const { variant } = parseEntryId(id);
76
+ const sizeKey = `${id.split('+')[0]}${variant ? `${ATTENDABLE_PATH_SEPARATOR}${variant}` : ''}`;
77
+ const size = deck.plankSizing[sizeKey] as number | undefined;
78
+ const setSize = useCallback(
79
+ debounce((nextSize: number) => {
80
+ return dispatch(createIntent(DeckAction.UpdatePlankSize, { id: sizeKey, size: nextSize }));
81
+ }, 200),
82
+ [dispatch, sizeKey],
83
+ );
84
+
85
+ // TODO(thure): Tabster’s focus group should handle moving focus to Main, but something is blocking it.
86
+ const handleKeyDown = useCallback((event: KeyboardEvent) => {
87
+ if (event.target === event.currentTarget && event.key === 'Escape') {
88
+ rootElement.current?.closest('main')?.focus();
89
+ }
90
+ }, []);
91
+
92
+ useLayoutEffect(() => {
93
+ if (scrollIntoView === id) {
94
+ // TODO(wittjosiah): When focused on page load, the focus is always visible.
95
+ // Forcing focus to something smaller than the plank prevents large focus ring in the interim.
96
+ const focusable = rootElement.current?.querySelector('button') || rootElement.current;
97
+ focusable?.focus({ preventScroll: true });
98
+ layoutMode === 'deck' && focusable?.scrollIntoView({ behavior: 'smooth', inline: 'center' });
99
+ // Clear the scroll into view state once it has been actioned.
100
+ void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: undefined }));
101
+ }
102
+ }, [id, scrollIntoView, layoutMode]);
103
+
104
+ const isSolo = layoutMode === 'solo' && part === 'solo';
105
+ const isAttendable =
106
+ (layoutMode === 'solo' && part.startsWith('solo')) || (layoutMode === 'deck' && part === 'deck');
107
+
108
+ const sizeAttrs = useMainSize();
109
+
110
+ const data = useMemo(
111
+ () =>
112
+ node && {
113
+ subject: node.data,
114
+ companionTo: primary?.data,
115
+ variant,
116
+ path,
117
+ popoverAnchorId,
118
+ },
119
+ [node, node?.data, path, popoverAnchorId, primary?.data],
120
+ );
121
+
122
+ // TODO(wittjosiah): Change prop to accept a component.
123
+ const placeholder = useMemo(() => <PlankLoading />, []);
124
+
125
+ const className = mx(
126
+ 'attention-surface relative',
127
+ isSolo && mainIntrinsicSize,
128
+ isSolo && railGridHorizontal,
129
+ isSolo && 'absolute inset-0',
130
+ part.startsWith('solo') && 'grid',
131
+ part === 'deck' && (companioned === 'companion' ? '!border-separator border-ie' : '!border-separator border-li'),
132
+ part.startsWith('solo-') && 'row-span-2 grid-rows-subgrid min-is-0',
133
+ part === 'solo-companion' && '!border-separator border-is',
134
+ );
135
+
136
+ return (
137
+ <Root
138
+ ref={rootElement}
139
+ data-testid='deck.plank'
140
+ tabIndex={0}
141
+ {...(part.startsWith('solo')
142
+ ? ({ ...sizeAttrs, className } as any)
143
+ : {
144
+ item: { id },
145
+ size,
146
+ onSizeChange: setSize,
147
+ classNames: className,
148
+ order,
149
+ role: 'article',
150
+ })}
151
+ {...(isAttendable ? attendableAttrs : {})}
152
+ onKeyDown={handleKeyDown}
153
+ >
154
+ {node ? (
155
+ <>
156
+ <PlankHeading
157
+ id={id}
158
+ part={part.startsWith('solo-') ? 'solo' : part}
159
+ node={node}
160
+ deckEnabled={settings?.enableDeck}
161
+ canIncrementStart={canIncrementStart}
162
+ canIncrementEnd={canIncrementEnd}
163
+ popoverAnchorId={popoverAnchorId}
164
+ primaryId={primary?.id}
165
+ companioned={companioned}
166
+ companions={companions}
167
+ />
168
+ <Surface
169
+ key={node.id}
170
+ role='article'
171
+ data={data}
172
+ limit={1}
173
+ fallback={PlankContentError}
174
+ placeholder={placeholder}
175
+ />
176
+ </>
177
+ ) : (
178
+ <PlankError id={id} part={part} />
179
+ )}
180
+ {canResize && <StackItem.ResizeHandle />}
181
+ </Root>
182
+ );
183
+ },
184
+ );
185
+
186
+ const SplitFrame = ({ children }: PropsWithChildren<{}>) => {
187
+ const sizeAttrs = useMainSize();
188
+ return (
189
+ <div
190
+ role='none'
191
+ className={mx('grid grid-cols-[1fr_1fr] absolute inset-0', railGridHorizontal, mainIntrinsicSize)}
192
+ {...sizeAttrs}
193
+ >
194
+ {children}
195
+ </div>
196
+ );
197
+ };
198
+
199
+ export const Plank = ({ id = UNKNOWN_ID, ...props }: PlankProps) => {
200
+ const { graph } = useAppGraph();
201
+ const node = useNode(graph, id);
202
+ const companions = useCompanions(id);
203
+ const currentCompanion = companions.find(({ id }) => id === props.companionId);
204
+
205
+ if (props.companionId) {
206
+ const Root = props.part === 'solo' ? SplitFrame : Fragment;
207
+ return (
208
+ <Root>
209
+ <PlankImpl
210
+ id={id}
211
+ node={node}
212
+ companioned='primary'
213
+ {...props}
214
+ {...(props.part === 'solo' ? { part: 'solo-primary' } : {})}
215
+ />
216
+ <PlankImpl
217
+ id={props.companionId}
218
+ node={currentCompanion}
219
+ companioned='companion'
220
+ primary={node}
221
+ companions={companions}
222
+ {...props}
223
+ {...(props.part === 'solo' ? { part: 'solo-companion' } : { order: props.order! + 1 })}
224
+ />
225
+ </Root>
226
+ );
227
+ } else {
228
+ return <PlankImpl id={id} node={node} companions={companions} {...props} />;
229
+ }
230
+ };
@@ -2,8 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { forwardRef } from 'react';
5
+ import React, { forwardRef, useCallback } from 'react';
6
6
 
7
+ import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
8
+ import { invariant } from '@dxos/invariant';
7
9
  import {
8
10
  Button,
9
11
  ButtonGroup,
@@ -15,14 +17,16 @@ import {
15
17
  } from '@dxos/react-ui';
16
18
 
17
19
  import { DECK_PLUGIN } from '../../meta';
18
- import { type DeckAction } from '../../types';
20
+ import { DeckAction } from '../../types';
19
21
 
20
22
  export type PlankControlHandler = (event: DeckAction.PartAdjustment) => void;
21
23
 
22
24
  export type PlankCapabilities = {
23
25
  incrementStart?: boolean;
24
26
  incrementEnd?: boolean;
27
+ deck?: boolean;
25
28
  solo?: boolean;
29
+ companion?: boolean;
26
30
  };
27
31
 
28
32
  export type PlankControlsProps = Omit<ButtonGroupProps, 'onClick'> & {
@@ -50,16 +54,46 @@ const PlankControl = ({ icon, label, ...props }: Omit<ButtonProps, 'children'> &
50
54
  );
51
55
  };
52
56
 
57
+ const plankControlSpacing = 'pli-2 plb-3';
58
+
59
+ type PlankComplimentControlsProps = {
60
+ primary?: string;
61
+ };
62
+
63
+ export const PlankCompanionControls = forwardRef<HTMLDivElement, PlankComplimentControlsProps>(
64
+ ({ primary }, forwardedRef) => {
65
+ const { t } = useTranslation(DECK_PLUGIN);
66
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
67
+ const handleCloseCompanion = useCallback(() => {
68
+ invariant(primary);
69
+ return dispatch(createIntent(DeckAction.ChangeCompanion, { primary, companion: null }));
70
+ }, []);
71
+ return (
72
+ <div ref={forwardedRef} className='contents app-no-drag'>
73
+ <PlankControl
74
+ label={t('close companion label')}
75
+ variant='ghost'
76
+ // icon='ph--minus--regular'
77
+ icon='ph--caret-left--regular'
78
+ onClick={handleCloseCompanion}
79
+ classNames={plankControlSpacing}
80
+ />
81
+ </div>
82
+ );
83
+ },
84
+ );
85
+
53
86
  // TODO(wittjosiah): Duplicate of stack LayoutControls?
54
87
  // Translations were to be duplicated between packages.
55
88
  // NOTE(thure): Pinning & unpinning are disabled indefinitely.
56
89
  export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
57
90
  (
58
- { onClick, variant = 'default', capabilities: can, isSolo, pin, close = false, children, classNames, ...props },
91
+ { children, classNames, variant = 'default', capabilities, isSolo, pin, close = false, onClick, ...props },
59
92
  forwardedRef,
60
93
  ) => {
61
94
  const { t } = useTranslation(DECK_PLUGIN);
62
- const buttonClassNames = variant === 'hide-disabled' ? 'disabled:hidden pli-2 plb-3' : 'pli-2 plb-3';
95
+ const buttonClassNames =
96
+ variant === 'hide-disabled' ? `disabled:hidden ${plankControlSpacing}` : plankControlSpacing;
63
97
 
64
98
  return (
65
99
  <ButtonGroup {...props} classNames={['app-no-drag', classNames]} ref={forwardedRef}>
@@ -73,31 +107,33 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
73
107
  />
74
108
  )} */}
75
109
 
76
- {can.solo && (
77
- <PlankControl
78
- label={isSolo ? t('show deck plank label') : t('show solo plank label')}
79
- classNames={buttonClassNames}
80
- onClick={() => onClick?.('solo')}
81
- icon={isSolo ? 'ph--corners-in--regular' : 'ph--corners-out--regular'}
82
- />
83
- )}
84
-
85
- {!isSolo && can.solo && (
110
+ {capabilities.deck && capabilities.solo && (
86
111
  <>
87
112
  <PlankControl
88
- label={t('increment start label')}
89
- disabled={!can.incrementStart}
113
+ label={isSolo ? t('show deck plank label') : t('show solo plank label')}
90
114
  classNames={buttonClassNames}
91
- onClick={() => onClick?.('increment-start')}
92
- icon='ph--caret-left--regular'
93
- />
94
- <PlankControl
95
- label={t('increment end label')}
96
- disabled={!can.incrementEnd}
97
- classNames={buttonClassNames}
98
- onClick={() => onClick?.('increment-end')}
99
- icon='ph--caret-right--regular'
115
+ icon={isSolo ? 'ph--corners-in--regular' : 'ph--corners-out--regular'}
116
+ onClick={() => onClick?.('solo')}
100
117
  />
118
+
119
+ {!isSolo && (
120
+ <>
121
+ <PlankControl
122
+ label={t('increment start label')}
123
+ disabled={!capabilities.incrementStart}
124
+ classNames={buttonClassNames}
125
+ icon='ph--caret-left--regular'
126
+ onClick={() => onClick?.('increment-start')}
127
+ />
128
+ <PlankControl
129
+ label={t('increment end label')}
130
+ disabled={!capabilities.incrementEnd}
131
+ classNames={buttonClassNames}
132
+ icon='ph--caret-right--regular'
133
+ onClick={() => onClick?.('increment-end')}
134
+ />
135
+ </>
136
+ )}
101
137
  </>
102
138
  )}
103
139
 
@@ -105,8 +141,8 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
105
141
  <PlankControl
106
142
  label={t('pin end label')}
107
143
  classNames={buttonClassNames}
108
- onClick={() => onClick?.('pin-end')}
109
144
  icon='ph--caret-line-right--regular'
145
+ onClick={() => onClick?.('pin-end')}
110
146
  />
111
147
  )} */}
112
148
 
@@ -114,7 +150,6 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
114
150
  <PlankControl
115
151
  label={t(`${typeof close === 'string' ? 'minify' : 'close'} label`)}
116
152
  classNames={buttonClassNames}
117
- onClick={() => onClick?.('close')}
118
153
  data-testid='plankHeading.close'
119
154
  icon={
120
155
  close === 'minify-start'
@@ -123,6 +158,17 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
123
158
  ? 'ph--caret-line-right--regular'
124
159
  : 'ph--x--regular'
125
160
  }
161
+ onClick={() => onClick?.('close')}
162
+ />
163
+ )}
164
+
165
+ {capabilities.companion && (
166
+ <PlankControl
167
+ label={t('open companion label')}
168
+ classNames={buttonClassNames}
169
+ data-testid='plankHeading.companion'
170
+ icon='ph--square-split-horizontal--regular'
171
+ onClick={() => onClick?.('companion')}
126
172
  />
127
173
  )}
128
174
  {children}
@@ -8,7 +8,7 @@ import { type Node } from '@dxos/plugin-graph';
8
8
  import { useTranslation } from '@dxos/react-ui';
9
9
  import { descriptionText, mx } from '@dxos/react-ui-theme';
10
10
 
11
- import { NodePlankHeading, type NodePlankHeadingProps } from './NodePlankHeading';
11
+ import { PlankHeading, type PlankHeadingProps } from './PlankHeading';
12
12
  import { PlankLoading } from './PlankLoading';
13
13
  import { DECK_PLUGIN } from '../../meta';
14
14
 
@@ -38,7 +38,7 @@ export const PlankError = ({
38
38
  error,
39
39
  }: {
40
40
  id: string;
41
- part: NodePlankHeadingProps['part'];
41
+ part: PlankHeadingProps['part'];
42
42
  node?: Node;
43
43
  error?: Error;
44
44
  }) => {
@@ -48,7 +48,7 @@ export const PlankError = ({
48
48
  }, []);
49
49
  return (
50
50
  <>
51
- <NodePlankHeading id={id} part={part} node={node} pending={!timedOut} />
51
+ <PlankHeading id={id} part={part} node={node} pending={!timedOut} />
52
52
  {timedOut ? <PlankContentError error={error} /> : <PlankLoading />}
53
53
  </>
54
54
  );