@dxos/plugin-testing 0.8.4-main.69d29f4 → 0.8.4-main.7996785055

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 (144) hide show
  1. package/dist/lib/browser/add-toast-M6ZVP2PQ.mjs +22 -0
  2. package/dist/lib/browser/add-toast-M6ZVP2PQ.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-YHPXIILW.mjs → chunk-F2MMDTKN.mjs} +3 -3
  4. package/dist/lib/browser/chunk-F2MMDTKN.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-M4TYMLOE.mjs +21 -0
  6. package/dist/lib/browser/chunk-M4TYMLOE.mjs.map +7 -0
  7. package/dist/lib/browser/close-M6YHTWUZ.mjs +10 -0
  8. package/dist/lib/browser/close-M6YHTWUZ.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +75 -42
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/meta.json +1 -1
  12. package/dist/lib/browser/open-ND4QPYWD.mjs +10 -0
  13. package/dist/lib/browser/open-ND4QPYWD.mjs.map +7 -0
  14. package/dist/lib/browser/operation-handler-DUWMXELV.mjs +16 -0
  15. package/dist/lib/browser/operation-handler-DUWMXELV.mjs.map +7 -0
  16. package/dist/lib/browser/scroll-into-view-UAINWR6E.mjs +10 -0
  17. package/dist/lib/browser/scroll-into-view-UAINWR6E.mjs.map +7 -0
  18. package/dist/lib/browser/set-layout-mode-E6SJ6ATN.mjs +10 -0
  19. package/dist/lib/browser/set-layout-mode-E6SJ6ATN.mjs.map +7 -0
  20. package/dist/lib/browser/{state-2M3RMJYA.mjs → state-MRRN7LZE.mjs} +6 -4
  21. package/dist/lib/browser/state-MRRN7LZE.mjs.map +7 -0
  22. package/dist/lib/browser/switch-workspace-RPYF6M6L.mjs +19 -0
  23. package/dist/lib/browser/switch-workspace-RPYF6M6L.mjs.map +7 -0
  24. package/dist/lib/browser/update-complementary-HNS4FIWF.mjs +25 -0
  25. package/dist/lib/browser/update-complementary-HNS4FIWF.mjs.map +7 -0
  26. package/dist/lib/browser/update-dialog-W7QOCQ2N.mjs +27 -0
  27. package/dist/lib/browser/update-dialog-W7QOCQ2N.mjs.map +7 -0
  28. package/dist/lib/browser/update-popover-UELZDJW6.mjs +45 -0
  29. package/dist/lib/browser/update-popover-UELZDJW6.mjs.map +7 -0
  30. package/dist/lib/browser/update-sidebar-RJZTREDE.mjs +25 -0
  31. package/dist/lib/browser/update-sidebar-RJZTREDE.mjs.map +7 -0
  32. package/dist/lib/node-esm/add-toast-VMEADC7Y.mjs +23 -0
  33. package/dist/lib/node-esm/add-toast-VMEADC7Y.mjs.map +7 -0
  34. package/dist/lib/node-esm/{chunk-OWK6XE6C.mjs → chunk-VDX7GMAD.mjs} +3 -3
  35. package/dist/lib/node-esm/chunk-VDX7GMAD.mjs.map +7 -0
  36. package/dist/lib/node-esm/chunk-ZLM7QUEK.mjs +22 -0
  37. package/dist/lib/node-esm/chunk-ZLM7QUEK.mjs.map +7 -0
  38. package/dist/lib/node-esm/close-B54TW3OK.mjs +12 -0
  39. package/dist/lib/node-esm/close-B54TW3OK.mjs.map +7 -0
  40. package/dist/lib/node-esm/index.mjs +75 -42
  41. package/dist/lib/node-esm/index.mjs.map +4 -4
  42. package/dist/lib/node-esm/meta.json +1 -1
  43. package/dist/lib/node-esm/open-OLJQ7VR3.mjs +12 -0
  44. package/dist/lib/node-esm/open-OLJQ7VR3.mjs.map +7 -0
  45. package/dist/lib/node-esm/operation-handler-TCIEZXBS.mjs +18 -0
  46. package/dist/lib/node-esm/operation-handler-TCIEZXBS.mjs.map +7 -0
  47. package/dist/lib/node-esm/scroll-into-view-C3XK4YMK.mjs +12 -0
  48. package/dist/lib/node-esm/scroll-into-view-C3XK4YMK.mjs.map +7 -0
  49. package/dist/lib/node-esm/set-layout-mode-AEQI6AVR.mjs +12 -0
  50. package/dist/lib/node-esm/set-layout-mode-AEQI6AVR.mjs.map +7 -0
  51. package/dist/lib/node-esm/{state-UF2MWBFU.mjs → state-PVDE6Q6G.mjs} +6 -4
  52. package/dist/lib/node-esm/state-PVDE6Q6G.mjs.map +7 -0
  53. package/dist/lib/node-esm/switch-workspace-ZDUCWF6E.mjs +20 -0
  54. package/dist/lib/node-esm/switch-workspace-ZDUCWF6E.mjs.map +7 -0
  55. package/dist/lib/node-esm/update-complementary-LAPUAZ5I.mjs +26 -0
  56. package/dist/lib/node-esm/update-complementary-LAPUAZ5I.mjs.map +7 -0
  57. package/dist/lib/node-esm/update-dialog-5M7FL4XG.mjs +28 -0
  58. package/dist/lib/node-esm/update-dialog-5M7FL4XG.mjs.map +7 -0
  59. package/dist/lib/node-esm/update-popover-2TDGFHL3.mjs +46 -0
  60. package/dist/lib/node-esm/update-popover-2TDGFHL3.mjs.map +7 -0
  61. package/dist/lib/node-esm/update-sidebar-CU6TUDK5.mjs +26 -0
  62. package/dist/lib/node-esm/update-sidebar-CU6TUDK5.mjs.map +7 -0
  63. package/dist/types/src/StorybookPlugin.d.ts.map +1 -1
  64. package/dist/types/src/capabilities/index.d.ts +1 -1
  65. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  66. package/dist/types/src/capabilities/operation-handler/index.d.ts +4 -0
  67. package/dist/types/src/capabilities/operation-handler/index.d.ts.map +1 -0
  68. package/dist/types/src/capabilities/operation-handler/operation-handler.d.ts +6 -0
  69. package/dist/types/src/capabilities/operation-handler/operation-handler.d.ts.map +1 -0
  70. package/dist/types/src/capabilities/state/index.d.ts +1 -1
  71. package/dist/types/src/capabilities/state/state.d.ts +1 -1
  72. package/dist/types/src/capabilities/state/state.d.ts.map +1 -1
  73. package/dist/types/src/components/{Layout.d.ts → Layout/Layout.d.ts} +1 -1
  74. package/dist/types/src/components/Layout/Layout.d.ts.map +1 -0
  75. package/dist/types/src/components/Layout/index.d.ts +2 -0
  76. package/dist/types/src/components/Layout/index.d.ts.map +1 -0
  77. package/dist/types/src/core.d.ts +1 -6
  78. package/dist/types/src/core.d.ts.map +1 -1
  79. package/dist/types/src/operations/add-toast.d.ts +5 -0
  80. package/dist/types/src/operations/add-toast.d.ts.map +1 -0
  81. package/dist/types/src/operations/close.d.ts +5 -0
  82. package/dist/types/src/operations/close.d.ts.map +1 -0
  83. package/dist/types/src/operations/index.d.ts +3 -0
  84. package/dist/types/src/operations/index.d.ts.map +1 -0
  85. package/dist/types/src/operations/open.d.ts +5 -0
  86. package/dist/types/src/operations/open.d.ts.map +1 -0
  87. package/dist/types/src/operations/scroll-into-view.d.ts +5 -0
  88. package/dist/types/src/operations/scroll-into-view.d.ts.map +1 -0
  89. package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
  90. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  91. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  92. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  93. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  94. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  95. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  96. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  97. package/dist/types/src/operations/update-popover.d.ts +5 -0
  98. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  99. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  100. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  101. package/dist/types/src/operations/update-state.d.ts +5 -0
  102. package/dist/types/src/operations/update-state.d.ts.map +1 -0
  103. package/dist/types/src/types/capabilities.d.ts +2 -0
  104. package/dist/types/src/types/capabilities.d.ts.map +1 -1
  105. package/dist/types/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +19 -18
  107. package/src/StorybookPlugin.ts +14 -13
  108. package/src/capabilities/index.ts +1 -1
  109. package/src/capabilities/operation-handler/index.ts +11 -0
  110. package/src/capabilities/operation-handler/operation-handler.ts +16 -0
  111. package/src/capabilities/state/state.tsx +5 -6
  112. package/src/components/Layout/Layout.tsx +230 -0
  113. package/src/components/Layout/index.ts +5 -0
  114. package/src/core.ts +2 -6
  115. package/src/meta.ts +1 -1
  116. package/src/operations/add-toast.ts +22 -0
  117. package/src/operations/close.ts +14 -0
  118. package/src/operations/index.ts +18 -0
  119. package/src/operations/open.ts +14 -0
  120. package/src/operations/scroll-into-view.ts +14 -0
  121. package/src/operations/set-layout-mode.ts +14 -0
  122. package/src/operations/switch-workspace.ts +20 -0
  123. package/src/operations/update-complementary.ts +27 -0
  124. package/src/operations/update-dialog.ts +27 -0
  125. package/src/operations/update-popover.ts +37 -0
  126. package/src/operations/update-sidebar.ts +26 -0
  127. package/src/operations/update-state.ts +17 -0
  128. package/src/types/capabilities.ts +4 -1
  129. package/dist/lib/browser/chunk-YHPXIILW.mjs.map +0 -7
  130. package/dist/lib/browser/operation-resolver-B2DOYB7C.mjs +0 -111
  131. package/dist/lib/browser/operation-resolver-B2DOYB7C.mjs.map +0 -7
  132. package/dist/lib/browser/state-2M3RMJYA.mjs.map +0 -7
  133. package/dist/lib/node-esm/chunk-OWK6XE6C.mjs.map +0 -7
  134. package/dist/lib/node-esm/operation-resolver-DJI7OPBP.mjs +0 -112
  135. package/dist/lib/node-esm/operation-resolver-DJI7OPBP.mjs.map +0 -7
  136. package/dist/lib/node-esm/state-UF2MWBFU.mjs.map +0 -7
  137. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  138. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  139. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  140. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  141. package/dist/types/src/components/Layout.d.ts.map +0 -1
  142. package/src/capabilities/operation-resolver/index.ts +0 -7
  143. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -98
  144. package/src/components/Layout.tsx +0 -172
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-testing",
3
- "version": "0.8.4-main.69d29f4",
3
+ "version": "0.8.4-main.7996785055",
4
4
  "description": "Plugin testing utils",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -25,33 +25,34 @@
25
25
  "src"
26
26
  ],
27
27
  "dependencies": {
28
- "@effect-atom/atom": "^0.4.13",
29
- "@effect-atom/atom-react": "^0.4.6",
30
- "@dxos/app-framework": "0.8.4-main.69d29f4",
31
- "@dxos/operation": "0.8.4-main.69d29f4",
32
- "@dxos/plugin-client": "0.8.4-main.69d29f4",
33
- "@dxos/plugin-graph": "0.8.4-main.69d29f4",
34
- "@dxos/plugin-attention": "0.8.4-main.69d29f4",
35
- "@dxos/plugin-theme": "0.8.4-main.69d29f4",
36
- "@dxos/react-ui-mosaic": "0.8.4-main.69d29f4",
37
- "@dxos/util": "0.8.4-main.69d29f4"
28
+ "@effect-atom/atom": "^0.5.1",
29
+ "@effect-atom/atom-react": "^0.5.0",
30
+ "@dxos/app-framework": "0.8.4-main.7996785055",
31
+ "@dxos/operation": "0.8.4-main.7996785055",
32
+ "@dxos/plugin-attention": "0.8.4-main.7996785055",
33
+ "@dxos/plugin-graph": "0.8.4-main.7996785055",
34
+ "@dxos/plugin-settings": "0.8.4-main.7996785055",
35
+ "@dxos/app-toolkit": "0.8.4-main.7996785055",
36
+ "@dxos/plugin-theme": "0.8.4-main.7996785055",
37
+ "@dxos/react-ui-mosaic": "0.8.4-main.7996785055",
38
+ "@dxos/util": "0.8.4-main.7996785055"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/react": "~19.2.7",
41
42
  "@types/react-dom": "~19.2.3",
42
- "effect": "3.19.11",
43
+ "effect": "3.20.0",
43
44
  "react": "~19.2.3",
44
45
  "react-dom": "~19.2.3",
45
- "vite": "7.1.9",
46
- "@dxos/react-ui": "0.8.4-main.69d29f4",
47
- "@dxos/ui-theme": "0.8.4-main.69d29f4"
46
+ "vite": "^7.1.11",
47
+ "@dxos/ui-theme": "0.8.4-main.7996785055",
48
+ "@dxos/react-ui": "0.8.4-main.7996785055"
48
49
  },
49
50
  "peerDependencies": {
50
- "effect": "3.19.11",
51
+ "effect": "3.20.0",
51
52
  "react": "~19.2.3",
52
53
  "react-dom": "~19.2.3",
53
- "@dxos/react-ui": "0.8.4-main.69d29f4",
54
- "@dxos/ui-theme": "0.8.4-main.69d29f4"
54
+ "@dxos/react-ui": "0.8.4-main.7996785055",
55
+ "@dxos/ui-theme": "0.8.4-main.7996785055"
55
56
  },
56
57
  "publishConfig": {
57
58
  "access": "public"
@@ -4,9 +4,10 @@
4
4
 
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
- import { Capability, Common, Plugin } from '@dxos/app-framework';
7
+ import { ActivationEvents, Capabilities, Capability, Plugin } from '@dxos/app-framework';
8
+ import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
8
9
 
9
- import { OperationResolver, State } from './capabilities';
10
+ import { OperationHandler, State } from './capabilities';
10
11
  import { Layout } from './components';
11
12
  import { meta } from './meta';
12
13
  import { type LayoutStateProps } from './types';
@@ -16,23 +17,23 @@ export type StorybookPluginOptions = {
16
17
  };
17
18
 
18
19
  export const StorybookPlugin = Plugin.define<StorybookPluginOptions>(meta).pipe(
19
- Plugin.addModule(({ initialState }) => ({
20
- id: Capability.getModuleTag(State),
21
- activatesOn: Common.ActivationEvent.Startup,
22
- activatesAfter: [Common.ActivationEvent.LayoutReady],
23
- activate: () => State({ initialState }),
24
- })),
25
- Common.Plugin.addReactContextModule({
20
+ AppPlugin.addOperationHandlerModule({
21
+ activate: OperationHandler,
22
+ }),
23
+ AppPlugin.addReactContextModule({
26
24
  activate: () =>
27
25
  Effect.succeed(
28
- Capability.contributes(Common.Capability.ReactContext, {
26
+ Capability.contributes(Capabilities.ReactContext, {
29
27
  id: 'storybook-layout',
30
28
  context: Layout,
31
29
  }),
32
30
  ),
33
31
  }),
34
- Common.Plugin.addOperationResolverModule({
35
- activate: OperationResolver,
36
- }),
32
+ Plugin.addModule(({ initialState }) => ({
33
+ id: Capability.getModuleTag(State),
34
+ activatesOn: ActivationEvents.Startup,
35
+ activatesAfter: [AppActivationEvents.LayoutReady],
36
+ activate: () => State({ initialState }),
37
+ })),
37
38
  Plugin.make,
38
39
  );
@@ -2,5 +2,5 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './operation-resolver';
5
+ export * from './operation-handler';
6
6
  export * from './state';
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capability } from '@dxos/app-framework';
6
+ import { OperationHandlerSet } from '@dxos/operation';
7
+
8
+ export const OperationHandler = Capability.lazy<OperationHandlerSet.OperationHandlerSet>(
9
+ 'OperationHandler',
10
+ () => import('./operation-handler'),
11
+ );
@@ -0,0 +1,16 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import type { OperationHandlerSet } from '@dxos/operation';
9
+
10
+ import { TestingOperationHandlerSet } from '../../operations';
11
+
12
+ export default Capability.makeModule<OperationHandlerSet.OperationHandlerSet>(
13
+ Effect.fnUntraced(function* () {
14
+ return Capability.contributes(Capabilities.OperationHandler, TestingOperationHandlerSet);
15
+ }),
16
+ );
@@ -5,7 +5,8 @@
5
5
  import { Atom } from '@effect-atom/atom-react';
6
6
  import * as Effect from 'effect/Effect';
7
7
 
8
- import { Capability, Common } from '@dxos/app-framework';
8
+ import { Capability } from '@dxos/app-framework';
9
+ import { AppCapabilities } from '@dxos/app-toolkit';
9
10
 
10
11
  import { LayoutState, type LayoutStateProps } from '../../types';
11
12
 
@@ -13,6 +14,7 @@ const defaultState: LayoutStateProps = {
13
14
  sidebarState: 'closed',
14
15
  complementarySidebarState: 'closed',
15
16
  dialogOpen: false,
17
+ toasts: [],
16
18
  workspace: 'default',
17
19
  };
18
20
 
@@ -21,7 +23,7 @@ export default Capability.makeModule(
21
23
  const { initialState } = props ?? {};
22
24
  const stateAtom = Atom.make<LayoutStateProps>({ ...defaultState, ...initialState });
23
25
 
24
- const layoutAtom = Atom.make((get): Common.Capability.Layout => {
26
+ const layoutAtom = Atom.make((get): AppCapabilities.Layout => {
25
27
  const state = get(stateAtom);
26
28
  return {
27
29
  mode: 'storybook',
@@ -35,9 +37,6 @@ export default Capability.makeModule(
35
37
  };
36
38
  });
37
39
 
38
- return [
39
- Capability.contributes(LayoutState, stateAtom),
40
- Capability.contributes(Common.Capability.Layout, layoutAtom),
41
- ];
40
+ return [Capability.contributes(LayoutState, stateAtom), Capability.contributes(AppCapabilities.Layout, layoutAtom)];
42
41
  }),
43
42
  );
@@ -0,0 +1,230 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { RegistryContext, useAtomValue } from '@effect-atom/atom-react';
6
+ import React, { type PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
7
+
8
+ import { Surface, useCapability } from '@dxos/app-framework/ui';
9
+ import { type LayoutOperation } from '@dxos/app-toolkit';
10
+ import {
11
+ AlertDialog,
12
+ Button,
13
+ Dialog,
14
+ Icon,
15
+ Main,
16
+ Popover,
17
+ type PopoverContentInteractOutsideEvent,
18
+ Toast,
19
+ toLocalizedString,
20
+ useTranslation,
21
+ } from '@dxos/react-ui';
22
+ import { Card } from '@dxos/react-ui';
23
+ import { Mosaic } from '@dxos/react-ui-mosaic';
24
+ import { descriptionMessage, mx } from '@dxos/ui-theme';
25
+
26
+ import { meta } from '../../meta';
27
+ import { LayoutState, type LayoutStateProps } from '../../types';
28
+
29
+ const debounce_delay = 100;
30
+
31
+ const StoryToast = ({ toast, onDismiss }: { toast: LayoutOperation.Toast; onDismiss: (id: string) => void }) => {
32
+ const { t } = useTranslation(meta.id);
33
+ return (
34
+ <Toast.Root
35
+ data-testid={toast.id}
36
+ defaultOpen
37
+ duration={toast.duration}
38
+ onOpenChange={(open) => {
39
+ if (!open) {
40
+ onDismiss(toast.id);
41
+ }
42
+ }}
43
+ >
44
+ <Toast.Body>
45
+ <Toast.Title classNames='items-center'>
46
+ {toast.icon && <Icon icon={toast.icon} classNames='inline mr-1' />}
47
+ {toast.title && <span>{toLocalizedString(toast.title, t)}</span>}
48
+ </Toast.Title>
49
+ {toast.description && <Toast.Description>{toLocalizedString(toast.description, t)}</Toast.Description>}
50
+ </Toast.Body>
51
+ <Toast.Actions>
52
+ {toast.onAction && toast.actionAlt && toast.actionLabel && (
53
+ <Toast.Action altText={toLocalizedString(toast.actionAlt, t)} asChild>
54
+ <Button variant='primary' onClick={() => toast.onAction?.()}>
55
+ {toLocalizedString(toast.actionLabel, t)}
56
+ </Button>
57
+ </Toast.Action>
58
+ )}
59
+ {toast.closeLabel && (
60
+ <Toast.Close asChild>
61
+ <Button>{toLocalizedString(toast.closeLabel, t)}</Button>
62
+ </Toast.Close>
63
+ )}
64
+ </Toast.Actions>
65
+ </Toast.Root>
66
+ );
67
+ };
68
+
69
+ export const Layout = ({ children }: PropsWithChildren<{}>) => {
70
+ const { t } = useTranslation(meta.id);
71
+ const trigger = useRef<HTMLButtonElement | null>(null);
72
+ const registry = useContext(RegistryContext);
73
+ const stateAtom = useCapability(LayoutState);
74
+ const layout = useAtomValue(stateAtom);
75
+ const [iter, setIter] = useState(0);
76
+ const [open, setOpen] = useState(false);
77
+ const debounceRef = useRef<NodeJS.Timeout | null>(null);
78
+
79
+ const updateState = useCallback(
80
+ (updates: Partial<LayoutStateProps>) => {
81
+ const current = registry.get(stateAtom);
82
+ registry.set(stateAtom, { ...current, ...updates });
83
+ },
84
+ [registry, stateAtom],
85
+ );
86
+
87
+ useEffect(() => {
88
+ setOpen(false);
89
+ if (debounceRef.current) {
90
+ clearTimeout(debounceRef.current);
91
+ debounceRef.current = null;
92
+ }
93
+ trigger.current = layout.popoverAnchor ?? null;
94
+ setIter((iter) => iter + 1);
95
+ if (layout.popoverOpen) {
96
+ debounceRef.current = setTimeout(() => setOpen(true), debounce_delay);
97
+ }
98
+ }, [layout.popoverAnchor, layout.popoverContent, layout.popoverOpen]);
99
+
100
+ const handleClose = useCallback(() => {
101
+ setOpen(false);
102
+ updateState({
103
+ popoverOpen: false,
104
+ popoverAnchor: undefined,
105
+ popoverAnchorId: undefined,
106
+ popoverSide: undefined,
107
+ });
108
+ }, [updateState]);
109
+
110
+ const handleInteractOutside = useCallback(
111
+ (event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
112
+ if (
113
+ // TODO(thure): CodeMirror should not focus itself when it updates.
114
+ event.type === 'dismissableLayer.focusOutside' &&
115
+ (event.currentTarget as HTMLElement | undefined)?.classList.contains('cm-content')
116
+ ) {
117
+ event.preventDefault();
118
+ } else {
119
+ handleClose();
120
+ }
121
+ },
122
+ [handleClose],
123
+ );
124
+
125
+ const handleDismissToast = useCallback(
126
+ (id: string) => {
127
+ updateState({ toasts: layout.toasts.filter((toast) => toast.id !== id) });
128
+ },
129
+ [updateState, layout.toasts],
130
+ );
131
+
132
+ const DialogRoot = layout.dialogType === 'alert' ? AlertDialog.Root : Dialog.Root;
133
+ const DialogOverlay = layout.dialogType === 'alert' ? AlertDialog.Overlay : Dialog.Overlay;
134
+
135
+ return (
136
+ <Toast.Provider>
137
+ <div role='none' className='fixed inset-0 flex overflow-hidden'>
138
+ <Mosaic.Root>
139
+ <Popover.Root open={open}>
140
+ <Main.Root
141
+ navigationSidebarState={layout.sidebarState}
142
+ complementarySidebarState={layout.complementarySidebarState}
143
+ onNavigationSidebarStateChange={(next) => updateState({ sidebarState: next })}
144
+ onComplementarySidebarStateChange={(next) => updateState({ complementarySidebarState: next })}
145
+ >
146
+ {children}
147
+ </Main.Root>
148
+
149
+ <DialogRoot
150
+ modal={layout.dialogBlockAlign !== 'end'}
151
+ open={layout.dialogOpen}
152
+ onOpenChange={(nextOpen) => updateState({ dialogOpen: nextOpen })}
153
+ >
154
+ {layout.dialogBlockAlign === 'end' ? (
155
+ <Surface.Surface
156
+ role='dialog'
157
+ data={layout.dialogContent}
158
+ limit={1}
159
+ fallback={ErrorFallback}
160
+ placeholder={<div />}
161
+ />
162
+ ) : (
163
+ <DialogOverlay
164
+ blockAlign={layout.dialogBlockAlign}
165
+ classNames={layout.dialogOverlayClasses}
166
+ style={layout.dialogOverlayStyle}
167
+ >
168
+ <Surface.Surface role='dialog' data={layout.dialogContent} limit={1} fallback={ErrorFallback} />
169
+ </DialogOverlay>
170
+ )}
171
+ </DialogRoot>
172
+
173
+ <Popover.VirtualTrigger key={iter} virtualRef={trigger} />
174
+ <Popover.Portal>
175
+ <Popover.Content
176
+ side={layout.popoverSide}
177
+ onInteractOutside={handleInteractOutside}
178
+ onEscapeKeyDown={handleInteractOutside}
179
+ sticky='always'
180
+ hideWhenDetached
181
+ >
182
+ <Popover.Viewport>
183
+ {layout.popoverKind === 'card' && (
184
+ <Card.Root>
185
+ <Card.Toolbar>
186
+ {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
187
+ <span />
188
+ {layout.popoverTitle ? (
189
+ <Card.Title>{toLocalizedString(layout.popoverTitle, t)}</Card.Title>
190
+ ) : (
191
+ <span />
192
+ )}
193
+ <Card.CloseIconButton onClick={handleClose} />
194
+ </Card.Toolbar>
195
+ <Surface.Surface role='card--content' data={layout.popoverContent} limit={1} />
196
+ </Card.Root>
197
+ )}
198
+ {layout.popoverKind === 'base' && (
199
+ <Surface.Surface role='popover' data={layout.popoverContent} limit={1} />
200
+ )}
201
+ </Popover.Viewport>
202
+ <Popover.Arrow />
203
+ </Popover.Content>
204
+ </Popover.Portal>
205
+ </Popover.Root>
206
+ </Mosaic.Root>
207
+ {layout.toasts.map((toast) => (
208
+ <StoryToast key={toast.id} toast={toast} onDismiss={handleDismissToast} />
209
+ ))}
210
+ <Toast.Viewport />
211
+ </div>
212
+ </Toast.Provider>
213
+ );
214
+ };
215
+
216
+ export const ErrorFallback = ({ error }: { error?: Error }) => {
217
+ const { t } = useTranslation(meta.id);
218
+ const errorString = error?.toString() ?? '';
219
+ return (
220
+ <div
221
+ role='alert'
222
+ data-testid='error-boundary-fallback'
223
+ className={mx('overflow-auto p-8 dx-attention-surface grid place-items-center')}
224
+ >
225
+ <p className={mx(descriptionMessage, 'break-words rounded-md p-8', errorString.length < 256 && 'text-lg')}>
226
+ {error ? errorString : t('error fallback message')}
227
+ </p>
228
+ </div>
229
+ );
230
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Layout';
package/src/core.ts CHANGED
@@ -2,17 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { OperationPlugin, type Plugin, RuntimePlugin, SettingsPlugin } from '@dxos/app-framework';
5
+ import { OperationPlugin, type Plugin, RuntimePlugin } from '@dxos/app-framework';
6
6
  import { AttentionPlugin } from '@dxos/plugin-attention';
7
- import { ClientPlugin } from '@dxos/plugin-client';
8
7
  import { GraphPlugin } from '@dxos/plugin-graph';
8
+ import { SettingsPlugin } from '@dxos/plugin-settings';
9
9
  import { ThemePlugin } from '@dxos/plugin-theme';
10
10
  import { defaultTx } from '@dxos/ui-theme';
11
11
 
12
- // TODO(burdon): Remove this.
13
- // Re-export common framework plugins.
14
- export { AttentionPlugin, ClientPlugin, GraphPlugin, OperationPlugin, RuntimePlugin, SettingsPlugin, ThemePlugin };
15
-
16
12
  /**
17
13
  * Core plugins for testing/storybook environments.
18
14
  * NOTE: Does not include SpacePlugin to avoid circular dependencies.
package/src/meta.ts CHANGED
@@ -6,7 +6,7 @@ import { type Plugin } from '@dxos/app-framework';
6
6
  import { trim } from '@dxos/util';
7
7
 
8
8
  export const meta: Plugin.Meta = {
9
- id: 'dxos.org/plugin/storybook-layout',
9
+ id: 'org.dxos.plugin.storybook-layout',
10
10
  name: 'Storybook',
11
11
  description: trim`
12
12
  Development layout optimized for Storybook component testing and documentation.
@@ -0,0 +1,22 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.AddToast> = LayoutOperation.AddToast.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* (input) {
15
+ yield* updateState((state) => ({
16
+ toasts: [...state.toasts, input as LayoutOperation.Toast],
17
+ }));
18
+ }),
19
+ ),
20
+ );
21
+
22
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.Close> = LayoutOperation.Close.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,18 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { OperationHandlerSet } from '@dxos/operation';
6
+
7
+ export const TestingOperationHandlerSet = OperationHandlerSet.lazy(
8
+ () => import('./add-toast'),
9
+ () => import('./close'),
10
+ () => import('./open'),
11
+ () => import('./scroll-into-view'),
12
+ () => import('./set-layout-mode'),
13
+ () => import('./switch-workspace'),
14
+ () => import('./update-complementary'),
15
+ () => import('./update-dialog'),
16
+ () => import('./update-popover'),
17
+ () => import('./update-sidebar'),
18
+ );
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.Open> = LayoutOperation.Open.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.ScrollIntoView> = LayoutOperation.ScrollIntoView.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,14 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ const handler: Operation.WithHandler<typeof LayoutOperation.SetLayoutMode> = LayoutOperation.SetLayoutMode.pipe(
11
+ Operation.withHandler(() => Effect.void),
12
+ );
13
+
14
+ export default handler;
@@ -0,0 +1,20 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.SwitchWorkspace> = LayoutOperation.SwitchWorkspace.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* ({ subject }) {
15
+ yield* updateState(() => ({ workspace: subject }));
16
+ }),
17
+ ),
18
+ );
19
+
20
+ export default handler;
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdateComplementary> =
13
+ LayoutOperation.UpdateComplementary.pipe(
14
+ Operation.withHandler(
15
+ Effect.fnUntraced(function* ({ state }) {
16
+ yield* updateState((layout) => {
17
+ const next = state ?? layout.complementarySidebarState;
18
+ if (next !== layout.complementarySidebarState) {
19
+ return { complementarySidebarState: next };
20
+ }
21
+ return {};
22
+ });
23
+ }),
24
+ ),
25
+ );
26
+
27
+ export default handler;
@@ -0,0 +1,27 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { updateState } from './update-state';
11
+
12
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdateDialog> = LayoutOperation.UpdateDialog.pipe(
13
+ Operation.withHandler(
14
+ Effect.fnUntraced(function* ({ subject, state, type, blockAlign, overlayClasses, overlayStyle, props }) {
15
+ yield* updateState(() => ({
16
+ dialogOpen: state ?? Boolean(subject),
17
+ dialogType: type ?? 'default',
18
+ dialogBlockAlign: blockAlign ?? 'center',
19
+ dialogOverlayClasses: overlayClasses,
20
+ dialogOverlayStyle: overlayStyle,
21
+ dialogContent: subject ? { component: subject, props } : null,
22
+ }));
23
+ }),
24
+ ),
25
+ );
26
+
27
+ export default handler;
@@ -0,0 +1,37 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { LayoutOperation } from '@dxos/app-toolkit';
8
+ import { Operation } from '@dxos/operation';
9
+
10
+ import { type LayoutStateProps } from '../types';
11
+ import { updateState } from './update-state';
12
+
13
+ const handler: Operation.WithHandler<typeof LayoutOperation.UpdatePopover> = LayoutOperation.UpdatePopover.pipe(
14
+ Operation.withHandler(
15
+ Effect.fnUntraced(function* (input) {
16
+ const { subject, state, side, kind, props } = input;
17
+ yield* updateState(() => {
18
+ const base: Partial<LayoutStateProps> = {
19
+ popoverKind: kind ?? 'base',
20
+ popoverTitle: kind === 'card' ? input.title : undefined,
21
+ popoverContent:
22
+ typeof subject === 'string' ? { component: subject, props } : subject ? { subject } : undefined,
23
+ popoverOpen: state ?? Boolean(subject),
24
+ popoverSide: side,
25
+ };
26
+ if ('variant' in input && input.variant === 'virtual') {
27
+ return { ...base, popoverVariant: 'virtual', popoverAnchor: input.anchor };
28
+ } else if ('anchorId' in input) {
29
+ return { ...base, popoverVariant: 'react', popoverAnchorId: input.anchorId };
30
+ }
31
+ return base;
32
+ });
33
+ }),
34
+ ),
35
+ );
36
+
37
+ export default handler;