@dxos/plugin-deck 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLUGIN.mdl +3 -1
- package/dist/lib/neutral/{DeckLayout-VX2BP4Q2.mjs → DeckLayout-B3RUUV5P.mjs} +4 -3
- package/dist/lib/neutral/DeckLayout-B3RUUV5P.mjs.map +7 -0
- package/dist/lib/neutral/DeckPlugin.mjs +2 -6
- package/dist/lib/neutral/DeckPlugin.mjs.map +3 -3
- package/dist/lib/neutral/DeckSettings-Z7GMB7XJ.mjs +32 -0
- package/dist/lib/neutral/DeckSettings-Z7GMB7XJ.mjs.map +7 -0
- package/dist/lib/neutral/{add-toast-APKTCLIA.mjs → add-toast-BHNEH5UY.mjs} +2 -2
- package/dist/lib/neutral/{adjust-7CZI4GK3.mjs → adjust-MJB6UMRQ.mjs} +16 -10
- package/dist/lib/neutral/adjust-MJB6UMRQ.mjs.map +7 -0
- package/dist/lib/neutral/{app-graph-builder-6HTXRTU2.mjs → app-graph-builder-2F7P4IFQ.mjs} +4 -3
- package/dist/lib/neutral/app-graph-builder-2F7P4IFQ.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +5 -5
- package/dist/lib/neutral/chunk-FKGYHBUB.mjs +8 -0
- package/dist/lib/neutral/{chunk-CL7JIOI5.mjs → chunk-ICOJ4GMB.mjs} +10 -6
- package/dist/lib/neutral/chunk-ICOJ4GMB.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-NGX6RXNV.mjs → chunk-PP32NEVY.mjs} +2 -2
- package/dist/lib/neutral/chunk-PP32NEVY.mjs.map +7 -0
- package/dist/lib/neutral/chunk-SONATB35.mjs +20 -0
- package/dist/lib/neutral/chunk-SONATB35.mjs.map +7 -0
- package/dist/lib/neutral/chunk-Z2B5RSVI.mjs +1096 -0
- package/dist/lib/neutral/chunk-Z2B5RSVI.mjs.map +7 -0
- package/dist/lib/neutral/{close-KJUCFIJ5.mjs → close-2YGB5IIP.mjs} +4 -3
- package/dist/lib/neutral/{close-KJUCFIJ5.mjs.map → close-2YGB5IIP.mjs.map} +1 -1
- package/dist/lib/neutral/components/index.mjs +201 -16
- package/dist/lib/neutral/components/index.mjs.map +4 -4
- package/dist/lib/neutral/containers/index.mjs +6 -11
- package/dist/lib/neutral/containers/index.mjs.map +3 -3
- package/dist/lib/neutral/hooks/index.mjs +101 -17
- package/dist/lib/neutral/hooks/index.mjs.map +4 -4
- package/dist/lib/neutral/index.mjs +7 -1
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/{notification-tracker-CUDFRDCF.mjs → notification-tracker-Y5CRTWJM.mjs} +4 -2
- package/dist/lib/neutral/notification-tracker-Y5CRTWJM.mjs.map +7 -0
- package/dist/lib/neutral/{open-VU7YS3HB.mjs → open-P6XQ56M7.mjs} +4 -3
- package/dist/lib/neutral/{open-VU7YS3HB.mjs.map → open-P6XQ56M7.mjs.map} +1 -1
- package/dist/lib/neutral/operations/index.mjs +1 -1
- package/dist/lib/neutral/plugin.mjs +1 -1
- package/dist/lib/neutral/{react-surface-O4POQWYL.mjs → react-surface-H63FXUK3.mjs} +2 -2
- package/dist/lib/neutral/{react-surface-O4POQWYL.mjs.map → react-surface-H63FXUK3.mjs.map} +1 -1
- package/dist/lib/neutral/{revert-workspace-24TKG3I7.mjs → revert-workspace-RXWP2TEE.mjs} +2 -2
- package/dist/lib/neutral/{scroll-into-view-3VXT6TWC.mjs → scroll-into-view-HFV5EGYZ.mjs} +2 -2
- package/dist/lib/neutral/{set-RFOLTI57.mjs → set-HOOVB7E4.mjs} +4 -3
- package/dist/lib/neutral/{set-RFOLTI57.mjs.map → set-HOOVB7E4.mjs.map} +1 -1
- package/dist/lib/neutral/{set-layout-mode-AZ73W52Z.mjs → set-layout-mode-XAGSZZGB.mjs} +2 -2
- package/dist/lib/neutral/{state-D3YXB5NP.mjs → state-F4X5GPGZ.mjs} +3 -2
- package/dist/lib/neutral/{state-D3YXB5NP.mjs.map → state-F4X5GPGZ.mjs.map} +1 -1
- package/dist/lib/neutral/{switch-workspace-TBPT23CZ.mjs → switch-workspace-PA7N6EUX.mjs} +2 -2
- package/dist/lib/neutral/translations.mjs +2 -2
- package/dist/lib/neutral/translations.mjs.map +2 -2
- package/dist/lib/neutral/types/index.mjs +1 -1
- package/dist/lib/neutral/{update-companion-5LCY6QME.mjs → update-companion-6A2SKQAI.mjs} +14 -6
- package/dist/lib/neutral/update-companion-6A2SKQAI.mjs.map +7 -0
- package/dist/lib/neutral/{update-complementary-HZ7QXZYE.mjs → update-complementary-K55I5EV5.mjs} +2 -2
- package/dist/lib/neutral/{update-dialog-A7W3R5EI.mjs → update-dialog-Z3GEXMVV.mjs} +2 -2
- package/dist/lib/neutral/{update-plank-size-XHTYBLSD.mjs → update-plank-size-BTESVV7N.mjs} +2 -2
- package/dist/lib/neutral/{update-popover-RDIHG73B.mjs → update-popover-QEVDSZSH.mjs} +2 -2
- package/dist/lib/neutral/{update-sidebar-GEI7USA5.mjs → update-sidebar-VSRCN47U.mjs} +2 -2
- package/dist/lib/neutral/{url-handler-PUS4X33T.mjs → url-handler-ERZXXEYR.mjs} +3 -2
- package/dist/lib/neutral/{url-handler-PUS4X33T.mjs.map → url-handler-ERZXXEYR.mjs.map} +1 -1
- package/dist/types/src/DeckPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/notification-tracker.d.ts +1 -1
- package/dist/types/src/capabilities/notification-tracker.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-root.d.ts +1 -5
- package/dist/types/src/capabilities/react-root.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/settings.d.ts +1 -3
- package/dist/types/src/capabilities/settings.d.ts.map +1 -1
- package/dist/types/src/capabilities/state.d.ts +1 -187
- package/dist/types/src/capabilities/state.d.ts.map +1 -1
- package/dist/types/src/capabilities/tools.d.ts +1 -1
- package/dist/types/src/capabilities/url-handler.d.ts +1 -1
- package/dist/types/src/components/Companion/Companion.d.ts +17 -0
- package/dist/types/src/components/Companion/Companion.d.ts.map +1 -0
- package/dist/types/src/components/Companion/index.d.ts +2 -0
- package/dist/types/src/components/Companion/index.d.ts.map +1 -0
- package/dist/types/src/components/Matrix/Matrix.d.ts +1 -1
- package/dist/types/src/components/Matrix/Matrix.d.ts.map +1 -1
- package/dist/types/src/components/Matrix/Matrix.stories.d.ts +2 -2
- package/dist/types/src/components/Matrix/Matrix.stories.d.ts.map +1 -1
- package/dist/types/src/components/Matrix/index.d.ts +1 -1
- package/dist/types/src/components/Matrix/index.d.ts.map +1 -1
- package/dist/types/src/components/Pane/Pane.d.ts +45 -0
- package/dist/types/src/components/Pane/Pane.d.ts.map +1 -0
- package/dist/types/src/components/Pane/Pane.stories.d.ts +7 -0
- package/dist/types/src/components/Pane/Pane.stories.d.ts.map +1 -0
- package/dist/types/src/components/Pane/index.d.ts +2 -0
- package/dist/types/src/components/Pane/index.d.ts.map +1 -0
- package/dist/types/src/components/Plank/Plank.d.ts +74 -0
- package/dist/types/src/components/Plank/Plank.d.ts.map +1 -0
- package/dist/types/src/components/Plank/Plank.stories.d.ts +6 -0
- package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -0
- package/dist/types/src/components/Plank/index.d.ts +2 -0
- package/dist/types/src/components/Plank/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +3 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/Deck/Deck.d.ts +1 -1
- package/dist/types/src/containers/Deck/Deck.d.ts.map +1 -1
- package/dist/types/src/containers/Deck/Deck.stories.d.ts +2 -2
- package/dist/types/src/containers/Deck/DeckPlank.d.ts +21 -0
- package/dist/types/src/containers/Deck/DeckPlank.d.ts.map +1 -0
- package/dist/types/src/containers/Deck/DeckViewport.d.ts +3 -1
- package/dist/types/src/containers/Deck/DeckViewport.d.ts.map +1 -1
- package/dist/types/src/containers/{Plank → Deck}/PlankControls.d.ts +3 -10
- package/dist/types/src/containers/Deck/PlankControls.d.ts.map +1 -0
- package/dist/types/src/containers/Deck/PlankFallback.d.ts +6 -0
- package/dist/types/src/containers/Deck/PlankFallback.d.ts.map +1 -0
- package/dist/types/src/containers/Deck/useDeckPlank.d.ts +49 -0
- package/dist/types/src/containers/Deck/useDeckPlank.d.ts.map +1 -0
- package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts +2 -2
- package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts.map +1 -1
- package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.d.ts +1 -1
- package/dist/types/src/containers/DeckSettings/DeckSettings.d.ts.map +1 -0
- package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.stories.d.ts +2 -2
- package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.stories.d.ts.map +1 -1
- package/dist/types/src/containers/DeckSettings/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +1 -1
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useCompanionSplit.d.ts +28 -0
- package/dist/types/src/hooks/useCompanionSplit.d.ts.map +1 -0
- package/dist/types/src/hooks/useCompanions.d.ts +11 -7
- package/dist/types/src/hooks/useCompanions.d.ts.map +1 -1
- package/dist/types/src/hooks/useDeckCompanions.d.ts +2 -2
- package/dist/types/src/hooks/useDeckCompanions.d.ts.map +1 -1
- package/dist/types/src/hooks/useDeckState.d.ts +1 -1
- package/dist/types/src/hooks/useDeckState.d.ts.map +1 -1
- package/dist/types/src/hooks/useSelectedCompanionVariant.d.ts +6 -0
- package/dist/types/src/hooks/useSelectedCompanionVariant.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +4 -0
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/adjust.d.ts.map +1 -1
- package/dist/types/src/operations/update-companion.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -2
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/DeckCapabilities.d.ts +2 -2
- package/dist/types/src/types/DeckCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/DeckOperation.d.ts +2 -2
- package/dist/types/src/types/DeckOperation.d.ts.map +1 -1
- package/dist/types/src/types/schema.d.ts +7 -7
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/util/companion-view-state.d.ts +16 -0
- package/dist/types/src/util/companion-view-state.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/plank-url-params.d.ts +2 -0
- package/dist/types/src/util/plank-url-params.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +49 -47
- package/src/DeckPlugin.ts +1 -2
- package/src/capabilities/app-graph-builder.ts +3 -2
- package/src/capabilities/notification-tracker.ts +3 -1
- package/src/capabilities/react-surface.tsx +1 -1
- package/src/components/Companion/Companion.tsx +91 -0
- package/src/components/Companion/index.ts +5 -0
- package/src/components/Matrix/Matrix.stories.tsx +13 -26
- package/src/components/Matrix/Matrix.tsx +3 -3
- package/src/components/Matrix/SPEC.md +1 -1
- package/src/components/Matrix/index.ts +1 -1
- package/src/components/Pane/Pane.stories.tsx +92 -0
- package/src/components/Pane/Pane.tsx +178 -0
- package/src/components/Pane/index.ts +5 -0
- package/src/components/Plank/Plank.stories.tsx +103 -0
- package/src/components/Plank/Plank.tsx +149 -0
- package/src/components/Plank/index.ts +5 -0
- package/src/components/index.ts +3 -4
- package/src/containers/Deck/Deck.tsx +3 -3
- package/src/containers/Deck/DeckPlank.tsx +187 -0
- package/src/containers/Deck/DeckViewport.tsx +116 -243
- package/src/containers/{Plank → Deck}/PlankControls.tsx +19 -17
- package/src/containers/Deck/PlankFallback.tsx +42 -0
- package/src/containers/Deck/useDeckPlank.ts +219 -0
- package/src/containers/DeckLayout/DeckLayout.stories.tsx +38 -51
- package/src/containers/DeckLayout/Dialog.tsx +1 -1
- package/src/{components → containers}/DeckSettings/DeckSettings.stories.tsx +1 -1
- package/src/containers/DeckSettings/DeckSettings.tsx +42 -0
- package/src/containers/Sidebar/ComplementarySidebar.tsx +2 -2
- package/src/containers/index.ts +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCompanionSplit.ts +78 -0
- package/src/hooks/useCompanions.ts +39 -7
- package/src/hooks/useDeckCompanions.ts +3 -3
- package/src/hooks/useDeckState.ts +1 -1
- package/src/hooks/useSelectedCompanionVariant.ts +14 -0
- package/src/index.ts +7 -0
- package/src/operations/adjust.ts +23 -12
- package/src/operations/update-companion.ts +10 -6
- package/src/translations.ts +2 -2
- package/src/types/DeckCapabilities.ts +1 -1
- package/src/types/DeckOperation.ts +2 -1
- package/src/types/schema.ts +5 -4
- package/src/util/companion-view-state.ts +28 -0
- package/src/util/index.ts +1 -0
- package/src/util/plank-url-params.test.ts +5 -0
- package/src/util/plank-url-params.ts +3 -1
- package/src/util/sanitize-persisted-state.test.ts +1 -1
- package/src/util/set-active.test.ts +1 -1
- package/dist/lib/neutral/DeckLayout-VX2BP4Q2.mjs.map +0 -7
- package/dist/lib/neutral/DeckSettings-CQQZJ2YS.mjs +0 -27
- package/dist/lib/neutral/DeckSettings-CQQZJ2YS.mjs.map +0 -7
- package/dist/lib/neutral/adjust-7CZI4GK3.mjs.map +0 -7
- package/dist/lib/neutral/app-graph-builder-6HTXRTU2.mjs.map +0 -7
- package/dist/lib/neutral/chunk-CL7JIOI5.mjs.map +0 -7
- package/dist/lib/neutral/chunk-DNG3L5QN.mjs +0 -1304
- package/dist/lib/neutral/chunk-DNG3L5QN.mjs.map +0 -7
- package/dist/lib/neutral/chunk-NGX6RXNV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UZLAR4DR.mjs +0 -8
- package/dist/lib/neutral/notification-tracker-CUDFRDCF.mjs.map +0 -7
- package/dist/lib/neutral/update-companion-5LCY6QME.mjs.map +0 -7
- package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +0 -1
- package/dist/types/src/components/DeckSettings/index.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/Plank.d.ts +0 -17
- package/dist/types/src/containers/Plank/Plank.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/Plank.stories.d.ts +0 -67
- package/dist/types/src/containers/Plank/Plank.stories.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/PlankComponent.d.ts +0 -17
- package/dist/types/src/containers/Plank/PlankComponent.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/PlankContent.d.ts +0 -10
- package/dist/types/src/containers/Plank/PlankContent.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/PlankControls.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/PlankError.d.ts +0 -15
- package/dist/types/src/containers/Plank/PlankError.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/PlankHeading.d.ts +0 -22
- package/dist/types/src/containers/Plank/PlankHeading.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/PlankLoading.d.ts +0 -3
- package/dist/types/src/containers/Plank/PlankLoading.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/PlankRoot.d.ts +0 -37
- package/dist/types/src/containers/Plank/PlankRoot.d.ts.map +0 -1
- package/dist/types/src/containers/Plank/index.d.ts +0 -4
- package/dist/types/src/containers/Plank/index.d.ts.map +0 -1
- package/src/components/DeckSettings/DeckSettings.tsx +0 -34
- package/src/containers/Plank/Plank.stories.tsx +0 -108
- package/src/containers/Plank/Plank.tsx +0 -22
- package/src/containers/Plank/PlankComponent.tsx +0 -223
- package/src/containers/Plank/PlankContent.tsx +0 -45
- package/src/containers/Plank/PlankError.tsx +0 -82
- package/src/containers/Plank/PlankHeading.tsx +0 -236
- package/src/containers/Plank/PlankLoading.tsx +0 -10
- package/src/containers/Plank/PlankRoot.tsx +0 -49
- package/src/containers/Plank/index.ts +0 -7
- /package/dist/lib/neutral/{add-toast-APKTCLIA.mjs.map → add-toast-BHNEH5UY.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-UZLAR4DR.mjs.map → chunk-FKGYHBUB.mjs.map} +0 -0
- /package/dist/lib/neutral/{revert-workspace-24TKG3I7.mjs.map → revert-workspace-RXWP2TEE.mjs.map} +0 -0
- /package/dist/lib/neutral/{scroll-into-view-3VXT6TWC.mjs.map → scroll-into-view-HFV5EGYZ.mjs.map} +0 -0
- /package/dist/lib/neutral/{set-layout-mode-AZ73W52Z.mjs.map → set-layout-mode-XAGSZZGB.mjs.map} +0 -0
- /package/dist/lib/neutral/{switch-workspace-TBPT23CZ.mjs.map → switch-workspace-PA7N6EUX.mjs.map} +0 -0
- /package/dist/lib/neutral/{update-complementary-HZ7QXZYE.mjs.map → update-complementary-K55I5EV5.mjs.map} +0 -0
- /package/dist/lib/neutral/{update-dialog-A7W3R5EI.mjs.map → update-dialog-Z3GEXMVV.mjs.map} +0 -0
- /package/dist/lib/neutral/{update-plank-size-XHTYBLSD.mjs.map → update-plank-size-BTESVV7N.mjs.map} +0 -0
- /package/dist/lib/neutral/{update-popover-RDIHG73B.mjs.map → update-popover-QEVDSZSH.mjs.map} +0 -0
- /package/dist/lib/neutral/{update-sidebar-GEI7USA5.mjs.map → update-sidebar-VSRCN47U.mjs.map} +0 -0
- /package/dist/types/src/{components → containers}/DeckSettings/index.d.ts +0 -0
- /package/src/{components → containers}/DeckSettings/index.ts +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
9
|
+
import { type AttentionSigilAction } from '@dxos/app-toolkit/ui';
|
|
10
|
+
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
11
|
+
import { Graph, type Node, useActionRunner, useActions, useNode } from '@dxos/plugin-graph';
|
|
12
|
+
import { getLinkedVariant, useAttention } from '@dxos/react-ui-attention';
|
|
13
|
+
|
|
14
|
+
import { useBreakpoints, useCompanions, useDeckState, useSelectedCompanion, useSelectedCompanionVariant } from '#hooks';
|
|
15
|
+
import { meta } from '#meta';
|
|
16
|
+
import { DeckOperation, type LayoutMode, PLANK_COMPANION_TYPE, type ResolvedPart } from '#types';
|
|
17
|
+
|
|
18
|
+
/** Sigil-menu dispositions surfaced as plank actions. */
|
|
19
|
+
const PLANK_ACTION_DISPOSITIONS = ['list-item', 'list-item-primary', 'heading-list-item'];
|
|
20
|
+
|
|
21
|
+
/** Capability flags that gate the plank toolbar controls. */
|
|
22
|
+
export type PlankCapabilities = {
|
|
23
|
+
deck?: boolean;
|
|
24
|
+
solo?: boolean;
|
|
25
|
+
incrementStart?: boolean;
|
|
26
|
+
incrementEnd?: boolean;
|
|
27
|
+
fullscreen?: boolean;
|
|
28
|
+
companion?: boolean;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type UseDeckPlankOptions = {
|
|
32
|
+
id: string;
|
|
33
|
+
/** Resolved part for the primary plank (`solo` | `multi` | `complementary`). */
|
|
34
|
+
part: ResolvedPart;
|
|
35
|
+
layoutMode: LayoutMode;
|
|
36
|
+
/** Ordered active planks (multi mode); enables increment/close-range semantics. */
|
|
37
|
+
active?: string[];
|
|
38
|
+
/** Whether the companion pane should be shown for this plank (gated further by attention in multi-mode). */
|
|
39
|
+
companionShown?: boolean;
|
|
40
|
+
deckEnabled?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type DeckPlank = {
|
|
44
|
+
node: Node.Node | undefined;
|
|
45
|
+
companions: Node.Node[];
|
|
46
|
+
resolvedCompanionId: string | undefined;
|
|
47
|
+
currentCompanion: Node.Node | undefined;
|
|
48
|
+
hasCompanion: boolean;
|
|
49
|
+
/** Splitter orientation for the companion pane (defaults to `horizontal`). */
|
|
50
|
+
companionOrientation: 'horizontal' | 'vertical';
|
|
51
|
+
capabilities: PlankCapabilities;
|
|
52
|
+
/** Grouped sigil-menu actions, or `undefined` when the node is unresolved. */
|
|
53
|
+
sigilActions: AttentionSigilAction[][] | undefined;
|
|
54
|
+
popoverAnchorId?: string;
|
|
55
|
+
scrollIntoView?: string;
|
|
56
|
+
onAction: (action: AttentionSigilAction) => void;
|
|
57
|
+
onAdjust: (type: DeckOperation.PartAdjustment) => void;
|
|
58
|
+
onResize: (size: number) => void;
|
|
59
|
+
onScrollIntoView: (subject?: string) => void;
|
|
60
|
+
onUpdateCompanion: (companion: string | null) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolves the graph node, companions, capabilities and sigil actions for a deck plank, and exposes the
|
|
65
|
+
* operation dispatchers that mutate deck layout state. This re-homes the framework wiring that the legacy
|
|
66
|
+
* `PlankContainer`/`PlankHeading` bundled, so the presentational components stay free of capabilities.
|
|
67
|
+
*/
|
|
68
|
+
export const useDeckPlank = ({
|
|
69
|
+
id,
|
|
70
|
+
part,
|
|
71
|
+
layoutMode,
|
|
72
|
+
active,
|
|
73
|
+
companionShown,
|
|
74
|
+
deckEnabled,
|
|
75
|
+
}: UseDeckPlankOptions): DeckPlank => {
|
|
76
|
+
const { graph } = useAppGraph();
|
|
77
|
+
const { invokePromise } = useOperationInvoker();
|
|
78
|
+
const { state, deck } = useDeckState();
|
|
79
|
+
const runAction = useActionRunner();
|
|
80
|
+
const breakpoint = useBreakpoints();
|
|
81
|
+
const node = useNode(graph, id);
|
|
82
|
+
// Subscribe reactively to the node's actions: they are loaded asynchronously by `Graph.expand`
|
|
83
|
+
// below, and the node atom does not re-emit when action edges arrive, so a one-shot read would
|
|
84
|
+
// leave a freshly-created plank's sigil menu empty until an unrelated re-render.
|
|
85
|
+
const actions = useActions(graph, node?.id);
|
|
86
|
+
const companions = useCompanions(id);
|
|
87
|
+
const { hasAttention } = useAttention(id);
|
|
88
|
+
const selectedVariant = useSelectedCompanionVariant();
|
|
89
|
+
|
|
90
|
+
// The companion is shown when open; in multi mode it attaches only to the attended plank (hidden until
|
|
91
|
+
// a plank gains attention). Which companion shows follows the globally-selected variant (view state),
|
|
92
|
+
// falling back to the first when none is stored.
|
|
93
|
+
const showCompanion = !!companionShown && (layoutMode !== 'multi' || hasAttention);
|
|
94
|
+
const { companionId } = useSelectedCompanion(companions, showCompanion ? selectedVariant : undefined);
|
|
95
|
+
const resolvedCompanionId = showCompanion ? companionId : undefined;
|
|
96
|
+
const currentCompanion = companions.find((companion) => companion.id === resolvedCompanionId);
|
|
97
|
+
const hasCompanion = !!(resolvedCompanionId && currentCompanion);
|
|
98
|
+
const companionOrientation = deck.companionOrientation ?? 'horizontal';
|
|
99
|
+
|
|
100
|
+
// Ordering within the active stack drives the increment-start/end affordances.
|
|
101
|
+
const index = active ? active.findIndex((entryId) => entryId === id) : -1;
|
|
102
|
+
const isOrdered = !!active && index >= 0;
|
|
103
|
+
const canIncrementStart = isOrdered && index > 0;
|
|
104
|
+
const canIncrementEnd = isOrdered && index < (active?.length ?? 1) - 1;
|
|
105
|
+
|
|
106
|
+
const isCompanionNode = node?.type === PLANK_COMPANION_TYPE;
|
|
107
|
+
const capabilities = useMemo<PlankCapabilities>(
|
|
108
|
+
() => ({
|
|
109
|
+
deck: deckEnabled ?? true,
|
|
110
|
+
solo: breakpoint !== 'mobile' && (part === 'solo' || part === 'multi'),
|
|
111
|
+
incrementStart: canIncrementStart,
|
|
112
|
+
incrementEnd: canIncrementEnd,
|
|
113
|
+
fullscreen: !isCompanionNode,
|
|
114
|
+
// Offer to open the companion (solo, or the attended plank in multi) when one exists and isn't shown.
|
|
115
|
+
companion: !isCompanionNode && companions.length > 0 && !hasCompanion && (layoutMode !== 'multi' || hasAttention),
|
|
116
|
+
}),
|
|
117
|
+
[
|
|
118
|
+
deckEnabled,
|
|
119
|
+
breakpoint,
|
|
120
|
+
part,
|
|
121
|
+
canIncrementStart,
|
|
122
|
+
canIncrementEnd,
|
|
123
|
+
isCompanionNode,
|
|
124
|
+
layoutMode,
|
|
125
|
+
companions.length,
|
|
126
|
+
hasCompanion,
|
|
127
|
+
hasAttention,
|
|
128
|
+
],
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Load the node's child actions so the sigil menu is populated.
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const frame = requestAnimationFrame(() => {
|
|
134
|
+
if (node) {
|
|
135
|
+
void Graph.expand(graph, node.id, 'child');
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return () => cancelAnimationFrame(frame);
|
|
140
|
+
}, [graph, node]);
|
|
141
|
+
|
|
142
|
+
const variant = isCompanionNode ? getLinkedVariant(id) : undefined;
|
|
143
|
+
const sigilActions = useMemo<AttentionSigilAction[][] | undefined>(() => {
|
|
144
|
+
if (!node) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
if (variant) {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return [actions.filter((action) => PLANK_ACTION_DISPOSITIONS.includes(action.properties.disposition))].filter(
|
|
152
|
+
(group) => group.length > 0,
|
|
153
|
+
);
|
|
154
|
+
}, [actions, node, variant]);
|
|
155
|
+
|
|
156
|
+
const onAction = useCallback(
|
|
157
|
+
(action: AttentionSigilAction) => {
|
|
158
|
+
// Only actions whose `data` is a function are runnable graph actions; the menu-action view type
|
|
159
|
+
// (AttentionSigilAction) is widened, so narrow at this runtime-checked boundary.
|
|
160
|
+
if (typeof action.data === 'function') {
|
|
161
|
+
void runAction(action as Node.Action, { parent: node, caller: meta.profile.key });
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
[node, runAction],
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const onAdjust = useCallback(
|
|
168
|
+
(type: DeckOperation.PartAdjustment) => {
|
|
169
|
+
if (type === 'close') {
|
|
170
|
+
if (part === 'complementary') {
|
|
171
|
+
return invokePromise(LayoutOperation.UpdateComplementary, { state: 'collapsed' });
|
|
172
|
+
}
|
|
173
|
+
if (active) {
|
|
174
|
+
// Close the plank and everything to its right (stack pop).
|
|
175
|
+
const closeIndex = active.indexOf(id);
|
|
176
|
+
const toClose = closeIndex !== -1 ? active.slice(closeIndex) : [id];
|
|
177
|
+
return invokePromise(LayoutOperation.Close, { subject: toClose });
|
|
178
|
+
}
|
|
179
|
+
return invokePromise(LayoutOperation.Close, { subject: [id] });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return invokePromise(DeckOperation.Adjust, { type, id });
|
|
183
|
+
},
|
|
184
|
+
[invokePromise, part, active, id],
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const onResize = useCallback(
|
|
188
|
+
(size: number) => invokePromise(DeckOperation.UpdatePlankSize, { id, size }),
|
|
189
|
+
[invokePromise, id],
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const onScrollIntoView = useCallback(
|
|
193
|
+
(subject?: string) => invokePromise(LayoutOperation.ScrollIntoView, { subject }),
|
|
194
|
+
[invokePromise],
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const onUpdateCompanion = useCallback(
|
|
198
|
+
(companion: string | null) => invokePromise(LayoutOperation.UpdateCompanion, { subject: companion }),
|
|
199
|
+
[invokePromise],
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
node,
|
|
204
|
+
companions,
|
|
205
|
+
resolvedCompanionId,
|
|
206
|
+
currentCompanion,
|
|
207
|
+
hasCompanion,
|
|
208
|
+
companionOrientation,
|
|
209
|
+
capabilities,
|
|
210
|
+
sigilActions,
|
|
211
|
+
popoverAnchorId: state.popoverAnchorId,
|
|
212
|
+
scrollIntoView: state.scrollIntoView,
|
|
213
|
+
onAction,
|
|
214
|
+
onAdjust,
|
|
215
|
+
onResize,
|
|
216
|
+
onScrollIntoView,
|
|
217
|
+
onUpdateCompanion,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
@@ -17,10 +17,12 @@ import { GraphBuilder, Node, NodeMatcher, useConnections } from '@dxos/plugin-gr
|
|
|
17
17
|
import { corePlugins } from '@dxos/plugin-testing';
|
|
18
18
|
import { random } from '@dxos/random';
|
|
19
19
|
import { useAsyncEffect } from '@dxos/react-hooks';
|
|
20
|
-
import {
|
|
20
|
+
import { Panel } from '@dxos/react-ui';
|
|
21
21
|
import { linkedSegment } from '@dxos/react-ui-attention';
|
|
22
|
+
import { Listbox } from '@dxos/react-ui-list';
|
|
22
23
|
import { Syntax } from '@dxos/react-ui-syntax-highlighter';
|
|
23
24
|
import { Loading, withLayout } from '@dxos/react-ui/testing';
|
|
25
|
+
import { Position } from '@dxos/util';
|
|
24
26
|
|
|
25
27
|
import { OperationHandler } from '#capabilities';
|
|
26
28
|
import { meta as pluginMeta } from '#meta';
|
|
@@ -28,11 +30,11 @@ import { translations } from '#translations';
|
|
|
28
30
|
import {
|
|
29
31
|
DeckCapabilities,
|
|
30
32
|
type EphemeralDeckState,
|
|
33
|
+
PLANK_COMPANION_TYPE,
|
|
31
34
|
type Settings,
|
|
32
35
|
type StoredDeckState,
|
|
33
36
|
defaultDeck,
|
|
34
37
|
getMode,
|
|
35
|
-
PLANK_COMPANION_TYPE,
|
|
36
38
|
} from '#types';
|
|
37
39
|
|
|
38
40
|
import { DeckLayout } from './DeckLayout';
|
|
@@ -245,7 +247,7 @@ const TestPlugin = Plugin.define(pluginMeta).pipe(
|
|
|
245
247
|
label: 'Companion Alpha',
|
|
246
248
|
icon: 'ph--sidebar--regular',
|
|
247
249
|
data: { variant: 'alpha', parentId: node.id },
|
|
248
|
-
position:
|
|
250
|
+
position: Position.first,
|
|
249
251
|
}),
|
|
250
252
|
AppNode.makeCompanion({
|
|
251
253
|
id: linkedSegment('beta'),
|
|
@@ -276,24 +278,23 @@ const NavContainer = forwardRef<HTMLDivElement, NavContainerProps>((_props, forw
|
|
|
276
278
|
|
|
277
279
|
return (
|
|
278
280
|
<div className='dx-container overflow-y-auto p-2' ref={forwardedRef}>
|
|
279
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
</List>
|
|
281
|
+
<Listbox.Root>
|
|
282
|
+
<Listbox.Content aria-label='Navigation'>
|
|
283
|
+
{items.map((node) => (
|
|
284
|
+
<Listbox.Item
|
|
285
|
+
key={node.id}
|
|
286
|
+
id={node.id}
|
|
287
|
+
classNames={activeSet.has(node.id) ? 'bg-current-surface' : undefined}
|
|
288
|
+
onClick={() => void invokePromise(LayoutOperation.Set, { subject: [node.id] })}
|
|
289
|
+
>
|
|
290
|
+
<Listbox.ItemContent
|
|
291
|
+
icon={node.properties.icon}
|
|
292
|
+
title={typeof node.properties.label === 'string' ? node.properties.label : node.id}
|
|
293
|
+
/>
|
|
294
|
+
</Listbox.Item>
|
|
295
|
+
))}
|
|
296
|
+
</Listbox.Content>
|
|
297
|
+
</Listbox.Root>
|
|
297
298
|
</div>
|
|
298
299
|
);
|
|
299
300
|
});
|
|
@@ -312,36 +313,22 @@ const ItemComponent = ({ id }: ItemComponentProps) => {
|
|
|
312
313
|
);
|
|
313
314
|
|
|
314
315
|
return (
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
key={node.id}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}}
|
|
332
|
-
>
|
|
333
|
-
{node.properties.icon && (
|
|
334
|
-
<ListItem.Endcap>
|
|
335
|
-
<Icon icon={node.properties.icon} size={4} />
|
|
336
|
-
</ListItem.Endcap>
|
|
337
|
-
)}
|
|
338
|
-
<ListItem.Heading classNames='truncate'>
|
|
339
|
-
{typeof node.properties.label === 'string' ? node.properties.label : node.id}
|
|
340
|
-
</ListItem.Heading>
|
|
341
|
-
</ListItem.Root>
|
|
342
|
-
);
|
|
343
|
-
})}
|
|
344
|
-
</List>
|
|
316
|
+
<Listbox.Root>
|
|
317
|
+
<Listbox.Content aria-label='Items'>
|
|
318
|
+
{items.map((node) => {
|
|
319
|
+
const open = () =>
|
|
320
|
+
void invokePromise(LayoutOperation.Open, { subject: [node.id], pivotId: id, navigation: 'immediate' });
|
|
321
|
+
return (
|
|
322
|
+
<Listbox.Item key={node.id} id={node.id} classNames='dx-hover cursor-pointer' onClick={open}>
|
|
323
|
+
<Listbox.ItemContent
|
|
324
|
+
icon={node.properties.icon}
|
|
325
|
+
title={typeof node.properties.label === 'string' ? node.properties.label : node.id}
|
|
326
|
+
/>
|
|
327
|
+
</Listbox.Item>
|
|
328
|
+
);
|
|
329
|
+
})}
|
|
330
|
+
</Listbox.Content>
|
|
331
|
+
</Listbox.Root>
|
|
345
332
|
);
|
|
346
333
|
};
|
|
347
334
|
|
|
@@ -10,7 +10,7 @@ import { AlertDialog, Dialog as NaturalDialog } from '@dxos/react-ui';
|
|
|
10
10
|
|
|
11
11
|
import { useDeckState } from '#hooks';
|
|
12
12
|
|
|
13
|
-
import { PlankErrorFallback } from '../
|
|
13
|
+
import { PlankErrorFallback } from '../Deck/PlankFallback';
|
|
14
14
|
|
|
15
15
|
export const Dialog = () => {
|
|
16
16
|
const { state, updateEphemeral } = useDeckState();
|
|
@@ -11,7 +11,7 @@ import { translations } from '#translations';
|
|
|
11
11
|
import { DeckSettings } from './DeckSettings';
|
|
12
12
|
|
|
13
13
|
const meta = {
|
|
14
|
-
title: 'plugins/plugin-deck/
|
|
14
|
+
title: 'plugins/plugin-deck/containers/DeckSettings',
|
|
15
15
|
tags: ['settings'],
|
|
16
16
|
component: DeckSettings,
|
|
17
17
|
decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
8
|
+
import { useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Form } from '@dxos/react-ui-form';
|
|
10
|
+
|
|
11
|
+
import { meta } from '#meta';
|
|
12
|
+
import { Settings } from '#types';
|
|
13
|
+
|
|
14
|
+
const isSocket = !!(globalThis as any).__args;
|
|
15
|
+
|
|
16
|
+
export type DeckSettingsProps = AppSurface.SettingsProps<Settings.Settings>;
|
|
17
|
+
|
|
18
|
+
export const DeckSettings = ({ settings, onSettingsChange }: DeckSettingsProps) => {
|
|
19
|
+
const { t } = useTranslation(meta.profile.key);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Form.Root
|
|
23
|
+
variant='settings'
|
|
24
|
+
schema={Settings.Settings}
|
|
25
|
+
values={settings}
|
|
26
|
+
readonly={!onSettingsChange}
|
|
27
|
+
onValuesChanged={(values) => onSettingsChange?.((current) => ({ ...current, ...values }))}
|
|
28
|
+
>
|
|
29
|
+
<Form.Viewport scroll>
|
|
30
|
+
<Form.Content>
|
|
31
|
+
<Form.Section title={meta.profile.name ?? meta.profile.key}>
|
|
32
|
+
<Form.FieldSet
|
|
33
|
+
filter={(properties) =>
|
|
34
|
+
isSocket ? properties.filter((property) => property.name !== 'enableNativeRedirect') : properties
|
|
35
|
+
}
|
|
36
|
+
/>
|
|
37
|
+
</Form.Section>
|
|
38
|
+
</Form.Content>
|
|
39
|
+
</Form.Viewport>
|
|
40
|
+
</Form.Root>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -7,7 +7,7 @@ import React, { type MouseEvent, useCallback, useEffect, useMemo, useState } fro
|
|
|
7
7
|
import { Surface, useOperationInvoker } from '@dxos/app-framework/ui';
|
|
8
8
|
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
9
9
|
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
10
|
-
import { IconButton, type Label, Main, Panel,
|
|
10
|
+
import { IconButton, type Label, Main, Panel, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
11
|
import { getLinkedVariant } from '@dxos/react-ui-attention';
|
|
12
12
|
import { Tabs } from '@dxos/react-ui-tabs';
|
|
13
13
|
import { iconSize, mx } from '@dxos/ui-theme';
|
|
@@ -17,7 +17,7 @@ import { meta } from '#meta';
|
|
|
17
17
|
import { getMode } from '#types';
|
|
18
18
|
|
|
19
19
|
import { layoutAppliesTopbar } from '../../util';
|
|
20
|
-
import { PlankErrorFallback, PlankLoading } from '../
|
|
20
|
+
import { PlankErrorFallback, PlankLoading } from '../Deck/PlankFallback';
|
|
21
21
|
import { ToggleComplementarySidebarButton } from './SidebarButton';
|
|
22
22
|
|
|
23
23
|
const label = ['complementary-sidebar.title', { ns: meta.profile.key }] satisfies Label;
|
package/src/containers/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type ComponentType, lazy } from 'react';
|
|
6
6
|
|
|
7
7
|
export * from './Deck';
|
|
8
|
-
export * from './Plank';
|
|
9
8
|
export * from './Sidebar';
|
|
10
9
|
|
|
11
10
|
export const DeckLayout: ComponentType<any> = lazy(() => import('./DeckLayout'));
|
|
11
|
+
export const DeckSettings: ComponentType<any> = lazy(() => import('./DeckSettings'));
|
package/src/hooks/index.ts
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
export * from './useBreakpoints';
|
|
6
6
|
export * from './useCompanions';
|
|
7
|
+
export * from './useCompanionSplit';
|
|
7
8
|
export * from './useDeckCompanions';
|
|
8
9
|
export * from './useDeckState';
|
|
9
10
|
export * from './useMainSize';
|
|
10
11
|
export * from './useNodeActionExpander';
|
|
11
12
|
export * from './useSelectedCompanion';
|
|
13
|
+
export * from './useSelectedCompanionVariant';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type SplitterOrientation } from '@dxos/react-ui';
|
|
9
|
+
import { type AspectDef, defineViewState, useViewState, useViewStateActions } from '@dxos/react-ui-attention';
|
|
10
|
+
|
|
11
|
+
import { COMPANION_VIEW_STATE_CONTEXT } from '../util';
|
|
12
|
+
|
|
13
|
+
/** Companion split point (rem) used until the user resizes a given orientation. */
|
|
14
|
+
export const DEFAULT_COMPANION_SIZE = 30;
|
|
15
|
+
|
|
16
|
+
/** Trailing delay before a resize is committed to storage (drags fire continuously). */
|
|
17
|
+
const PERSIST_DELAY = 250;
|
|
18
|
+
|
|
19
|
+
const CompanionSplitSchema = Schema.Struct({
|
|
20
|
+
horizontal: Schema.optional(Schema.Number),
|
|
21
|
+
vertical: Schema.optional(Schema.Number),
|
|
22
|
+
}).pipe(Schema.mutable);
|
|
23
|
+
|
|
24
|
+
export type CompanionSplit = Schema.Schema.Type<typeof CompanionSplitSchema>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Companion splitter sizes (rem), memoized separately for each orientation so toggling the companion
|
|
28
|
+
* between side-by-side and stacked restores that orientation's last split point. Stored in the `local`
|
|
29
|
+
* backend (localStorage, persisted across reloads) via react-ui-attention view state.
|
|
30
|
+
*/
|
|
31
|
+
export const companionSplitAspect: AspectDef<CompanionSplit> = defineViewState<CompanionSplit>({
|
|
32
|
+
key: 'deck-companion-split',
|
|
33
|
+
backend: 'local',
|
|
34
|
+
schema: CompanionSplitSchema,
|
|
35
|
+
defaultValue: () => ({}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type UseCompanionSplit = {
|
|
39
|
+
/** The remembered split point for the current orientation (or the default). */
|
|
40
|
+
size: number;
|
|
41
|
+
onSizeChange: (size: number) => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reads and writes the global companion splitter size keyed by `orientation`. A live size keeps dragging
|
|
46
|
+
* smooth while the persisted write is debounced (and flushed on unmount) to avoid per-frame storage writes.
|
|
47
|
+
*/
|
|
48
|
+
export const useCompanionSplit = (orientation: SplitterOrientation): UseCompanionSplit => {
|
|
49
|
+
const split = useViewState(companionSplitAspect, COMPANION_VIEW_STATE_CONTEXT);
|
|
50
|
+
const { update } = useViewStateActions(companionSplitAspect, COMPANION_VIEW_STATE_CONTEXT);
|
|
51
|
+
const storedSize = split[orientation] ?? DEFAULT_COMPANION_SIZE;
|
|
52
|
+
|
|
53
|
+
// Drive the controlled Splitter from local state so a drag tracks the pointer without a storage
|
|
54
|
+
// round-trip per frame; reseed when the orientation switches (recall) or storage changes externally.
|
|
55
|
+
const [liveSize, setLiveSize] = useState(storedSize);
|
|
56
|
+
useEffect(() => setLiveSize(storedSize), [storedSize, orientation]);
|
|
57
|
+
|
|
58
|
+
// Flush the trailing write on unmount so the final size is not dropped inside the debounce window.
|
|
59
|
+
const pending = useRef<{ timer: ReturnType<typeof setTimeout>; flush: () => void } | undefined>(undefined);
|
|
60
|
+
useEffect(() => () => pending.current?.flush(), []);
|
|
61
|
+
|
|
62
|
+
const onSizeChange = useCallback(
|
|
63
|
+
(next: number) => {
|
|
64
|
+
setLiveSize(next);
|
|
65
|
+
if (pending.current) {
|
|
66
|
+
clearTimeout(pending.current.timer);
|
|
67
|
+
}
|
|
68
|
+
const flush = () => {
|
|
69
|
+
pending.current = undefined;
|
|
70
|
+
update((prev) => ({ ...prev, [orientation]: next }));
|
|
71
|
+
};
|
|
72
|
+
pending.current = { timer: setTimeout(flush, PERSIST_DELAY), flush };
|
|
73
|
+
},
|
|
74
|
+
[update, orientation],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return useMemo(() => ({ size: liveSize, onSizeChange }), [liveSize, onSizeChange]);
|
|
78
|
+
};
|
|
@@ -2,17 +2,49 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import { useContext, useEffect, useState } from 'react';
|
|
6
7
|
|
|
7
8
|
import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { type Node } from '@dxos/plugin-graph';
|
|
10
|
+
import { Position } from '@dxos/util';
|
|
10
11
|
|
|
11
12
|
import { PLANK_COMPANION_TYPE } from '#types';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Companion (child) nodes for a plank.
|
|
16
|
+
*
|
|
17
|
+
* The node's child-connections atom is read in a commit-phase effect rather than during render.
|
|
18
|
+
* Subscribing to it during render (via `useConnections`/`useAtomValue`) recomputes the shared atom and
|
|
19
|
+
* synchronously notifies its other subscribers — notably navtree items rendering the same node — which
|
|
20
|
+
* surfaces as a React "cannot update a component while rendering a different component" warning. Reading
|
|
21
|
+
* it from an effect defers that notification to the commit phase where cross-component updates are allowed.
|
|
22
|
+
*/
|
|
23
|
+
export const useCompanions = (id?: string): Node.Node[] => {
|
|
14
24
|
const { graph } = useAppGraph();
|
|
15
|
-
const
|
|
16
|
-
const companions =
|
|
17
|
-
|
|
25
|
+
const registry = useContext(RegistryContext);
|
|
26
|
+
const [companions, setCompanions] = useState<Node.Node[]>([]);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!id) {
|
|
30
|
+
setCompanions([]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const atom = graph.connections(id, 'child');
|
|
35
|
+
const update = () => {
|
|
36
|
+
const next = registry
|
|
37
|
+
.get(atom)
|
|
38
|
+
.filter((node) => node.type === PLANK_COMPANION_TYPE)
|
|
39
|
+
.toSorted((a, b) => Position.compare(a.properties, b.properties));
|
|
40
|
+
setCompanions((prev) =>
|
|
41
|
+
prev.length === next.length && prev.every((node, index) => node === next[index]) ? prev : next,
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
update();
|
|
46
|
+
return registry.subscribe(atom, update);
|
|
47
|
+
}, [graph, registry, id]);
|
|
48
|
+
|
|
49
|
+
return companions;
|
|
18
50
|
};
|
|
@@ -6,7 +6,7 @@ import { useAppGraph } from '@dxos/app-toolkit/ui';
|
|
|
6
6
|
import { Node, type Node as NodeType } from '@dxos/plugin-graph';
|
|
7
7
|
import { useConnections } from '@dxos/plugin-graph';
|
|
8
8
|
import { type Label } from '@dxos/ui-types/translations';
|
|
9
|
-
import {
|
|
9
|
+
import { Position } from '@dxos/util';
|
|
10
10
|
|
|
11
11
|
import { DECK_COMPANION_TYPE } from '#types';
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ export type DeckCompanion = NodeType.Node<
|
|
|
18
18
|
// TODO(burdon): Scroll area should be controlled by surface.
|
|
19
19
|
/** If true, the panel will not be wrapped in a scroll area. */
|
|
20
20
|
fixed?: boolean;
|
|
21
|
-
position?: Position;
|
|
21
|
+
position?: Position.Position;
|
|
22
22
|
joyride?: string;
|
|
23
23
|
}
|
|
24
24
|
>;
|
|
@@ -27,5 +27,5 @@ export const useDeckCompanions = (): DeckCompanion[] => {
|
|
|
27
27
|
const { graph } = useAppGraph();
|
|
28
28
|
const connections = useConnections(graph, Node.RootId, 'child');
|
|
29
29
|
const companions = connections.filter((node) => node.type === DECK_COMPANION_TYPE) as DeckCompanion[];
|
|
30
|
-
return companions.toSorted((a, b) =>
|
|
30
|
+
return companions.toSorted((a, b) => Position.compare(a.properties, b.properties));
|
|
31
31
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useViewState } from '@dxos/react-ui-attention';
|
|
6
|
+
|
|
7
|
+
import { COMPANION_VIEW_STATE_CONTEXT, companionVariantAspect } from '../util';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reads the globally-selected companion variant (persisted via view state) so reopening the companion
|
|
11
|
+
* restores the last-selected tab rather than resetting to the first.
|
|
12
|
+
*/
|
|
13
|
+
export const useSelectedCompanionVariant = (): string | undefined =>
|
|
14
|
+
useViewState(companionVariantAspect, COMPANION_VIEW_STATE_CONTEXT).variant;
|
package/src/index.ts
CHANGED
|
@@ -4,3 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
export * from './meta';
|
|
6
6
|
export * from './types';
|
|
7
|
+
// Companion selection view-state aspect — public so consumers (e.g. plugin-assistant) can read the
|
|
8
|
+
// globally-selected companion variant that used to live on `DeckState.companionVariant`.
|
|
9
|
+
export {
|
|
10
|
+
COMPANION_VIEW_STATE_CONTEXT,
|
|
11
|
+
type CompanionSelection,
|
|
12
|
+
companionVariantAspect,
|
|
13
|
+
} from './util/companion-view-state';
|