@dxos/react-ui-mosaic 0.8.4-main.fcfe5033a5 → 0.9.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.
- package/LICENSE +102 -5
- package/dist/lib/browser/{chunk-33NNDFD6.mjs → chunk-I3M5WM6C.mjs} +45 -73
- package/dist/lib/browser/chunk-I3M5WM6C.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +125 -59
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/playwright/index.mjs +118 -10
- package/dist/lib/browser/playwright/index.mjs.map +3 -3
- package/dist/lib/browser/testing/index.mjs +10 -19
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/translations.mjs +18 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-Y4LG7O67.mjs → chunk-DVJLDZLE.mjs} +45 -73
- package/dist/lib/node-esm/chunk-DVJLDZLE.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +125 -59
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/playwright/index.mjs +118 -10
- package/dist/lib/node-esm/playwright/index.mjs.map +3 -3
- package/dist/lib/node-esm/testing/index.mjs +10 -19
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/translations.mjs +20 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/types/src/components/Board/Board.d.ts +12 -4
- package/dist/types/src/components/Board/Board.d.ts.map +1 -1
- package/dist/types/src/components/Board/Board.stories.d.ts +3 -3
- package/dist/types/src/components/Board/Board.stories.d.ts.map +1 -1
- package/dist/types/src/components/Board/Column.d.ts +5 -5
- package/dist/types/src/components/Board/Column.d.ts.map +1 -1
- package/dist/types/src/components/Board/Item.d.ts +1 -1
- package/dist/types/src/components/Board/Item.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Container.d.ts +9 -1
- package/dist/types/src/components/Mosaic/Container.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Mosaic.d.ts +3 -2
- package/dist/types/src/components/Mosaic/Mosaic.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Placeholder.d.ts +8 -8
- package/dist/types/src/components/Mosaic/Placeholder.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Stack.d.ts +6 -1
- package/dist/types/src/components/Mosaic/Stack.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Stack.stories.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Tile.d.ts +6 -2
- package/dist/types/src/components/Mosaic/Tile.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/extension.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/styles.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/types.d.ts.map +1 -1
- package/dist/types/src/components/SearchStack/SearchStack.d.ts +20 -0
- package/dist/types/src/components/SearchStack/SearchStack.d.ts.map +1 -0
- package/dist/types/src/components/SearchStack/SearchStack.stories.d.ts +10 -0
- package/dist/types/src/components/SearchStack/SearchStack.stories.d.ts.map +1 -0
- package/dist/types/src/components/SearchStack/index.d.ts +2 -0
- package/dist/types/src/components/SearchStack/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useContainerDebug.d.ts.map +1 -1
- package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useEventHandlerAdapter.d.ts +1 -1
- package/dist/types/src/hooks/useEventHandlerAdapter.d.ts.map +1 -1
- package/dist/types/src/hooks/useVisibleItems.d.ts.map +1 -1
- package/dist/types/src/playwright/board-manager.d.ts +20 -5
- package/dist/types/src/playwright/board-manager.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
- package/dist/types/src/testing/CardContainer.d.ts.map +1 -1
- package/dist/types/src/testing/DefaultStackTile.d.ts.map +1 -1
- package/dist/types/src/testing/types.d.ts +7 -9
- package/dist/types/src/testing/types.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +3 -3
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +42 -40
- package/src/components/Board/Board.stories.tsx +11 -10
- package/src/components/Board/Board.tsx +4 -3
- package/src/components/Board/Column.tsx +22 -18
- package/src/components/Board/Item.tsx +14 -10
- package/src/components/Mosaic/Container.tsx +18 -2
- package/src/components/Mosaic/Placeholder.tsx +6 -0
- package/src/components/Mosaic/Stack.tsx +20 -5
- package/src/components/Mosaic/Tile.tsx +11 -2
- package/src/components/Mosaic/extension.ts +1 -1
- package/src/components/SearchStack/SearchStack.stories.tsx +47 -0
- package/src/components/SearchStack/SearchStack.tsx +135 -0
- package/src/components/SearchStack/index.ts +5 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/useContainerDebug.tsx +3 -3
- package/src/hooks/useEventHandlerAdapter.ts +1 -1
- package/src/playwright/board-manager.ts +168 -13
- package/src/playwright/smoke.spec.ts +3 -3
- package/src/testing/CardContainer.tsx +2 -5
- package/src/testing/DefaultStackTile.tsx +11 -5
- package/src/testing/types.ts +7 -17
- package/dist/lib/browser/chunk-33NNDFD6.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y4LG7O67.mjs.map +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-mosaic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Modular UI components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/dxos/dxos"
|
|
10
10
|
},
|
|
11
|
-
"license": "
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
12
12
|
"author": "DXOS.org",
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"type": "module",
|
|
15
|
+
"imports": {
|
|
16
|
+
"#translations": "./src/translations.ts"
|
|
17
|
+
},
|
|
15
18
|
"exports": {
|
|
16
19
|
".": {
|
|
17
20
|
"source": "./src/index.ts",
|
|
@@ -30,19 +33,15 @@
|
|
|
30
33
|
"types": "./dist/types/src/testing/index.d.ts",
|
|
31
34
|
"browser": "./dist/lib/browser/testing/index.mjs",
|
|
32
35
|
"node": "./dist/lib/node-esm/testing/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./translations": {
|
|
38
|
+
"source": "./src/translations.ts",
|
|
39
|
+
"types": "./dist/types/src/translations.d.ts",
|
|
40
|
+
"browser": "./dist/lib/browser/translations.mjs",
|
|
41
|
+
"node": "./dist/lib/node-esm/translations.mjs"
|
|
33
42
|
}
|
|
34
43
|
},
|
|
35
44
|
"types": "dist/types/src/index.d.ts",
|
|
36
|
-
"typesVersions": {
|
|
37
|
-
"*": {
|
|
38
|
-
"playwright": [
|
|
39
|
-
"dist/types/src/playwright/index.d.ts"
|
|
40
|
-
],
|
|
41
|
-
"testing": [
|
|
42
|
-
"dist/types/src/testing/index.d.ts"
|
|
43
|
-
]
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
45
|
"files": [
|
|
47
46
|
"dist",
|
|
48
47
|
"src"
|
|
@@ -52,9 +51,10 @@
|
|
|
52
51
|
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.2",
|
|
53
52
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
|
|
54
53
|
"@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "3.2.10",
|
|
55
|
-
"@codemirror/state": "^6.
|
|
56
|
-
"@codemirror/view": "^6.
|
|
54
|
+
"@codemirror/state": "^6.6.0",
|
|
55
|
+
"@codemirror/view": "^6.43.0",
|
|
57
56
|
"@fluentui/react-tabster": "9.26.11",
|
|
57
|
+
"@playwright/test": "1.57.0",
|
|
58
58
|
"@radix-ui/react-compose-refs": "1.1.1",
|
|
59
59
|
"@radix-ui/react-context": "1.1.1",
|
|
60
60
|
"@radix-ui/react-primitive": "2.0.2",
|
|
@@ -62,44 +62,46 @@
|
|
|
62
62
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
63
63
|
"@tanstack/react-virtual": "^3.13.18",
|
|
64
64
|
"bind-event-listener": "^3.0.0",
|
|
65
|
-
"@dxos/async": "0.
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/
|
|
69
|
-
"@dxos/
|
|
70
|
-
"@dxos/
|
|
71
|
-
"@dxos/react-ui-
|
|
72
|
-
"@dxos/react-ui-
|
|
73
|
-
"@dxos/react-ui-
|
|
74
|
-
"@dxos/
|
|
75
|
-
"@dxos/ui-
|
|
65
|
+
"@dxos/async": "0.9.0",
|
|
66
|
+
"@dxos/keys": "0.9.0",
|
|
67
|
+
"@dxos/echo": "0.9.0",
|
|
68
|
+
"@dxos/log": "0.9.0",
|
|
69
|
+
"@dxos/react-client": "0.9.0",
|
|
70
|
+
"@dxos/invariant": "0.9.0",
|
|
71
|
+
"@dxos/react-ui-dnd": "0.9.0",
|
|
72
|
+
"@dxos/react-ui-attention": "0.9.0",
|
|
73
|
+
"@dxos/react-ui-list": "0.9.0",
|
|
74
|
+
"@dxos/react-ui-menu": "0.9.0",
|
|
75
|
+
"@dxos/react-ui-search": "0.9.0",
|
|
76
|
+
"@dxos/ui-theme": "0.9.0",
|
|
77
|
+
"@dxos/react-ui-syntax-highlighter": "0.9.0",
|
|
78
|
+
"@dxos/util": "0.9.0"
|
|
76
79
|
},
|
|
77
80
|
"devDependencies": {
|
|
78
81
|
"@effect-atom/atom-react": "^0.5.0",
|
|
79
82
|
"@types/react": "~19.2.7",
|
|
80
83
|
"@types/react-dom": "~19.2.3",
|
|
81
|
-
"effect": "3.
|
|
84
|
+
"effect": "3.21.3",
|
|
82
85
|
"react": "~19.2.3",
|
|
83
86
|
"react-dom": "~19.2.3",
|
|
84
|
-
"vite": "^
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/
|
|
87
|
-
"@dxos/
|
|
88
|
-
"@dxos/
|
|
89
|
-
"@dxos/
|
|
90
|
-
"@dxos/
|
|
91
|
-
"@dxos/
|
|
92
|
-
"@dxos/
|
|
93
|
-
"@dxos/test-utils": "0.8.4-main.fcfe5033a5"
|
|
87
|
+
"vite": "^8.0.16",
|
|
88
|
+
"@dxos/random": "0.9.0",
|
|
89
|
+
"@dxos/react-ui": "0.9.0",
|
|
90
|
+
"@dxos/schema": "0.9.0",
|
|
91
|
+
"@dxos/test-utils": "0.9.0",
|
|
92
|
+
"@dxos/react-ui-syntax-highlighter": "0.9.0",
|
|
93
|
+
"@dxos/types": "0.9.0",
|
|
94
|
+
"@dxos/ui-theme": "0.9.0",
|
|
95
|
+
"@dxos/storybook-utils": "0.9.0"
|
|
94
96
|
},
|
|
95
97
|
"peerDependencies": {
|
|
96
98
|
"@effect-atom/atom-react": "^0.5.0",
|
|
97
|
-
"effect": "3.
|
|
99
|
+
"effect": "3.21.3",
|
|
98
100
|
"react": "~19.2.3",
|
|
99
101
|
"react-dom": "~19.2.3",
|
|
100
|
-
"@dxos/
|
|
101
|
-
"@dxos/ui
|
|
102
|
-
"@dxos/
|
|
102
|
+
"@dxos/random": "0.9.0",
|
|
103
|
+
"@dxos/react-ui": "0.9.0",
|
|
104
|
+
"@dxos/ui-theme": "0.9.0"
|
|
103
105
|
},
|
|
104
106
|
"publishConfig": {
|
|
105
107
|
"access": "public"
|
|
@@ -8,7 +8,6 @@ import React, { useContext, useMemo } from 'react';
|
|
|
8
8
|
import { expect, within } from 'storybook/test';
|
|
9
9
|
|
|
10
10
|
import { type Database, Filter, Obj, Ref } from '@dxos/echo';
|
|
11
|
-
import { AtomObj, AtomQuery } from '@dxos/echo-atom';
|
|
12
11
|
import { invariant } from '@dxos/invariant';
|
|
13
12
|
import { random } from '@dxos/random';
|
|
14
13
|
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
@@ -16,9 +15,10 @@ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
|
16
15
|
import { withRegistry } from '@dxos/storybook-utils';
|
|
17
16
|
import { mx } from '@dxos/ui-theme';
|
|
18
17
|
|
|
18
|
+
import { translations } from '#translations';
|
|
19
|
+
|
|
19
20
|
import { useEventHandlerAdapter } from '../../hooks';
|
|
20
21
|
import { TestColumn, TestItem } from '../../testing';
|
|
21
|
-
import { translations } from '../../translations';
|
|
22
22
|
import { Focus } from '../Focus';
|
|
23
23
|
import { Mosaic, type MosaicEventHandler } from '../Mosaic';
|
|
24
24
|
import { Board, type BoardModel } from './Board';
|
|
@@ -72,14 +72,15 @@ const useTestBoardModel = (): TestBoardModelResult => {
|
|
|
72
72
|
const orderAtom = Atom.make<string[]>([]);
|
|
73
73
|
|
|
74
74
|
// Source of truth for which columns exist; subscribed to ECHO query.
|
|
75
|
-
const columnsAtom =
|
|
76
|
-
space?.db != null ? AtomQuery.make(space.db, Filter.type(TestColumn)) : Atom.make<TestColumn[]>([]);
|
|
75
|
+
const columnsAtom = space?.db != null ? space.db.query(Filter.type(TestColumn)).atom : Atom.make<TestColumn[]>([]);
|
|
77
76
|
|
|
78
77
|
// If orderAtom is empty, use Echo order as-is; else sort by orderAtom and append new columns.
|
|
79
78
|
const orderedColumnsAtom = Atom.make((get) => {
|
|
80
79
|
const cols = get(columnsAtom);
|
|
81
80
|
const order = get(orderAtom);
|
|
82
|
-
if (order.length === 0)
|
|
81
|
+
if (order.length === 0) {
|
|
82
|
+
return cols;
|
|
83
|
+
}
|
|
83
84
|
const byId = new Map(cols.map((column) => [getColumnId(column), column]));
|
|
84
85
|
const ordered = order.map((id) => byId.get(id)).filter((column): column is TestColumn => column != null);
|
|
85
86
|
const appended = cols.filter((column) => !order.includes(getColumnId(column)));
|
|
@@ -90,10 +91,10 @@ const useTestBoardModel = (): TestBoardModelResult => {
|
|
|
90
91
|
space?.db != null
|
|
91
92
|
? Atom.family((column: TestColumn) =>
|
|
92
93
|
Atom.make((get) => {
|
|
93
|
-
const refs = get(
|
|
94
|
+
const refs = get(Obj.atomProperty(column, 'items'));
|
|
94
95
|
return (
|
|
95
96
|
refs
|
|
96
|
-
?.map((ref: Ref.Ref<TestItem>) => get(
|
|
97
|
+
?.map((ref: Ref.Ref<TestItem>) => get(Obj.atomReactive(ref)))
|
|
97
98
|
.filter((item): item is TestItem => item != null) ?? []
|
|
98
99
|
);
|
|
99
100
|
}),
|
|
@@ -118,14 +119,14 @@ const useTestBoardModel = (): TestBoardModelResult => {
|
|
|
118
119
|
}),
|
|
119
120
|
);
|
|
120
121
|
|
|
121
|
-
Obj.
|
|
122
|
+
Obj.update(column, (column) => {
|
|
122
123
|
column.items.push(Ref.make(item));
|
|
123
124
|
});
|
|
124
125
|
|
|
125
126
|
return item;
|
|
126
127
|
},
|
|
127
128
|
onItemDelete: (column: TestColumn, current: TestItem) => {
|
|
128
|
-
Obj.
|
|
129
|
+
Obj.update(column, (column) => {
|
|
129
130
|
if (!column.items) {
|
|
130
131
|
return;
|
|
131
132
|
}
|
|
@@ -172,7 +173,7 @@ const DefaultStory = ({ debug = false, columns: columnsProp = 0 }: DefaultStoryP
|
|
|
172
173
|
|
|
173
174
|
return (
|
|
174
175
|
<Mosaic.Root asChild debug={debug}>
|
|
175
|
-
<div
|
|
176
|
+
<div className={mx('grid md:p-2 overflow-hidden', debug && 'grid-cols-[1fr_20rem] gap-2')}>
|
|
176
177
|
<Board.Root model={model}>
|
|
177
178
|
<Board.Content id='board' debug={debug} eventHandler={eventHandler} Tile={DefaultBoardColumn} />
|
|
178
179
|
</Board.Root>
|
|
@@ -14,8 +14,9 @@ import React, {
|
|
|
14
14
|
} from 'react';
|
|
15
15
|
|
|
16
16
|
import { ScrollArea, type ThemedClassName } from '@dxos/react-ui';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { composable, composableProps } from '@dxos/react-ui';
|
|
18
|
+
import { JsonHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
19
|
+
import { mx } from '@dxos/ui-theme';
|
|
19
20
|
|
|
20
21
|
import { useContainerDebug } from '../../hooks';
|
|
21
22
|
import { Focus } from '../Focus';
|
|
@@ -162,7 +163,7 @@ export const BoardDebug = forwardRef<HTMLDivElement, ThemedClassName>(({ classNa
|
|
|
162
163
|
const { containers, dragging } = useMosaic(BOARD_DEBUG_NAME);
|
|
163
164
|
const counter = useRef(0);
|
|
164
165
|
return (
|
|
165
|
-
<
|
|
166
|
+
<JsonHighlighter
|
|
166
167
|
data={{ containers, dragging, count: counter.current++ }}
|
|
167
168
|
classNames={mx('text-xs', classNames)}
|
|
168
169
|
ref={forwardedRef}
|
|
@@ -8,21 +8,21 @@ import React, {
|
|
|
8
8
|
type PropsWithChildren,
|
|
9
9
|
type ReactElement,
|
|
10
10
|
type Ref as ReactRef,
|
|
11
|
-
type RefObject,
|
|
12
11
|
forwardRef,
|
|
13
12
|
useMemo,
|
|
14
|
-
useRef,
|
|
15
13
|
useState,
|
|
16
14
|
} from 'react';
|
|
17
15
|
|
|
18
16
|
import { Obj, Ref } from '@dxos/echo';
|
|
19
17
|
import { useObject } from '@dxos/react-client/echo';
|
|
20
18
|
import { IconButton, ScrollArea, type ThemedClassName, Toolbar, useTranslation } from '@dxos/react-ui';
|
|
19
|
+
import { composable, composableProps } from '@dxos/react-ui';
|
|
21
20
|
import { Menu, createMenuAction } from '@dxos/react-ui-menu';
|
|
22
|
-
import {
|
|
21
|
+
import { mx } from '@dxos/ui-theme';
|
|
22
|
+
|
|
23
|
+
import { translationKey } from '#translations';
|
|
23
24
|
|
|
24
25
|
import { useContainerDebug, useEventHandlerAdapter } from '../../hooks';
|
|
25
|
-
import { translationKey } from '../../translations';
|
|
26
26
|
import { Focus } from '../Focus';
|
|
27
27
|
import { Mosaic, type MosaicContainerProps, type MosaicStackProps, type MosaicTileProps } from '../Mosaic';
|
|
28
28
|
import { useBoard } from './Board';
|
|
@@ -49,7 +49,10 @@ export function useBoardColumn<TColumn = unknown>(): TColumn | undefined {
|
|
|
49
49
|
return value?.column as TColumn | undefined;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
type BoardColumnProps<TColumn = any> = Pick<
|
|
52
|
+
type BoardColumnProps<TColumn = any> = Pick<
|
|
53
|
+
MosaicTileProps<TColumn>,
|
|
54
|
+
'classNames' | 'location' | 'data' | 'debug' | 'draggable'
|
|
55
|
+
>;
|
|
53
56
|
|
|
54
57
|
//
|
|
55
58
|
// Column Root
|
|
@@ -58,17 +61,16 @@ type BoardColumnProps<TColumn = any> = Pick<MosaicTileProps<TColumn>, 'className
|
|
|
58
61
|
const BOARD_COLUMN_ROOT_NAME = 'Board.Column.Root';
|
|
59
62
|
|
|
60
63
|
type BoardColumnRootProps<TColumn = any> = PropsWithChildren<BoardColumnProps<TColumn>> & {
|
|
61
|
-
|
|
64
|
+
// Pass the actual button element (not a ref). Refs don't trigger re-renders, so reading
|
|
65
|
+
// `ref.current` at render time leaves Mosaic.Tile's `dragHandle` prop null forever.
|
|
66
|
+
// Consumers should track this with `useState` and a callback ref on `Board.Column.Header`.
|
|
67
|
+
dragHandle?: HTMLButtonElement | null;
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
const BoardColumnRootInner = composable<HTMLDivElement, BoardColumnRootProps>(
|
|
65
|
-
({ classNames, children, location, data, debug,
|
|
71
|
+
({ classNames, children, location, data, debug, draggable, dragHandle, ...rest }, forwardedRef) => {
|
|
66
72
|
const { model } = useBoard(BOARD_COLUMN_ROOT_NAME);
|
|
67
73
|
|
|
68
|
-
// TODO(burdon): Use merge ref hook (see react-hooks).
|
|
69
|
-
const internalRef = useRef<HTMLButtonElement>(null);
|
|
70
|
-
const dragHandleRef = dragHandleRefProp ?? internalRef;
|
|
71
|
-
|
|
72
74
|
return (
|
|
73
75
|
<Mosaic.Tile
|
|
74
76
|
asChild
|
|
@@ -76,7 +78,8 @@ const BoardColumnRootInner = composable<HTMLDivElement, BoardColumnRootProps>(
|
|
|
76
78
|
id={model.getColumnId(data)}
|
|
77
79
|
data={data}
|
|
78
80
|
debug={debug}
|
|
79
|
-
|
|
81
|
+
draggable={draggable}
|
|
82
|
+
dragHandle={dragHandle}
|
|
80
83
|
>
|
|
81
84
|
<Focus.Group
|
|
82
85
|
{...rest}
|
|
@@ -239,16 +242,16 @@ const BOARD_DEFAULT_COLUMN_NAME = 'Board.DefaultColumn';
|
|
|
239
242
|
type DefaultBoardColumnProps = BoardColumnProps & Pick<BoardColumnBodyProps, 'Tile'>;
|
|
240
243
|
|
|
241
244
|
const DefaultBoardColumn = forwardRef<HTMLDivElement, DefaultBoardColumnProps>(
|
|
242
|
-
({ classNames, location, data, debug, Tile = BoardItem }, forwardedRef) => {
|
|
245
|
+
({ classNames, location, data, debug, draggable, Tile = BoardItem }, forwardedRef) => {
|
|
243
246
|
const { model } = useBoard(BOARD_DEFAULT_COLUMN_NAME);
|
|
244
247
|
const [DebugInfo, debugHandler] = useContainerDebug(debug);
|
|
245
|
-
const
|
|
248
|
+
const [dragHandle, setDragHandle] = useState<HTMLButtonElement | null>(null);
|
|
246
249
|
const [column, updateColumn] = useObject(data);
|
|
247
250
|
|
|
248
251
|
const eventHandler = useEventHandlerAdapter<Ref.Unknown>({
|
|
249
252
|
id: model.getColumnId(data),
|
|
250
253
|
items: column?.items ?? [],
|
|
251
|
-
getId: (ref) => ref.target?.id ?? ref.
|
|
254
|
+
getId: (ref) => ref.target?.id ?? ref.uri,
|
|
252
255
|
get: (refOrObj) => (Ref.isRef(refOrObj) ? refOrObj.target! : refOrObj),
|
|
253
256
|
make: (object) => Ref.make(object),
|
|
254
257
|
canDrop: ({ source }) => {
|
|
@@ -269,12 +272,13 @@ const DefaultBoardColumn = forwardRef<HTMLDivElement, DefaultBoardColumnProps>(
|
|
|
269
272
|
)}
|
|
270
273
|
location={location}
|
|
271
274
|
data={data}
|
|
272
|
-
|
|
275
|
+
draggable={draggable}
|
|
276
|
+
dragHandle={dragHandle}
|
|
273
277
|
ref={forwardedRef}
|
|
274
278
|
>
|
|
275
|
-
<BoardColumnHeader label={Obj.getLabel(data) ?? data.id} dragHandleRef={
|
|
279
|
+
<BoardColumnHeader label={Obj.getLabel(data) ?? data.id} dragHandleRef={setDragHandle} />
|
|
276
280
|
<BoardColumnBody data={data} eventHandler={eventHandler} debug={debugHandler} Tile={Tile} />
|
|
277
|
-
<div
|
|
281
|
+
<div className='flex flex-col col-span-full'>
|
|
278
282
|
<BoardColumnFooter data={data} />
|
|
279
283
|
<DebugInfo />
|
|
280
284
|
</div>
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { useComposedRefs } from '@radix-ui/react-compose-refs';
|
|
6
|
-
import React, { type ReactElement, type Ref as ReactRef, forwardRef, useMemo, useRef } from 'react';
|
|
6
|
+
import React, { type ReactElement, type Ref as ReactRef, forwardRef, useMemo, useRef, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
9
|
import { Card, Tag, Toolbar, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { Menu, createMenuAction } from '@dxos/react-ui-menu';
|
|
11
11
|
import { getHashStyles } from '@dxos/ui-theme';
|
|
12
12
|
|
|
13
|
-
import { translationKey } from '
|
|
13
|
+
import { translationKey } from '#translations';
|
|
14
|
+
|
|
14
15
|
import { Focus } from '../Focus';
|
|
15
16
|
import { Mosaic, type MosaicTileProps } from '../Mosaic';
|
|
16
17
|
import { useBoard } from './Board';
|
|
@@ -20,15 +21,17 @@ const BOARD_ITEM_NAME = 'Board.Item';
|
|
|
20
21
|
|
|
21
22
|
type BoardItemProps<TItem extends Obj.Unknown = any> = Pick<
|
|
22
23
|
MosaicTileProps<TItem>,
|
|
23
|
-
'classNames' | 'location' | 'data' | 'debug'
|
|
24
|
+
'classNames' | 'location' | 'data' | 'debug' | 'draggable'
|
|
24
25
|
>;
|
|
25
26
|
|
|
26
27
|
const BoardItemInner = forwardRef<HTMLDivElement, BoardItemProps>(
|
|
27
|
-
({ classNames, data, location, debug }, forwardedRef) => {
|
|
28
|
+
({ classNames, data, location, debug, draggable }, forwardedRef) => {
|
|
28
29
|
const { t } = useTranslation(translationKey);
|
|
29
30
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
30
31
|
const composedRef = useComposedRefs<HTMLDivElement>(rootRef, forwardedRef);
|
|
31
|
-
|
|
32
|
+
// Use state (callback ref) so the dragHandle prop updates when the button mounts.
|
|
33
|
+
// Refs don't trigger re-renders, so reading `.current` at render time leaves the prop null.
|
|
34
|
+
const [dragHandle, setDragHandle] = useState<HTMLButtonElement | null>(null);
|
|
32
35
|
|
|
33
36
|
const { model } = useBoard(BOARD_ITEM_NAME);
|
|
34
37
|
const column = useBoardColumn();
|
|
@@ -57,7 +60,8 @@ const BoardItemInner = forwardRef<HTMLDivElement, BoardItemProps>(
|
|
|
57
60
|
<Mosaic.Tile
|
|
58
61
|
ref={rootRef}
|
|
59
62
|
asChild
|
|
60
|
-
|
|
63
|
+
draggable={draggable}
|
|
64
|
+
dragHandle={dragHandle}
|
|
61
65
|
id={data.id}
|
|
62
66
|
data={data}
|
|
63
67
|
location={location}
|
|
@@ -70,8 +74,8 @@ const BoardItemInner = forwardRef<HTMLDivElement, BoardItemProps>(
|
|
|
70
74
|
ref={composedRef}
|
|
71
75
|
onClick={(event) => event.currentTarget.focus()}
|
|
72
76
|
>
|
|
73
|
-
<Card.
|
|
74
|
-
<Card.DragHandle ref={
|
|
77
|
+
<Card.Header>
|
|
78
|
+
<Card.DragHandle ref={setDragHandle} testId='mosaicBoard.cardDragHandle' />
|
|
75
79
|
<Card.Title data-testid='mosaicBoard.cardTitle'>{label}</Card.Title>
|
|
76
80
|
{/* TODO(wittjosiah): Reconcile with Card.Menu. */}
|
|
77
81
|
<Menu.Trigger asChild disabled={!items?.length}>
|
|
@@ -83,14 +87,14 @@ const BoardItemInner = forwardRef<HTMLDivElement, BoardItemProps>(
|
|
|
83
87
|
/>
|
|
84
88
|
</Menu.Trigger>
|
|
85
89
|
<Menu.Content items={items} />
|
|
86
|
-
</Card.
|
|
90
|
+
</Card.Header>
|
|
87
91
|
{/* TODO(burdon): Replace with surface. */}
|
|
88
92
|
<Card.Row icon='ph--note--regular' classNames='text-description'>
|
|
89
93
|
<Card.Text>{description}</Card.Text>
|
|
90
94
|
</Card.Row>
|
|
91
95
|
<Card.Row icon='ph--tag--regular'>
|
|
92
96
|
{label && (
|
|
93
|
-
<div
|
|
97
|
+
<div className='shrink-0 flex gap-1 items-center text-xs'>
|
|
94
98
|
<Tag palette={getHashStyles(label).hue}>{label}</Tag>
|
|
95
99
|
</div>
|
|
96
100
|
)}
|
|
@@ -21,8 +21,7 @@ import React, {
|
|
|
21
21
|
useState,
|
|
22
22
|
} from 'react';
|
|
23
23
|
|
|
24
|
-
import { type AllowedAxis } from '@dxos/react-ui';
|
|
25
|
-
import { composable, composableProps } from '@dxos/ui-theme';
|
|
24
|
+
import { type AllowedAxis, composable, composableProps } from '@dxos/react-ui';
|
|
26
25
|
import { isTruthy } from '@dxos/util';
|
|
27
26
|
|
|
28
27
|
import { useFocus } from '../Focus';
|
|
@@ -60,6 +59,11 @@ type MosaicContainerContextValue<TData = any, Location = LocationType> = {
|
|
|
60
59
|
/** Set the current item by ID. */
|
|
61
60
|
setCurrentId: (id: string | undefined) => void;
|
|
62
61
|
|
|
62
|
+
/** IDs of selected (aria-selected) items. */
|
|
63
|
+
selectedIds?: ReadonlySet<string>;
|
|
64
|
+
/** Request to set or unset selection on an item by ID. */
|
|
65
|
+
setSelected: (id: string, selected: boolean) => void;
|
|
66
|
+
|
|
63
67
|
/** Register a scroll-to-item callback (provided by Stack/VirtualStack). */
|
|
64
68
|
registerScrollTo: (fn: ((id: string) => void) | undefined) => void;
|
|
65
69
|
};
|
|
@@ -86,6 +90,10 @@ type MosaicContainerProps = PropsWithChildren<
|
|
|
86
90
|
currentId?: string;
|
|
87
91
|
/** Called when a tile requests to become current. */
|
|
88
92
|
onCurrentChange?: (id: string | undefined) => void;
|
|
93
|
+
/** Controlled set of selected item IDs. */
|
|
94
|
+
selectedIds?: ReadonlySet<string>;
|
|
95
|
+
/** Called when a tile requests to toggle selection. */
|
|
96
|
+
onSelectionChange?: (id: string, selected: boolean) => void;
|
|
89
97
|
debug?: () => ReactNode;
|
|
90
98
|
}
|
|
91
99
|
>;
|
|
@@ -102,6 +110,8 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
|
|
|
102
110
|
withFocus,
|
|
103
111
|
currentId,
|
|
104
112
|
onCurrentChange,
|
|
113
|
+
selectedIds,
|
|
114
|
+
onSelectionChange,
|
|
105
115
|
debug,
|
|
106
116
|
...props
|
|
107
117
|
},
|
|
@@ -126,6 +136,10 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
|
|
|
126
136
|
const [activeLocation, setActiveLocation] = useState<LocationType | undefined>();
|
|
127
137
|
const [scrolling, setScrolling] = useState(false);
|
|
128
138
|
const setCurrentId = useCallback((id: string | undefined) => onCurrentChange?.(id), [onCurrentChange]);
|
|
139
|
+
const setSelected = useCallback(
|
|
140
|
+
(id: string, selected: boolean) => onSelectionChange?.(id, selected),
|
|
141
|
+
[onSelectionChange],
|
|
142
|
+
);
|
|
129
143
|
|
|
130
144
|
// Scroll-to-item: Stack/VirtualStack registers its implementation.
|
|
131
145
|
const scrollToRef = useRef<((id: string) => void) | undefined>(undefined);
|
|
@@ -283,6 +297,8 @@ const MosaicContainer = composable<HTMLDivElement, MosaicContainerProps>(
|
|
|
283
297
|
setActiveLocation={setActiveLocation}
|
|
284
298
|
currentId={currentId}
|
|
285
299
|
setCurrentId={setCurrentId}
|
|
300
|
+
selectedIds={selectedIds}
|
|
301
|
+
setSelected={setSelected}
|
|
286
302
|
registerScrollTo={registerScrollTo}
|
|
287
303
|
>
|
|
288
304
|
<Comp
|
|
@@ -30,6 +30,11 @@ const MOSAIC_PLACEHOLDER_ORIENTATION_ATTR = 'mosaic-placeholder-orientation';
|
|
|
30
30
|
// State attribute: data-[mosaic-placeholder-state=active]
|
|
31
31
|
const MOSAIC_PLACEHOLDER_STATE_ATTR = 'mosaic-placeholder-state';
|
|
32
32
|
|
|
33
|
+
// Location attribute: data-[mosaic-placeholder-location=0.5]. Identifies the
|
|
34
|
+
// placeholder's slot in its container (0.5, 1.5, 2.5, …) so tests can target
|
|
35
|
+
// a specific gap unambiguously without relying on layout-dependent indices.
|
|
36
|
+
const MOSAIC_PLACEHOLDER_LOCATION_ATTR = 'mosaic-placeholder-location';
|
|
37
|
+
|
|
33
38
|
type MosaicPlaceholderProps<Location = LocationType> = ThemedClassName<
|
|
34
39
|
PropsWithChildren<{
|
|
35
40
|
asChild?: boolean;
|
|
@@ -94,6 +99,7 @@ const MosaicPlaceholder = <Location extends LocationType = LocationType>({
|
|
|
94
99
|
{...{
|
|
95
100
|
[`data-${MOSAIC_PLACEHOLDER_ORIENTATION_ATTR}`]: orientation,
|
|
96
101
|
[`data-${MOSAIC_PLACEHOLDER_STATE_ATTR}`]: data.location === activeLocation ? 'active' : 'idle',
|
|
102
|
+
[`data-${MOSAIC_PLACEHOLDER_LOCATION_ATTR}`]: String(location),
|
|
97
103
|
}}
|
|
98
104
|
role='none'
|
|
99
105
|
className={mx('relative', classNames)}
|
|
@@ -16,8 +16,8 @@ import React, {
|
|
|
16
16
|
} from 'react';
|
|
17
17
|
|
|
18
18
|
import { invariant } from '@dxos/invariant';
|
|
19
|
-
import { type Axis, type ThemedClassName } from '@dxos/react-ui';
|
|
20
|
-
import {
|
|
19
|
+
import { type Axis, type ThemedClassName, composable, composableProps } from '@dxos/react-ui';
|
|
20
|
+
import { mx } from '@dxos/ui-theme';
|
|
21
21
|
|
|
22
22
|
import { useVisibleItems } from '../../hooks';
|
|
23
23
|
import { useMosaicContainerContext } from './Container';
|
|
@@ -85,6 +85,7 @@ const MosaicStackInner = composable<HTMLDivElement, MosaicStackProps>(
|
|
|
85
85
|
orientation = orientationProp,
|
|
86
86
|
dragging,
|
|
87
87
|
currentId,
|
|
88
|
+
selectedIds,
|
|
88
89
|
registerScrollTo,
|
|
89
90
|
} = useMosaicContainerContext(MOSAIC_STACK_NAME);
|
|
90
91
|
const visibleItems = useVisibleItems({ id, items, dragging: dragging?.source.data, getId });
|
|
@@ -130,6 +131,7 @@ const MosaicStackInner = composable<HTMLDivElement, MosaicStackProps>(
|
|
|
130
131
|
location={index + 1}
|
|
131
132
|
draggable={draggable}
|
|
132
133
|
current={getId(item) === currentId}
|
|
134
|
+
selected={selectedIds?.has(getId(item))}
|
|
133
135
|
debug={debug}
|
|
134
136
|
/>
|
|
135
137
|
{draggable && <InternalPlaceholder orientation={orientation} location={index + 1.5} />}
|
|
@@ -178,7 +180,8 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
178
180
|
forwardedRef,
|
|
179
181
|
) => {
|
|
180
182
|
invariant(Tile);
|
|
181
|
-
const { id, dragging, currentId, registerScrollTo } =
|
|
183
|
+
const { id, dragging, currentId, selectedIds, registerScrollTo } =
|
|
184
|
+
useMosaicContainerContext(MOSAIC_VIRTUAL_STACK_NAME);
|
|
182
185
|
const visibleItems = useVisibleItems({ id, items, dragging: dragging?.source.data, getId });
|
|
183
186
|
// In draggable mode virtual indices alternate: even=placeholder, odd=tile.
|
|
184
187
|
// Wrap estimateSize so placeholders get 0 (their actual negligible height) and
|
|
@@ -193,6 +196,10 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
193
196
|
count: draggable ? visibleItems.length * 2 + 1 : visibleItems.length,
|
|
194
197
|
estimateSize: wrappedEstimateSize,
|
|
195
198
|
gap,
|
|
199
|
+
// Inset the stack from both ends by `gap` (matching the inter-item spacing). The virtualizer
|
|
200
|
+
// folds this into item offsets, getTotalSize(), and scrollToIndex, so no manual compensation.
|
|
201
|
+
paddingStart: gap,
|
|
202
|
+
paddingEnd: gap,
|
|
196
203
|
// Key measurements by stable item ID so the size cache survives scrolling;
|
|
197
204
|
// without this, measurements are indexed by position and are lost when items reorder.
|
|
198
205
|
getItemKey: draggable
|
|
@@ -213,10 +220,16 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
const virtualIndex = draggable ? itemIndex * 2 + 1 : itemIndex;
|
|
216
|
-
|
|
223
|
+
// Align to the item's start, but offset by the inter-item `gap` so the tile lands
|
|
224
|
+
// one gap below the viewport edge rather than flush against it (a plain
|
|
225
|
+
// `scrollToIndex({ align: 'start' })` scrolls the preceding gap out of view).
|
|
226
|
+
const [offset] = virtualizer.getOffsetForIndex(virtualIndex, 'start') ?? [];
|
|
227
|
+
if (offset != null) {
|
|
228
|
+
virtualizer.scrollToOffset(Math.max(0, offset - (gap ?? 0)), { behavior: 'smooth' });
|
|
229
|
+
}
|
|
217
230
|
}
|
|
218
231
|
},
|
|
219
|
-
[visibleItems, getId, draggable, virtualizer, scrollIntoView],
|
|
232
|
+
[visibleItems, getId, draggable, virtualizer, scrollIntoView, gap],
|
|
220
233
|
);
|
|
221
234
|
|
|
222
235
|
useEffect(() => {
|
|
@@ -293,6 +306,7 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
293
306
|
location={location}
|
|
294
307
|
draggable={draggable}
|
|
295
308
|
current={getId(data) === currentId}
|
|
309
|
+
selected={selectedIds?.has(getId(data))}
|
|
296
310
|
debug={debug}
|
|
297
311
|
/>
|
|
298
312
|
) : (
|
|
@@ -311,6 +325,7 @@ MosaicVirtualStackInner.displayName = MOSAIC_VIRTUAL_STACK_NAME;
|
|
|
311
325
|
const MosaicVirtualStack = MosaicVirtualStackInner as <TData = any>(
|
|
312
326
|
props: MosaicVirtualStackProps<TData> & { ref?: Ref<HTMLDivElement> },
|
|
313
327
|
) => ReactElement;
|
|
328
|
+
|
|
314
329
|
(MosaicVirtualStack as any)[Symbol.for('dxos.composable')] = true;
|
|
315
330
|
|
|
316
331
|
//
|
|
@@ -24,7 +24,7 @@ import React, { type PropsWithChildren, useLayoutEffect, useMemo, useRef, useSta
|
|
|
24
24
|
import { createPortal } from 'react-dom';
|
|
25
25
|
|
|
26
26
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
27
|
-
import { composableProps, slottable } from '@dxos/ui
|
|
27
|
+
import { composableProps, slottable } from '@dxos/react-ui';
|
|
28
28
|
|
|
29
29
|
import { useMosaicContainerContext } from './Container';
|
|
30
30
|
import { type LocationType, type MosaicTileData, getSourceData } from './types';
|
|
@@ -61,6 +61,8 @@ type MosaicTileProps<TData = any, TLocation = LocationType> = ThemedClassName<
|
|
|
61
61
|
draggable?: boolean;
|
|
62
62
|
/** Whether this tile is the current (aria-current) item. */
|
|
63
63
|
current?: boolean;
|
|
64
|
+
/** Whether this tile is selected (aria-selected). */
|
|
65
|
+
selected?: boolean;
|
|
64
66
|
debug?: boolean;
|
|
65
67
|
}>
|
|
66
68
|
>;
|
|
@@ -76,7 +78,8 @@ const MosaicTile = slottable<HTMLDivElement, MosaicTileProps>(
|
|
|
76
78
|
id,
|
|
77
79
|
data: dataProp,
|
|
78
80
|
draggable: draggableProp,
|
|
79
|
-
current
|
|
81
|
+
current,
|
|
82
|
+
selected,
|
|
80
83
|
debug: _,
|
|
81
84
|
...props
|
|
82
85
|
},
|
|
@@ -211,6 +214,12 @@ const MosaicTile = slottable<HTMLDivElement, MosaicTileProps>(
|
|
|
211
214
|
[`data-${MOSAIC_TILE_STATE_ATTR}`]: state.type,
|
|
212
215
|
}}
|
|
213
216
|
role='listitem'
|
|
217
|
+
// Pair `current` / `selected` props with `aria-current` / `aria-selected`
|
|
218
|
+
// so the `dx-current` / `dx-selected` / `dx-hover` utilities (which key
|
|
219
|
+
// off those attributes) actually fire. Without this the props are purely
|
|
220
|
+
// advisory and the corresponding styling silently no-ops.
|
|
221
|
+
aria-current={current ? 'true' : undefined}
|
|
222
|
+
aria-selected={selected ? 'true' : undefined}
|
|
214
223
|
className={className}
|
|
215
224
|
ref={composedRef}
|
|
216
225
|
>
|
|
@@ -123,7 +123,7 @@ const dropField = StateField.define<DecorationSet>({
|
|
|
123
123
|
toDOM() {
|
|
124
124
|
const dom = document.createElement('span');
|
|
125
125
|
dom.className = 'cm-drop-cursor';
|
|
126
|
-
dom.style.borderLeft = '2px solid var(--color-accent-
|
|
126
|
+
dom.style.borderLeft = '2px solid var(--color-accent-bg)';
|
|
127
127
|
dom.style.display = 'inline-block';
|
|
128
128
|
dom.style.height = '1em';
|
|
129
129
|
dom.style.verticalAlign = 'middle';
|