@dxos/plugin-deck 0.8.1-main.ae460ac → 0.8.1-staging.31c3ee1

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 (193) 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-J2B4CMLT.mjs → chunk-FJBMNSUC.mjs} +649 -488
  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-VFIADBSU.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-HUPQXFTF.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 +2 -1
  47. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -1
  48. package/dist/types/src/components/DeckLayout/Banner.d.ts +2 -1
  49. package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -1
  50. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts +2 -1
  51. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +1 -1
  52. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +2 -4
  53. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  54. package/dist/types/src/components/DeckLayout/Fallback.d.ts +2 -1
  55. package/dist/types/src/components/DeckLayout/Fallback.d.ts.map +1 -1
  56. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts +2 -1
  57. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts.map +1 -1
  58. package/dist/types/src/components/DeckLayout/StatusBar.d.ts +2 -1
  59. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
  60. package/dist/types/src/components/DeckLayout/Toast.d.ts +2 -1
  61. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  62. package/dist/types/src/components/DeckLayout/Topbar.d.ts +2 -1
  63. package/dist/types/src/components/DeckLayout/Topbar.d.ts.map +1 -1
  64. package/dist/types/src/components/DeckLayout/index.d.ts +1 -0
  65. package/dist/types/src/components/DeckLayout/index.d.ts.map +1 -1
  66. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts +6 -0
  67. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -0
  68. package/dist/types/src/components/DeckSettings/index.d.ts +2 -0
  69. package/dist/types/src/components/DeckSettings/index.d.ts.map +1 -0
  70. package/dist/types/src/components/Plank/Plank.d.ts +14 -0
  71. package/dist/types/src/components/Plank/Plank.d.ts.map +1 -0
  72. package/dist/types/src/components/Plank/Plank.stories.d.ts +8 -0
  73. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -0
  74. package/dist/types/src/components/{DeckLayout → Plank}/PlankControls.d.ts +8 -1
  75. package/dist/types/src/components/Plank/PlankControls.d.ts.map +1 -0
  76. package/dist/types/src/components/Plank/PlankError.d.ts +13 -0
  77. package/dist/types/src/components/Plank/PlankError.d.ts.map +1 -0
  78. package/dist/types/src/components/Plank/PlankHeading.d.ts +20 -0
  79. package/dist/types/src/components/Plank/PlankHeading.d.ts.map +1 -0
  80. package/dist/types/src/components/Plank/PlankLoading.d.ts +3 -0
  81. package/dist/types/src/components/Plank/PlankLoading.d.ts.map +1 -0
  82. package/dist/types/src/components/Plank/index.d.ts +6 -0
  83. package/dist/types/src/components/Plank/index.d.ts.map +1 -0
  84. package/dist/types/src/components/{DeckLayout → Sidebar}/ComplementarySidebar.d.ts +2 -1
  85. package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +1 -0
  86. package/dist/types/src/components/Sidebar/Sidebar.d.ts +3 -0
  87. package/dist/types/src/components/Sidebar/Sidebar.d.ts.map +1 -0
  88. package/dist/types/src/components/Sidebar/SidebarButton.d.ts +8 -0
  89. package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +1 -0
  90. package/dist/types/src/components/Sidebar/index.d.ts +4 -0
  91. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -0
  92. package/dist/types/src/components/index.d.ts +1 -1
  93. package/dist/types/src/components/index.d.ts.map +1 -1
  94. package/dist/types/src/events.d.ts +0 -1
  95. package/dist/types/src/events.d.ts.map +1 -1
  96. package/dist/types/src/hooks/index.d.ts +0 -1
  97. package/dist/types/src/hooks/index.d.ts.map +1 -1
  98. package/dist/types/src/index.d.ts +1 -0
  99. package/dist/types/src/index.d.ts.map +1 -1
  100. package/dist/types/src/layout.d.ts +7 -1
  101. package/dist/types/src/layout.d.ts.map +1 -1
  102. package/dist/types/src/meta.d.ts +2 -5
  103. package/dist/types/src/meta.d.ts.map +1 -1
  104. package/dist/types/src/translations.d.ts +4 -0
  105. package/dist/types/src/translations.d.ts.map +1 -1
  106. package/dist/types/src/types.d.ts +60 -58
  107. package/dist/types/src/types.d.ts.map +1 -1
  108. package/dist/types/src/util/index.d.ts +1 -0
  109. package/dist/types/src/util/index.d.ts.map +1 -1
  110. package/dist/types/src/util/set-active.d.ts +2 -2
  111. package/dist/types/src/util/set-active.d.ts.map +1 -1
  112. package/dist/types/src/util/useCompanions.d.ts +8 -0
  113. package/dist/types/src/util/useCompanions.d.ts.map +1 -0
  114. package/dist/types/src/util/useHoistStatusbar.d.ts.map +1 -1
  115. package/package.json +30 -31
  116. package/src/DeckPlugin.ts +0 -1
  117. package/src/capabilities/capabilities.ts +3 -4
  118. package/src/capabilities/intent-resolver.ts +63 -9
  119. package/src/capabilities/react-root.tsx +1 -9
  120. package/src/capabilities/react-surface.tsx +3 -4
  121. package/src/capabilities/settings.ts +7 -2
  122. package/src/capabilities/state.ts +4 -11
  123. package/src/capabilities/tools.ts +34 -22
  124. package/src/capabilities/url-handler.ts +2 -8
  125. package/src/components/DeckLayout/ActiveNode.tsx +2 -1
  126. package/src/components/DeckLayout/Banner.tsx +5 -3
  127. package/src/components/DeckLayout/ContentEmpty.tsx +1 -1
  128. package/src/components/DeckLayout/DeckLayout.tsx +62 -26
  129. package/src/components/DeckLayout/Fullscreen.tsx +1 -1
  130. package/src/components/DeckLayout/Toast.tsx +1 -1
  131. package/src/components/DeckLayout/index.ts +2 -0
  132. package/src/components/{LayoutSettings.tsx → DeckSettings/DeckSettings.tsx} +15 -10
  133. package/src/components/DeckSettings/index.ts +5 -0
  134. package/src/components/Plank/Plank.stories.tsx +43 -0
  135. package/src/components/Plank/Plank.tsx +230 -0
  136. package/src/components/{DeckLayout → Plank}/PlankControls.tsx +73 -27
  137. package/src/components/{DeckLayout → Plank}/PlankError.tsx +3 -3
  138. package/src/components/Plank/PlankHeading.tsx +207 -0
  139. package/src/components/Plank/index.ts +9 -0
  140. package/src/components/{DeckLayout → Sidebar}/ComplementarySidebar.tsx +65 -81
  141. package/src/components/Sidebar/index.ts +7 -0
  142. package/src/components/index.ts +1 -1
  143. package/src/events.ts +0 -1
  144. package/src/hooks/index.ts +0 -1
  145. package/src/index.ts +1 -0
  146. package/src/layout.ts +19 -2
  147. package/src/meta.ts +4 -4
  148. package/src/translations.ts +4 -0
  149. package/src/types.ts +81 -79
  150. package/src/util/index.ts +1 -0
  151. package/src/util/set-active.ts +2 -2
  152. package/src/util/useCompanions.ts +18 -0
  153. package/src/util/useHoistStatusbar.ts +2 -2
  154. package/dist/lib/browser/chunk-22AQ5IVX.mjs.map +0 -7
  155. package/dist/lib/browser/chunk-FYKBOM3C.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-J2B4CMLT.mjs.map +0 -7
  157. package/dist/lib/browser/chunk-KTIGBCFT.mjs +0 -24
  158. package/dist/lib/browser/chunk-YCKJNTKG.mjs.map +0 -7
  159. package/dist/lib/browser/intent-resolver-P5BVUQKU.mjs.map +0 -7
  160. package/dist/lib/browser/react-root-VFIADBSU.mjs.map +0 -7
  161. package/dist/lib/browser/react-surface-HUPQXFTF.mjs.map +0 -7
  162. package/dist/lib/browser/settings-X3P2HKQJ.mjs.map +0 -7
  163. package/dist/lib/browser/state-2MOTLKVR.mjs.map +0 -7
  164. package/dist/lib/browser/tools-64LXGLYR.mjs +0 -59
  165. package/dist/lib/browser/tools-64LXGLYR.mjs.map +0 -7
  166. package/dist/lib/browser/url-handler-MVHTKUYA.mjs.map +0 -7
  167. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +0 -1
  168. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +0 -15
  169. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +0 -1
  170. package/dist/types/src/components/DeckLayout/Plank.d.ts +0 -13
  171. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +0 -1
  172. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +0 -1
  173. package/dist/types/src/components/DeckLayout/PlankError.d.ts +0 -12
  174. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +0 -1
  175. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts +0 -2
  176. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts.map +0 -1
  177. package/dist/types/src/components/DeckLayout/Sidebar.d.ts +0 -2
  178. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +0 -1
  179. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts +0 -7
  180. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts.map +0 -1
  181. package/dist/types/src/components/LayoutSettings.d.ts +0 -5
  182. package/dist/types/src/components/LayoutSettings.d.ts.map +0 -1
  183. package/dist/types/src/hooks/useNode.d.ts +0 -11
  184. package/dist/types/src/hooks/useNode.d.ts.map +0 -1
  185. package/src/components/DeckLayout/NodePlankHeading.tsx +0 -148
  186. package/src/components/DeckLayout/Plank.tsx +0 -149
  187. package/src/hooks/useNode.ts +0 -46
  188. /package/dist/lib/browser/{app-graph-builder-IYHAGFA3.mjs.map → app-graph-builder-VYZ4IWI3.mjs.map} +0 -0
  189. /package/dist/lib/browser/{check-app-scheme-S3EYUPMF.mjs.map → check-app-scheme-SEYECDHI.mjs.map} +0 -0
  190. /package/dist/lib/browser/{chunk-KTIGBCFT.mjs.map → chunk-B4LOJUWW.mjs.map} +0 -0
  191. /package/src/components/{DeckLayout → Plank}/PlankLoading.tsx +0 -0
  192. /package/src/components/{DeckLayout → Sidebar}/Sidebar.tsx +0 -0
  193. /package/src/components/{DeckLayout → Sidebar}/SidebarButton.tsx +0 -0
@@ -6,9 +6,10 @@ import { untracked } from '@preact/signals-core';
6
6
  import React, { useCallback, useEffect, useMemo, useRef, type UIEvent, Fragment, useState } from 'react';
7
7
 
8
8
  import {
9
+ Capabilities,
9
10
  LayoutAction,
10
- createIntent,
11
11
  Surface,
12
+ createIntent,
12
13
  useCapability,
13
14
  useIntentDispatcher,
14
15
  usePluginManager,
@@ -19,39 +20,37 @@ import {
19
20
  Dialog as NaturalDialog,
20
21
  Main,
21
22
  Popover,
22
- useOnTransition,
23
23
  type MainProps,
24
24
  useMediaQuery,
25
+ useOnTransition,
25
26
  } from '@dxos/react-ui';
26
27
  import { Stack, StackContext, DEFAULT_HORIZONTAL_SIZE } from '@dxos/react-ui-stack';
27
28
  import { mainPaddingTransitions } from '@dxos/react-ui-theme';
28
29
 
29
30
  import { ActiveNode } from './ActiveNode';
30
- import { ComplementarySidebar } from './ComplementarySidebar';
31
31
  import { ContentEmpty } from './ContentEmpty';
32
32
  import { Fullscreen } from './Fullscreen';
33
- import { Plank } from './Plank';
34
- import { Sidebar } from './Sidebar';
35
- import { ToggleComplementarySidebarButton, ToggleSidebarButton } from './SidebarButton';
36
33
  import { StatusBar } from './StatusBar';
37
34
  import { Toast } from './Toast';
38
35
  import { Topbar } from './Topbar';
39
36
  import { DeckCapabilities } from '../../capabilities';
40
- import { getMode, type Overscroll } from '../../types';
37
+ import { DECK_PLUGIN } from '../../meta';
38
+ import { type DeckSettingsProps, getMode } from '../../types';
41
39
  import { calculateOverscroll, layoutAppliesTopbar, useBreakpoints, useHoistStatusbar } from '../../util';
40
+ import { Plank, PlankContentError } from '../Plank';
41
+ import { ComplementarySidebar, Sidebar, ToggleComplementarySidebarButton, ToggleSidebarButton } from '../Sidebar';
42
42
  import { fixedComplementarySidebarToggleStyles, fixedSidebarToggleStyles } from '../fragments';
43
43
 
44
44
  export type DeckLayoutProps = {
45
- overscroll: Overscroll;
46
- showHints: boolean;
47
45
  onDismissToast: (id: string) => void;
48
46
  };
49
47
 
50
- const PlankSeparator = ({ index }: { index: number }) =>
51
- index > 0 ? <span role='separator' className='row-span-2 bg-deck is-4' style={{ gridColumn: index * 2 }} /> : null;
48
+ const PlankSeparator = ({ order }: { order: number }) =>
49
+ order > 0 ? <span role='separator' className='row-span-2 bg-deck is-4' style={{ gridColumn: order }} /> : null;
52
50
 
53
- export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayoutProps) => {
51
+ export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
54
52
  const { dispatchPromise: dispatch } = useIntentDispatcher();
53
+ const settings = useCapability(Capabilities.SettingsStore).getStore<DeckSettingsProps>(DECK_PLUGIN)!.value;
55
54
  const context = useCapability(DeckCapabilities.MutableDeckState);
56
55
  const {
57
56
  sidebarState,
@@ -67,7 +66,7 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
67
66
  deck,
68
67
  toasts,
69
68
  } = context;
70
- const { active, fullscreen, solo, plankSizing } = deck;
69
+ const { active, activeCompanions, fullscreen, solo, plankSizing } = deck;
71
70
  const breakpoint = useBreakpoints();
72
71
  const topbar = layoutAppliesTopbar(breakpoint);
73
72
  const hoistStatusbar = useHoistStatusbar(breakpoint);
@@ -119,6 +118,15 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
119
118
  }
120
119
  }, [isNotMobile, deck, dispatch]);
121
120
 
121
+ // If deck is disabled in settings, ensure that the layout is in solo mode.
122
+ useEffect(() => {
123
+ if (!settings.enableDeck) {
124
+ void dispatch(
125
+ createIntent(LayoutAction.SetLayoutMode, { part: 'mode', subject: active[0], options: { mode: 'solo' } }),
126
+ );
127
+ }
128
+ }, [settings.enableDeck, dispatch, active]);
129
+
122
130
  /**
123
131
  * Clear scroll restoration state if the window is resized
124
132
  */
@@ -155,11 +163,11 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
155
163
  const isEmpty = !solo && active.length === 0;
156
164
 
157
165
  const padding = useMemo(() => {
158
- if (!solo && overscroll === 'centering') {
166
+ if (!solo && settings.overscroll === 'centering') {
159
167
  return calculateOverscroll(active.length);
160
168
  }
161
169
  return {};
162
- }, [solo, overscroll, deck]);
170
+ }, [solo, settings.overscroll, deck]);
163
171
 
164
172
  const mainPosition = useMemo(
165
173
  () => [
@@ -186,6 +194,17 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
186
194
  );
187
195
  const handlePopoverClose = useCallback(() => handlePopoverOpenChange(false), [handlePopoverOpenChange]);
188
196
 
197
+ const { order, itemsCount }: { order: Record<string, number>; itemsCount: number } = useMemo(() => {
198
+ return active.reduce(
199
+ (acc: { order: Record<string, number>; itemsCount: number }, entryId) => {
200
+ acc.order[entryId] = acc.itemsCount + 1;
201
+ acc.itemsCount += activeCompanions?.[entryId] ? 3 : 2;
202
+ return acc;
203
+ },
204
+ { order: {}, itemsCount: 0 },
205
+ );
206
+ }, [active, activeCompanions]);
207
+
189
208
  return (
190
209
  <Popover.Root modal open={!!(popoverAnchorId && delayedPopoverVisibility)} onOpenChange={handlePopoverOpenChange}>
191
210
  <ActiveNode />
@@ -248,18 +267,26 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
248
267
  {!topbar && <ToggleSidebarButton classNames={fixedSidebarToggleStyles} />}
249
268
  {!topbar && <ToggleComplementarySidebarButton classNames={fixedComplementarySidebarToggleStyles} />}
250
269
  <Stack
270
+ ref={deckRef}
251
271
  orientation='horizontal'
252
272
  size='contain'
253
273
  classNames={['absolute inset-block-0 -inset-inline-px', mainPaddingTransitions]}
254
- onScroll={handleScroll}
255
- itemsCount={2 * (active.length ?? 0) - 1}
274
+ itemsCount={itemsCount - 1}
256
275
  style={padding}
257
- ref={deckRef}
276
+ onScroll={handleScroll}
258
277
  >
259
- {active.map((entryId, index) => (
278
+ {active.map((entryId) => (
260
279
  <Fragment key={entryId}>
261
- <PlankSeparator index={index} />
262
- <Plank id={entryId} part='deck' order={index * 2 + 1} active={active} layoutMode={layoutMode} />
280
+ <PlankSeparator order={order[entryId] - 1} />
281
+ <Plank
282
+ id={entryId}
283
+ companionId={activeCompanions?.[entryId]}
284
+ part='deck'
285
+ order={order[entryId]}
286
+ active={active}
287
+ layoutMode={layoutMode}
288
+ settings={settings}
289
+ />
263
290
  </Fragment>
264
291
  ))}
265
292
  </Stack>
@@ -272,15 +299,23 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
272
299
  {!topbar && <ToggleSidebarButton classNames={fixedSidebarToggleStyles} />}
273
300
  {!topbar && <ToggleComplementarySidebarButton classNames={fixedComplementarySidebarToggleStyles} />}
274
301
  <StackContext.Provider value={{ size: 'contain', orientation: 'horizontal', rail: true }}>
275
- <Plank id={solo} part='solo' layoutMode={layoutMode} />
302
+ <Plank
303
+ id={solo}
304
+ companionId={solo ? activeCompanions?.[solo] : undefined}
305
+ part='solo'
306
+ layoutMode={layoutMode}
307
+ settings={settings}
308
+ />
276
309
  </StackContext.Provider>
277
310
  </div>
278
311
  </Main.Content>
279
312
  )}
280
313
 
281
- {/* Status bar. */}
314
+ {/* Topbar. */}
282
315
  {topbar && <Topbar />}
283
- {hoistStatusbar && <StatusBar showHints={showHints} />}
316
+
317
+ {/* Status bar. */}
318
+ {hoistStatusbar && <StatusBar showHints={settings.showHints} />}
284
319
  </Main.Root>
285
320
  )}
286
321
 
@@ -303,10 +338,11 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
303
338
  onOpenChange={(nextOpen) => (context.dialogOpen = nextOpen)}
304
339
  >
305
340
  {dialogBlockAlign === 'end' ? (
306
- <Surface role='dialog' data={dialogContent} limit={1} />
341
+ // TODO(burdon): Placeholder creates a suspense boundary; replace with defaults.
342
+ <Surface role='dialog' data={dialogContent} limit={1} fallback={PlankContentError} placeholder={<div />} />
307
343
  ) : (
308
344
  <Dialog.Overlay blockAlign={dialogBlockAlign}>
309
- <Surface role='dialog' data={dialogContent} limit={1} />
345
+ <Surface role='dialog' data={dialogContent} limit={1} fallback={PlankContentError} />
310
346
  </Dialog.Overlay>
311
347
  )}
312
348
  </Dialog.Root>
@@ -5,11 +5,11 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { Surface, useAppGraph } from '@dxos/app-framework';
8
+ import { useNode } from '@dxos/plugin-graph';
8
9
  import { fixedInsetFlexLayout } from '@dxos/react-ui-theme';
9
10
 
10
11
  import { Fallback } from './Fallback';
11
12
  import { SURFACE_PREFIX } from './constants';
12
- import { useNode } from '../../hooks';
13
13
 
14
14
  export const Fullscreen = ({ id }: { id?: string }) => {
15
15
  const { graph } = useAppGraph();
@@ -9,9 +9,9 @@ import {
9
9
  Button,
10
10
  Icon,
11
11
  Toast as NaturalToast,
12
+ type ToastRootProps,
12
13
  toLocalizedString,
13
14
  useTranslation,
14
- type ToastRootProps,
15
15
  } from '@dxos/react-ui';
16
16
 
17
17
  import { DECK_PLUGIN } from '../../meta';
@@ -3,4 +3,6 @@
3
3
  //
4
4
 
5
5
  export { NAV_ID } from './constants';
6
+
7
+ export * from './Banner';
6
8
  export * from './DeckLayout';
@@ -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
+ };