@dxos/plugin-deck 0.9.1-main.c7dcc2e112 → 0.10.0

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 (258) hide show
  1. package/PLUGIN.mdl +3 -1
  2. package/dist/lib/neutral/{DeckLayout-VX2BP4Q2.mjs → DeckLayout-B3RUUV5P.mjs} +4 -3
  3. package/dist/lib/neutral/DeckLayout-B3RUUV5P.mjs.map +7 -0
  4. package/dist/lib/neutral/DeckPlugin.mjs +2 -6
  5. package/dist/lib/neutral/DeckPlugin.mjs.map +3 -3
  6. package/dist/lib/neutral/DeckSettings-Z7GMB7XJ.mjs +32 -0
  7. package/dist/lib/neutral/DeckSettings-Z7GMB7XJ.mjs.map +7 -0
  8. package/dist/lib/neutral/{add-toast-APKTCLIA.mjs → add-toast-BHNEH5UY.mjs} +2 -2
  9. package/dist/lib/neutral/{adjust-7CZI4GK3.mjs → adjust-MJB6UMRQ.mjs} +16 -10
  10. package/dist/lib/neutral/adjust-MJB6UMRQ.mjs.map +7 -0
  11. package/dist/lib/neutral/{app-graph-builder-6HTXRTU2.mjs → app-graph-builder-2F7P4IFQ.mjs} +4 -3
  12. package/dist/lib/neutral/app-graph-builder-2F7P4IFQ.mjs.map +7 -0
  13. package/dist/lib/neutral/capabilities/index.mjs +5 -5
  14. package/dist/lib/neutral/chunk-FKGYHBUB.mjs +8 -0
  15. package/dist/lib/neutral/{chunk-CL7JIOI5.mjs → chunk-ICOJ4GMB.mjs} +10 -6
  16. package/dist/lib/neutral/chunk-ICOJ4GMB.mjs.map +7 -0
  17. package/dist/lib/neutral/{chunk-NGX6RXNV.mjs → chunk-PP32NEVY.mjs} +2 -2
  18. package/dist/lib/neutral/chunk-PP32NEVY.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-SONATB35.mjs +20 -0
  20. package/dist/lib/neutral/chunk-SONATB35.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-Z2B5RSVI.mjs +1096 -0
  22. package/dist/lib/neutral/chunk-Z2B5RSVI.mjs.map +7 -0
  23. package/dist/lib/neutral/{close-KJUCFIJ5.mjs → close-2YGB5IIP.mjs} +4 -3
  24. package/dist/lib/neutral/{close-KJUCFIJ5.mjs.map → close-2YGB5IIP.mjs.map} +1 -1
  25. package/dist/lib/neutral/components/index.mjs +201 -16
  26. package/dist/lib/neutral/components/index.mjs.map +4 -4
  27. package/dist/lib/neutral/containers/index.mjs +6 -11
  28. package/dist/lib/neutral/containers/index.mjs.map +3 -3
  29. package/dist/lib/neutral/hooks/index.mjs +101 -17
  30. package/dist/lib/neutral/hooks/index.mjs.map +4 -4
  31. package/dist/lib/neutral/index.mjs +7 -1
  32. package/dist/lib/neutral/meta.json +1 -1
  33. package/dist/lib/neutral/{notification-tracker-CUDFRDCF.mjs → notification-tracker-Y5CRTWJM.mjs} +4 -2
  34. package/dist/lib/neutral/notification-tracker-Y5CRTWJM.mjs.map +7 -0
  35. package/dist/lib/neutral/{open-VU7YS3HB.mjs → open-P6XQ56M7.mjs} +4 -3
  36. package/dist/lib/neutral/{open-VU7YS3HB.mjs.map → open-P6XQ56M7.mjs.map} +1 -1
  37. package/dist/lib/neutral/operations/index.mjs +1 -1
  38. package/dist/lib/neutral/plugin.mjs +1 -1
  39. package/dist/lib/neutral/{react-surface-O4POQWYL.mjs → react-surface-H63FXUK3.mjs} +2 -2
  40. package/dist/lib/neutral/{react-surface-O4POQWYL.mjs.map → react-surface-H63FXUK3.mjs.map} +1 -1
  41. package/dist/lib/neutral/{revert-workspace-24TKG3I7.mjs → revert-workspace-RXWP2TEE.mjs} +2 -2
  42. package/dist/lib/neutral/{scroll-into-view-3VXT6TWC.mjs → scroll-into-view-HFV5EGYZ.mjs} +2 -2
  43. package/dist/lib/neutral/{set-RFOLTI57.mjs → set-HOOVB7E4.mjs} +4 -3
  44. package/dist/lib/neutral/{set-RFOLTI57.mjs.map → set-HOOVB7E4.mjs.map} +1 -1
  45. package/dist/lib/neutral/{set-layout-mode-AZ73W52Z.mjs → set-layout-mode-XAGSZZGB.mjs} +2 -2
  46. package/dist/lib/neutral/{state-D3YXB5NP.mjs → state-F4X5GPGZ.mjs} +3 -2
  47. package/dist/lib/neutral/{state-D3YXB5NP.mjs.map → state-F4X5GPGZ.mjs.map} +1 -1
  48. package/dist/lib/neutral/{switch-workspace-TBPT23CZ.mjs → switch-workspace-PA7N6EUX.mjs} +2 -2
  49. package/dist/lib/neutral/translations.mjs +2 -2
  50. package/dist/lib/neutral/translations.mjs.map +2 -2
  51. package/dist/lib/neutral/types/index.mjs +1 -1
  52. package/dist/lib/neutral/{update-companion-5LCY6QME.mjs → update-companion-6A2SKQAI.mjs} +14 -6
  53. package/dist/lib/neutral/update-companion-6A2SKQAI.mjs.map +7 -0
  54. package/dist/lib/neutral/{update-complementary-HZ7QXZYE.mjs → update-complementary-K55I5EV5.mjs} +2 -2
  55. package/dist/lib/neutral/{update-dialog-A7W3R5EI.mjs → update-dialog-Z3GEXMVV.mjs} +2 -2
  56. package/dist/lib/neutral/{update-plank-size-XHTYBLSD.mjs → update-plank-size-BTESVV7N.mjs} +2 -2
  57. package/dist/lib/neutral/{update-popover-RDIHG73B.mjs → update-popover-QEVDSZSH.mjs} +2 -2
  58. package/dist/lib/neutral/{update-sidebar-GEI7USA5.mjs → update-sidebar-VSRCN47U.mjs} +2 -2
  59. package/dist/lib/neutral/{url-handler-PUS4X33T.mjs → url-handler-ERZXXEYR.mjs} +3 -2
  60. package/dist/lib/neutral/{url-handler-PUS4X33T.mjs.map → url-handler-ERZXXEYR.mjs.map} +1 -1
  61. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  62. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
  63. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  64. package/dist/types/src/capabilities/notification-tracker.d.ts +1 -1
  65. package/dist/types/src/capabilities/notification-tracker.d.ts.map +1 -1
  66. package/dist/types/src/capabilities/react-root.d.ts +1 -5
  67. package/dist/types/src/capabilities/react-root.d.ts.map +1 -1
  68. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  69. package/dist/types/src/capabilities/settings.d.ts +1 -3
  70. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  71. package/dist/types/src/capabilities/state.d.ts +1 -187
  72. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  73. package/dist/types/src/capabilities/tools.d.ts +1 -1
  74. package/dist/types/src/capabilities/url-handler.d.ts +1 -1
  75. package/dist/types/src/components/Companion/Companion.d.ts +17 -0
  76. package/dist/types/src/components/Companion/Companion.d.ts.map +1 -0
  77. package/dist/types/src/components/Companion/index.d.ts +2 -0
  78. package/dist/types/src/components/Companion/index.d.ts.map +1 -0
  79. package/dist/types/src/components/Matrix/Matrix.d.ts +1 -1
  80. package/dist/types/src/components/Matrix/Matrix.d.ts.map +1 -1
  81. package/dist/types/src/components/Matrix/Matrix.stories.d.ts +2 -2
  82. package/dist/types/src/components/Matrix/Matrix.stories.d.ts.map +1 -1
  83. package/dist/types/src/components/Matrix/index.d.ts +1 -1
  84. package/dist/types/src/components/Matrix/index.d.ts.map +1 -1
  85. package/dist/types/src/components/Pane/Pane.d.ts +45 -0
  86. package/dist/types/src/components/Pane/Pane.d.ts.map +1 -0
  87. package/dist/types/src/components/Pane/Pane.stories.d.ts +7 -0
  88. package/dist/types/src/components/Pane/Pane.stories.d.ts.map +1 -0
  89. package/dist/types/src/components/Pane/index.d.ts +2 -0
  90. package/dist/types/src/components/Pane/index.d.ts.map +1 -0
  91. package/dist/types/src/components/Plank/Plank.d.ts +74 -0
  92. package/dist/types/src/components/Plank/Plank.d.ts.map +1 -0
  93. package/dist/types/src/components/Plank/Plank.stories.d.ts +6 -0
  94. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -0
  95. package/dist/types/src/components/Plank/index.d.ts +2 -0
  96. package/dist/types/src/components/Plank/index.d.ts.map +1 -0
  97. package/dist/types/src/components/index.d.ts +3 -2
  98. package/dist/types/src/components/index.d.ts.map +1 -1
  99. package/dist/types/src/containers/Deck/Deck.d.ts +1 -1
  100. package/dist/types/src/containers/Deck/Deck.d.ts.map +1 -1
  101. package/dist/types/src/containers/Deck/Deck.stories.d.ts +2 -2
  102. package/dist/types/src/containers/Deck/DeckPlank.d.ts +21 -0
  103. package/dist/types/src/containers/Deck/DeckPlank.d.ts.map +1 -0
  104. package/dist/types/src/containers/Deck/DeckViewport.d.ts +3 -1
  105. package/dist/types/src/containers/Deck/DeckViewport.d.ts.map +1 -1
  106. package/dist/types/src/containers/{Plank → Deck}/PlankControls.d.ts +3 -10
  107. package/dist/types/src/containers/Deck/PlankControls.d.ts.map +1 -0
  108. package/dist/types/src/containers/Deck/PlankFallback.d.ts +6 -0
  109. package/dist/types/src/containers/Deck/PlankFallback.d.ts.map +1 -0
  110. package/dist/types/src/containers/Deck/useDeckPlank.d.ts +49 -0
  111. package/dist/types/src/containers/Deck/useDeckPlank.d.ts.map +1 -0
  112. package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts +2 -2
  113. package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts.map +1 -1
  114. package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.d.ts +1 -1
  115. package/dist/types/src/containers/DeckSettings/DeckSettings.d.ts.map +1 -0
  116. package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.stories.d.ts +2 -2
  117. package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.stories.d.ts.map +1 -1
  118. package/dist/types/src/containers/DeckSettings/index.d.ts.map +1 -0
  119. package/dist/types/src/containers/index.d.ts +1 -1
  120. package/dist/types/src/containers/index.d.ts.map +1 -1
  121. package/dist/types/src/hooks/index.d.ts +2 -0
  122. package/dist/types/src/hooks/index.d.ts.map +1 -1
  123. package/dist/types/src/hooks/useCompanionSplit.d.ts +28 -0
  124. package/dist/types/src/hooks/useCompanionSplit.d.ts.map +1 -0
  125. package/dist/types/src/hooks/useCompanions.d.ts +11 -7
  126. package/dist/types/src/hooks/useCompanions.d.ts.map +1 -1
  127. package/dist/types/src/hooks/useDeckCompanions.d.ts +2 -2
  128. package/dist/types/src/hooks/useDeckCompanions.d.ts.map +1 -1
  129. package/dist/types/src/hooks/useDeckState.d.ts +1 -1
  130. package/dist/types/src/hooks/useDeckState.d.ts.map +1 -1
  131. package/dist/types/src/hooks/useSelectedCompanionVariant.d.ts +6 -0
  132. package/dist/types/src/hooks/useSelectedCompanionVariant.d.ts.map +1 -0
  133. package/dist/types/src/index.d.ts +1 -0
  134. package/dist/types/src/index.d.ts.map +1 -1
  135. package/dist/types/src/meta.d.ts +4 -0
  136. package/dist/types/src/meta.d.ts.map +1 -1
  137. package/dist/types/src/operations/adjust.d.ts.map +1 -1
  138. package/dist/types/src/operations/update-companion.d.ts.map +1 -1
  139. package/dist/types/src/translations.d.ts +2 -2
  140. package/dist/types/src/translations.d.ts.map +1 -1
  141. package/dist/types/src/types/DeckCapabilities.d.ts +2 -2
  142. package/dist/types/src/types/DeckCapabilities.d.ts.map +1 -1
  143. package/dist/types/src/types/DeckOperation.d.ts +2 -2
  144. package/dist/types/src/types/DeckOperation.d.ts.map +1 -1
  145. package/dist/types/src/types/schema.d.ts +7 -7
  146. package/dist/types/src/types/schema.d.ts.map +1 -1
  147. package/dist/types/src/util/companion-view-state.d.ts +16 -0
  148. package/dist/types/src/util/companion-view-state.d.ts.map +1 -0
  149. package/dist/types/src/util/index.d.ts +1 -0
  150. package/dist/types/src/util/index.d.ts.map +1 -1
  151. package/dist/types/src/util/plank-url-params.d.ts +2 -0
  152. package/dist/types/src/util/plank-url-params.d.ts.map +1 -1
  153. package/dist/types/tsconfig.tsbuildinfo +1 -1
  154. package/package.json +49 -47
  155. package/src/DeckPlugin.ts +1 -2
  156. package/src/capabilities/app-graph-builder.ts +3 -2
  157. package/src/capabilities/notification-tracker.ts +3 -1
  158. package/src/capabilities/react-surface.tsx +1 -1
  159. package/src/components/Companion/Companion.tsx +91 -0
  160. package/src/components/Companion/index.ts +5 -0
  161. package/src/components/Matrix/Matrix.stories.tsx +13 -26
  162. package/src/components/Matrix/Matrix.tsx +3 -3
  163. package/src/components/Matrix/SPEC.md +1 -1
  164. package/src/components/Matrix/index.ts +1 -1
  165. package/src/components/Pane/Pane.stories.tsx +92 -0
  166. package/src/components/Pane/Pane.tsx +178 -0
  167. package/src/components/Pane/index.ts +5 -0
  168. package/src/components/Plank/Plank.stories.tsx +103 -0
  169. package/src/components/Plank/Plank.tsx +149 -0
  170. package/src/components/Plank/index.ts +5 -0
  171. package/src/components/index.ts +3 -4
  172. package/src/containers/Deck/Deck.tsx +3 -3
  173. package/src/containers/Deck/DeckPlank.tsx +187 -0
  174. package/src/containers/Deck/DeckViewport.tsx +116 -243
  175. package/src/containers/{Plank → Deck}/PlankControls.tsx +19 -17
  176. package/src/containers/Deck/PlankFallback.tsx +42 -0
  177. package/src/containers/Deck/useDeckPlank.ts +219 -0
  178. package/src/containers/DeckLayout/DeckLayout.stories.tsx +38 -51
  179. package/src/containers/DeckLayout/Dialog.tsx +1 -1
  180. package/src/{components → containers}/DeckSettings/DeckSettings.stories.tsx +1 -1
  181. package/src/containers/DeckSettings/DeckSettings.tsx +42 -0
  182. package/src/containers/Sidebar/ComplementarySidebar.tsx +2 -2
  183. package/src/containers/index.ts +1 -1
  184. package/src/hooks/index.ts +2 -0
  185. package/src/hooks/useCompanionSplit.ts +78 -0
  186. package/src/hooks/useCompanions.ts +39 -7
  187. package/src/hooks/useDeckCompanions.ts +3 -3
  188. package/src/hooks/useDeckState.ts +1 -1
  189. package/src/hooks/useSelectedCompanionVariant.ts +14 -0
  190. package/src/index.ts +7 -0
  191. package/src/operations/adjust.ts +23 -12
  192. package/src/operations/update-companion.ts +10 -6
  193. package/src/translations.ts +2 -2
  194. package/src/types/DeckCapabilities.ts +1 -1
  195. package/src/types/DeckOperation.ts +2 -1
  196. package/src/types/schema.ts +5 -4
  197. package/src/util/companion-view-state.ts +28 -0
  198. package/src/util/index.ts +1 -0
  199. package/src/util/plank-url-params.test.ts +5 -0
  200. package/src/util/plank-url-params.ts +3 -1
  201. package/src/util/sanitize-persisted-state.test.ts +1 -1
  202. package/src/util/set-active.test.ts +1 -1
  203. package/dist/lib/neutral/DeckLayout-VX2BP4Q2.mjs.map +0 -7
  204. package/dist/lib/neutral/DeckSettings-CQQZJ2YS.mjs +0 -27
  205. package/dist/lib/neutral/DeckSettings-CQQZJ2YS.mjs.map +0 -7
  206. package/dist/lib/neutral/adjust-7CZI4GK3.mjs.map +0 -7
  207. package/dist/lib/neutral/app-graph-builder-6HTXRTU2.mjs.map +0 -7
  208. package/dist/lib/neutral/chunk-CL7JIOI5.mjs.map +0 -7
  209. package/dist/lib/neutral/chunk-DNG3L5QN.mjs +0 -1304
  210. package/dist/lib/neutral/chunk-DNG3L5QN.mjs.map +0 -7
  211. package/dist/lib/neutral/chunk-NGX6RXNV.mjs.map +0 -7
  212. package/dist/lib/neutral/chunk-UZLAR4DR.mjs +0 -8
  213. package/dist/lib/neutral/notification-tracker-CUDFRDCF.mjs.map +0 -7
  214. package/dist/lib/neutral/update-companion-5LCY6QME.mjs.map +0 -7
  215. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +0 -1
  216. package/dist/types/src/components/DeckSettings/index.d.ts.map +0 -1
  217. package/dist/types/src/containers/Plank/Plank.d.ts +0 -17
  218. package/dist/types/src/containers/Plank/Plank.d.ts.map +0 -1
  219. package/dist/types/src/containers/Plank/Plank.stories.d.ts +0 -67
  220. package/dist/types/src/containers/Plank/Plank.stories.d.ts.map +0 -1
  221. package/dist/types/src/containers/Plank/PlankComponent.d.ts +0 -17
  222. package/dist/types/src/containers/Plank/PlankComponent.d.ts.map +0 -1
  223. package/dist/types/src/containers/Plank/PlankContent.d.ts +0 -10
  224. package/dist/types/src/containers/Plank/PlankContent.d.ts.map +0 -1
  225. package/dist/types/src/containers/Plank/PlankControls.d.ts.map +0 -1
  226. package/dist/types/src/containers/Plank/PlankError.d.ts +0 -15
  227. package/dist/types/src/containers/Plank/PlankError.d.ts.map +0 -1
  228. package/dist/types/src/containers/Plank/PlankHeading.d.ts +0 -22
  229. package/dist/types/src/containers/Plank/PlankHeading.d.ts.map +0 -1
  230. package/dist/types/src/containers/Plank/PlankLoading.d.ts +0 -3
  231. package/dist/types/src/containers/Plank/PlankLoading.d.ts.map +0 -1
  232. package/dist/types/src/containers/Plank/PlankRoot.d.ts +0 -37
  233. package/dist/types/src/containers/Plank/PlankRoot.d.ts.map +0 -1
  234. package/dist/types/src/containers/Plank/index.d.ts +0 -4
  235. package/dist/types/src/containers/Plank/index.d.ts.map +0 -1
  236. package/src/components/DeckSettings/DeckSettings.tsx +0 -34
  237. package/src/containers/Plank/Plank.stories.tsx +0 -108
  238. package/src/containers/Plank/Plank.tsx +0 -22
  239. package/src/containers/Plank/PlankComponent.tsx +0 -223
  240. package/src/containers/Plank/PlankContent.tsx +0 -45
  241. package/src/containers/Plank/PlankError.tsx +0 -82
  242. package/src/containers/Plank/PlankHeading.tsx +0 -236
  243. package/src/containers/Plank/PlankLoading.tsx +0 -10
  244. package/src/containers/Plank/PlankRoot.tsx +0 -49
  245. package/src/containers/Plank/index.ts +0 -7
  246. /package/dist/lib/neutral/{add-toast-APKTCLIA.mjs.map → add-toast-BHNEH5UY.mjs.map} +0 -0
  247. /package/dist/lib/neutral/{chunk-UZLAR4DR.mjs.map → chunk-FKGYHBUB.mjs.map} +0 -0
  248. /package/dist/lib/neutral/{revert-workspace-24TKG3I7.mjs.map → revert-workspace-RXWP2TEE.mjs.map} +0 -0
  249. /package/dist/lib/neutral/{scroll-into-view-3VXT6TWC.mjs.map → scroll-into-view-HFV5EGYZ.mjs.map} +0 -0
  250. /package/dist/lib/neutral/{set-layout-mode-AZ73W52Z.mjs.map → set-layout-mode-XAGSZZGB.mjs.map} +0 -0
  251. /package/dist/lib/neutral/{switch-workspace-TBPT23CZ.mjs.map → switch-workspace-PA7N6EUX.mjs.map} +0 -0
  252. /package/dist/lib/neutral/{update-complementary-HZ7QXZYE.mjs.map → update-complementary-K55I5EV5.mjs.map} +0 -0
  253. /package/dist/lib/neutral/{update-dialog-A7W3R5EI.mjs.map → update-dialog-Z3GEXMVV.mjs.map} +0 -0
  254. /package/dist/lib/neutral/{update-plank-size-XHTYBLSD.mjs.map → update-plank-size-BTESVV7N.mjs.map} +0 -0
  255. /package/dist/lib/neutral/{update-popover-RDIHG73B.mjs.map → update-popover-QEVDSZSH.mjs.map} +0 -0
  256. /package/dist/lib/neutral/{update-sidebar-GEI7USA5.mjs.map → update-sidebar-VSRCN47U.mjs.map} +0 -0
  257. /package/dist/types/src/{components → containers}/DeckSettings/index.d.ts +0 -0
  258. /package/src/{components → containers}/DeckSettings/index.ts +0 -0
@@ -2,42 +2,36 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
- import React, {
6
- Fragment,
7
- memo,
8
- type PropsWithChildren,
9
- type UIEvent,
10
- useCallback,
11
- useEffect,
12
- useMemo,
13
- useRef,
14
- } from 'react';
5
+ import React, { type PropsWithChildren, useCallback, useEffect, useRef } from 'react';
15
6
 
16
7
  import { Surface, useOperationInvoker } from '@dxos/app-framework/ui';
17
8
  import { LayoutOperation } from '@dxos/app-toolkit';
18
- import { useAppGraph } from '@dxos/app-toolkit/ui';
19
9
  import { addEventListener } from '@dxos/async';
20
10
  import { invariant } from '@dxos/invariant';
21
- import { useNode } from '@dxos/plugin-graph';
22
- import { IconButton, Main, type MainContentProps, useOnTransition, useTranslation } from '@dxos/react-ui';
23
- import { mainPaddingTransitions } from '@dxos/react-ui';
24
- import { DEFAULT_HORIZONTAL_SIZE, Stack, StackContext } from '@dxos/react-ui-stack';
11
+ import { IconButton, Main, type MainContentProps, ScrollArea, useOnTransition, useTranslation } from '@dxos/react-ui';
12
+ import { mainIntrinsicSize, mainPaddingTransitions } from '@dxos/react-ui';
13
+ import { Mosaic, type MosaicStackTileComponent, type MosaicTileProps } from '@dxos/react-ui-mosaic';
25
14
  import { hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';
26
15
 
27
- import { useBreakpoints, useCompanions, useDeckState, useSelectedCompanion } from '#hooks';
16
+ import { useBreakpoints, useDeckState } from '#hooks';
28
17
  import { meta } from '#meta';
29
- import { DeckOperation, getMode, Keyshortcuts } from '#types';
18
+ import { DeckOperation, Keyshortcuts, getMode } from '#types';
30
19
 
31
20
  import { layoutAppliesTopbar } from '../../util';
32
- import { Plank, PlankRootProps, type PlankComponentProps } from '../Plank';
33
21
  import {
34
22
  ToggleComplementarySidebarButton as NaturalToggleComplementarySidebarButton,
35
23
  ToggleSidebarButton as NaturalToggleSidebarButton,
36
24
  } from '../Sidebar';
25
+ import { DeckPlank } from './DeckPlank';
37
26
  import { useDeckContext } from './DeckRoot';
38
27
 
39
28
  const DECK_VIEWPORT_NAME = 'DeckViewport';
40
29
 
30
+ // Multi-mode plank extents (rem); each plank persists its own width via `plankSizing`.
31
+ const DEFAULT_PLANK_SIZE = 50;
32
+ const MIN_PLANK_SIZE = 20;
33
+ const MAX_PLANK_SIZE = 120;
34
+
41
35
  //
42
36
  // DeckViewport
43
37
  //
@@ -45,11 +39,10 @@ const DECK_VIEWPORT_NAME = 'DeckViewport';
45
39
  export type DeckViewportProps = PropsWithChildren;
46
40
 
47
41
  /**
48
- * Deck viewport that renders the main content area and sets CSS variables for plank sizing.
42
+ * Deck viewport that renders the main content area and sets CSS variables for sidebar widths.
49
43
  */
50
44
  export const DeckViewport = ({ children }: DeckViewportProps) => {
51
45
  const {
52
- deck: { active, plankSizing },
53
46
  state: { sidebarState, complementarySidebarState },
54
47
  settings,
55
48
  layoutMode,
@@ -81,8 +74,6 @@ export const DeckViewport = ({ children }: DeckViewportProps) => {
81
74
  : complementarySidebarState === 'collapsed'
82
75
  ? 'var(--dx-rail-size)'
83
76
  : '0',
84
- '--main-content-first-width': `${plankSizing[active[0] ?? 'never'] ?? DEFAULT_HORIZONTAL_SIZE}rem`,
85
- '--main-content-last-width': `${plankSizing[active[(active.length ?? 1) - 1] ?? 'never'] ?? DEFAULT_HORIZONTAL_SIZE}rem`,
86
77
  } as MainContentProps['style']
87
78
  }
88
79
  >
@@ -119,8 +110,7 @@ export const DeckContentEmpty = () => {
119
110
  */
120
111
  export const DeckSoloMode = () => {
121
112
  const { deck, settings, layoutMode, onLayoutChange } = useDeckContext('DeckSoloMode');
122
- const { companionOpen, companionVariant, fullscreen, solo } = deck;
123
- const effectiveCompanionVariant = fullscreen ? undefined : companionOpen ? companionVariant : undefined;
113
+ const { companionOpen, fullscreen, solo } = deck;
124
114
  const breakpoint = useBreakpoints();
125
115
  const topbar = layoutAppliesTopbar(breakpoint, layoutMode);
126
116
  invariant(solo);
@@ -146,21 +136,14 @@ export const DeckSoloMode = () => {
146
136
  <div className='relative overflow-hidden bg-deck-surface'>
147
137
  <DeckSidebarToggles topbar={topbar} fullscreen={fullscreen} />
148
138
  {fullscreen && <ExitFullscreenButton onExit={() => onLayoutChange({ mode: 'solo--fullscreen' })} />}
149
- <StackContext.Provider
150
- value={{
151
- orientation: 'horizontal',
152
- size: 'contain',
153
- rail: true,
154
- }}
155
- >
156
- <PlankContainer
157
- id={solo}
158
- part='solo'
159
- layoutMode={layoutMode}
160
- companionVariant={effectiveCompanionVariant}
161
- settings={settings}
162
- />
163
- </StackContext.Provider>
139
+ <DeckPlank
140
+ id={solo}
141
+ part='solo'
142
+ layoutMode={layoutMode}
143
+ companionShown={!fullscreen && companionOpen}
144
+ settings={settings}
145
+ classNames={mx('absolute inset-0', mainIntrinsicSize)}
146
+ />
164
147
  </div>
165
148
  );
166
149
  };
@@ -169,25 +152,65 @@ export const DeckSoloMode = () => {
169
152
  // DeckMultiMode
170
153
  //
171
154
 
155
+ const getPlankId = (id: string) => id;
156
+
172
157
  /**
173
- * Multi-plank horizontal scrolling layout.
158
+ * Tile wrapping a {@link DeckPlank} in a resizable {@link Mosaic.Tile}; the committed width persists via
159
+ * `plankSizing`. Reads the deck context directly since the Mosaic stack renders tiles by id.
160
+ */
161
+ const DeckPlankTile: MosaicStackTileComponent<string> = (props) => {
162
+ const id = props.data;
163
+ const { deck, settings, layoutMode } = useDeckContext('DeckPlankTile');
164
+ const { invokePromise } = useOperationInvoker();
165
+
166
+ const handleSizeChange = useCallback<NonNullable<MosaicTileProps['onSizeChange']>>(
167
+ (size) => {
168
+ if (typeof size === 'number') {
169
+ void invokePromise(DeckOperation.UpdatePlankSize, { id, size: Math.round(size) });
170
+ }
171
+ },
172
+ [invokePromise, id],
173
+ );
174
+
175
+ return (
176
+ <Mosaic.Tile
177
+ {...props}
178
+ classNames='relative h-full'
179
+ size={deck.plankSizing[id] ?? DEFAULT_PLANK_SIZE}
180
+ minSize={MIN_PLANK_SIZE}
181
+ maxSize={MAX_PLANK_SIZE}
182
+ onSizeChange={handleSizeChange}
183
+ >
184
+ <DeckPlank
185
+ id={id}
186
+ part='multi'
187
+ layoutMode={layoutMode}
188
+ active={deck.active}
189
+ companionShown={deck.companionOpen}
190
+ settings={settings}
191
+ classNames='size-full'
192
+ />
193
+ <Mosaic.ResizeHandle />
194
+ </Mosaic.Tile>
195
+ );
196
+ };
197
+
198
+ /**
199
+ * Multi-plank horizontal scrolling layout. Planks are resizable {@link Mosaic.Tile}s in a horizontal
200
+ * {@link Mosaic.Stack}; the scroll position is preserved across solo↔multi transitions and the
201
+ * just-navigated plank is scrolled into view.
174
202
  */
175
203
  export const DeckMultiMode = () => {
176
- const {
177
- deck: { active, companionVariant, fullscreen },
178
- settings,
179
- layoutMode,
180
- } = useDeckContext('DeckMultiMode');
181
- /** In multi mode the companion column is always shown when the last plank has companions (not gated by `companionOpen`). */
182
- const effectiveCompanionVariant = companionVariant;
183
- const lastPlankId = active[active.length - 1];
184
- const lastPlankCompanions = useCompanions(lastPlankId);
204
+ const { deck, layoutMode } = useDeckContext('DeckMultiMode');
205
+ const { active, fullscreen } = deck;
206
+ const { state } = useDeckState();
207
+ const { invokePromise } = useOperationInvoker();
185
208
  const breakpoint = useBreakpoints();
186
209
  const topbar = layoutAppliesTopbar(breakpoint, layoutMode);
187
- const deckRef = useRef<HTMLDivElement>(null);
210
+ const viewportRef = useRef<HTMLDivElement>(null);
188
211
 
189
- /** Clear scroll restoration state if the window is resized. */
190
- const scrollLeftRef = useRef<number>(null);
212
+ // Preserve horizontal scroll position across solo↔multi transitions; a window resize invalidates it.
213
+ const scrollLeftRef = useRef<number | null>(null);
191
214
  useEffect(
192
215
  () =>
193
216
  addEventListener(window, 'resize', () => {
@@ -195,67 +218,58 @@ export const DeckMultiMode = () => {
195
218
  }),
196
219
  [],
197
220
  );
198
-
199
221
  const restoreScroll = useCallback(() => {
200
- if (deckRef.current && scrollLeftRef.current != null) {
201
- deckRef.current.scrollLeft = scrollLeftRef.current;
222
+ if (viewportRef.current && scrollLeftRef.current != null) {
223
+ viewportRef.current.scrollLeft = scrollLeftRef.current;
202
224
  }
203
225
  }, []);
204
-
205
226
  useOnTransition(layoutMode, (mode) => mode !== 'multi', 'multi', restoreScroll);
206
-
207
- /** Save scroll position as the user scrolls. */
208
- const handleScroll = useCallback((event: UIEvent) => {
209
- if (event.currentTarget === event.target) {
210
- scrollLeftRef.current = (event.target as HTMLDivElement).scrollLeft;
227
+ // Save scroll position as the user scrolls (ScrollArea.Viewport does not forward `onScroll`).
228
+ useEffect(() => {
229
+ const viewport = viewportRef.current;
230
+ if (!viewport) {
231
+ return;
211
232
  }
233
+ return addEventListener(viewport, 'scroll', () => {
234
+ scrollLeftRef.current = viewport.scrollLeft;
235
+ });
212
236
  }, []);
213
237
 
214
- // Create order map.
215
- // In multi mode only the last plank hosts the companion pane, so only that plank adds the extra column.
216
- const { order, itemsCount } = useMemo(() => {
217
- const lastHasCompanions = lastPlankCompanions.length > 0;
218
- return active.reduce(
219
- (acc: { order: Record<string, number>; itemsCount: number }, entryId, index) => {
220
- const isLastPlank = index === active.length - 1;
221
- acc.order[entryId] = acc.itemsCount + 1;
222
- acc.itemsCount += lastHasCompanions && isLastPlank ? 3 : 2;
223
- return acc;
224
- },
225
- { order: {}, itemsCount: 0 },
226
- );
227
- }, [active, lastPlankCompanions.length]);
238
+ // Scroll the just-navigated plank into view, then clear the one-shot flag.
239
+ useEffect(() => {
240
+ const id = state.scrollIntoView;
241
+ const viewport = viewportRef.current;
242
+ if (!id || !viewport) {
243
+ return;
244
+ }
245
+
246
+ const tile = viewport.querySelector<HTMLElement>(`[data-object-id="${CSS.escape(id)}"]`);
247
+ if (tile) {
248
+ const offset = tile.getBoundingClientRect().left - viewport.getBoundingClientRect().left + viewport.scrollLeft;
249
+ viewport.scrollTo({ left: offset, behavior: 'smooth' });
250
+ }
251
+ void invokePromise(LayoutOperation.ScrollIntoView, { subject: undefined });
252
+ }, [state.scrollIntoView, invokePromise]);
228
253
 
229
254
  return (
230
255
  <div className='relative bg-deck-surface overflow-hidden'>
231
256
  <DeckSidebarToggles topbar={topbar} fullscreen={fullscreen} />
232
- <Stack
233
- classNames={[
234
- 'absolute h-[calc(100%-2*var(--main-spacing))] w-full inset-y-(--main-spacing) -inset-w-px',
235
- mainPaddingTransitions,
236
- ]}
237
- orientation='horizontal'
238
- size='contain'
239
- itemsCount={itemsCount - 1}
240
- onScroll={handleScroll}
241
- ref={deckRef}
242
- >
243
- {active.map((entryId) => (
244
- <Fragment key={entryId}>
245
- {/* TODO(burdon): Setting for separator. */}
246
- <PlankSeparator hidden order={order[entryId] - 1} encapsulate={!!settings?.encapsulatedPlanks} />
247
- <PlankContainer
248
- id={entryId}
249
- part='multi'
250
- active={active}
251
- order={order[entryId]}
252
- layoutMode={layoutMode}
253
- companionVariant={effectiveCompanionVariant}
254
- settings={settings}
255
- />
256
- </Fragment>
257
- ))}
258
- </Stack>
257
+ <Mosaic.Root>
258
+ <Mosaic.Container orientation='horizontal' classNames={['absolute inset-0', mainPaddingTransitions]}>
259
+ <ScrollArea.Root orientation='horizontal' classNames='size-full'>
260
+ <ScrollArea.Viewport ref={viewportRef}>
261
+ <Mosaic.Stack
262
+ orientation='horizontal'
263
+ classNames='h-full gap-(--main-spacing) px-(--main-spacing)'
264
+ getId={getPlankId}
265
+ items={active}
266
+ Tile={DeckPlankTile}
267
+ draggable={false}
268
+ />
269
+ </ScrollArea.Viewport>
270
+ </ScrollArea.Root>
271
+ </Mosaic.Container>
272
+ </Mosaic.Root>
259
273
  </div>
260
274
  );
261
275
  };
@@ -306,144 +320,3 @@ const DeckSidebarToggles = ({ topbar, fullscreen }: { topbar: boolean; fullscree
306
320
  </>
307
321
  );
308
322
  };
309
-
310
- //
311
- // PlankSeparator
312
- //
313
-
314
- const PlankSeparator = ({ order, hidden, encapsulate }: { order: number; hidden?: boolean; encapsulate?: boolean }) =>
315
- order > 0 && (
316
- <span
317
- role='separator'
318
- className={mx('row-span-2 bg-deck-surface', hidden && 'hidden', encapsulate ? 'w-0' : 'w-4')}
319
- style={{ gridColumn: order }}
320
- />
321
- );
322
-
323
- //
324
- // PlankContainer
325
- //
326
-
327
- type PlankContainerProps = Pick<PlankRootProps, 'layoutMode' | 'part' | 'settings'> &
328
- Pick<PlankComponentProps, 'id'> &
329
- Partial<Pick<PlankComponentProps, 'path' | 'order' | 'active'>> & {
330
- companionVariant?: string;
331
- };
332
-
333
- /**
334
- * Connected Plank that calls hooks and renders the radix-style Plank tree.
335
- * This is the bridge between DeckViewport (which knows about framework hooks) and
336
- * the pure Plank components (which receive everything via context).
337
- */
338
- const PlankContainer = memo(
339
- ({ id, layoutMode, part, order, settings, companionVariant, active, ...props }: PlankContainerProps) => {
340
- const { graph } = useAppGraph();
341
- const { invokePromise } = useOperationInvoker();
342
- const { state, deck } = useDeckState();
343
- const node = useNode(graph, id);
344
- const companions = useCompanions(id);
345
- const isLastPlankInMulti =
346
- layoutMode === 'multi' && active && active.length > 0 && active[active.length - 1] === id;
347
- const variantForThisPlank =
348
- layoutMode === 'multi' ? (isLastPlankInMulti ? companionVariant : undefined) : companionVariant;
349
- const { companionId } = useSelectedCompanion(companions, variantForThisPlank);
350
- const resolvedCompanionId =
351
- layoutMode === 'multi' && isLastPlankInMulti && companions.length > 0
352
- ? companionId
353
- : variantForThisPlank
354
- ? companionId
355
- : undefined;
356
- const currentCompanion = companions.find(({ id }) => id === resolvedCompanionId);
357
- const hasCompanion = !!(resolvedCompanionId && currentCompanion);
358
-
359
- const handleAdjust = useCallback(
360
- (plankId: string, type: DeckOperation.PartAdjustment) => {
361
- if (type === 'close') {
362
- if (part === 'complementary') {
363
- return invokePromise(LayoutOperation.UpdateComplementary, { state: 'collapsed' });
364
- } else if (active) {
365
- // Close the plank and everything to the right (stack pop).
366
- const index = active.indexOf(plankId);
367
- const toClose = index !== -1 ? active.slice(index) : [plankId];
368
- return invokePromise(LayoutOperation.Close, { subject: toClose });
369
- } else {
370
- return invokePromise(LayoutOperation.Close, { subject: [plankId] });
371
- }
372
- } else {
373
- return invokePromise(DeckOperation.Adjust, { type, id: plankId });
374
- }
375
- },
376
- [invokePromise, part, active],
377
- );
378
-
379
- const handleResize = useCallback(
380
- (plankId: string, size: number) => invokePromise(DeckOperation.UpdatePlankSize, { id: plankId, size }),
381
- [invokePromise],
382
- );
383
-
384
- const handleScrollIntoView = useCallback(
385
- (subject?: string) => invokePromise(LayoutOperation.ScrollIntoView, { subject }),
386
- [invokePromise],
387
- );
388
-
389
- const handleUpdateCompanion = useCallback(
390
- (companion: string | null) => invokePromise(LayoutOperation.UpdateCompanion, { subject: companion }),
391
- [invokePromise],
392
- );
393
-
394
- return (
395
- <Plank.Root
396
- graph={graph}
397
- layoutMode={layoutMode}
398
- part={part}
399
- settings={settings}
400
- popoverAnchorId={state.popoverAnchorId}
401
- scrollIntoView={state.scrollIntoView}
402
- plankSizing={deck.plankSizing}
403
- onAdjust={handleAdjust}
404
- onResize={handleResize}
405
- onScrollIntoView={handleScrollIntoView}
406
- onUpdateCompanion={handleUpdateCompanion}
407
- >
408
- <Plank.Content solo={part === 'solo'} companion={hasCompanion} encapsulate={!!settings?.encapsulatedPlanks}>
409
- <Plank.Component
410
- {...props}
411
- active={active}
412
- id={id}
413
- node={node}
414
- companioned={hasCompanion ? 'primary' : undefined}
415
- companions={hasCompanion ? [] : companions}
416
- order={order}
417
- {...(part === 'solo'
418
- ? {
419
- part: 'solo-primary',
420
- }
421
- : {
422
- part,
423
- })}
424
- />
425
- {hasCompanion && (
426
- <Plank.Component
427
- {...props}
428
- active={active}
429
- id={resolvedCompanionId}
430
- node={currentCompanion}
431
- companions={companions}
432
- companioned='companion'
433
- primary={node}
434
- {...(part === 'solo'
435
- ? {
436
- order,
437
- part: 'solo-companion',
438
- }
439
- : {
440
- order: (order ?? 0) + 1,
441
- part,
442
- })}
443
- />
444
- )}
445
- </Plank.Content>
446
- </Plank.Root>
447
- );
448
- },
449
- );
@@ -9,18 +9,11 @@ import { LayoutOperation } from '@dxos/app-toolkit';
9
9
  import { ButtonGroup, type ButtonGroupProps, type ButtonProps, IconButton, useTranslation } from '@dxos/react-ui';
10
10
 
11
11
  import { meta } from '#meta';
12
- import { type DeckAction, type LayoutMode } from '#types';
12
+ import { type DeckOperation, type LayoutMode } from '#types';
13
13
 
14
- export type PlankControlHandler = (event: DeckAction.PartAdjustment) => void;
14
+ import { type PlankCapabilities } from './useDeckPlank';
15
15
 
16
- export type PlankCapabilities = {
17
- incrementStart?: boolean;
18
- incrementEnd?: boolean;
19
- deck?: boolean;
20
- solo?: boolean;
21
- fullscreen?: boolean;
22
- companion?: boolean;
23
- };
16
+ export type PlankControlHandler = (event: DeckOperation.PartAdjustment) => void;
24
17
 
25
18
  //
26
19
  // Controls
@@ -166,13 +159,22 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
166
159
  )}
167
160
 
168
161
  {capabilities.companion && (
169
- <PlankControl
170
- label={t('open-companion.label')}
171
- classNames={buttonClassNames}
172
- data-testid='plankHeading.companion'
173
- icon='ph--square-split-horizontal--regular'
174
- onClick={() => onClick?.('companion')}
175
- />
162
+ <ButtonGroup>
163
+ <PlankControl
164
+ label={t('open-companion.label')}
165
+ classNames={buttonClassNames}
166
+ data-testid='plankHeading.companion'
167
+ icon='ph--square-split-horizontal--regular'
168
+ onClick={() => onClick?.('companion')}
169
+ />
170
+ <PlankControl
171
+ label={t('open-companion-vertical.label')}
172
+ classNames={buttonClassNames}
173
+ data-testid='plankHeading.companion-vertical'
174
+ icon='ph--square-split-vertical--regular'
175
+ onClick={() => onClick?.('companion-vertical')}
176
+ />
177
+ </ButtonGroup>
176
178
  )}
177
179
  {children}
178
180
  </ButtonGroup>
@@ -0,0 +1,42 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { useEffect } from 'react';
6
+
7
+ import { log } from '@dxos/log';
8
+ import { ErrorFallback, type ErrorFallbackProps, useTranslation } from '@dxos/react-ui';
9
+ import { descriptionMessage, mx } from '@dxos/ui-theme';
10
+
11
+ import { meta } from '#meta';
12
+
13
+ // TODO(burdon): Show skeleton: https://github.com/dxos/dxos/issues/8259
14
+ export const PlankLoading = () => <div className='grid place-items-center dx-attention-surface' />;
15
+
16
+ /** User-facing error fallback for a plank's content Surface. */
17
+ export const PlankErrorFallback = ({ error }: ErrorFallbackProps) => {
18
+ const { t } = useTranslation(meta.profile.key);
19
+
20
+ useEffect(() => {
21
+ if (error) {
22
+ log.error('plank error', { error });
23
+ }
24
+ }, [error]);
25
+
26
+ if (process.env.NODE_ENV === 'development') {
27
+ return <ErrorFallback title='Plank Error' error={error} />;
28
+ }
29
+
30
+ // Show only a generic message to end users; raw error details stay in logs / the dev fallback above.
31
+ return (
32
+ <div
33
+ role='alert'
34
+ data-testid='plank-content-error'
35
+ className='dx-attention-surface overflow-y-auto p-8 grid place-items-center'
36
+ >
37
+ <div className='flex flex-col items-center gap-2'>
38
+ <p className={mx(descriptionMessage, 'break-all rounded-md p-4')}>{t('error-fallback.message')}</p>
39
+ </div>
40
+ </div>
41
+ );
42
+ };