@dxos/react-ui-mosaic 0.9.1-staging.ee54ba693a → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/lib/chunk-Mosaic.mjs +1024 -0
  2. package/dist/lib/chunk-Mosaic.mjs.map +1 -0
  3. package/dist/lib/index.mjs +653 -0
  4. package/dist/lib/index.mjs.map +1 -0
  5. package/dist/lib/playwright.mjs +200 -0
  6. package/dist/lib/playwright.mjs.map +1 -0
  7. package/dist/lib/testing.mjs +127 -0
  8. package/dist/lib/testing.mjs.map +1 -0
  9. package/dist/lib/translations.mjs +11 -0
  10. package/dist/lib/translations.mjs.map +1 -0
  11. package/dist/types/src/components/Board/Board.d.ts +7 -27
  12. package/dist/types/src/components/Board/Board.d.ts.map +1 -1
  13. package/dist/types/src/components/Board/Board.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Board/BoardColumnContext.d.ts +10 -0
  15. package/dist/types/src/components/Board/BoardColumnContext.d.ts.map +1 -0
  16. package/dist/types/src/components/Board/BoardContext.d.ts +26 -0
  17. package/dist/types/src/components/Board/BoardContext.d.ts.map +1 -0
  18. package/dist/types/src/components/Board/Column.d.ts +2 -7
  19. package/dist/types/src/components/Board/Column.d.ts.map +1 -1
  20. package/dist/types/src/components/Board/index.d.ts +2 -0
  21. package/dist/types/src/components/Board/index.d.ts.map +1 -1
  22. package/dist/types/src/components/Focus/Focus.d.ts.map +1 -1
  23. package/dist/types/src/components/Mosaic/Container.d.ts +6 -34
  24. package/dist/types/src/components/Mosaic/Container.d.ts.map +1 -1
  25. package/dist/types/src/components/Mosaic/Mosaic.d.ts +10 -15
  26. package/dist/types/src/components/Mosaic/Mosaic.d.ts.map +1 -1
  27. package/dist/types/src/components/Mosaic/MosaicContainerContext.d.ts +34 -0
  28. package/dist/types/src/components/Mosaic/MosaicContainerContext.d.ts.map +1 -0
  29. package/dist/types/src/components/Mosaic/MosaicTileContext.d.ts +31 -0
  30. package/dist/types/src/components/Mosaic/MosaicTileContext.d.ts.map +1 -0
  31. package/dist/types/src/components/Mosaic/Placeholder.d.ts +3 -3
  32. package/dist/types/src/components/Mosaic/Placeholder.d.ts.map +1 -1
  33. package/dist/types/src/components/Mosaic/Stack.d.ts +8 -2
  34. package/dist/types/src/components/Mosaic/Stack.d.ts.map +1 -1
  35. package/dist/types/src/components/Mosaic/Stack.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Mosaic/Tile.d.ts +6 -32
  37. package/dist/types/src/components/Mosaic/Tile.d.ts.map +1 -1
  38. package/dist/types/src/components/Mosaic/VirtualStackPagination.stories.d.ts +13 -0
  39. package/dist/types/src/components/Mosaic/VirtualStackPagination.stories.d.ts.map +1 -0
  40. package/dist/types/src/components/Mosaic/index.d.ts +0 -1
  41. package/dist/types/src/components/Mosaic/index.d.ts.map +1 -1
  42. package/dist/types/src/components/SearchStack/SearchStack.d.ts +1 -1
  43. package/dist/types/src/components/SearchStack/SearchStack.d.ts.map +1 -1
  44. package/dist/types/src/hooks/index.d.ts +1 -0
  45. package/dist/types/src/hooks/index.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts +2 -2
  47. package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts.map +1 -1
  48. package/dist/types/src/hooks/useEventHandlerAdapter.d.ts +3 -3
  49. package/dist/types/src/hooks/useEventHandlerAdapter.d.ts.map +1 -1
  50. package/dist/types/src/hooks/useVirtualizerPagination.d.ts +60 -0
  51. package/dist/types/src/hooks/useVirtualizerPagination.d.ts.map +1 -0
  52. package/dist/types/src/hooks/useVirtualizerPagination.test.d.ts +2 -0
  53. package/dist/types/src/hooks/useVirtualizerPagination.test.d.ts.map +1 -0
  54. package/dist/types/src/hooks/useVisibleItems.d.ts +2 -2
  55. package/dist/types/src/hooks/useVisibleItems.d.ts.map +1 -1
  56. package/dist/types/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +35 -37
  58. package/src/components/Board/Board.stories.tsx +7 -6
  59. package/src/components/Board/Board.tsx +9 -54
  60. package/src/components/Board/BoardColumnContext.ts +25 -0
  61. package/src/components/Board/BoardContext.ts +43 -0
  62. package/src/components/Board/Column.tsx +13 -27
  63. package/src/components/Board/Item.tsx +2 -2
  64. package/src/components/Board/index.ts +3 -0
  65. package/src/components/Mosaic/Container.tsx +31 -53
  66. package/src/components/Mosaic/DragHandle.tsx +1 -1
  67. package/src/components/Mosaic/{Mosaic.tsx → Mosaic.ts} +5 -16
  68. package/src/components/Mosaic/MosaicContainerContext.ts +44 -0
  69. package/src/components/Mosaic/MosaicTileContext.ts +34 -0
  70. package/src/components/Mosaic/Placeholder.tsx +7 -8
  71. package/src/components/Mosaic/ResizeHandle.stories.tsx +4 -4
  72. package/src/components/Mosaic/ResizeHandle.tsx +2 -2
  73. package/src/components/Mosaic/Stack.stories.tsx +49 -13
  74. package/src/components/Mosaic/Stack.tsx +30 -9
  75. package/src/components/Mosaic/Tile.tsx +33 -33
  76. package/src/components/Mosaic/VirtualStackPagination.stories.tsx +423 -0
  77. package/src/components/Mosaic/index.ts +0 -1
  78. package/src/hooks/index.ts +1 -0
  79. package/src/hooks/useDefaultColumnEventHandler.ts +2 -2
  80. package/src/hooks/useEventHandlerAdapter.ts +4 -5
  81. package/src/hooks/useVirtualizerPagination.test.tsx +105 -0
  82. package/src/hooks/useVirtualizerPagination.ts +473 -0
  83. package/src/hooks/useVisibleItems.ts +2 -2
  84. package/src/playwright/smoke.spec.ts +4 -1
  85. package/src/testing/DefaultStackTile.tsx +1 -1
  86. package/src/testing/decorator.tsx +3 -3
  87. package/dist/lib/browser/chunk-OBVIYZLR.mjs +0 -1119
  88. package/dist/lib/browser/chunk-OBVIYZLR.mjs.map +0 -7
  89. package/dist/lib/browser/index.mjs +0 -701
  90. package/dist/lib/browser/index.mjs.map +0 -7
  91. package/dist/lib/browser/meta.json +0 -1
  92. package/dist/lib/browser/playwright/index.mjs +0 -256
  93. package/dist/lib/browser/playwright/index.mjs.map +0 -7
  94. package/dist/lib/browser/testing/index.mjs +0 -136
  95. package/dist/lib/browser/testing/index.mjs.map +0 -7
  96. package/dist/lib/browser/translations.mjs +0 -18
  97. package/dist/lib/browser/translations.mjs.map +0 -7
  98. package/dist/lib/node-esm/chunk-4IEMCMIV.mjs +0 -1121
  99. package/dist/lib/node-esm/chunk-4IEMCMIV.mjs.map +0 -7
  100. package/dist/lib/node-esm/index.mjs +0 -702
  101. package/dist/lib/node-esm/index.mjs.map +0 -7
  102. package/dist/lib/node-esm/meta.json +0 -1
  103. package/dist/lib/node-esm/playwright/index.mjs +0 -258
  104. package/dist/lib/node-esm/playwright/index.mjs.map +0 -7
  105. package/dist/lib/node-esm/testing/index.mjs +0 -137
  106. package/dist/lib/node-esm/testing/index.mjs.map +0 -7
  107. package/dist/lib/node-esm/translations.mjs +0 -20
  108. package/dist/lib/node-esm/translations.mjs.map +0 -7
  109. package/dist/types/src/components/Mosaic/Root.d.ts +0 -35
  110. package/dist/types/src/components/Mosaic/Root.d.ts.map +0 -1
  111. package/dist/types/src/components/Mosaic/types.d.ts +0 -86
  112. package/dist/types/src/components/Mosaic/types.d.ts.map +0 -1
  113. package/src/components/Mosaic/Root.tsx +0 -288
  114. package/src/components/Mosaic/types.ts +0 -94
  115. /package/src/components/Focus/{Focus.tsx → Focus.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-mosaic",
3
- "version": "0.9.1-staging.ee54ba693a",
3
+ "version": "0.11.0",
4
4
  "description": "Modular UI components.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -19,26 +19,22 @@
19
19
  ".": {
20
20
  "source": "./src/index.ts",
21
21
  "types": "./dist/types/src/index.d.ts",
22
- "browser": "./dist/lib/browser/index.mjs",
23
- "node": "./dist/lib/node-esm/index.mjs"
22
+ "import": "./dist/lib/index.mjs"
24
23
  },
25
24
  "./playwright": {
26
25
  "source": "./src/playwright/index.ts",
27
26
  "types": "./dist/types/src/playwright/index.d.ts",
28
- "browser": "./dist/lib/browser/playwright/index.mjs",
29
- "node": "./dist/lib/node-esm/playwright/index.mjs"
27
+ "import": "./dist/lib/playwright.mjs"
30
28
  },
31
29
  "./testing": {
32
30
  "source": "./src/testing/index.ts",
33
31
  "types": "./dist/types/src/testing/index.d.ts",
34
- "browser": "./dist/lib/browser/testing/index.mjs",
35
- "node": "./dist/lib/node-esm/testing/index.mjs"
32
+ "import": "./dist/lib/testing.mjs"
36
33
  },
37
34
  "./translations": {
38
35
  "source": "./src/translations.ts",
39
36
  "types": "./dist/types/src/translations.d.ts",
40
- "browser": "./dist/lib/browser/translations.mjs",
41
- "node": "./dist/lib/node-esm/translations.mjs"
37
+ "import": "./dist/lib/translations.mjs"
42
38
  }
43
39
  },
44
40
  "types": "dist/types/src/index.d.ts",
@@ -51,8 +47,8 @@
51
47
  "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.2",
52
48
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
53
49
  "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "3.2.10",
54
- "@codemirror/state": "^6.6.0",
55
- "@codemirror/view": "^6.43.0",
50
+ "@codemirror/state": "^6.7.1",
51
+ "@codemirror/view": "^6.43.6",
56
52
  "@fluentui/react-tabster": "9.26.11",
57
53
  "@playwright/test": "1.57.0",
58
54
  "@radix-ui/react-compose-refs": "1.1.1",
@@ -62,46 +58,48 @@
62
58
  "@radix-ui/react-use-controllable-state": "1.1.0",
63
59
  "@tanstack/react-virtual": "^3.13.18",
64
60
  "bind-event-listener": "^3.0.0",
65
- "@dxos/async": "0.9.1-staging.ee54ba693a",
66
- "@dxos/invariant": "0.9.1-staging.ee54ba693a",
67
- "@dxos/log": "0.9.1-staging.ee54ba693a",
68
- "@dxos/keys": "0.9.1-staging.ee54ba693a",
69
- "@dxos/react-client": "0.9.1-staging.ee54ba693a",
70
- "@dxos/echo": "0.9.1-staging.ee54ba693a",
71
- "@dxos/react-ui-list": "0.9.1-staging.ee54ba693a",
72
- "@dxos/react-ui-attention": "0.9.1-staging.ee54ba693a",
73
- "@dxos/react-ui-dnd": "0.9.1-staging.ee54ba693a",
74
- "@dxos/react-ui-menu": "0.9.1-staging.ee54ba693a",
75
- "@dxos/react-ui-syntax-highlighter": "0.9.1-staging.ee54ba693a",
76
- "@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
77
- "@dxos/react-ui-search": "0.9.1-staging.ee54ba693a",
78
- "@dxos/util": "0.9.1-staging.ee54ba693a"
61
+ "@dxos/async": "0.11.0",
62
+ "@dxos/echo": "0.11.0",
63
+ "@dxos/keys": "0.11.0",
64
+ "@dxos/echo-react": "0.11.0",
65
+ "@dxos/invariant": "0.11.0",
66
+ "@dxos/log": "0.11.0",
67
+ "@dxos/react-client": "0.11.0",
68
+ "@dxos/react-ui-dnd": "0.11.0",
69
+ "@dxos/react-ui-menu": "0.11.0",
70
+ "@dxos/react-ui-attention": "0.11.0",
71
+ "@dxos/react-ui-list": "0.11.0",
72
+ "@dxos/react-ui-search": "0.11.0",
73
+ "@dxos/ui-theme": "0.11.0",
74
+ "@dxos/util": "0.11.0",
75
+ "@dxos/react-ui-syntax-highlighter": "0.11.0"
79
76
  },
80
77
  "devDependencies": {
81
78
  "@effect-atom/atom-react": "^0.5.0",
79
+ "@testing-library/react": "^16.3.2",
82
80
  "@types/react": "~19.2.17",
83
81
  "@types/react-dom": "~19.2.3",
84
82
  "effect": "3.21.4",
85
83
  "react": "~19.2.7",
86
84
  "react-dom": "~19.2.7",
87
- "vite": "^8.0.16",
88
- "@dxos/random": "0.9.1-staging.ee54ba693a",
89
- "@dxos/react-ui": "0.9.1-staging.ee54ba693a",
90
- "@dxos/react-ui-syntax-highlighter": "0.9.1-staging.ee54ba693a",
91
- "@dxos/schema": "0.9.1-staging.ee54ba693a",
92
- "@dxos/test-utils": "0.9.1-staging.ee54ba693a",
93
- "@dxos/storybook-utils": "0.9.1-staging.ee54ba693a",
94
- "@dxos/types": "0.9.1-staging.ee54ba693a",
95
- "@dxos/ui-theme": "0.9.1-staging.ee54ba693a"
85
+ "vite": "^8.1.4",
86
+ "@dxos/react-ui": "0.11.0",
87
+ "@dxos/random": "0.11.0",
88
+ "@dxos/schema": "0.11.0",
89
+ "@dxos/test-utils": "0.11.0",
90
+ "@dxos/react-ui-syntax-highlighter": "0.11.0",
91
+ "@dxos/types": "0.11.0",
92
+ "@dxos/ui-theme": "0.11.0",
93
+ "@dxos/storybook-utils": "0.11.0"
96
94
  },
97
95
  "peerDependencies": {
98
96
  "@effect-atom/atom-react": "^0.5.0",
99
97
  "effect": "3.21.4",
100
98
  "react": "~19.2.7",
101
99
  "react-dom": "~19.2.7",
102
- "@dxos/ui-theme": "0.9.1-staging.ee54ba693a",
103
- "@dxos/random": "0.9.1-staging.ee54ba693a",
104
- "@dxos/react-ui": "0.9.1-staging.ee54ba693a"
100
+ "@dxos/react-ui": "^0.11.0",
101
+ "@dxos/random": "^0.11.0",
102
+ "@dxos/ui-theme": "^0.11.0"
105
103
  },
106
104
  "publishConfig": {
107
105
  "access": "public"
@@ -11,6 +11,7 @@ import { type Database, Filter, Obj, Ref } from '@dxos/echo';
11
11
  import { invariant } from '@dxos/invariant';
12
12
  import { random } from '@dxos/random';
13
13
  import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
14
+ import { Dnd, type DndContainerHandler } from '@dxos/react-ui-dnd';
14
15
  import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
15
16
  import { withRegistry } from '@dxos/storybook-utils';
16
17
  import { mx } from '@dxos/ui-theme';
@@ -20,8 +21,8 @@ import { translations } from '#translations';
20
21
  import { useEventHandlerAdapter } from '../../hooks';
21
22
  import { TestColumn, TestItem } from '../../testing';
22
23
  import { Focus } from '../Focus';
23
- import { Mosaic, type MosaicEventHandler } from '../Mosaic';
24
- import { Board, type BoardModel } from './Board';
24
+ import { Board } from './Board';
25
+ import { type BoardModel } from './BoardContext';
25
26
  import { DefaultBoardColumn } from './Column';
26
27
 
27
28
  random.seed(999);
@@ -57,7 +58,7 @@ type StoryArgs = {
57
58
 
58
59
  type TestBoardModelResult = {
59
60
  model: BoardModel<TestColumn, TestItem>;
60
- eventHandler: MosaicEventHandler<TestColumn>;
61
+ eventHandler: DndContainerHandler<TestColumn>;
61
62
  };
62
63
 
63
64
  const useTestBoardModel = (): TestBoardModelResult => {
@@ -172,10 +173,10 @@ const DefaultStory = ({ debug = false, columns: columnsProp = 0 }: StoryArgs) =>
172
173
  }
173
174
 
174
175
  return (
175
- <Mosaic.Root asChild debug={debug}>
176
+ <Dnd.Root>
176
177
  <div className={mx('grid md:p-2 overflow-hidden', debug && 'grid-cols-[1fr_20rem] gap-2')}>
177
178
  <Board.Root model={model}>
178
- <Board.Content id='board' debug={debug} eventHandler={eventHandler} Tile={DefaultBoardColumn} />
179
+ <Board.Content debug={debug} eventHandler={eventHandler} Tile={DefaultBoardColumn} />
179
180
  </Board.Root>
180
181
  {debug && (
181
182
  <Focus.Group classNames='flex flex-col gap-2 overflow-hidden'>
@@ -183,7 +184,7 @@ const DefaultStory = ({ debug = false, columns: columnsProp = 0 }: StoryArgs) =>
183
184
  </Focus.Group>
184
185
  )}
185
186
  </div>
186
- </Mosaic.Root>
187
+ </Dnd.Root>
187
188
  );
188
189
  };
189
190
 
@@ -2,8 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Atom, useAtomValue } from '@effect-atom/atom-react';
6
- import { createContext } from '@radix-ui/react-context';
5
+ import { useAtomValue } from '@effect-atom/atom-react';
7
6
  import React, {
8
7
  type PropsWithChildren,
9
8
  type ReactElement,
@@ -15,59 +14,17 @@ import React, {
15
14
 
16
15
  import { ScrollArea, type ThemedClassName } from '@dxos/react-ui';
17
16
  import { composable, composableProps } from '@dxos/react-ui';
17
+ import { type DndContainerHandler, useDndRootContext } from '@dxos/react-ui-dnd';
18
18
  import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
19
19
  import { mx } from '@dxos/ui-theme';
20
20
 
21
21
  import { useContainerDebug } from '../../hooks';
22
22
  import { Focus } from '../Focus';
23
- import {
24
- type GetId,
25
- Mosaic,
26
- type MosaicEventHandler,
27
- type MosaicPlaceholderProps,
28
- type MosaicStackProps,
29
- mosaicStyles,
30
- useMosaic,
31
- } from '../Mosaic';
32
- import { BoardColumn, type BoardColumnProps, DefaultBoardColumn, useBoardColumn } from './Column';
23
+ import { Mosaic, type MosaicPlaceholderProps, type MosaicStackProps, mosaicStyles } from '../Mosaic';
24
+ import { BoardContextProvider, type BoardContextValue, useBoardContext } from './BoardContext';
25
+ import { BoardColumn, type BoardColumnProps, DefaultBoardColumn } from './Column';
33
26
  import { BoardItem, type BoardItemProps } from './Item';
34
27
 
35
- //
36
- // Model
37
- //
38
-
39
- export interface BoardModel<TColumn = any, TItem = any> {
40
- getColumnId: GetId<TColumn>;
41
- getItemId: GetId<TItem>;
42
- isColumn: (obj: unknown) => obj is TColumn;
43
- isItem: (obj: unknown) => obj is TItem;
44
- columns: Atom.Atom<readonly TColumn[] | TColumn[]>;
45
- items: (column: TColumn) => Atom.Atom<TItem[]>;
46
- getColumns: () => TColumn[];
47
- getItems: (column: TColumn) => TItem[];
48
- onColumnCreate?: () => Promise<TColumn>;
49
- onColumnDelete?: (column: TColumn) => void;
50
- onItemCreate?: (column: TColumn) => Promise<TItem>;
51
- onItemDelete?: (column: TColumn, item: TItem) => void;
52
- }
53
-
54
- //
55
- // Context
56
- //
57
-
58
- const BOARD_NAME = 'Board';
59
-
60
- type BoardContextValue<TColumn = any, TItem = any> = {
61
- model: BoardModel<TColumn, TItem>;
62
- };
63
-
64
- const [BoardContextProvider, useBoardContext] = createContext<BoardContextValue>(BOARD_NAME);
65
-
66
- /** Hook to read the board model from context (e.g. in custom column tiles). Pass TColumn and TItem for typed model. */
67
- function useBoard<TColumn = any, TItem = any>(displayName?: string): BoardContextValue<TColumn, TItem> {
68
- return useBoardContext(displayName ?? BOARD_NAME) as BoardContextValue<TColumn, TItem>;
69
- }
70
-
71
28
  //
72
29
  // Root
73
30
  //
@@ -91,14 +48,13 @@ const BoardRoot = BoardRootInner as <TColumn = any, TItem = any>(props: BoardRoo
91
48
  const BOARD_CONTENT_NAME = 'Board.Content';
92
49
 
93
50
  type BoardContentProps<TColumn = any> = ThemedClassName<{
94
- id: string;
95
51
  debug?: boolean;
96
- eventHandler?: MosaicEventHandler<TColumn>;
52
+ eventHandler?: DndContainerHandler<TColumn>;
97
53
  Tile?: MosaicStackProps<TColumn>['Tile'];
98
54
  }>;
99
55
 
100
56
  const BoardContentInner = composable<HTMLDivElement, BoardContentProps>(
101
- ({ id: _id, debug, eventHandler, Tile = DefaultBoardColumn, ...props }, forwardedRef) => {
57
+ ({ debug, eventHandler, Tile = DefaultBoardColumn, ...props }, forwardedRef) => {
102
58
  const { model } = useBoardContext(BOARD_CONTENT_NAME);
103
59
  const [DebugInfo, debugHandler] = useContainerDebug(debug);
104
60
  const [viewport, setViewport] = useState<HTMLElement | null>(null);
@@ -115,6 +71,7 @@ const BoardContentInner = composable<HTMLDivElement, BoardContentProps>(
115
71
  autoScroll={viewport}
116
72
  eventHandler={eventHandler}
117
73
  debug={debugHandler}
74
+ placeholderDebug={debug}
118
75
  >
119
76
  <ScrollArea.Root orientation='horizontal' centered padding>
120
77
  <ScrollArea.Viewport classNames='snap-mandatory snap-x md:snap-none' ref={setViewport}>
@@ -160,7 +117,7 @@ BoardPlaceholder.displayName = BOARD_PLACEHOLDER_NAME;
160
117
  const BOARD_DEBUG_NAME = 'Board.Debug';
161
118
 
162
119
  export const BoardDebug = forwardRef<HTMLDivElement, ThemedClassName>(({ classNames }, forwardedRef) => {
163
- const { containers, dragging } = useMosaic(BOARD_DEBUG_NAME);
120
+ const { containers, dragging } = useDndRootContext(BOARD_DEBUG_NAME);
164
121
  const counter = useRef(0);
165
122
  return (
166
123
  <JsonHighlighter
@@ -186,6 +143,4 @@ export const Board = {
186
143
  Debug: BoardDebug,
187
144
  };
188
145
 
189
- export { useBoard, useBoardColumn };
190
-
191
146
  export type { BoardColumnProps, BoardContentProps, BoardItemProps, BoardRootProps };
@@ -0,0 +1,25 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+
7
+ // Kept out of `Column.tsx`: react-refresh only fast-refreshes a module whose exports are all
8
+ // components, so the context and hook exported beside them force a full page reload on every edit.
9
+
10
+ export const BOARD_COLUMN_CONTEXT_NAME = 'Board.Column';
11
+
12
+ export type BoardColumnContextValue<TColumn = unknown> = {
13
+ column: TColumn;
14
+ };
15
+
16
+ export const [BoardColumnProvider, useBoardColumnContext] = createContext<BoardColumnContextValue | null>(
17
+ BOARD_COLUMN_CONTEXT_NAME,
18
+ null,
19
+ );
20
+
21
+ /** Returns the current column when rendered inside a board column (e.g., in column header or item tile). */
22
+ export function useBoardColumn<TColumn = unknown>(): TColumn | undefined {
23
+ const value = useBoardColumnContext(BOARD_COLUMN_CONTEXT_NAME);
24
+ return value?.column as TColumn | undefined;
25
+ }
@@ -0,0 +1,43 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type Atom } from '@effect-atom/atom-react';
6
+ import { createContext } from '@radix-ui/react-context';
7
+
8
+ import { type GetId } from '@dxos/react-ui-dnd';
9
+
10
+ // Kept out of `Board.tsx`: react-refresh only fast-refreshes a module whose exports are all
11
+ // components, so the model, context and hook exported beside them force a full page reload on every edit.
12
+
13
+ export interface BoardModel<TColumn = any, TItem = any> {
14
+ getColumnId: GetId<TColumn>;
15
+ getItemId: GetId<TItem>;
16
+ isColumn: (obj: unknown) => obj is TColumn;
17
+ isItem: (obj: unknown) => obj is TItem;
18
+ columns: Atom.Atom<readonly TColumn[] | TColumn[]>;
19
+ items: (column: TColumn) => Atom.Atom<TItem[]>;
20
+ getColumns: () => TColumn[];
21
+ getItems: (column: TColumn) => TItem[];
22
+ onColumnCreate?: () => Promise<TColumn>;
23
+ onColumnDelete?: (column: TColumn) => void;
24
+ onItemCreate?: (column: TColumn) => Promise<TItem>;
25
+ onItemDelete?: (column: TColumn, item: TItem) => void;
26
+ }
27
+
28
+ //
29
+ // Context
30
+ //
31
+
32
+ export const BOARD_NAME = 'Board';
33
+
34
+ export type BoardContextValue<TColumn = any, TItem = any> = {
35
+ model: BoardModel<TColumn, TItem>;
36
+ };
37
+
38
+ export const [BoardContextProvider, useBoardContext] = createContext<BoardContextValue>(BOARD_NAME);
39
+
40
+ /** Hook to read the board model from context (e.g. in custom column tiles). Pass TColumn and TItem for typed model. */
41
+ export function useBoard<TColumn = any, TItem = any>(displayName?: string): BoardContextValue<TColumn, TItem> {
42
+ return useBoardContext(displayName ?? BOARD_NAME) as BoardContextValue<TColumn, TItem>;
43
+ }
@@ -3,7 +3,6 @@
3
3
  //
4
4
 
5
5
  import { useAtomValue } from '@effect-atom/atom-react';
6
- import { createContext } from '@radix-ui/react-context';
7
6
  import React, {
8
7
  type PropsWithChildren,
9
8
  type ReactElement,
@@ -14,7 +13,7 @@ import React, {
14
13
  } from 'react';
15
14
 
16
15
  import { Obj, Ref } from '@dxos/echo';
17
- import { useObject } from '@dxos/react-client/echo';
16
+ import { useObject } from '@dxos/echo-react';
18
17
  import { IconButton, ScrollArea, type ThemedClassName, Toolbar, useTranslation } from '@dxos/react-ui';
19
18
  import { composable, composableProps } from '@dxos/react-ui';
20
19
  import { Menu, createMenuAction } from '@dxos/react-ui-menu';
@@ -25,30 +24,10 @@ import { translationKey } from '#translations';
25
24
  import { useContainerDebug, useEventHandlerAdapter } from '../../hooks';
26
25
  import { Focus } from '../Focus';
27
26
  import { Mosaic, type MosaicContainerProps, type MosaicStackProps, type MosaicTileProps } from '../Mosaic';
28
- import { useBoard } from './Board';
27
+ import { BoardColumnProvider, useBoardColumn } from './BoardColumnContext';
28
+ import { useBoard } from './BoardContext';
29
29
  import { BoardItem } from './Item';
30
30
 
31
- //
32
- // Column context
33
- //
34
-
35
- const BOARD_COLUMN_CONTEXT_NAME = 'Board.Column';
36
-
37
- export type BoardColumnContextValue<TColumn = unknown> = {
38
- column: TColumn;
39
- };
40
-
41
- const [BoardColumnProvider, useBoardColumnContext] = createContext<BoardColumnContextValue | null>(
42
- BOARD_COLUMN_CONTEXT_NAME,
43
- null,
44
- );
45
-
46
- /** Returns the current column when rendered inside a board column (e.g., in column header or item tile). */
47
- export function useBoardColumn<TColumn = unknown>(): TColumn | undefined {
48
- const value = useBoardColumnContext(BOARD_COLUMN_CONTEXT_NAME);
49
- return value?.column as TColumn | undefined;
50
- }
51
-
52
31
  type BoardColumnProps<TColumn = any> = Pick<
53
32
  MosaicTileProps<TColumn>,
54
33
  'classNames' | 'location' | 'data' | 'debug' | 'draggable'
@@ -87,7 +66,7 @@ const BoardColumnRootInner = composable<HTMLDivElement, BoardColumnRootProps>(
87
66
  border
88
67
  classNames={mx(
89
68
  'group/column',
90
- 'h-full w-full md:w-card-default-width snap-center bg-deck-surface',
69
+ 'h-full w-full md:w-card-default-width snap-center dx-deck-surface',
91
70
  'overflow-hidden',
92
71
  classNames,
93
72
  )}
@@ -134,9 +113,16 @@ const BoardColumnHeader = composable<HTMLDivElement, BoardColumnHeaderProps>(
134
113
 
135
114
  return (
136
115
  <Menu.Root>
137
- <Toolbar.Root {...composableProps(props)} data-testid='board-column-header' ref={forwardedRef}>
116
+ {/* TODO(burdon): Use Card.Header. */}
117
+ <Toolbar.Root
118
+ {...composableProps(props, { classNames: 'gap-0' })}
119
+ data-testid='board-column-header'
120
+ ref={forwardedRef}
121
+ >
138
122
  <Toolbar.DragHandle ref={dragHandleRef} testId='mosaicBoard.columnDragHandle' />
139
- <Toolbar.Text data-testid='mosaicBoard.columnTitle'>{label}</Toolbar.Text>
123
+ <Toolbar.Text classNames='grow px-0' data-testid='mosaicBoard.columnTitle'>
124
+ {label}
125
+ </Toolbar.Text>
140
126
  {/* TODO(wittjosiah): Reconcile with Card.Menu. */}
141
127
  <Menu.Trigger asChild disabled={!columnMenuItems?.length}>
142
128
  <Toolbar.IconButton
@@ -14,8 +14,8 @@ import { translationKey } from '#translations';
14
14
 
15
15
  import { Focus } from '../Focus';
16
16
  import { Mosaic, type MosaicTileProps } from '../Mosaic';
17
- import { useBoard } from './Board';
18
- import { useBoardColumn } from './Column';
17
+ import { useBoardColumn } from './BoardColumnContext';
18
+ import { useBoard } from './BoardContext';
19
19
 
20
20
  const BOARD_ITEM_NAME = 'Board.Item';
21
21
 
@@ -2,4 +2,7 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
+ export { type BoardColumnContextValue, useBoardColumn } from './BoardColumnContext';
6
+ export { type BoardModel, useBoard } from './BoardContext';
7
+
5
8
  export * from './Board';
@@ -6,7 +6,6 @@ import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-sc
6
6
  import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
7
7
  import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
8
8
  import { useComposedRefs } from '@radix-ui/react-compose-refs';
9
- import { createContext } from '@radix-ui/react-context';
10
9
  import { Primitive } from '@radix-ui/react-primitive';
11
10
  import { Slot } from '@radix-ui/react-slot';
12
11
  import { bind } from 'bind-event-listener';
@@ -23,59 +22,34 @@ import React, {
23
22
  useState,
24
23
  } from 'react';
25
24
 
26
- import { type AllowedAxis, composable, composableProps } from '@dxos/react-ui';
25
+ import { composable, composableProps } from '@dxos/react-ui';
26
+ import {
27
+ type DndContainerData,
28
+ type DndLocation,
29
+ type DndTileData,
30
+ getSourceData,
31
+ useDndRootContext,
32
+ } from '@dxos/react-ui-dnd';
27
33
  import { isTruthy } from '@dxos/util';
28
34
 
29
35
  import { useFocus } from '../Focus';
30
- import { type MosaicDraggingState, useMosaicRootContext } from './Root';
31
36
  import {
32
- type LocationType,
33
- type MosaicContainerData,
34
- type MosaicEventHandler,
35
- type MosaicTileData,
36
- getSourceData,
37
- } from './types';
37
+ MOSAIC_CONTAINER_NAME,
38
+ MosaicContainerContextProvider,
39
+ type MosaicContainerContextValue,
40
+ type MosaicContainerState,
41
+ } from './MosaicContainerContext';
38
42
 
39
43
  //
40
44
  // Container
41
45
  //
42
46
 
43
- const MOSAIC_CONTAINER_NAME = 'Mosaic.Container';
44
-
45
- type MosaicContainerState = { type: 'idle' } | { type: 'active'; bounds?: DOMRect };
46
-
47
- type MosaicContainerContextValue<TData = any, Location = LocationType> = {
48
- id: string;
49
- eventHandler: MosaicEventHandler<TData>;
50
- orientation?: AllowedAxis;
51
- dragging?: MosaicDraggingState;
52
- scrolling?: boolean;
53
- state: MosaicContainerState;
54
-
55
- /** Active drop location. */
56
- activeLocation?: Location;
57
- setActiveLocation: (location: Location | undefined) => void;
58
-
59
- /** ID of the current (aria-current) item. */
60
- currentId?: string;
61
- /** Set the current item by ID. */
62
- setCurrentId: (id: string | undefined) => void;
63
-
64
- /** IDs of selected (aria-selected) items. */
65
- selectedIds?: ReadonlySet<string>;
66
- /** Request to set or unset selection on an item by ID. */
67
- setSelected: (id: string, selected: boolean) => void;
68
-
69
- /** Register a scroll-to-item callback (provided by Stack/VirtualStack). */
70
- registerScrollTo: (fn: ((id: string) => void) | undefined) => void;
71
- };
72
-
73
- const [MosaicContainerContextProvider, useMosaicContainerContext] =
74
- createContext<MosaicContainerContextValue>('MosaicContainer');
75
-
76
47
  // State attribute: [&:has(>_[data-mosaic-container-state=active])]
77
48
  const MOSAIC_CONTAINER_STATE_ATTR = 'mosaic-container-state';
78
49
 
50
+ // Debug flag on the container's `group` element; Placeholder reads it via `group-data-[mosaic-debug=true]` (see styles.ts).
51
+ const MOSAIC_CONTAINER_DEBUG_ATTR = 'mosaic-debug';
52
+
79
53
  // CSS variables: [var(--mosaic-placeholder-xxx)]
80
54
  const MOSAIC_CONTAINER_PLACEHOLDER_WIDTH = '--mosaic-placeholder-width';
81
55
  const MOSAIC_CONTAINER_PLACEHOLDER_HEIGHT = '--mosaic-placeholder-height';
@@ -104,6 +78,8 @@ type MosaicContainerProps = PropsWithChildren<
104
78
  /** Called when a tile requests to toggle selection. */
105
79
  onSelectionChange?: (id: string, selected: boolean) => void;
106
80
  debug?: () => ReactNode;
81
+ /** Toggles the `group` target that Placeholder's debug-highlight selectors read (see styles.ts). */
82
+ placeholderDebug?: boolean;
107
83
  }
108
84
  >;
109
85
 
@@ -126,6 +102,7 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
126
102
  selectedIds,
127
103
  onSelectionChange,
128
104
  debug,
105
+ placeholderDebug,
129
106
  ...props
130
107
  },
131
108
  forwardedRef,
@@ -144,9 +121,9 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
144
121
  );
145
122
 
146
123
  // State.
147
- const { dragging } = useMosaicRootContext(MOSAIC_CONTAINER_NAME);
124
+ const { dragging } = useDndRootContext(MOSAIC_CONTAINER_NAME);
148
125
  const [state, setState] = useState<MosaicContainerState>({ type: 'idle' });
149
- const [activeLocation, setActiveLocation] = useState<LocationType | undefined>();
126
+ const [activeLocation, setActiveLocation] = useState<DndLocation | undefined>();
150
127
  const [scrolling, setScrolling] = useState(false);
151
128
  const setCurrentId = useCallback((id: string | undefined) => onCurrentChange?.(id), [onCurrentChange]);
152
129
  const setSelected = useCallback(
@@ -179,18 +156,18 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
179
156
  }, [setFocus, withFocus, state]);
180
157
 
181
158
  // Register handler.
182
- const { addContainer, removeContainer } = useMosaicRootContext(eventHandler.id);
159
+ const { addContainer, removeContainer } = useDndRootContext(eventHandler.id);
183
160
  useEffect(() => {
184
161
  addContainer(eventHandler);
185
162
  return () => removeContainer(eventHandler.id);
186
163
  }, [eventHandler]);
187
164
 
188
- const data = useMemo<MosaicContainerData>(
165
+ const data = useMemo<DndContainerData>(
189
166
  () =>
190
167
  ({
191
168
  type: 'container',
192
169
  id: eventHandler.id,
193
- }) satisfies MosaicContainerData,
170
+ }) satisfies DndContainerData,
194
171
  [eventHandler.id],
195
172
  );
196
173
 
@@ -242,14 +219,14 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
242
219
  * Dragging started in this container.
243
220
  */
244
221
  onDragStart: ({ source }) => {
245
- const sourceData = source.data as MosaicTileData;
222
+ const sourceData = source.data as DndTileData;
246
223
  setState({ type: 'active', bounds: sourceData.bounds });
247
224
  },
248
225
  /**
249
226
  * Dragging entered this container.
250
227
  */
251
228
  onDragEnter: ({ source }) => {
252
- const sourceData = source.data as MosaicTileData;
229
+ const sourceData = source.data as DndTileData;
253
230
  setState({ type: 'active', bounds: sourceData.bounds });
254
231
  },
255
232
  /**
@@ -258,7 +235,7 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
258
235
  * triggering `onDragLeave`, which then causes the item to be added and removed continually (flickering).
259
236
  */
260
237
  onDragLeave: ({ source }) => {
261
- const sourceData = source.data as MosaicTileData;
238
+ const sourceData = source.data as DndTileData;
262
239
  if (sourceData.containerId !== eventHandler.id) {
263
240
  setState({ type: 'idle' });
264
241
  }
@@ -317,7 +294,7 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
317
294
  >
318
295
  <Comp
319
296
  {...composableProps(props, {
320
- classNames: 'h-full',
297
+ classNames: 'h-full group',
321
298
  style: {
322
299
  [MOSAIC_CONTAINER_PLACEHOLDER_WIDTH]:
323
300
  state.type === 'active' && state.bounds ? `${state.bounds.width}px` : '0px',
@@ -327,6 +304,7 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
327
304
  })}
328
305
  {...{
329
306
  [`data-${MOSAIC_CONTAINER_STATE_ATTR}`]: state.type,
307
+ [`data-${MOSAIC_CONTAINER_DEBUG_ATTR}`]: placeholderDebug,
330
308
  }}
331
309
  ref={composedRef}
332
310
  >
@@ -340,6 +318,6 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
340
318
 
341
319
  MosaicContainer.displayName = MOSAIC_CONTAINER_NAME;
342
320
 
343
- export { MosaicContainer, useMosaicContainerContext };
321
+ export { MosaicContainer };
344
322
 
345
- export type { MosaicContainerProps, MosaicContainerState };
323
+ export type { MosaicContainerProps };
@@ -7,7 +7,7 @@ import React, { type ReactNode, forwardRef } from 'react';
7
7
 
8
8
  import { Button, IconButton, type IconButtonProps, type ThemedClassName, useTranslation } from '@dxos/react-ui';
9
9
 
10
- import { useMosaicTileContext } from './Tile';
10
+ import { useMosaicTileContext } from './MosaicTileContext';
11
11
 
12
12
  const MOSAIC_DRAG_HANDLE_NAME = 'Mosaic.DragHandle';
13
13