@dxos/plugin-navtree 0.8.4-main.7ace549 → 0.8.4-main.937b3ca

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 (166) hide show
  1. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs +43 -0
  2. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-6YPQ625I.mjs +1293 -0
  4. package/dist/lib/browser/chunk-6YPQ625I.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-AGQDZBPL.mjs +23 -0
  6. package/dist/lib/browser/chunk-AGQDZBPL.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-E6MZBCU5.mjs → chunk-YKTYPINZ.mjs} +1 -1
  8. package/dist/lib/browser/chunk-YKTYPINZ.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +82 -91
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/{keyboard-YJEK2HTH.mjs → keyboard-32EH3N4K.mjs} +17 -14
  12. package/dist/lib/browser/keyboard-32EH3N4K.mjs.map +7 -0
  13. package/dist/lib/browser/meta.json +1 -1
  14. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs +52 -0
  15. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs.map +7 -0
  16. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs +58 -0
  17. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs.map +7 -0
  18. package/dist/lib/browser/state-EVYQOUI3.mjs +120 -0
  19. package/dist/lib/browser/state-EVYQOUI3.mjs.map +7 -0
  20. package/dist/types/src/NavTreePlugin.d.ts +2 -1
  21. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  22. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
  23. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
  25. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
  26. package/dist/types/src/capabilities/index.d.ts +5 -108
  27. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  28. package/dist/types/src/capabilities/keyboard/index.d.ts +3 -0
  29. package/dist/types/src/capabilities/keyboard/index.d.ts.map +1 -0
  30. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +5 -0
  31. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -0
  32. package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
  33. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
  35. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
  37. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
  38. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
  39. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
  40. package/dist/types/src/capabilities/state/index.d.ts +3 -0
  41. package/dist/types/src/capabilities/state/index.d.ts.map +1 -0
  42. package/dist/types/src/capabilities/state/state.d.ts +6 -0
  43. package/dist/types/src/capabilities/state/state.d.ts.map +1 -0
  44. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts +3 -2
  45. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
  46. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
  47. package/dist/types/src/components/NavTree/NavTree.d.ts +30 -1
  48. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  49. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +4 -1
  50. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/NavTreeContainer.d.ts +8 -1
  52. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  53. package/dist/types/src/components/NavTreeDocumentTitle.d.ts +1 -1
  54. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -1
  55. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +6 -6
  56. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  57. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +2 -1
  58. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/Sidebar/L0Menu.d.ts +4 -4
  60. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  61. package/dist/types/src/components/Sidebar/L1Panel.d.ts +1 -1
  62. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  63. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +2 -2
  64. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  65. package/dist/types/src/components/index.d.ts.map +1 -1
  66. package/dist/types/src/components/types.d.ts +7 -7
  67. package/dist/types/src/components/types.d.ts.map +1 -1
  68. package/dist/types/src/hooks.d.ts +32 -1
  69. package/dist/types/src/hooks.d.ts.map +1 -1
  70. package/dist/types/src/index.d.ts +1 -2
  71. package/dist/types/src/index.d.ts.map +1 -1
  72. package/dist/types/src/meta.d.ts +2 -2
  73. package/dist/types/src/meta.d.ts.map +1 -1
  74. package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
  75. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  76. package/dist/types/src/testing/generator.d.ts +5 -6
  77. package/dist/types/src/testing/generator.d.ts.map +1 -1
  78. package/dist/types/src/testing/update-state.d.ts +2 -2
  79. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  80. package/dist/types/src/types/capabilities.d.ts +16 -0
  81. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  82. package/dist/types/src/types/events.d.ts +5 -0
  83. package/dist/types/src/types/events.d.ts.map +1 -0
  84. package/dist/types/src/types/index.d.ts +4 -0
  85. package/dist/types/src/types/index.d.ts.map +1 -0
  86. package/dist/types/src/{types.d.ts → types/schema.d.ts} +6 -6
  87. package/dist/types/src/types/schema.d.ts.map +1 -0
  88. package/dist/types/src/util.d.ts +7 -7
  89. package/dist/types/src/util.d.ts.map +1 -1
  90. package/dist/types/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +53 -52
  92. package/src/NavTreePlugin.tsx +53 -70
  93. package/src/capabilities/app-graph-builder/app-graph-builder.ts +41 -0
  94. package/src/capabilities/app-graph-builder/index.ts +7 -0
  95. package/src/capabilities/index.ts +5 -9
  96. package/src/capabilities/keyboard/index.ts +7 -0
  97. package/src/capabilities/keyboard/keyboard.ts +65 -0
  98. package/src/capabilities/operation-resolver/index.ts +7 -0
  99. package/src/capabilities/operation-resolver/operation-resolver.ts +41 -0
  100. package/src/capabilities/react-surface/index.ts +7 -0
  101. package/src/capabilities/react-surface/react-surface.tsx +54 -0
  102. package/src/capabilities/state/index.ts +7 -0
  103. package/src/capabilities/state/state.ts +121 -0
  104. package/src/components/CommandsDialog/CommandsDialog.tsx +107 -104
  105. package/src/components/CommandsDialog/CommandsTrigger.tsx +4 -14
  106. package/src/components/NavTree/NavTree.stories.tsx +32 -41
  107. package/src/components/NavTree/NavTree.tsx +5 -15
  108. package/src/components/NavTreeContainer.tsx +251 -292
  109. package/src/components/NavTreeDocumentTitle.tsx +3 -2
  110. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +17 -7
  111. package/src/components/NavTreeItem/NavTreeItemAction.tsx +18 -12
  112. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +5 -5
  113. package/src/components/Sidebar/L0Menu.tsx +65 -91
  114. package/src/components/Sidebar/L1Panel.tsx +9 -7
  115. package/src/components/Sidebar/L1Tabs.tsx +6 -31
  116. package/src/components/index.ts +1 -0
  117. package/src/components/types.ts +7 -7
  118. package/src/experimental/Tree.stories.tsx +2 -2
  119. package/src/experimental/Tree.tsx +1 -1
  120. package/src/hooks.ts +54 -2
  121. package/src/index.ts +1 -2
  122. package/src/meta.ts +2 -2
  123. package/src/testing/app-graph-builder.ts +158 -234
  124. package/src/testing/generator.ts +12 -12
  125. package/src/testing/update-state.ts +6 -6
  126. package/src/types/capabilities.ts +21 -0
  127. package/src/types/events.ts +11 -0
  128. package/src/types/index.ts +7 -0
  129. package/src/{types.ts → types/schema.ts} +5 -5
  130. package/src/util.ts +13 -16
  131. package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs +0 -46
  132. package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs.map +0 -7
  133. package/dist/lib/browser/chunk-5VLWX4PK.mjs +0 -15
  134. package/dist/lib/browser/chunk-5VLWX4PK.mjs.map +0 -7
  135. package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +0 -7
  136. package/dist/lib/browser/chunk-UDAINHB6.mjs +0 -1488
  137. package/dist/lib/browser/chunk-UDAINHB6.mjs.map +0 -7
  138. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs +0 -47
  139. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +0 -7
  140. package/dist/lib/browser/keyboard-YJEK2HTH.mjs.map +0 -7
  141. package/dist/lib/browser/react-surface-V6LS3R4L.mjs +0 -51
  142. package/dist/lib/browser/react-surface-V6LS3R4L.mjs.map +0 -7
  143. package/dist/lib/browser/state-BFD5KJCV.mjs +0 -117
  144. package/dist/lib/browser/state-BFD5KJCV.mjs.map +0 -7
  145. package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -4
  146. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
  147. package/dist/types/src/capabilities/capabilities.d.ts +0 -21
  148. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  149. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  150. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  151. package/dist/types/src/capabilities/keyboard.d.ts +0 -4
  152. package/dist/types/src/capabilities/keyboard.d.ts.map +0 -1
  153. package/dist/types/src/capabilities/react-surface.d.ts +0 -4
  154. package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
  155. package/dist/types/src/capabilities/state.d.ts +0 -20
  156. package/dist/types/src/capabilities/state.d.ts.map +0 -1
  157. package/dist/types/src/events.d.ts +0 -4
  158. package/dist/types/src/events.d.ts.map +0 -1
  159. package/dist/types/src/types.d.ts.map +0 -1
  160. package/src/capabilities/app-graph-builder.ts +0 -53
  161. package/src/capabilities/capabilities.ts +0 -20
  162. package/src/capabilities/intent-resolver.ts +0 -37
  163. package/src/capabilities/keyboard.ts +0 -57
  164. package/src/capabilities/react-surface.tsx +0 -45
  165. package/src/capabilities/state.ts +0 -104
  166. package/src/events.ts +0 -11
@@ -0,0 +1,121 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import * as Effect from 'effect/Effect';
7
+
8
+ import { Capability, Common } from '@dxos/app-framework';
9
+ import { Path } from '@dxos/react-ui-list';
10
+
11
+ import { meta } from '../../meta';
12
+ import { type NavTreeCapabilities as NC, NavTreeCapabilities } from '../../types';
13
+
14
+ const KEY = `${meta.id}/state/v1`;
15
+
16
+ /** Default item state for new entries. */
17
+ const defaultItemState: NC.NavTreeItemState = { open: false, current: false, alternateTree: false };
18
+
19
+ /** Default state entries for initial tree structure. */
20
+ // TODO(thure): Initialize these dynamically.
21
+ const defaultStateEntries: [string, NC.NavTreeItemState][] = [
22
+ ['root', { open: true, current: false }],
23
+ ['root~dxos.org/plugin/space-spaces', { open: true, current: false }],
24
+ ['root~dxos.org/plugin/files', { open: true, current: false }],
25
+ ];
26
+
27
+ const getInitialState = (): Map<string, NC.NavTreeItemState> => {
28
+ const stringified = localStorage.getItem(KEY);
29
+ if (!stringified) {
30
+ return new Map(defaultStateEntries);
31
+ }
32
+
33
+ try {
34
+ const cached: [string, { open: boolean; current: boolean }][] = JSON.parse(stringified);
35
+ return new Map(cached.map(([key, value]) => [key, { open: value.open, current: false }]));
36
+ } catch {
37
+ return new Map(defaultStateEntries);
38
+ }
39
+ };
40
+
41
+ export default Capability.makeModule(
42
+ Effect.fnUntraced(function* () {
43
+ const registry = yield* Capability.get(Common.Capability.AtomRegistry);
44
+ const layoutAtom = yield* Capability.get(Common.Capability.Layout);
45
+
46
+ // Backing state for persistence (not reactive).
47
+ const backingState = getInitialState();
48
+
49
+ // Per-path atom family for fine-grained reactivity.
50
+ // keepAlive prevents atoms from being garbage collected when components unmount,
51
+ // ensuring state is preserved across deletion/restoration cycles.
52
+ const itemAtomFamily = Atom.family((pathString: string) =>
53
+ Atom.make<NC.NavTreeItemState>(backingState.get(pathString) ?? { ...defaultItemState }).pipe(Atom.keepAlive),
54
+ );
55
+
56
+ const getItemAtom = (path: string[]): Atom.Atom<NC.NavTreeItemState> => {
57
+ const pathString = Path.create(...path);
58
+ return itemAtomFamily(pathString);
59
+ };
60
+
61
+ const getItem = (path: string[]): NC.NavTreeItemState => {
62
+ return registry.get(getItemAtom(path));
63
+ };
64
+
65
+ const setItem = (path: string[], key: 'open' | 'current' | 'alternateTree', next: boolean) => {
66
+ const pathString = Path.create(...path);
67
+ const atom = itemAtomFamily(pathString);
68
+ const currentValue = registry.get(atom);
69
+ const newValue = { ...currentValue, [key]: next };
70
+
71
+ // Update the atom.
72
+ registry.set(atom, newValue);
73
+
74
+ // Update backing state and persist to localStorage.
75
+ backingState.set(pathString, newValue);
76
+ localStorage.setItem(KEY, JSON.stringify(Array.from(backingState.entries())));
77
+ };
78
+
79
+ // Subscribe to layout changes to update current state.
80
+ let previous: string[] = [];
81
+ const unsubscribe = registry.subscribe(layoutAtom, (layout) => {
82
+ const removed = previous.filter((id) => !layout.active.includes(id));
83
+ previous = layout.active;
84
+
85
+ const handleUpdate = () => {
86
+ // Mark removed items as not current.
87
+ removed.forEach((id) => {
88
+ const keys = Array.from(backingState.keys()).filter((key) => Path.last(key) === id);
89
+ keys.forEach((key) => {
90
+ setItem(Path.parts(key), 'current', false);
91
+ });
92
+ });
93
+
94
+ // Mark active items as current.
95
+ layout.active.forEach((id: string) => {
96
+ const keys = Array.from(new Set([...backingState.keys(), id])).filter((key) => Path.last(key) === id);
97
+ keys.forEach((key) => {
98
+ setItem(Path.parts(key), 'current', true);
99
+ });
100
+ });
101
+ };
102
+
103
+ // TODO(wittjosiah): This is setTimeout because there's a race between the keys being initialized.
104
+ // Keys are initialized on the first render of an item in the navtree.
105
+ // This could be avoided if the location was a path as well and not just an id.
106
+ const timeout = setTimeout(handleUpdate, 500);
107
+ handleUpdate();
108
+ return () => clearTimeout(timeout);
109
+ });
110
+
111
+ return Capability.contributes(
112
+ NavTreeCapabilities.State,
113
+ {
114
+ getItem,
115
+ getItemAtom,
116
+ setItem,
117
+ },
118
+ () => Effect.sync(() => unsubscribe()),
119
+ );
120
+ }),
121
+ );
@@ -2,125 +2,128 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React, { useMemo, useState } from 'react';
5
+ import React, { forwardRef, useMemo, useState } from 'react';
6
6
 
7
- import { LayoutAction, createIntent } from '@dxos/app-framework';
8
- import { useAppGraph, useIntentDispatcher } from '@dxos/app-framework/react';
9
- import { type ActionLike, isAction, isActionGroup } from '@dxos/app-graph';
7
+ import { Common } from '@dxos/app-framework';
8
+ import { useAppGraph, useOperationInvoker } from '@dxos/app-framework/react';
10
9
  import { Keyboard, keySymbols } from '@dxos/keyboard';
10
+ import { Graph, Node, useActionRunner } from '@dxos/plugin-graph';
11
11
  import { useActions } from '@dxos/plugin-graph';
12
- import { Button, Dialog, Icon, toLocalizedString, useTranslation } from '@dxos/react-ui';
13
- import { SearchList } from '@dxos/react-ui-searchlist';
14
- import {
15
- cardDialogContent,
16
- cardDialogHeader,
17
- cardDialogPaddedOverflow,
18
- cardDialogSearchListRoot,
19
- } from '@dxos/react-ui-stack';
20
- import { descriptionText, mx } from '@dxos/react-ui-theme';
12
+ import { Button, Dialog, toLocalizedString, useTranslation } from '@dxos/react-ui';
13
+ import { SearchList, useSearchListResults } from '@dxos/react-ui-searchlist';
14
+ import { osTranslations } from '@dxos/ui-theme';
21
15
  import { getHostPlatform } from '@dxos/util';
22
16
 
23
17
  import { KEY_BINDING, meta } from '../../meta';
24
18
 
19
+ export type CommandsDialogContentProps = {
20
+ selected?: string;
21
+ };
22
+
25
23
  // TODO(wittjosiah): This probably deserves its own plugin but for now it lives here w/ other navigation UI.
26
- export const CommandsDialogContent = ({ selected: initial }: { selected?: string }) => {
27
- const { t } = useTranslation(meta.id);
28
- const [selected, setSelected] = useState<string | undefined>(initial);
29
- const { dispatchPromise: dispatch } = useIntentDispatcher();
30
- const { graph } = useAppGraph();
24
+ export const CommandsDialogContent = forwardRef<HTMLDivElement, CommandsDialogContentProps>(
25
+ ({ selected: initial }, forwardedRef) => {
26
+ const { t } = useTranslation(meta.id);
27
+ const { invokeSync } = useOperationInvoker();
28
+ const runAction = useActionRunner();
29
+ const { graph } = useAppGraph();
30
+ const [selected, setSelected] = useState<string | undefined>(initial);
31
31
 
32
- // Traverse graph.
33
- // TODO(burdon): Factor out commonality with shortcut dialog.
34
- const allActions = useMemo(() => {
35
- // TODO(burdon): Get from navtree (not keyboard).
36
- const current = Keyboard.singleton.getCurrentContext();
37
- const actionMap = new Set<string>();
38
- const actions: ActionLike[] = [];
39
- graph.traverse({
40
- visitor: (node, path) => {
41
- if (
42
- (isAction(node) || isActionGroup(node)) &&
43
- !actionMap.has(node.id) &&
44
- current.startsWith(path.slice(0, -1).join('/'))
45
- ) {
46
- actionMap.add(node.id);
47
- actions.push(node);
48
- }
49
- },
50
- });
32
+ // Traverse graph.
33
+ // TODO(burdon): Factor out commonality with shortcut dialog.
34
+ const allActions = useMemo(() => {
35
+ // TODO(burdon): Get from navtree (not keyboard).
36
+ const current = Keyboard.singleton.getCurrentContext();
37
+ const actionMap = new Set<string>();
38
+ const actions: Node.ActionLike[] = [];
39
+ Graph.traverse(graph, {
40
+ visitor: (node, path) => {
41
+ if (
42
+ (Node.isAction(node) || Node.isActionGroup(node)) &&
43
+ !actionMap.has(node.id) &&
44
+ current.startsWith(path.slice(0, -1).join('/'))
45
+ ) {
46
+ actionMap.add(node.id);
47
+ actions.push(node);
48
+ }
49
+ },
50
+ });
51
51
 
52
- actions.sort((a, b) => {
53
- return toLocalizedString(a.properties.label, t)
54
- ?.toLowerCase()
55
- .localeCompare(toLocalizedString(b.properties.label, t)?.toLowerCase());
56
- });
52
+ actions.sort((a, b) => {
53
+ return toLocalizedString(a.properties.label, t)
54
+ ?.toLowerCase()
55
+ .localeCompare(toLocalizedString(b.properties.label, t)?.toLowerCase());
56
+ });
57
57
 
58
- return actions;
59
- }, [graph]);
58
+ return actions;
59
+ }, [graph]);
60
60
 
61
- const group = allActions.find(({ id }) => id === selected);
62
- const groupActions = useActions(graph, group?.id);
63
- const actions = isActionGroup(group) ? groupActions : allActions;
61
+ const group = allActions.find(({ id }) => id === selected);
62
+ const groupActions = useActions(graph, group?.id);
63
+ const actions = Node.isActionGroup(group) ? groupActions : allActions;
64
64
 
65
- return (
66
- <Dialog.Content classNames={cardDialogContent}>
67
- <Dialog.Title classNames={cardDialogHeader}>{t('commands dialog title', { ns: meta.id })}</Dialog.Title>
65
+ const { results, handleSearch } = useSearchListResults({
66
+ items: actions,
67
+ extract: (action) => toLocalizedString(action.properties.label, t),
68
+ });
69
+
70
+ return (
71
+ <Dialog.Content ref={forwardedRef}>
72
+ <Dialog.Title>{t('commands dialog title', { ns: meta.id })}</Dialog.Title>
73
+ <SearchList.Root onSearch={handleSearch}>
74
+ <div aria-label={t('command list input placeholder')} role='combobox' aria-expanded='true'>
75
+ <SearchList.Input placeholder={t('command list input placeholder')} />
76
+ <SearchList.Content>
77
+ <SearchList.Viewport>
78
+ {results.map((action) => {
79
+ const shortcut =
80
+ typeof action.properties.keyBinding === 'string'
81
+ ? action.properties.keyBinding
82
+ : action.properties.keyBinding?.[getHostPlatform()];
68
83
 
69
- {/* TODO(burdon): BUG: Overscrolls container. */}
70
- <SearchList.Root label={t('command list input placeholder')} classNames={cardDialogSearchListRoot}>
71
- <SearchList.Input placeholder={t('command list input placeholder')} />
72
- <SearchList.Content classNames={cardDialogPaddedOverflow}>
73
- {actions?.map((action) => {
74
- const label = toLocalizedString(action.properties.label, t);
75
- const shortcut =
76
- typeof action.properties.keyBinding === 'string'
77
- ? action.properties.keyBinding
78
- : action.properties.keyBinding?.[getHostPlatform()];
79
- return (
80
- <SearchList.Item
81
- value={label}
82
- key={action.id}
83
- onSelect={() => {
84
- if (action.properties.disabled) {
85
- return;
86
- }
84
+ return (
85
+ <SearchList.Item
86
+ value={action.id}
87
+ key={action.id}
88
+ label={toLocalizedString(action.properties.label, t)}
89
+ icon={action.properties.icon}
90
+ suffix={shortcut ? keySymbols(shortcut).join('') : undefined}
91
+ onSelect={() => {
92
+ if (action.properties.disabled) {
93
+ return;
94
+ }
87
95
 
88
- if (isActionGroup(action)) {
89
- setSelected(action.id);
90
- return;
91
- }
96
+ if (Node.isActionGroup(action)) {
97
+ setSelected(action.id);
98
+ return;
99
+ }
92
100
 
93
- void dispatch(
94
- createIntent(LayoutAction.UpdateDialog, {
95
- part: 'dialog',
96
- options: { state: false },
97
- }),
101
+ invokeSync(Common.LayoutOperation.UpdateDialog, { state: false });
102
+ setTimeout(() => {
103
+ const node = Graph.getConnections(graph, group?.id ?? action.id, 'inbound')[0];
104
+ if (node && Node.isAction(action)) {
105
+ void runAction(action, { parent: node, caller: KEY_BINDING });
106
+ }
107
+ });
108
+ }}
109
+ classNames='flex items-center gap-2'
110
+ disabled={action.properties.disabled}
111
+ {...(action.properties?.testId && {
112
+ 'data-testid': action.properties.testId,
113
+ })}
114
+ />
98
115
  );
99
- setTimeout(() => {
100
- const node = graph.getConnections(group?.id ?? action.id, 'inbound')[0];
101
- void (node && isAction(action) && action.data({ parent: node, caller: KEY_BINDING }));
102
- });
103
- }}
104
- classNames='flex items-center gap-2'
105
- disabled={action.properties.disabled}
106
- {...(action.properties?.testId && {
107
- 'data-testid': action.properties.testId,
108
116
  })}
109
- >
110
- <Icon icon={action.properties.icon} size={4} />
111
- <span className='grow truncate'>{label}</span>
112
- {shortcut && <span className={mx('shrink-0', descriptionText)}>{keySymbols(shortcut).join('')}</span>}
113
- </SearchList.Item>
114
- );
115
- })}
116
- </SearchList.Content>
117
- </SearchList.Root>
118
-
119
- <div role='none' className='pli-cardSpacingInline pbe-cardSpacingBlock'>
120
- <Dialog.Close asChild>
121
- <Button classNames='is-full'>{t('close label', { ns: 'os' })}</Button>
122
- </Dialog.Close>
123
- </div>
124
- </Dialog.Content>
125
- );
126
- };
117
+ </SearchList.Viewport>
118
+ </SearchList.Content>
119
+ </div>
120
+ </SearchList.Root>
121
+ <div role='none' className='pli-cardSpacingInline pbe-cardSpacingBlock'>
122
+ <Dialog.Close asChild>
123
+ <Button classNames='is-full'>{t('close label', { ns: osTranslations })}</Button>
124
+ </Dialog.Close>
125
+ </div>
126
+ </Dialog.Content>
127
+ );
128
+ },
129
+ );
@@ -4,30 +4,20 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { LayoutAction, createIntent } from '@dxos/app-framework';
8
- import { useIntentDispatcher } from '@dxos/app-framework/react';
7
+ import { Common } from '@dxos/app-framework';
8
+ import { useOperationInvoker } from '@dxos/app-framework/react';
9
9
  import { Button, Icon, useTranslation } from '@dxos/react-ui';
10
10
 
11
11
  import { COMMANDS_DIALOG, meta } from '../../meta';
12
12
 
13
13
  // TODO(thure): Refactor to be handled by a more appropriate plugin.
14
14
  export const CommandsTrigger = () => {
15
- const { dispatchPromise: dispatch } = useIntentDispatcher();
15
+ const { invokeSync } = useOperationInvoker();
16
16
  const { t } = useTranslation(meta.id);
17
17
  return (
18
18
  <Button
19
19
  classNames='m-1 pli-1 lg:pli-2'
20
- onClick={() =>
21
- dispatch(
22
- createIntent(LayoutAction.UpdateDialog, {
23
- part: 'dialog',
24
- subject: COMMANDS_DIALOG,
25
- options: {
26
- blockAlign: 'start',
27
- },
28
- }),
29
- )
30
- }
20
+ onClick={() => invokeSync(Common.LayoutOperation.UpdateDialog, { subject: COMMANDS_DIALOG, blockAlign: 'start' })}
31
21
  >
32
22
  <span className='text-description font-normal grow text-start'>{t('command list input placeholder')}</span>
33
23
  <Icon icon='ph--magnifying-glass--regular' size={5} />
@@ -2,33 +2,23 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { Atom, type Registry } from '@effect-atom/atom-react';
5
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
- import * as Schema from 'effect/Schema';
7
+ import * as Effect from 'effect/Effect';
7
8
  import React, { type KeyboardEvent, useCallback, useRef } from 'react';
8
9
  import { expect, userEvent, within } from 'storybook/test';
9
10
 
10
- import {
11
- Capabilities,
12
- IntentPlugin,
13
- LayoutAction,
14
- SettingsPlugin,
15
- contributes,
16
- createResolver,
17
- defineCapability,
18
- } from '@dxos/app-framework';
19
- import { useCapability } from '@dxos/app-framework/react';
11
+ import { Capability, Common } from '@dxos/app-framework';
12
+ import { useAtomCapability } from '@dxos/app-framework/react';
20
13
  import { withPluginManager } from '@dxos/app-framework/testing';
21
- import { live } from '@dxos/live-object';
22
- import { AttentionPlugin } from '@dxos/plugin-attention';
23
- import { GraphPlugin } from '@dxos/plugin-graph';
24
- import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
25
- import { ThemePlugin } from '@dxos/plugin-theme';
14
+ import { OperationResolver } from '@dxos/operation';
15
+ import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
26
16
  import { faker } from '@dxos/random';
27
17
  import { IconButton, Input, Main, Toolbar } from '@dxos/react-ui';
28
18
  import { withTheme } from '@dxos/react-ui/testing';
29
19
  import { useAttention, useAttentionAttributes } from '@dxos/react-ui-attention';
30
20
  import { Stack, StackItem } from '@dxos/react-ui-stack';
31
- import { defaultTx, mx } from '@dxos/react-ui-theme';
21
+ import { mx } from '@dxos/ui-theme';
32
22
 
33
23
  import { NavTreePlugin } from '../../NavTreePlugin';
34
24
  import { storybookGraphBuilders } from '../../testing';
@@ -37,7 +27,7 @@ import { NavTreeContainer } from '../NavTreeContainer';
37
27
 
38
28
  faker.seed(1234);
39
29
 
40
- const StoryState = defineCapability<{ tab: string }>('story-state');
30
+ const StoryState = Capability.make<Atom.Atom<{ tab: string }>>('story-state');
41
31
 
42
32
  // TODO(burdon): Fix outline (e.g., button in sidebar nav is clipped when focused).
43
33
  // TODO(burdon): Consider similar containment of: Table, Sheet, Kanban Column, Form, etc.
@@ -49,7 +39,7 @@ const StoryPlankHeading = ({ attendableId }: { attendableId: string }) => {
49
39
  const { hasAttention } = useAttention(attendableId);
50
40
  console.log('hasAttention', hasAttention);
51
41
  return (
52
- <div className='flex p-1 items-center border-b border-separator'>
42
+ <div className='flex p-1 items-center border-be border-separator'>
53
43
  <IconButton
54
44
  density='coarse'
55
45
  icon='ph--atom--regular'
@@ -107,7 +97,7 @@ const StoryPlank = ({ attendableId }: { attendableId: string }) => {
107
97
  };
108
98
 
109
99
  const DefaultStory = () => {
110
- const state = useCapability(StoryState);
100
+ const state = useAtomCapability(StoryState);
111
101
 
112
102
  return (
113
103
  <Main.Root complementarySidebarState='closed'>
@@ -132,29 +122,30 @@ const meta = {
132
122
  withTheme,
133
123
  withPluginManager({
134
124
  plugins: [
135
- ThemePlugin({ tx: defaultTx }),
136
- GraphPlugin(),
137
- IntentPlugin(),
138
- SettingsPlugin(),
139
- AttentionPlugin(),
125
+ ...corePlugins(),
126
+ StorybookPlugin({
127
+ initialState: { sidebarState: 'expanded' },
128
+ }),
129
+
140
130
  NavTreePlugin(),
141
- StorybookLayoutPlugin({ initialState: { sidebarState: 'expanded' } }),
142
- ],
143
- capabilities: (context) => [
144
- contributes(StoryState, live({ tab: 'space-0' })),
145
- contributes(Capabilities.AppGraphBuilder, storybookGraphBuilders(context)),
146
- contributes(Capabilities.IntentResolver, [
147
- createResolver({
148
- intent: LayoutAction.UpdateLayout,
149
- filter: (data): data is Schema.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
150
- Schema.is(LayoutAction.SwitchWorkspace.fields.input)(data),
151
- resolve: ({ subject }) => {
152
- const state = context.getCapability(StoryState);
153
- state.tab = subject;
154
- },
155
- }),
156
- ]),
157
131
  ],
132
+ capabilities: () => {
133
+ const storyStateAtom = Atom.make({ tab: 'space-0' }).pipe(Atom.keepAlive);
134
+ return [
135
+ Capability.contributes(StoryState, storyStateAtom),
136
+ Capability.contributes(Common.Capability.AppGraphBuilder, storybookGraphBuilders()),
137
+ Capability.contributes(Common.Capability.OperationResolver, [
138
+ OperationResolver.make({
139
+ operation: Common.LayoutOperation.SwitchWorkspace,
140
+ handler: ({ subject }) =>
141
+ Effect.gen(function* () {
142
+ const registry: Registry.Registry = yield* Capability.get(Common.Capability.AtomRegistry);
143
+ registry.set(storyStateAtom, { tab: subject });
144
+ }),
145
+ }),
146
+ ]),
147
+ ];
148
+ },
158
149
  }),
159
150
  ],
160
151
  parameters: {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { useMemo } from 'react';
5
+ import React, { forwardRef, useMemo } from 'react';
6
6
 
7
7
  import { type TreeProps } from '@dxos/react-ui-list';
8
8
  import { Tabs } from '@dxos/react-ui-tabs';
@@ -16,21 +16,11 @@ export const NAV_TREE_ITEM = 'NavTreeItem';
16
16
 
17
17
  export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'> & Pick<L1TabsProps, 'open'>;
18
18
 
19
- export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
19
+ export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, ...props }, forwardedRef) => {
20
20
  const { tab, useItems, onBack } = useNavTreeContext();
21
21
  const topLevelActions = useItems(root, { disposition: 'menu', sort: true });
22
- const topLevelCollections = useItems(root, { disposition: 'collection' });
23
22
  const topLevelWorkspaces = useItems(root, { disposition: 'workspace' });
24
- const topLevelNavigation = useItems(root, { disposition: 'navigation' });
25
- const l0Items = useMemo(
26
- () => [
27
- // prettier-ignore
28
- ...topLevelCollections,
29
- ...topLevelWorkspaces,
30
- ...topLevelNavigation,
31
- ],
32
- [topLevelCollections, topLevelWorkspaces, topLevelNavigation],
33
- );
23
+ const l0Items = topLevelWorkspaces;
34
24
  const pinnedItems = useItems(root, { disposition: 'pin-end', sort: true });
35
25
  const userAccountItem = useItems(root, { disposition: 'user-account' })[0];
36
26
  const topLevelItems = useMemo(
@@ -51,7 +41,7 @@ export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
51
41
  // it uses RovingFocus and doesn’t support moving focus to an item that is not a tab. Assess whether this situation
52
42
  // should change including whether it should motivate a change in the design/taxonomy, or if this means this should
53
43
  // not use `react-ui-tabs` at all.
54
- <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative'>
44
+ <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative' ref={forwardedRef}>
55
45
  <L0Menu
56
46
  menuActions={topLevelActions}
57
47
  topLevelItems={l0Items}
@@ -63,4 +53,4 @@ export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
63
53
  <L1Tabs topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
64
54
  </Tabs.Root>
65
55
  );
66
- };
56
+ });