@dxos/plugin-deck 0.8.1-main.303c73a → 0.8.1-main.81238a8

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 (75) hide show
  1. package/dist/lib/browser/{app-graph-builder-IYHAGFA3.mjs → app-graph-builder-K4KVSHNT.mjs} +3 -3
  2. package/dist/lib/browser/{check-app-scheme-S3EYUPMF.mjs → check-app-scheme-6SS6I3RN.mjs} +2 -2
  3. package/dist/lib/browser/{chunk-N7TEPFVR.mjs → chunk-2WTHB3TG.mjs} +1 -1
  4. package/dist/lib/browser/{chunk-N7TEPFVR.mjs.map → chunk-2WTHB3TG.mjs.map} +2 -2
  5. package/dist/lib/browser/chunk-7A5DLPQ3.mjs +24 -0
  6. package/dist/lib/browser/{chunk-FYKBOM3C.mjs → chunk-7X43JKZG.mjs} +33 -3
  7. package/dist/lib/browser/chunk-7X43JKZG.mjs.map +7 -0
  8. package/dist/lib/browser/{chunk-63K74E3J.mjs → chunk-7YPIAXSW.mjs} +174 -69
  9. package/dist/lib/browser/chunk-7YPIAXSW.mjs.map +7 -0
  10. package/dist/lib/browser/{chunk-YCKJNTKG.mjs → chunk-RZLH5F56.mjs} +2 -2
  11. package/dist/lib/browser/{chunk-22AQ5IVX.mjs → chunk-WCNPMAR4.mjs} +2 -2
  12. package/dist/lib/browser/index.mjs +5 -4
  13. package/dist/lib/browser/index.mjs.map +2 -2
  14. package/dist/lib/browser/{intent-resolver-P5BVUQKU.mjs → intent-resolver-MEBOMCYI.mjs} +46 -17
  15. package/dist/lib/browser/intent-resolver-MEBOMCYI.mjs.map +7 -0
  16. package/dist/lib/browser/meta.json +1 -1
  17. package/dist/lib/browser/{react-root-ZQCTDM4Y.mjs → react-root-PPKFVPO7.mjs} +7 -7
  18. package/dist/lib/browser/{react-surface-RG3PVPY3.mjs → react-surface-7JBPPUHM.mjs} +7 -7
  19. package/dist/lib/browser/{settings-X3P2HKQJ.mjs → settings-DYS3FFMN.mjs} +3 -3
  20. package/dist/lib/browser/{state-2MOTLKVR.mjs → state-DRRCGMU2.mjs} +7 -11
  21. package/dist/lib/browser/state-DRRCGMU2.mjs.map +7 -0
  22. package/dist/lib/browser/tools-NDEUSO4R.mjs +78 -0
  23. package/dist/lib/browser/tools-NDEUSO4R.mjs.map +7 -0
  24. package/dist/lib/browser/types.mjs +10 -4
  25. package/dist/lib/browser/{url-handler-MVHTKUYA.mjs → url-handler-4BCN7AYC.mjs} +7 -9
  26. package/dist/lib/browser/url-handler-4BCN7AYC.mjs.map +7 -0
  27. package/dist/types/src/capabilities/capabilities.d.ts +26 -2
  28. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  29. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  30. package/dist/types/src/capabilities/state.d.ts +13 -1
  31. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  32. package/dist/types/src/capabilities/tools.d.ts +1 -0
  33. package/dist/types/src/capabilities/tools.d.ts.map +1 -1
  34. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -1
  35. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  36. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +6 -2
  37. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  38. package/dist/types/src/components/DeckLayout/Plank.d.ts +4 -4
  39. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  40. package/dist/types/src/components/DeckLayout/PlankControls.d.ts +6 -1
  41. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +1 -1
  42. package/dist/types/src/meta.d.ts +2 -5
  43. package/dist/types/src/meta.d.ts.map +1 -1
  44. package/dist/types/src/translations.d.ts +1 -0
  45. package/dist/types/src/translations.d.ts.map +1 -1
  46. package/dist/types/src/types.d.ts +41 -10
  47. package/dist/types/src/types.d.ts.map +1 -1
  48. package/package.json +31 -31
  49. package/src/capabilities/intent-resolver.ts +29 -3
  50. package/src/capabilities/state.ts +2 -9
  51. package/src/capabilities/tools.ts +34 -22
  52. package/src/capabilities/url-handler.ts +2 -8
  53. package/src/components/DeckLayout/DeckLayout.tsx +34 -10
  54. package/src/components/DeckLayout/NodePlankHeading.tsx +49 -29
  55. package/src/components/DeckLayout/Plank.tsx +181 -111
  56. package/src/components/DeckLayout/PlankControls.tsx +34 -3
  57. package/src/meta.ts +2 -2
  58. package/src/translations.ts +1 -0
  59. package/src/types.ts +29 -0
  60. package/dist/lib/browser/chunk-63K74E3J.mjs.map +0 -7
  61. package/dist/lib/browser/chunk-ATPSUXXK.mjs +0 -24
  62. package/dist/lib/browser/chunk-FYKBOM3C.mjs.map +0 -7
  63. package/dist/lib/browser/intent-resolver-P5BVUQKU.mjs.map +0 -7
  64. package/dist/lib/browser/state-2MOTLKVR.mjs.map +0 -7
  65. package/dist/lib/browser/tools-64LXGLYR.mjs +0 -59
  66. package/dist/lib/browser/tools-64LXGLYR.mjs.map +0 -7
  67. package/dist/lib/browser/url-handler-MVHTKUYA.mjs.map +0 -7
  68. /package/dist/lib/browser/{app-graph-builder-IYHAGFA3.mjs.map → app-graph-builder-K4KVSHNT.mjs.map} +0 -0
  69. /package/dist/lib/browser/{check-app-scheme-S3EYUPMF.mjs.map → check-app-scheme-6SS6I3RN.mjs.map} +0 -0
  70. /package/dist/lib/browser/{chunk-ATPSUXXK.mjs.map → chunk-7A5DLPQ3.mjs.map} +0 -0
  71. /package/dist/lib/browser/{chunk-YCKJNTKG.mjs.map → chunk-RZLH5F56.mjs.map} +0 -0
  72. /package/dist/lib/browser/{chunk-22AQ5IVX.mjs.map → chunk-WCNPMAR4.mjs.map} +0 -0
  73. /package/dist/lib/browser/{react-root-ZQCTDM4Y.mjs.map → react-root-PPKFVPO7.mjs.map} +0 -0
  74. /package/dist/lib/browser/{react-surface-RG3PVPY3.mjs.map → react-surface-7JBPPUHM.mjs.map} +0 -0
  75. /package/dist/lib/browser/{settings-X3P2HKQJ.mjs.map → settings-DYS3FFMN.mjs.map} +0 -0
@@ -2,7 +2,16 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type KeyboardEvent, memo, useCallback, useLayoutEffect, useMemo, useRef } from 'react';
5
+ import React, {
6
+ Fragment,
7
+ type KeyboardEvent,
8
+ memo,
9
+ type PropsWithChildren,
10
+ useCallback,
11
+ useLayoutEffect,
12
+ useMemo,
13
+ useRef,
14
+ } from 'react';
6
15
 
7
16
  import {
8
17
  createIntent,
@@ -17,133 +26,194 @@ import { useAttendableAttributes } from '@dxos/react-ui-attention';
17
26
  import { StackItem, railGridHorizontal } from '@dxos/react-ui-stack';
18
27
  import { mainIntrinsicSize, mx } from '@dxos/react-ui-theme';
19
28
 
20
- import { NodePlankHeading, type NodePlankHeadingProps } from './NodePlankHeading';
29
+ import { NodePlankHeading } from './NodePlankHeading';
21
30
  import { PlankContentError, PlankError } from './PlankError';
22
31
  import { PlankLoading } from './PlankLoading';
23
32
  import { DeckCapabilities } from '../../capabilities';
24
33
  import { useNode, useMainSize } from '../../hooks';
25
- import { DeckAction, type LayoutMode } from '../../types';
34
+ import { DeckAction, type LayoutMode, type Part, type ResolvedPart, surfaceVariantSeparator } from '../../types';
26
35
 
27
36
  const UNKNOWN_ID = 'unknown_id';
28
37
 
29
38
  export type PlankProps = {
30
39
  id?: string;
31
- part: NodePlankHeadingProps['part'];
40
+ companionId?: string;
41
+ part: Part;
32
42
  path?: string[];
33
43
  order?: number;
34
44
  active?: string[];
35
45
  layoutMode: LayoutMode;
36
46
  };
37
47
 
38
- export const Plank = memo(({ id = UNKNOWN_ID, part, path, order, active, layoutMode }: PlankProps) => {
39
- const { dispatchPromise: dispatch } = useIntentDispatcher();
40
- const { deck, popoverAnchorId, scrollIntoView } = useCapability(DeckCapabilities.DeckState);
41
- const { graph } = useAppGraph();
42
- const node = useNode(graph, id);
43
- const rootElement = useRef<HTMLDivElement | null>(null);
44
- const canResize = layoutMode === 'deck';
45
- const Root = part === 'solo' ? 'article' : StackItem.Root;
46
-
47
- const attendableAttrs = useAttendableAttributes(id);
48
- const index = active ? active.findIndex((entryId) => entryId === id) : 0;
49
- const length = active?.length ?? 1;
50
- const canIncrementStart = active && index !== undefined && index > 0 && length !== undefined && length > 1;
51
- const canIncrementEnd = active && index !== undefined && index < length - 1 && length !== undefined;
52
-
53
- const key = id.split('+')[0];
54
- const size = deck.plankSizing[key] as number | undefined;
55
- const setSize = useCallback(
56
- debounce((nextSize: number) => {
57
- return dispatch(createIntent(DeckAction.UpdatePlankSize, { id: key, size: nextSize }));
58
- }, 200),
59
- [dispatch, key],
60
- );
61
-
62
- // TODO(thure): Tabster’s focus group should handle moving focus to Main, but something is blocking it.
63
- const handleKeyDown = useCallback((event: KeyboardEvent) => {
64
- if (event.target === event.currentTarget && event.key === 'Escape') {
65
- rootElement.current?.closest('main')?.focus();
66
- }
67
- }, []);
68
-
69
- useLayoutEffect(() => {
70
- if (scrollIntoView === id) {
71
- // TODO(wittjosiah): When focused on page load, the focus is always visible.
72
- // Forcing focus to something smaller than the plank prevents large focus ring in the interim.
73
- const focusable = rootElement.current?.querySelector('button') || rootElement.current;
74
- focusable?.focus({ preventScroll: true });
75
- layoutMode === 'deck' && focusable?.scrollIntoView({ behavior: 'smooth', inline: 'center' });
76
- // Clear the scroll into view state once it has been actioned.
77
- void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: undefined }));
78
- }
79
- }, [id, scrollIntoView, layoutMode]);
80
-
81
- const isSolo = layoutMode === 'solo' && part === 'solo';
82
- const isAttendable = isSolo || (layoutMode === 'deck' && part === 'deck');
48
+ type PlankImplProps = Omit<PlankProps, 'companionId' | 'part'> & {
49
+ part: ResolvedPart;
50
+ surfaceVariant?: string;
51
+ companioned?: 'primary' | 'companion';
52
+ primaryId?: string;
53
+ };
83
54
 
55
+ const PlankImpl = memo(
56
+ ({
57
+ id = UNKNOWN_ID,
58
+ part,
59
+ path,
60
+ order,
61
+ active,
62
+ layoutMode,
63
+ surfaceVariant,
64
+ companioned,
65
+ primaryId,
66
+ }: PlankImplProps) => {
67
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
68
+ const { deck, popoverAnchorId, scrollIntoView } = useCapability(DeckCapabilities.DeckState);
69
+ const { graph } = useAppGraph();
70
+ const node = useNode(graph, id);
71
+ const rootElement = useRef<HTMLDivElement | null>(null);
72
+ const canResize = layoutMode === 'deck';
73
+ const Root = part.startsWith('solo') ? 'article' : StackItem.Root;
74
+
75
+ const attendableAttrs = useAttendableAttributes(id);
76
+ const index = active ? active.findIndex((entryId) => entryId === id) : 0;
77
+ const length = active?.length ?? 1;
78
+ const canIncrementStart = active && index !== undefined && index > 0 && length !== undefined && length > 1;
79
+ const canIncrementEnd = active && index !== undefined && index < length - 1 && length !== undefined;
80
+
81
+ const sizeKey = `${id.split('+')[0]}${surfaceVariant ? `${surfaceVariantSeparator}${surfaceVariant}` : ''}`;
82
+ const size = deck.plankSizing[sizeKey] as number | undefined;
83
+ const setSize = useCallback(
84
+ debounce((nextSize: number) => {
85
+ return dispatch(createIntent(DeckAction.UpdatePlankSize, { id: sizeKey, size: nextSize }));
86
+ }, 200),
87
+ [dispatch, sizeKey],
88
+ );
89
+
90
+ // TODO(thure): Tabster’s focus group should handle moving focus to Main, but something is blocking it.
91
+ const handleKeyDown = useCallback((event: KeyboardEvent) => {
92
+ if (event.target === event.currentTarget && event.key === 'Escape') {
93
+ rootElement.current?.closest('main')?.focus();
94
+ }
95
+ }, []);
96
+
97
+ useLayoutEffect(() => {
98
+ if (scrollIntoView === id) {
99
+ // TODO(wittjosiah): When focused on page load, the focus is always visible.
100
+ // Forcing focus to something smaller than the plank prevents large focus ring in the interim.
101
+ const focusable = rootElement.current?.querySelector('button') || rootElement.current;
102
+ focusable?.focus({ preventScroll: true });
103
+ layoutMode === 'deck' && focusable?.scrollIntoView({ behavior: 'smooth', inline: 'center' });
104
+ // Clear the scroll into view state once it has been actioned.
105
+ void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: undefined }));
106
+ }
107
+ }, [id, scrollIntoView, layoutMode]);
108
+
109
+ const isSolo = layoutMode === 'solo' && part === 'solo';
110
+ const isAttendable = isSolo || (layoutMode === 'deck' && part === 'deck');
111
+
112
+ const sizeAttrs = useMainSize();
113
+
114
+ const data = useMemo(
115
+ () =>
116
+ node && {
117
+ subject: node.data,
118
+ variant: surfaceVariant,
119
+ path,
120
+ popoverAnchorId,
121
+ },
122
+ [node, node?.data, path, popoverAnchorId, surfaceVariant],
123
+ );
124
+
125
+ // TODO(wittjosiah): Change prop to accept a component.
126
+ const placeholder = useMemo(() => <PlankLoading />, []);
127
+
128
+ const className = mx(
129
+ 'attention-surface relative',
130
+ isSolo && mainIntrinsicSize,
131
+ isSolo && railGridHorizontal,
132
+ isSolo && 'grid absolute inset-0',
133
+ part === 'deck' && (companioned === 'companion' ? '!border-separator border-ie' : '!border-separator border-li'),
134
+ part.startsWith('solo-') && 'row-span-2 min-is-0',
135
+ part === 'solo-companion' && '!border-separator border-is',
136
+ );
137
+
138
+ return (
139
+ <Root
140
+ ref={rootElement}
141
+ data-testid='deck.plank'
142
+ tabIndex={0}
143
+ {...(part.startsWith('solo')
144
+ ? ({ ...sizeAttrs, className } as any)
145
+ : {
146
+ item: { id },
147
+ size,
148
+ onSizeChange: setSize,
149
+ classNames: className,
150
+ order,
151
+ role: 'article',
152
+ })}
153
+ {...(isAttendable ? attendableAttrs : {})}
154
+ onKeyDown={handleKeyDown}
155
+ >
156
+ {node ? (
157
+ <>
158
+ <NodePlankHeading
159
+ id={id}
160
+ part={part.startsWith('solo-') ? 'solo' : part}
161
+ node={node}
162
+ canIncrementStart={canIncrementStart}
163
+ canIncrementEnd={canIncrementEnd}
164
+ popoverAnchorId={popoverAnchorId}
165
+ companioned={companioned}
166
+ primaryId={primaryId}
167
+ surfaceVariant={surfaceVariant}
168
+ />
169
+ <Surface
170
+ key={node.id}
171
+ role='article'
172
+ data={data}
173
+ limit={1}
174
+ fallback={PlankContentError}
175
+ placeholder={placeholder}
176
+ />
177
+ </>
178
+ ) : (
179
+ <PlankError id={id} part={part} />
180
+ )}
181
+ {canResize && <StackItem.ResizeHandle />}
182
+ </Root>
183
+ );
184
+ },
185
+ );
186
+
187
+ const SplitFrame = ({ children }: PropsWithChildren<{}>) => {
84
188
  const sizeAttrs = useMainSize();
85
-
86
- const data = useMemo(
87
- () =>
88
- node && {
89
- subject: node.data,
90
- path,
91
- popoverAnchorId,
92
- },
93
- [node, node?.data, path, popoverAnchorId],
94
- );
95
-
96
- // TODO(wittjosiah): Change prop to accept a component.
97
- const placeholder = useMemo(() => <PlankLoading />, []);
98
-
99
- const className = mx(
100
- 'attention-surface relative',
101
- isSolo && mainIntrinsicSize,
102
- isSolo && railGridHorizontal,
103
- isSolo ? 'grid absolute inset-0' : '!border-separator border-li',
104
- );
105
-
106
189
  return (
107
- <Root
108
- ref={rootElement}
109
- data-testid='deck.plank'
110
- tabIndex={0}
111
- {...(part === 'solo'
112
- ? ({ ...sizeAttrs, className } as any)
113
- : {
114
- item: { id },
115
- size,
116
- onSizeChange: setSize,
117
- classNames: className,
118
- order,
119
- role: 'article',
120
- })}
121
- {...(isAttendable ? attendableAttrs : {})}
122
- onKeyDown={handleKeyDown}
190
+ <div
191
+ role='none'
192
+ className={mx('grid grid-cols-[1fr_1fr] absolute inset-0', railGridHorizontal, mainIntrinsicSize)}
193
+ {...sizeAttrs}
123
194
  >
124
- {node ? (
125
- <>
126
- <NodePlankHeading
127
- id={id}
128
- part={part}
129
- node={node}
130
- canIncrementStart={canIncrementStart}
131
- canIncrementEnd={canIncrementEnd}
132
- popoverAnchorId={popoverAnchorId}
133
- />
134
- <Surface
135
- key={node.id}
136
- role='article'
137
- data={data}
138
- limit={1}
139
- fallback={PlankContentError}
140
- placeholder={placeholder}
141
- />
142
- </>
143
- ) : (
144
- <PlankError id={id} part={part} />
145
- )}
146
- {canResize && <StackItem.ResizeHandle />}
147
- </Root>
195
+ {children}
196
+ </div>
148
197
  );
149
- });
198
+ };
199
+
200
+ export const Plank = (props: PlankProps) => {
201
+ if (props.companionId) {
202
+ const Root = props.part === 'solo' ? SplitFrame : Fragment;
203
+ return (
204
+ <Root>
205
+ <PlankImpl {...props} {...(props.part === 'solo' ? { part: 'solo-primary' } : {})} companioned='primary' />
206
+ <PlankImpl
207
+ {...props}
208
+ {...(props.companionId.startsWith(surfaceVariantSeparator)
209
+ ? { surfaceVariant: props.companionId.substring(2) }
210
+ : { id: props.companionId, primaryId: props.id })}
211
+ {...(props.part === 'solo' ? { part: 'solo-companion' } : { order: props.order! + 1 })}
212
+ companioned='companion'
213
+ />
214
+ </Root>
215
+ );
216
+ } else {
217
+ return <PlankImpl {...props} />;
218
+ }
219
+ };
@@ -2,8 +2,10 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { forwardRef } from 'react';
5
+ import React, { forwardRef, useCallback } from 'react';
6
6
 
7
+ import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
8
+ import { invariant } from '@dxos/invariant';
7
9
  import {
8
10
  Button,
9
11
  ButtonGroup,
@@ -15,7 +17,7 @@ import {
15
17
  } from '@dxos/react-ui';
16
18
 
17
19
  import { DECK_PLUGIN } from '../../meta';
18
- import { type DeckAction } from '../../types';
20
+ import { DeckAction } from '../../types';
19
21
 
20
22
  export type PlankControlHandler = (event: DeckAction.PartAdjustment) => void;
21
23
 
@@ -50,6 +52,34 @@ const PlankControl = ({ icon, label, ...props }: Omit<ButtonProps, 'children'> &
50
52
  );
51
53
  };
52
54
 
55
+ const plankControlSpacing = 'pli-2 plb-3';
56
+
57
+ type PlankComplimentControlsProps = {
58
+ primary?: string;
59
+ };
60
+
61
+ export const PlankCompanionControls = forwardRef<HTMLDivElement, PlankComplimentControlsProps>(
62
+ ({ primary }, forwardedRef) => {
63
+ const { t } = useTranslation(DECK_PLUGIN);
64
+ const { dispatchPromise: dispatch } = useIntentDispatcher();
65
+ const handleCloseCompanion = useCallback(() => {
66
+ invariant(primary);
67
+ return dispatch(createIntent(DeckAction.ChangeCompanion, { primary, companion: null }));
68
+ }, []);
69
+ return (
70
+ <div ref={forwardedRef} className='contents app-no-drag'>
71
+ <PlankControl
72
+ label={t('close companion label')}
73
+ variant='ghost'
74
+ icon='ph--minus--regular'
75
+ onClick={handleCloseCompanion}
76
+ classNames={plankControlSpacing}
77
+ />
78
+ </div>
79
+ );
80
+ },
81
+ );
82
+
53
83
  // TODO(wittjosiah): Duplicate of stack LayoutControls?
54
84
  // Translations were to be duplicated between packages.
55
85
  // NOTE(thure): Pinning & unpinning are disabled indefinitely.
@@ -59,7 +89,8 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
59
89
  forwardedRef,
60
90
  ) => {
61
91
  const { t } = useTranslation(DECK_PLUGIN);
62
- const buttonClassNames = variant === 'hide-disabled' ? 'disabled:hidden pli-2 plb-3' : 'pli-2 plb-3';
92
+ const buttonClassNames =
93
+ variant === 'hide-disabled' ? `disabled:hidden ${plankControlSpacing}` : plankControlSpacing;
63
94
 
64
95
  return (
65
96
  <ButtonGroup {...props} classNames={['app-no-drag', classNames]} ref={forwardedRef}>
package/src/meta.ts CHANGED
@@ -6,8 +6,8 @@ import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
7
  export const DECK_PLUGIN = 'dxos.org/plugin/deck' as const;
8
8
 
9
- export const meta = {
9
+ export const meta: PluginMeta = {
10
10
  id: DECK_PLUGIN,
11
11
  name: 'Deck',
12
12
  icon: 'ph--columns--regular',
13
- } satisfies PluginMeta;
13
+ };
@@ -55,6 +55,7 @@ export default [
55
55
  'close current label': 'Close current plank',
56
56
  'close others label': 'Close other planks',
57
57
  'close all label': 'Close all planks',
58
+ 'companion plank heading fallback label': 'Related',
58
59
  },
59
60
  },
60
61
  },
package/src/types.ts CHANGED
@@ -17,6 +17,9 @@ export type NewPlankPositioning = (typeof NewPlankPositions)[number];
17
17
  export const OverscrollOptions = ['none', 'centering'] as const;
18
18
  export type Overscroll = (typeof OverscrollOptions)[number];
19
19
 
20
+ export type Part = 'solo' | 'deck' | 'complementary';
21
+ export type ResolvedPart = Part | 'solo-primary' | 'solo-companion';
22
+
20
23
  export type Panel = {
21
24
  id: string;
22
25
  label: Label;
@@ -53,13 +56,31 @@ export const Deck = S.Struct({
53
56
  description: "If false, the deck has not yet left solo mode and new planks should be solo'd.",
54
57
  }),
55
58
  active: S.mutable(S.Array(S.String)),
59
+ // TODO(wittjosiah): Piping into both mutable and optional caused invalid typescript output.
60
+ activeCompanions: S.optional(S.mutable(S.Record({ key: S.String, value: S.String }))),
56
61
  inactive: S.mutable(S.Array(S.String)),
57
62
  solo: S.optional(S.String),
58
63
  fullscreen: S.Boolean,
59
64
  plankSizing: S.mutable(PlankSizing),
65
+ companionFrameSizing: S.mutable(PlankSizing),
60
66
  });
61
67
  export type Deck = S.Schema.Type<typeof Deck>;
62
68
 
69
+ export const defaultDeck = {
70
+ initialized: false,
71
+ active: [],
72
+ activeCompanions: {},
73
+ inactive: [],
74
+ fullscreen: false,
75
+ solo: undefined,
76
+ plankSizing: {},
77
+ companionFrameSizing: {},
78
+ } satisfies Deck;
79
+
80
+ export const surfaceVariantSeparator = '--';
81
+
82
+ export const surfaceVariant = (id: string) => `${surfaceVariantSeparator}${id}`;
83
+
63
84
  export const DeckState = S.mutable(
64
85
  S.Struct({
65
86
  sidebarState: S.Literal('closed', 'collapsed', 'expanded'),
@@ -139,4 +160,12 @@ export namespace DeckAction {
139
160
  }),
140
161
  output: S.Void,
141
162
  }) {}
163
+
164
+ export class ChangeCompanion extends S.TaggedClass<ChangeCompanion>()(`${DECK_ACTION}/change-companion`, {
165
+ input: S.Struct({
166
+ primary: S.String,
167
+ companion: S.Union(S.String, S.Null),
168
+ }),
169
+ output: S.Void,
170
+ }) {}
142
171
  }