@dxos/react-ui-mosaic 0.10.0 → 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.
- package/dist/lib/chunk-Mosaic.mjs +1024 -0
- package/dist/lib/chunk-Mosaic.mjs.map +1 -0
- package/dist/lib/index.mjs +653 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/playwright.mjs +200 -0
- package/dist/lib/playwright.mjs.map +1 -0
- package/dist/lib/testing.mjs +127 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/translations.mjs +11 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/types/src/components/Board/Board.d.ts +7 -27
- package/dist/types/src/components/Board/Board.d.ts.map +1 -1
- package/dist/types/src/components/Board/Board.stories.d.ts.map +1 -1
- package/dist/types/src/components/Board/BoardColumnContext.d.ts +10 -0
- package/dist/types/src/components/Board/BoardColumnContext.d.ts.map +1 -0
- package/dist/types/src/components/Board/BoardContext.d.ts +26 -0
- package/dist/types/src/components/Board/BoardContext.d.ts.map +1 -0
- package/dist/types/src/components/Board/Column.d.ts +2 -7
- package/dist/types/src/components/Board/Column.d.ts.map +1 -1
- package/dist/types/src/components/Board/index.d.ts +2 -0
- package/dist/types/src/components/Board/index.d.ts.map +1 -1
- package/dist/types/src/components/Focus/Focus.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Container.d.ts +6 -34
- package/dist/types/src/components/Mosaic/Container.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Mosaic.d.ts +10 -15
- package/dist/types/src/components/Mosaic/Mosaic.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/MosaicContainerContext.d.ts +34 -0
- package/dist/types/src/components/Mosaic/MosaicContainerContext.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/MosaicTileContext.d.ts +31 -0
- package/dist/types/src/components/Mosaic/MosaicTileContext.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/Placeholder.d.ts +3 -3
- package/dist/types/src/components/Mosaic/Placeholder.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Stack.d.ts +8 -2
- 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 -32
- package/dist/types/src/components/Mosaic/Tile.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/VirtualStackPagination.stories.d.ts +13 -0
- package/dist/types/src/components/Mosaic/VirtualStackPagination.stories.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/index.d.ts +0 -1
- package/dist/types/src/components/Mosaic/index.d.ts.map +1 -1
- package/dist/types/src/components/SearchStack/SearchStack.d.ts +1 -1
- package/dist/types/src/components/SearchStack/SearchStack.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts +2 -2
- package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useEventHandlerAdapter.d.ts +3 -3
- package/dist/types/src/hooks/useEventHandlerAdapter.d.ts.map +1 -1
- package/dist/types/src/hooks/useVirtualizerPagination.d.ts +60 -0
- package/dist/types/src/hooks/useVirtualizerPagination.d.ts.map +1 -0
- package/dist/types/src/hooks/useVirtualizerPagination.test.d.ts +2 -0
- package/dist/types/src/hooks/useVirtualizerPagination.test.d.ts.map +1 -0
- package/dist/types/src/hooks/useVisibleItems.d.ts +2 -2
- package/dist/types/src/hooks/useVisibleItems.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -37
- package/src/components/Board/Board.stories.tsx +7 -6
- package/src/components/Board/Board.tsx +9 -54
- package/src/components/Board/BoardColumnContext.ts +25 -0
- package/src/components/Board/BoardContext.ts +43 -0
- package/src/components/Board/Column.tsx +13 -27
- package/src/components/Board/Item.tsx +2 -2
- package/src/components/Board/index.ts +3 -0
- package/src/components/Mosaic/Container.tsx +31 -53
- package/src/components/Mosaic/DragHandle.tsx +1 -1
- package/src/components/Mosaic/{Mosaic.tsx → Mosaic.ts} +5 -16
- package/src/components/Mosaic/MosaicContainerContext.ts +44 -0
- package/src/components/Mosaic/MosaicTileContext.ts +34 -0
- package/src/components/Mosaic/Placeholder.tsx +7 -8
- package/src/components/Mosaic/ResizeHandle.stories.tsx +4 -4
- package/src/components/Mosaic/ResizeHandle.tsx +2 -2
- package/src/components/Mosaic/Stack.stories.tsx +49 -13
- package/src/components/Mosaic/Stack.tsx +30 -9
- package/src/components/Mosaic/Tile.tsx +33 -33
- package/src/components/Mosaic/VirtualStackPagination.stories.tsx +423 -0
- package/src/components/Mosaic/index.ts +0 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useDefaultColumnEventHandler.ts +2 -2
- package/src/hooks/useEventHandlerAdapter.ts +4 -5
- package/src/hooks/useVirtualizerPagination.test.tsx +105 -0
- package/src/hooks/useVirtualizerPagination.ts +473 -0
- package/src/hooks/useVisibleItems.ts +2 -2
- package/src/playwright/smoke.spec.ts +4 -1
- package/src/testing/DefaultStackTile.tsx +1 -1
- package/src/testing/decorator.tsx +3 -3
- package/dist/lib/browser/chunk-OBVIYZLR.mjs +0 -1119
- package/dist/lib/browser/chunk-OBVIYZLR.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -701
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/playwright/index.mjs +0 -256
- package/dist/lib/browser/playwright/index.mjs.map +0 -7
- package/dist/lib/browser/testing/index.mjs +0 -136
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/browser/translations.mjs +0 -18
- package/dist/lib/browser/translations.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-4IEMCMIV.mjs +0 -1121
- package/dist/lib/node-esm/chunk-4IEMCMIV.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -702
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/playwright/index.mjs +0 -258
- package/dist/lib/node-esm/playwright/index.mjs.map +0 -7
- package/dist/lib/node-esm/testing/index.mjs +0 -137
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/translations.mjs +0 -20
- package/dist/lib/node-esm/translations.mjs.map +0 -7
- package/dist/types/src/components/Mosaic/Root.d.ts +0 -35
- package/dist/types/src/components/Mosaic/Root.d.ts.map +0 -1
- package/dist/types/src/components/Mosaic/types.d.ts +0 -86
- package/dist/types/src/components/Mosaic/types.d.ts.map +0 -1
- package/src/components/Mosaic/Root.tsx +0 -288
- package/src/components/Mosaic/types.ts +0 -94
- /package/src/components/Focus/{Focus.tsx → Focus.ts} +0 -0
|
@@ -2,14 +2,10 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
MosaicContainer,
|
|
7
|
-
type MosaicContainerProps,
|
|
8
|
-
type MosaicContainerState,
|
|
9
|
-
type MosaicScrollController,
|
|
10
|
-
useMosaicContainerContext,
|
|
11
|
-
} from './Container';
|
|
5
|
+
import { MosaicContainer, type MosaicContainerProps, type MosaicScrollController } from './Container';
|
|
12
6
|
import { MosaicDragHandle, type MosaicDragHandleProps } from './DragHandle';
|
|
7
|
+
import { type MosaicContainerState, useMosaicContainerContext } from './MosaicContainerContext';
|
|
8
|
+
import { type MosaicTileState, useMosaicTileContext } from './MosaicTileContext';
|
|
13
9
|
import {
|
|
14
10
|
MosaicDropIndicator,
|
|
15
11
|
type MosaicDropIndicatorProps,
|
|
@@ -17,16 +13,14 @@ import {
|
|
|
17
13
|
type MosaicPlaceholderProps,
|
|
18
14
|
} from './Placeholder';
|
|
19
15
|
import { MosaicResizeHandle, type MosaicResizeHandleProps } from './ResizeHandle';
|
|
20
|
-
import { MosaicRoot, type MosaicRootProps, useMosaicRootContext } from './Root';
|
|
21
16
|
import { MosaicStack, type MosaicStackProps, type MosaicStackTileComponent, MosaicVirtualStack } from './Stack';
|
|
22
|
-
import { MosaicTile, type MosaicTileProps
|
|
17
|
+
import { MosaicTile, type MosaicTileProps } from './Tile';
|
|
23
18
|
|
|
24
19
|
//
|
|
25
20
|
// Mosaic
|
|
26
21
|
//
|
|
27
22
|
|
|
28
23
|
export const Mosaic = {
|
|
29
|
-
Root: MosaicRoot,
|
|
30
24
|
Container: MosaicContainer,
|
|
31
25
|
Tile: MosaicTile,
|
|
32
26
|
DragHandle: MosaicDragHandle,
|
|
@@ -44,7 +38,6 @@ export type {
|
|
|
44
38
|
MosaicDropIndicatorProps,
|
|
45
39
|
MosaicPlaceholderProps,
|
|
46
40
|
MosaicResizeHandleProps,
|
|
47
|
-
MosaicRootProps,
|
|
48
41
|
MosaicScrollController,
|
|
49
42
|
MosaicStackProps,
|
|
50
43
|
MosaicStackTileComponent,
|
|
@@ -52,8 +45,4 @@ export type {
|
|
|
52
45
|
MosaicTileState,
|
|
53
46
|
};
|
|
54
47
|
|
|
55
|
-
export {
|
|
56
|
-
useMosaicRootContext as useMosaic,
|
|
57
|
-
useMosaicContainerContext as useMosaicContainer,
|
|
58
|
-
useMosaicTileContext as useMosaicTile,
|
|
59
|
-
};
|
|
48
|
+
export { useMosaicContainerContext as useMosaicContainer, useMosaicTileContext as useMosaicTile };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
|
|
7
|
+
import { type AllowedAxis } from '@dxos/react-ui';
|
|
8
|
+
import { type DndContainerHandler, type DndDraggingState, type DndLocation } from '@dxos/react-ui-dnd';
|
|
9
|
+
|
|
10
|
+
// Kept out of `Container.tsx`: react-refresh only fast-refreshes a module whose exports are all
|
|
11
|
+
// components, so the context and hook exported beside them force a full page reload on every edit.
|
|
12
|
+
|
|
13
|
+
export const MOSAIC_CONTAINER_NAME = 'Mosaic.Container';
|
|
14
|
+
|
|
15
|
+
export type MosaicContainerState = { type: 'idle' } | { type: 'active'; bounds?: DOMRect };
|
|
16
|
+
|
|
17
|
+
export type MosaicContainerContextValue<TData = any, Location = DndLocation> = {
|
|
18
|
+
id: string;
|
|
19
|
+
eventHandler: DndContainerHandler<TData>;
|
|
20
|
+
orientation?: AllowedAxis;
|
|
21
|
+
dragging?: DndDraggingState;
|
|
22
|
+
scrolling?: boolean;
|
|
23
|
+
state: MosaicContainerState;
|
|
24
|
+
|
|
25
|
+
/** Active drop location. */
|
|
26
|
+
activeLocation?: Location;
|
|
27
|
+
setActiveLocation: (location: Location | undefined) => void;
|
|
28
|
+
|
|
29
|
+
/** ID of the current (aria-current) item. */
|
|
30
|
+
currentId?: string;
|
|
31
|
+
/** Set the current item by ID. */
|
|
32
|
+
setCurrentId: (id: string | undefined) => void;
|
|
33
|
+
|
|
34
|
+
/** IDs of selected (aria-selected) items. */
|
|
35
|
+
selectedIds?: ReadonlySet<string>;
|
|
36
|
+
/** Request to set or unset selection on an item by ID. */
|
|
37
|
+
setSelected: (id: string, selected: boolean) => void;
|
|
38
|
+
|
|
39
|
+
/** Register a scroll-to-item callback (provided by Stack/VirtualStack). */
|
|
40
|
+
registerScrollTo: (fn: ((id: string) => void) | undefined) => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const [MosaicContainerContextProvider, useMosaicContainerContext] =
|
|
44
|
+
createContext<MosaicContainerContextValue>('MosaicContainer');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
6
|
+
import { createContext } from '@radix-ui/react-context';
|
|
7
|
+
|
|
8
|
+
import { type Size } from '@dxos/react-ui-dnd';
|
|
9
|
+
|
|
10
|
+
// Kept out of `Tile.tsx`: react-refresh only fast-refreshes a module whose exports are all
|
|
11
|
+
// components, so the context and hook exported beside them force a full page reload on every edit.
|
|
12
|
+
|
|
13
|
+
export const MOSAIC_TILE_NAME = 'Mosaic.Tile';
|
|
14
|
+
|
|
15
|
+
export type MosaicTileState =
|
|
16
|
+
| { type: 'idle' }
|
|
17
|
+
| { type: 'preview'; container: HTMLElement; rect: DOMRect }
|
|
18
|
+
| { type: 'dragging' }
|
|
19
|
+
| { type: 'target'; closestEdge: Edge | null };
|
|
20
|
+
|
|
21
|
+
export type MosaicTileContextValue = {
|
|
22
|
+
state: MosaicTileState;
|
|
23
|
+
/** Register the element that initiates dragging; set by a child `Mosaic.DragHandle`. */
|
|
24
|
+
setDragHandle: (element: HTMLElement | null) => void;
|
|
25
|
+
/** Current extent (rem) during/after resize; undefined when the tile is not sized. */
|
|
26
|
+
size?: Size;
|
|
27
|
+
/** Update the tile extent. A `commit` (drop) propagates to the consumer's `onSizeChange`. */
|
|
28
|
+
setSize: (size: Size, commit?: boolean) => void;
|
|
29
|
+
/** Resize bounds (rem) declared by the tile; consumed by `Mosaic.ResizeHandle`. */
|
|
30
|
+
minSize?: number;
|
|
31
|
+
maxSize?: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const [MosaicTileContextProvider, useMosaicTileContext] = createContext<MosaicTileContextValue>('MosaicTile');
|
|
@@ -12,11 +12,11 @@ import { Slot } from '@radix-ui/react-slot';
|
|
|
12
12
|
import React, { type PropsWithChildren, useLayoutEffect, useMemo, useRef } from 'react';
|
|
13
13
|
|
|
14
14
|
import { type Axis, type ThemedClassName } from '@dxos/react-ui';
|
|
15
|
+
import { type DndLocation, type DndPlaceholderData, getSourceData } from '@dxos/react-ui-dnd';
|
|
15
16
|
import { mx } from '@dxos/ui-theme';
|
|
16
17
|
|
|
17
|
-
import { useMosaicContainerContext } from './
|
|
18
|
-
import { useMosaicTileContext } from './
|
|
19
|
-
import { type LocationType, type MosaicPlaceholderData, getSourceData } from './types';
|
|
18
|
+
import { useMosaicContainerContext } from './MosaicContainerContext';
|
|
19
|
+
import { useMosaicTileContext } from './MosaicTileContext';
|
|
20
20
|
|
|
21
21
|
//
|
|
22
22
|
// Placeholder
|
|
@@ -35,7 +35,7 @@ const MOSAIC_PLACEHOLDER_STATE_ATTR = 'mosaic-placeholder-state';
|
|
|
35
35
|
// a specific gap unambiguously without relying on layout-dependent indices.
|
|
36
36
|
const MOSAIC_PLACEHOLDER_LOCATION_ATTR = 'mosaic-placeholder-location';
|
|
37
37
|
|
|
38
|
-
type MosaicPlaceholderProps<Location =
|
|
38
|
+
type MosaicPlaceholderProps<Location = DndLocation> = ThemedClassName<
|
|
39
39
|
PropsWithChildren<{
|
|
40
40
|
asChild?: boolean;
|
|
41
41
|
orientation?: Axis;
|
|
@@ -43,7 +43,7 @@ type MosaicPlaceholderProps<Location = LocationType> = ThemedClassName<
|
|
|
43
43
|
}>
|
|
44
44
|
>;
|
|
45
45
|
|
|
46
|
-
const MosaicPlaceholder = <Location extends
|
|
46
|
+
const MosaicPlaceholder = <Location extends DndLocation = DndLocation>({
|
|
47
47
|
classNames,
|
|
48
48
|
children,
|
|
49
49
|
asChild,
|
|
@@ -59,13 +59,13 @@ const MosaicPlaceholder = <Location extends LocationType = LocationType>({
|
|
|
59
59
|
activeLocation,
|
|
60
60
|
setActiveLocation,
|
|
61
61
|
} = useMosaicContainerContext(MOSAIC_PLACEHOLDER_NAME);
|
|
62
|
-
const data = useMemo<
|
|
62
|
+
const data = useMemo<DndPlaceholderData<Location>>(
|
|
63
63
|
() =>
|
|
64
64
|
({
|
|
65
65
|
type: 'placeholder',
|
|
66
66
|
containerId,
|
|
67
67
|
location,
|
|
68
|
-
}) satisfies
|
|
68
|
+
}) satisfies DndPlaceholderData<Location>,
|
|
69
69
|
[containerId, location],
|
|
70
70
|
);
|
|
71
71
|
|
|
@@ -103,7 +103,6 @@ const MosaicPlaceholder = <Location extends LocationType = LocationType>({
|
|
|
103
103
|
[`data-${MOSAIC_PLACEHOLDER_STATE_ATTR}`]: data.location === activeLocation ? 'active' : 'idle',
|
|
104
104
|
[`data-${MOSAIC_PLACEHOLDER_LOCATION_ATTR}`]: String(location),
|
|
105
105
|
}}
|
|
106
|
-
role='none'
|
|
107
106
|
className={mx('relative', classNames)}
|
|
108
107
|
ref={rootRef}
|
|
109
108
|
>
|
|
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import React, { useCallback, useMemo, useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type Axis, ScrollArea } from '@dxos/react-ui';
|
|
9
|
-
import { type Size } from '@dxos/react-ui-dnd';
|
|
9
|
+
import { Dnd, type Size } from '@dxos/react-ui-dnd';
|
|
10
10
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
11
|
|
|
12
12
|
import { Mosaic, type MosaicTileProps } from './Mosaic';
|
|
@@ -40,7 +40,7 @@ const ResizableTile = ({ data, size, onResize, ...tileProps }: ResizableTileProp
|
|
|
40
40
|
return (
|
|
41
41
|
<Mosaic.Tile
|
|
42
42
|
{...tileProps}
|
|
43
|
-
classNames='flex flex-col p-2 m-1 border border-separator rounded-sm
|
|
43
|
+
classNames='flex flex-col p-2 m-1 border border-separator rounded-sm dx-base-surface overflow-hidden'
|
|
44
44
|
data={data}
|
|
45
45
|
size={size}
|
|
46
46
|
onSizeChange={handleSizeChange}
|
|
@@ -70,7 +70,7 @@ const DefaultStory = ({ orientation = 'vertical' }: { orientation?: Axis }) => {
|
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
return (
|
|
73
|
-
<
|
|
73
|
+
<Dnd.Root>
|
|
74
74
|
<Mosaic.Container
|
|
75
75
|
asChild
|
|
76
76
|
orientation={orientation}
|
|
@@ -89,7 +89,7 @@ const DefaultStory = ({ orientation = 'vertical' }: { orientation?: Axis }) => {
|
|
|
89
89
|
</ScrollArea.Viewport>
|
|
90
90
|
</ScrollArea.Root>
|
|
91
91
|
</Mosaic.Container>
|
|
92
|
-
</
|
|
92
|
+
</Dnd.Root>
|
|
93
93
|
);
|
|
94
94
|
};
|
|
95
95
|
|
|
@@ -6,8 +6,8 @@ import React from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { ResizeHandle } from '@dxos/react-ui-dnd';
|
|
8
8
|
|
|
9
|
-
import { useMosaicContainerContext } from './
|
|
10
|
-
import { useMosaicTileContext } from './
|
|
9
|
+
import { useMosaicContainerContext } from './MosaicContainerContext';
|
|
10
|
+
import { useMosaicTileContext } from './MosaicTileContext';
|
|
11
11
|
|
|
12
12
|
const MOSAIC_RESIZE_HANDLE_NAME = 'Mosaic.ResizeHandle';
|
|
13
13
|
|
|
@@ -9,7 +9,9 @@ import { useMemo } from 'react';
|
|
|
9
9
|
import { Obj } from '@dxos/echo';
|
|
10
10
|
import { random } from '@dxos/random';
|
|
11
11
|
import { Panel, ScrollArea, Toolbar } from '@dxos/react-ui';
|
|
12
|
+
import { Dnd, type DndContainerHandler } from '@dxos/react-ui-dnd';
|
|
12
13
|
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
14
|
+
import { arrayMove } from '@dxos/util';
|
|
13
15
|
|
|
14
16
|
import { useContainerDebug } from '../../hooks';
|
|
15
17
|
import { DefaultStackTile, TestItem } from '../../testing';
|
|
@@ -30,17 +32,50 @@ const createTestItems = (n: number) =>
|
|
|
30
32
|
}),
|
|
31
33
|
);
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
// Stateful items plus a same-container reorder handler so the stories actually apply drops
|
|
36
|
+
// (the container is headless — reordering is the consumer's responsibility).
|
|
37
|
+
const useReorderableStack = () => {
|
|
34
38
|
// Create items at render time to avoid Storybook serialization issues with ECHO objects.
|
|
35
|
-
const items =
|
|
39
|
+
const [items, setItems] = useState(() => createTestItems(NUM_ITEMS));
|
|
40
|
+
const eventHandler = useMemo<DndContainerHandler<Obj.Any>>(
|
|
41
|
+
() => ({
|
|
42
|
+
id: 'test',
|
|
43
|
+
canDrop: () => true,
|
|
44
|
+
onDrop: ({ source, target }) => {
|
|
45
|
+
const to =
|
|
46
|
+
(target?.type === 'tile' || target?.type === 'placeholder') && typeof target.location === 'number'
|
|
47
|
+
? Math.floor(target.location)
|
|
48
|
+
: undefined;
|
|
49
|
+
if (to === undefined || to < 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setItems((prev) => {
|
|
53
|
+
const from = prev.findIndex((item) => item.id === source.id);
|
|
54
|
+
if (from === -1) {
|
|
55
|
+
return prev;
|
|
56
|
+
}
|
|
57
|
+
const next = prev.slice();
|
|
58
|
+
arrayMove(next, from, to);
|
|
59
|
+
return next;
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
[],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return { items, eventHandler };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const DefaultStackStory = (props: MosaicStackProps<Obj.Any>) => {
|
|
70
|
+
const { items, eventHandler } = useReorderableStack();
|
|
36
71
|
const [DebugInfo, debugHandler] = useContainerDebug(props.debug);
|
|
37
72
|
const [viewport, setViewport] = useState<HTMLElement | null>(null);
|
|
38
73
|
return (
|
|
39
|
-
<
|
|
74
|
+
<Dnd.Root>
|
|
40
75
|
<Panel.Root>
|
|
41
76
|
<Panel.Toolbar asChild>
|
|
42
77
|
<Toolbar.Root>
|
|
43
|
-
<
|
|
78
|
+
<Toolbar.Text>Items: {items.length}</Toolbar.Text>
|
|
44
79
|
</Toolbar.Root>
|
|
45
80
|
</Panel.Toolbar>
|
|
46
81
|
<Panel.Content asChild>
|
|
@@ -49,8 +84,9 @@ const DefaultStackStory = (props: MosaicStackProps<Obj.Any>) => {
|
|
|
49
84
|
asChild
|
|
50
85
|
orientation='vertical'
|
|
51
86
|
autoScroll={viewport}
|
|
52
|
-
eventHandler={
|
|
87
|
+
eventHandler={eventHandler}
|
|
53
88
|
debug={debugHandler}
|
|
89
|
+
placeholderDebug={props.debug}
|
|
54
90
|
>
|
|
55
91
|
<ScrollArea.Root orientation='vertical'>
|
|
56
92
|
<ScrollArea.Viewport ref={setViewport}>
|
|
@@ -61,23 +97,22 @@ const DefaultStackStory = (props: MosaicStackProps<Obj.Any>) => {
|
|
|
61
97
|
</Focus.Group>
|
|
62
98
|
</Panel.Content>
|
|
63
99
|
{props.debug && (
|
|
64
|
-
<Panel.Statusbar
|
|
100
|
+
<Panel.Statusbar classNames='h-[40dvh]'>
|
|
65
101
|
<DebugInfo />
|
|
66
102
|
</Panel.Statusbar>
|
|
67
103
|
)}
|
|
68
104
|
</Panel.Root>
|
|
69
|
-
</
|
|
105
|
+
</Dnd.Root>
|
|
70
106
|
);
|
|
71
107
|
};
|
|
72
108
|
|
|
73
109
|
const VirtualStackStory = (props: MosaicStackProps<Obj.Any>) => {
|
|
74
|
-
|
|
75
|
-
const items = useMemo(() => createTestItems(NUM_ITEMS), []);
|
|
110
|
+
const { items, eventHandler } = useReorderableStack();
|
|
76
111
|
const [info, setInfo] = useState<any>(null);
|
|
77
112
|
const [DebugInfo, debugHandler] = useContainerDebug(props.debug);
|
|
78
113
|
const [viewport, setViewport] = useState<HTMLElement | null>(null);
|
|
79
114
|
return (
|
|
80
|
-
<
|
|
115
|
+
<Dnd.Root>
|
|
81
116
|
<Panel.Root>
|
|
82
117
|
<Panel.Toolbar asChild>
|
|
83
118
|
<Toolbar.Root>
|
|
@@ -89,8 +124,9 @@ const VirtualStackStory = (props: MosaicStackProps<Obj.Any>) => {
|
|
|
89
124
|
asChild
|
|
90
125
|
orientation='vertical'
|
|
91
126
|
autoScroll={viewport}
|
|
92
|
-
eventHandler={
|
|
127
|
+
eventHandler={eventHandler}
|
|
93
128
|
debug={debugHandler}
|
|
129
|
+
placeholderDebug={props.debug}
|
|
94
130
|
>
|
|
95
131
|
<ScrollArea.Root orientation='vertical'>
|
|
96
132
|
<ScrollArea.Viewport ref={setViewport}>
|
|
@@ -108,12 +144,12 @@ const VirtualStackStory = (props: MosaicStackProps<Obj.Any>) => {
|
|
|
108
144
|
</Mosaic.Container>
|
|
109
145
|
</Panel.Content>
|
|
110
146
|
{props.debug && (
|
|
111
|
-
<Panel.Statusbar
|
|
147
|
+
<Panel.Statusbar classNames='h-[40dvh]'>
|
|
112
148
|
<DebugInfo />
|
|
113
149
|
</Panel.Statusbar>
|
|
114
150
|
)}
|
|
115
151
|
</Panel.Root>
|
|
116
|
-
</
|
|
152
|
+
</Dnd.Root>
|
|
117
153
|
);
|
|
118
154
|
};
|
|
119
155
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { useComposedRefs } from '@radix-ui/react-compose-refs';
|
|
6
|
-
import { type ReactVirtualizerOptions, useVirtualizer } from '@tanstack/react-virtual';
|
|
6
|
+
import { type ReactVirtualizerOptions, type Virtualizer, useVirtualizer } from '@tanstack/react-virtual';
|
|
7
7
|
import React, {
|
|
8
8
|
type FC,
|
|
9
9
|
Fragment,
|
|
@@ -19,14 +19,14 @@ import React, {
|
|
|
19
19
|
|
|
20
20
|
import { invariant } from '@dxos/invariant';
|
|
21
21
|
import { type Axis, type ThemedClassName, composable, composableProps } from '@dxos/react-ui';
|
|
22
|
+
import { type GetId } from '@dxos/react-ui-dnd';
|
|
22
23
|
import { mx } from '@dxos/ui-theme';
|
|
23
24
|
|
|
24
|
-
import { useVisibleItems } from '../../hooks';
|
|
25
|
-
import { useMosaicContainerContext } from './
|
|
25
|
+
import { type VirtualizerPaginationController, useVirtualizerPagination, useVisibleItems } from '../../hooks';
|
|
26
|
+
import { useMosaicContainerContext } from './MosaicContainerContext';
|
|
26
27
|
import { MosaicPlaceholder, type MosaicPlaceholderProps } from './Placeholder';
|
|
27
28
|
import { styles } from './styles';
|
|
28
29
|
import { type MosaicTileProps } from './Tile';
|
|
29
|
-
import { type GetId } from './types';
|
|
30
30
|
|
|
31
31
|
//
|
|
32
32
|
// Mosaic Drag-and-drop
|
|
@@ -179,7 +179,12 @@ type MosaicVirtualStackProps<TData = any> = MosaicStackProps<TData> &
|
|
|
179
179
|
Pick<
|
|
180
180
|
ReactVirtualizerOptions<HTMLElement, HTMLDivElement>,
|
|
181
181
|
'estimateSize' | 'gap' | 'getScrollElement' | 'overscan' | 'onChange'
|
|
182
|
-
|
|
182
|
+
> & {
|
|
183
|
+
/** Enables paginated loading; see `useVirtualizerPagination`. Requires `draggable={false}`. */
|
|
184
|
+
pagination?: VirtualizerPaginationController;
|
|
185
|
+
/** Rows from either loaded edge at which the adjacent page is requested. @default 12. */
|
|
186
|
+
paginationThreshold?: number;
|
|
187
|
+
};
|
|
183
188
|
|
|
184
189
|
const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackProps>(
|
|
185
190
|
(
|
|
@@ -193,6 +198,8 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
193
198
|
getScrollElement,
|
|
194
199
|
overscan = 8,
|
|
195
200
|
gap,
|
|
201
|
+
pagination,
|
|
202
|
+
paginationThreshold,
|
|
196
203
|
onChange,
|
|
197
204
|
draggable = true,
|
|
198
205
|
debug,
|
|
@@ -201,6 +208,7 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
201
208
|
forwardedRef,
|
|
202
209
|
) => {
|
|
203
210
|
invariant(Tile);
|
|
211
|
+
invariant(!pagination || !draggable, 'pagination requires draggable={false}');
|
|
204
212
|
const { id, dragging, currentId, selectedIds, registerScrollTo } =
|
|
205
213
|
useMosaicContainerContext(MOSAIC_VIRTUAL_STACK_NAME);
|
|
206
214
|
const visibleItems = useVisibleItems({ id, items, dragging: dragging?.source.data, getId });
|
|
@@ -212,14 +220,27 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
212
220
|
? (index: number) => (index % 2 === 0 ? 0 : estimateSize(Math.floor(index / 2)))
|
|
213
221
|
: estimateSize;
|
|
214
222
|
|
|
223
|
+
const { onChange: handlePaginationChange, leadingSpace } = useVirtualizerPagination({
|
|
224
|
+
items: visibleItems,
|
|
225
|
+
getId,
|
|
226
|
+
pagination,
|
|
227
|
+
threshold: paginationThreshold,
|
|
228
|
+
});
|
|
229
|
+
const handleChange = useCallback(
|
|
230
|
+
(nextVirtualizer: Virtualizer<any, any>, sync: boolean) => {
|
|
231
|
+
handlePaginationChange(nextVirtualizer);
|
|
232
|
+
onChange?.(nextVirtualizer, sync);
|
|
233
|
+
},
|
|
234
|
+
[handlePaginationChange, onChange],
|
|
235
|
+
);
|
|
236
|
+
|
|
215
237
|
const virtualizer = useVirtualizer({
|
|
216
238
|
// NOTE: When draggable we double the number of items to allow for placeholders.
|
|
217
239
|
count: draggable ? visibleItems.length * 2 + 1 : visibleItems.length,
|
|
218
240
|
estimateSize: wrappedEstimateSize,
|
|
219
241
|
gap,
|
|
220
|
-
// Inset
|
|
221
|
-
|
|
222
|
-
paddingStart: gap,
|
|
242
|
+
// Inset by `gap` (inter-item spacing) plus any pagination spacer; see `useVirtualizerPagination`.
|
|
243
|
+
paddingStart: (gap ?? 0) + leadingSpace,
|
|
223
244
|
paddingEnd: gap,
|
|
224
245
|
// Key measurements by stable item ID so the size cache survives scrolling;
|
|
225
246
|
// without this, measurements are indexed by position and are lost when items reorder.
|
|
@@ -228,7 +249,7 @@ const MosaicVirtualStackInner = forwardRef<HTMLDivElement, MosaicVirtualStackPro
|
|
|
228
249
|
: (index) => getId(visibleItems![index]),
|
|
229
250
|
getScrollElement,
|
|
230
251
|
overscan,
|
|
231
|
-
onChange,
|
|
252
|
+
onChange: handleChange,
|
|
232
253
|
});
|
|
233
254
|
|
|
234
255
|
// Register scroll-to-item via virtualizer index lookup.
|
|
@@ -17,50 +17,41 @@ import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/elemen
|
|
|
17
17
|
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
|
18
18
|
import { type DropTargetRecord } from '@atlaskit/pragmatic-drag-and-drop/types';
|
|
19
19
|
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
20
|
-
import { createContext } from '@radix-ui/react-context';
|
|
21
20
|
import { Primitive } from '@radix-ui/react-primitive';
|
|
22
21
|
import { Slot } from '@radix-ui/react-slot';
|
|
23
|
-
import React, {
|
|
22
|
+
import React, {
|
|
23
|
+
type PropsWithChildren,
|
|
24
|
+
useCallback,
|
|
25
|
+
useEffect,
|
|
26
|
+
useLayoutEffect,
|
|
27
|
+
useMemo,
|
|
28
|
+
useRef,
|
|
29
|
+
useState,
|
|
30
|
+
} from 'react';
|
|
24
31
|
import { createPortal } from 'react-dom';
|
|
25
32
|
|
|
26
33
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
27
34
|
import { composableProps, slottable } from '@dxos/react-ui';
|
|
28
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
type DndLocation,
|
|
37
|
+
type DndTileData,
|
|
38
|
+
type Size,
|
|
39
|
+
getSourceData,
|
|
40
|
+
resizeAttributes,
|
|
41
|
+
sizeStyle,
|
|
42
|
+
} from '@dxos/react-ui-dnd';
|
|
29
43
|
|
|
30
|
-
import { useMosaicContainerContext } from './
|
|
31
|
-
import {
|
|
44
|
+
import { useMosaicContainerContext } from './MosaicContainerContext';
|
|
45
|
+
import { MOSAIC_TILE_NAME, MosaicTileContextProvider, type MosaicTileState } from './MosaicTileContext';
|
|
32
46
|
|
|
33
47
|
//
|
|
34
48
|
// Tile
|
|
35
49
|
//
|
|
36
50
|
|
|
37
|
-
const MOSAIC_TILE_NAME = 'Mosaic.Tile';
|
|
38
|
-
|
|
39
|
-
type MosaicTileState =
|
|
40
|
-
| { type: 'idle' }
|
|
41
|
-
| { type: 'preview'; container: HTMLElement; rect: DOMRect }
|
|
42
|
-
| { type: 'dragging' }
|
|
43
|
-
| { type: 'target'; closestEdge: Edge | null };
|
|
44
|
-
|
|
45
|
-
type MosaicTileContextValue = {
|
|
46
|
-
state: MosaicTileState;
|
|
47
|
-
/** Register the element that initiates dragging; set by a child `Mosaic.DragHandle`. */
|
|
48
|
-
setDragHandle: (element: HTMLElement | null) => void;
|
|
49
|
-
/** Current extent (rem) during/after resize; undefined when the tile is not sized. */
|
|
50
|
-
size?: Size;
|
|
51
|
-
/** Update the tile extent. A `commit` (drop) propagates to the consumer's `onSizeChange`. */
|
|
52
|
-
setSize: (size: Size, commit?: boolean) => void;
|
|
53
|
-
/** Resize bounds (rem) declared by the tile; consumed by `Mosaic.ResizeHandle`. */
|
|
54
|
-
minSize?: number;
|
|
55
|
-
maxSize?: number;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const [MosaicTileContextProvider, useMosaicTileContext] = createContext<MosaicTileContextValue>('MosaicTile');
|
|
59
|
-
|
|
60
51
|
// State attribute: data-[mosaic-tile-state=dragging]
|
|
61
52
|
const MOSAIC_TILE_STATE_ATTR = 'mosaic-tile-state';
|
|
62
53
|
|
|
63
|
-
type MosaicTileProps<TData = any, TLocation =
|
|
54
|
+
type MosaicTileProps<TData = any, TLocation = DndLocation> = ThemedClassName<
|
|
64
55
|
PropsWithChildren<{
|
|
65
56
|
className?: string;
|
|
66
57
|
dragHandle?: HTMLElement | null;
|
|
@@ -141,13 +132,22 @@ const MosaicTile = slottable<HTMLDivElement, MosaicTileProps>(
|
|
|
141
132
|
},
|
|
142
133
|
[onSizeChange],
|
|
143
134
|
);
|
|
135
|
+
// The `size` prop is the source of truth; internal state exists only to reflect the live extent
|
|
136
|
+
// during a drag (which commits back through `onSizeChange`). Re-sync when the prop changes so a
|
|
137
|
+
// tile that first rendered without a size — e.g. before the breakpoint settled, or a
|
|
138
|
+
// mobile/fullbleed → sliding branch switch that swaps `size` in — applies it once it arrives,
|
|
139
|
+
// instead of staying stuck at its initial (often `undefined`) extent. A live drag never changes
|
|
140
|
+
// the prop (only the committed drop does), so this does not fight the drag.
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
setInternalSize(sizeProp);
|
|
143
|
+
}, [sizeProp]);
|
|
144
144
|
|
|
145
145
|
const allowedEdges = useMemo<Edge[]>(
|
|
146
146
|
() => allowedEdgesProp || (orientation === 'vertical' ? ['top', 'bottom'] : ['left', 'right']),
|
|
147
147
|
[allowedEdgesProp, orientation],
|
|
148
148
|
);
|
|
149
149
|
|
|
150
|
-
const data = useMemo<
|
|
150
|
+
const data = useMemo<DndTileData>(
|
|
151
151
|
() =>
|
|
152
152
|
({
|
|
153
153
|
type: 'tile',
|
|
@@ -155,7 +155,7 @@ const MosaicTile = slottable<HTMLDivElement, MosaicTileProps>(
|
|
|
155
155
|
containerId,
|
|
156
156
|
data: dataProp,
|
|
157
157
|
location,
|
|
158
|
-
}) satisfies
|
|
158
|
+
}) satisfies DndTileData,
|
|
159
159
|
[containerId, location, dataProp],
|
|
160
160
|
);
|
|
161
161
|
|
|
@@ -329,6 +329,6 @@ const MosaicTile = slottable<HTMLDivElement, MosaicTileProps>(
|
|
|
329
329
|
|
|
330
330
|
MosaicTile.displayName = MOSAIC_TILE_NAME;
|
|
331
331
|
|
|
332
|
-
export { MosaicTile
|
|
332
|
+
export { MosaicTile };
|
|
333
333
|
|
|
334
|
-
export type { MosaicTileProps
|
|
334
|
+
export type { MosaicTileProps };
|