@dxos/react-ui-stack 0.8.2-main.fbd8ed0 → 0.8.2-staging.4d6ad0f
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/browser/index.mjs +454 -322
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +452 -319
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/lib/node-esm/index.mjs +454 -322
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/components/Stack/Stack.d.ts +2 -0
- package/dist/types/src/components/Stack/Stack.d.ts.map +1 -1
- package/dist/types/src/components/Stack/Stack.stories.d.ts.map +1 -1
- package/dist/types/src/components/StackContext.d.ts +13 -0
- package/dist/types/src/components/StackContext.d.ts.map +1 -1
- package/dist/types/src/components/StackItem/StackItem.d.ts +12 -3
- package/dist/types/src/components/StackItem/StackItem.d.ts.map +1 -1
- package/dist/types/src/components/StackItem/StackItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/StackItem/StackItemSigil.d.ts.map +1 -1
- package/dist/types/src/testing/stack-manager.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/components/Stack/Stack.stories.tsx +14 -4
- package/src/components/Stack/Stack.tsx +34 -3
- package/src/components/StackContext.tsx +20 -0
- package/src/components/StackItem/StackItem.tsx +87 -10
- package/src/components/StackItem/StackItemHeading.tsx +2 -1
- package/src/components/StackItem/StackItemSigil.tsx +2 -14
- package/src/testing/stack-manager.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-stack",
|
|
3
|
-
"version": "0.8.2-
|
|
3
|
+
"version": "0.8.2-staging.4d6ad0f",
|
|
4
4
|
"description": "A stack component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
|
|
41
41
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
|
42
42
|
"@fluentui/react-tabster": "^9.24.2",
|
|
43
|
+
"@preact-signals/safe-react": "^0.9.0",
|
|
43
44
|
"@radix-ui/primitive": "1.1.1",
|
|
44
45
|
"@radix-ui/react-compose-refs": "1.1.1",
|
|
45
46
|
"@radix-ui/react-context": "1.1.1",
|
|
@@ -47,12 +48,12 @@
|
|
|
47
48
|
"@radix-ui/react-slot": "1.1.2",
|
|
48
49
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
49
50
|
"react-resize-detector": "^11.0.1",
|
|
50
|
-
"@dxos/
|
|
51
|
-
"@dxos/
|
|
52
|
-
"@dxos/react-ui-
|
|
53
|
-
"@dxos/
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
51
|
+
"@dxos/echo-schema": "0.8.2-staging.4d6ad0f",
|
|
52
|
+
"@dxos/keyboard": "0.8.2-staging.4d6ad0f",
|
|
53
|
+
"@dxos/react-ui-dnd": "0.8.2-staging.4d6ad0f",
|
|
54
|
+
"@dxos/live-object": "0.8.2-staging.4d6ad0f",
|
|
55
|
+
"@dxos/util": "0.8.2-staging.4d6ad0f",
|
|
56
|
+
"@dxos/react-ui-attention": "0.8.2-staging.4d6ad0f"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@phosphor-icons/react": "^2.1.5",
|
|
@@ -61,24 +62,24 @@
|
|
|
61
62
|
"react": "~18.2.0",
|
|
62
63
|
"react-dom": "~18.2.0",
|
|
63
64
|
"vite": "5.4.7",
|
|
64
|
-
"@dxos/app-graph": "0.8.2-
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/
|
|
69
|
-
"@dxos/react-ui": "0.8.2-
|
|
70
|
-
"@dxos/react-ui-
|
|
71
|
-
"@dxos/storybook-utils": "0.8.2-
|
|
72
|
-
"@dxos/test-utils": "0.8.2-
|
|
65
|
+
"@dxos/app-graph": "0.8.2-staging.4d6ad0f",
|
|
66
|
+
"@dxos/client": "0.8.2-staging.4d6ad0f",
|
|
67
|
+
"@dxos/random": "0.8.2-staging.4d6ad0f",
|
|
68
|
+
"@dxos/react-ui": "0.8.2-staging.4d6ad0f",
|
|
69
|
+
"@dxos/echo-schema": "0.8.2-staging.4d6ad0f",
|
|
70
|
+
"@dxos/react-ui-theme": "0.8.2-staging.4d6ad0f",
|
|
71
|
+
"@dxos/react-ui-editor": "0.8.2-staging.4d6ad0f",
|
|
72
|
+
"@dxos/storybook-utils": "0.8.2-staging.4d6ad0f",
|
|
73
|
+
"@dxos/test-utils": "0.8.2-staging.4d6ad0f"
|
|
73
74
|
},
|
|
74
75
|
"peerDependencies": {
|
|
75
76
|
"@phosphor-icons/react": "^2.1.5",
|
|
76
77
|
"react": "~18.2.0",
|
|
77
78
|
"react-dom": "~18.2.0",
|
|
78
|
-
"@dxos/
|
|
79
|
-
"@dxos/react-ui": "0.8.2-
|
|
80
|
-
"@dxos/
|
|
81
|
-
"@dxos/react-ui
|
|
79
|
+
"@dxos/client": "0.8.2-staging.4d6ad0f",
|
|
80
|
+
"@dxos/react-ui-theme": "0.8.2-staging.4d6ad0f",
|
|
81
|
+
"@dxos/random": "0.8.2-staging.4d6ad0f",
|
|
82
|
+
"@dxos/react-ui": "0.8.2-staging.4d6ad0f"
|
|
82
83
|
},
|
|
83
84
|
"publishConfig": {
|
|
84
85
|
"access": "public"
|
|
@@ -97,14 +97,24 @@ const DefaultStory = () => {
|
|
|
97
97
|
return (
|
|
98
98
|
<main className='fixed inset-0'>
|
|
99
99
|
<Stack orientation='horizontal' size='contain' onRearrange={reorderItem}>
|
|
100
|
-
{columns.map((column) => (
|
|
101
|
-
<StackItem.Root
|
|
100
|
+
{columns.map((column, columnIndex, columnsArray) => (
|
|
101
|
+
<StackItem.Root
|
|
102
|
+
key={column.id}
|
|
103
|
+
item={column}
|
|
104
|
+
prevSiblingId={columnIndex > 0 ? columnsArray[columnIndex - 1].id : undefined}
|
|
105
|
+
nextSiblingId={columnIndex < columnsArray.length - 1 ? columnsArray[columnIndex + 1].id : undefined}
|
|
106
|
+
>
|
|
102
107
|
<StackItem.Heading>
|
|
103
108
|
<StackItem.ResizeHandle />
|
|
104
109
|
</StackItem.Heading>
|
|
105
110
|
<Stack orientation='vertical' size='contain'>
|
|
106
|
-
{column.items?.map((card) => (
|
|
107
|
-
<StackItem.Root
|
|
111
|
+
{column.items?.map((card, cardIndex, cardsArray) => (
|
|
112
|
+
<StackItem.Root
|
|
113
|
+
key={card.id}
|
|
114
|
+
item={card}
|
|
115
|
+
prevSiblingId={cardIndex > 0 ? cardsArray[cardIndex - 1].id : undefined}
|
|
116
|
+
nextSiblingId={cardIndex < cardsArray.length - 1 ? cardsArray[cardIndex + 1].id : undefined}
|
|
117
|
+
>
|
|
108
118
|
<StackItem.Heading>
|
|
109
119
|
<StackItem.ResizeHandle />
|
|
110
120
|
</StackItem.Heading>
|
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import { useArrowNavigationGroup } from '@fluentui/react-tabster';
|
|
6
6
|
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
7
|
-
import React, {
|
|
7
|
+
import React, {
|
|
8
|
+
Children,
|
|
9
|
+
type CSSProperties,
|
|
10
|
+
type ComponentPropsWithRef,
|
|
11
|
+
forwardRef,
|
|
12
|
+
useState,
|
|
13
|
+
useMemo,
|
|
14
|
+
useCallback,
|
|
15
|
+
} from 'react';
|
|
8
16
|
|
|
9
17
|
import { type ThemedClassName, ListItem } from '@dxos/react-ui';
|
|
10
18
|
import { mx } from '@dxos/react-ui-theme';
|
|
@@ -17,7 +25,11 @@ export type Orientation = 'horizontal' | 'vertical';
|
|
|
17
25
|
export type Size = 'intrinsic' | 'contain' | 'contain-fit-content';
|
|
18
26
|
|
|
19
27
|
export type StackProps = Omit<ThemedClassName<ComponentPropsWithRef<'div'>>, 'aria-orientation'> &
|
|
20
|
-
Partial<StackContextValue> & {
|
|
28
|
+
Partial<StackContextValue> & {
|
|
29
|
+
itemsCount?: number;
|
|
30
|
+
getDropElement?: (stackElement: HTMLDivElement) => HTMLDivElement;
|
|
31
|
+
separatorOnScroll?: number;
|
|
32
|
+
};
|
|
21
33
|
|
|
22
34
|
export const railGridHorizontal = 'grid-rows-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
|
|
23
35
|
export const railGridVertical = 'grid-cols-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
|
|
@@ -41,6 +53,8 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
|
41
53
|
size = 'intrinsic',
|
|
42
54
|
onRearrange,
|
|
43
55
|
itemsCount = Children.count(children),
|
|
56
|
+
getDropElement,
|
|
57
|
+
separatorOnScroll,
|
|
44
58
|
...props
|
|
45
59
|
},
|
|
46
60
|
forwardedRef,
|
|
@@ -59,12 +73,28 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
|
59
73
|
|
|
60
74
|
const { dropping } = useStackDropForElements({
|
|
61
75
|
id: props.id,
|
|
62
|
-
element: stackElement,
|
|
76
|
+
element: getDropElement && stackElement ? getDropElement(stackElement) : stackElement,
|
|
63
77
|
selfDroppable,
|
|
64
78
|
orientation,
|
|
65
79
|
onRearrange,
|
|
66
80
|
});
|
|
67
81
|
|
|
82
|
+
const handleScroll = useCallback(() => {
|
|
83
|
+
if (stackElement && Number.isFinite(separatorOnScroll)) {
|
|
84
|
+
const scrollPosition = orientation === 'horizontal' ? stackElement.scrollLeft : stackElement.scrollTop;
|
|
85
|
+
const scrollSize = orientation === 'horizontal' ? stackElement.scrollWidth : stackElement.scrollHeight;
|
|
86
|
+
const clientSize = orientation === 'horizontal' ? stackElement.clientWidth : stackElement.clientHeight;
|
|
87
|
+
const separatorHost = stackElement.closest('[data-scroll-separator]');
|
|
88
|
+
if (separatorHost) {
|
|
89
|
+
separatorHost.setAttribute('data-scroll-separator', String(scrollPosition > separatorOnScroll!));
|
|
90
|
+
separatorHost.setAttribute(
|
|
91
|
+
'data-scroll-separator-end',
|
|
92
|
+
String(scrollSize - (scrollPosition + clientSize) > separatorOnScroll!),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}, [stackElement, separatorOnScroll, orientation]);
|
|
97
|
+
|
|
68
98
|
const gridClasses = useMemo(() => {
|
|
69
99
|
if (!rail) {
|
|
70
100
|
return orientation === 'horizontal' ? 'grid-rows-1 pli-1' : 'grid-cols-1 plb-1';
|
|
@@ -94,6 +124,7 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
|
|
|
94
124
|
aria-orientation={orientation}
|
|
95
125
|
style={styles}
|
|
96
126
|
ref={composedItemRef}
|
|
127
|
+
{...(Number.isFinite(separatorOnScroll) && { onScroll: handleScroll })}
|
|
97
128
|
>
|
|
98
129
|
{children}
|
|
99
130
|
{selfDroppable && dropping && (
|
|
@@ -22,16 +22,36 @@ export const StackContext = createContext<StackContextValue>({
|
|
|
22
22
|
|
|
23
23
|
export const useStack = () => useContext(StackContext);
|
|
24
24
|
|
|
25
|
+
export type ItemDragState =
|
|
26
|
+
| {
|
|
27
|
+
type: 'idle';
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: 'preview';
|
|
31
|
+
container: HTMLElement;
|
|
32
|
+
item: any;
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
type: 'is-dragging';
|
|
36
|
+
item: any;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const idle: ItemDragState = { type: 'idle' };
|
|
40
|
+
|
|
25
41
|
export type StackItemContextValue = {
|
|
26
42
|
selfDragHandleRef: (element: HTMLDivElement | null) => void;
|
|
27
43
|
size: StackItemSize;
|
|
28
44
|
setSize: (nextSize: StackItemSize, commit?: boolean) => void;
|
|
45
|
+
state: ItemDragState;
|
|
46
|
+
setState: (state: ItemDragState) => void;
|
|
29
47
|
};
|
|
30
48
|
|
|
31
49
|
export const StackItemContext = createContext<StackItemContextValue>({
|
|
32
50
|
selfDragHandleRef: () => {},
|
|
33
51
|
size: 'min-content',
|
|
34
52
|
setSize: () => {},
|
|
53
|
+
state: idle,
|
|
54
|
+
setState: () => {},
|
|
35
55
|
});
|
|
36
56
|
|
|
37
57
|
export const useStackItem = () => useContext(StackItemContext);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
6
6
|
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
7
7
|
import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source';
|
|
8
|
-
import {
|
|
8
|
+
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
|
9
9
|
import {
|
|
10
10
|
attachClosestEdge,
|
|
11
11
|
extractClosestEdge,
|
|
@@ -13,7 +13,15 @@ import {
|
|
|
13
13
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
14
14
|
import { useFocusableGroup } from '@fluentui/react-tabster';
|
|
15
15
|
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
16
|
-
import React, {
|
|
16
|
+
import React, {
|
|
17
|
+
forwardRef,
|
|
18
|
+
useLayoutEffect,
|
|
19
|
+
useState,
|
|
20
|
+
type ComponentPropsWithRef,
|
|
21
|
+
useCallback,
|
|
22
|
+
type ReactNode,
|
|
23
|
+
} from 'react';
|
|
24
|
+
import { createPortal } from 'react-dom';
|
|
17
25
|
|
|
18
26
|
import { type ThemedClassName, ListItem } from '@dxos/react-ui';
|
|
19
27
|
import { resizeAttributes, sizeStyle } from '@dxos/react-ui-dnd';
|
|
@@ -35,7 +43,7 @@ import {
|
|
|
35
43
|
type StackItemSigilButtonProps,
|
|
36
44
|
StackItemSigilButton,
|
|
37
45
|
} from './StackItemSigil';
|
|
38
|
-
import { useStack, StackItemContext } from '../StackContext';
|
|
46
|
+
import { useStack, StackItemContext, idle, type ItemDragState, useStackItem } from '../StackContext';
|
|
39
47
|
import { type StackItemSize, type StackItemData } from '../defs';
|
|
40
48
|
|
|
41
49
|
// NOTE: 48rem fills the screen on a MacbookPro with the sidebars closed.
|
|
@@ -46,6 +54,8 @@ export const DEFAULT_EXTRINSIC_SIZE = DEFAULT_HORIZONTAL_SIZE satisfies StackIte
|
|
|
46
54
|
type StackItemRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> & {
|
|
47
55
|
item: Omit<StackItemData, 'type'>;
|
|
48
56
|
order?: number;
|
|
57
|
+
prevSiblingId?: string;
|
|
58
|
+
nextSiblingId?: string;
|
|
49
59
|
size?: StackItemSize;
|
|
50
60
|
onSizeChange?: (nextSize: StackItemSize) => void;
|
|
51
61
|
role?: 'article' | 'section';
|
|
@@ -63,6 +73,8 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
63
73
|
onSizeChange,
|
|
64
74
|
role,
|
|
65
75
|
order,
|
|
76
|
+
prevSiblingId,
|
|
77
|
+
nextSiblingId,
|
|
66
78
|
style,
|
|
67
79
|
disableRearrange,
|
|
68
80
|
focusIndicatorVariant = 'over-all',
|
|
@@ -73,6 +85,8 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
73
85
|
const [itemElement, itemRef] = useState<HTMLDivElement | null>(null);
|
|
74
86
|
const [selfDragHandleElement, selfDragHandleRef] = useState<HTMLDivElement | null>(null);
|
|
75
87
|
const [closestEdge, setEdge] = useState<Edge | null>(null);
|
|
88
|
+
const [sourceId, setSourceId] = useState<string | null>(null);
|
|
89
|
+
const [dragState, setDragState] = useState<ItemDragState>(idle);
|
|
76
90
|
const { orientation, rail, onRearrange } = useStack();
|
|
77
91
|
const [size = orientation === 'horizontal' ? DEFAULT_HORIZONTAL_SIZE : DEFAULT_VERTICAL_SIZE, setInternalSize] =
|
|
78
92
|
useState(propsSize);
|
|
@@ -105,18 +119,28 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
105
119
|
getInitialData: () => ({ id: item.id, type }),
|
|
106
120
|
onGenerateDragPreview: ({ nativeSetDragImage, source, location }) => {
|
|
107
121
|
document.body.setAttribute('data-drag-preview', 'true');
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
122
|
+
const offsetFn = preserveOffsetOnSource({ element: source.element, input: location.current.input });
|
|
123
|
+
const rect = source.element.getBoundingClientRect();
|
|
124
|
+
setCustomNativeDragPreview({
|
|
125
|
+
nativeSetDragImage,
|
|
126
|
+
getOffset: ({ container }) => {
|
|
127
|
+
return offsetFn({ container });
|
|
128
|
+
},
|
|
129
|
+
render: ({ container }) => {
|
|
130
|
+
container.style.width = rect.width + 'px';
|
|
131
|
+
setDragState({ type: 'preview', container, item });
|
|
132
|
+
return () => {};
|
|
133
|
+
},
|
|
111
134
|
});
|
|
112
|
-
nativeSetDragImage?.(source.element, x, y);
|
|
113
135
|
},
|
|
114
136
|
onDragStart: () => {
|
|
115
137
|
document.body.removeAttribute('data-drag-preview');
|
|
116
138
|
itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'active');
|
|
139
|
+
setDragState({ type: 'is-dragging', item });
|
|
117
140
|
},
|
|
118
141
|
onDrop: () => {
|
|
119
142
|
itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'idle');
|
|
143
|
+
setDragState(idle);
|
|
120
144
|
},
|
|
121
145
|
}),
|
|
122
146
|
dropTargetForElements({
|
|
@@ -130,16 +154,22 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
130
154
|
onDragEnter: ({ self, source }) => {
|
|
131
155
|
if (source.data.type === self.data.type) {
|
|
132
156
|
setEdge(extractClosestEdge(self.data));
|
|
157
|
+
setSourceId(source.data.id as string);
|
|
133
158
|
}
|
|
134
159
|
},
|
|
135
160
|
onDrag: ({ self, source }) => {
|
|
136
161
|
if (source.data.type === self.data.type) {
|
|
137
162
|
setEdge(extractClosestEdge(self.data));
|
|
163
|
+
setSourceId(source.data.id as string);
|
|
138
164
|
}
|
|
139
165
|
},
|
|
140
|
-
onDragLeave: () =>
|
|
166
|
+
onDragLeave: () => {
|
|
167
|
+
setEdge(null);
|
|
168
|
+
setSourceId(null);
|
|
169
|
+
},
|
|
141
170
|
onDrop: ({ self, source }) => {
|
|
142
171
|
setEdge(null);
|
|
172
|
+
setSourceId(null);
|
|
143
173
|
if (source.data.type === self.data.type) {
|
|
144
174
|
onRearrange(source.data as StackItemData, self.data as StackItemData, extractClosestEdge(self.data));
|
|
145
175
|
}
|
|
@@ -150,8 +180,42 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
150
180
|
|
|
151
181
|
const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited' });
|
|
152
182
|
|
|
183
|
+
// Determine if the drop would result in any changes
|
|
184
|
+
const shouldShowDropIndicator = () => {
|
|
185
|
+
if (!closestEdge || !sourceId) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Don't show indicator when dragged item is over itself
|
|
190
|
+
if (sourceId === item.id) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Don't show indicator when dragged item is over the trailing edge of its previous sibling
|
|
195
|
+
const isTrailingEdgeOfPrevSibling =
|
|
196
|
+
prevSiblingId !== undefined &&
|
|
197
|
+
sourceId === prevSiblingId &&
|
|
198
|
+
((orientation === 'horizontal' && closestEdge === 'left') ||
|
|
199
|
+
(orientation === 'vertical' && closestEdge === 'top'));
|
|
200
|
+
if (isTrailingEdgeOfPrevSibling) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Don't show indicator when dragged item is over the leading edge of its next sibling
|
|
205
|
+
const isLeadingEdgeOfNextSibling =
|
|
206
|
+
nextSiblingId !== undefined &&
|
|
207
|
+
sourceId === nextSiblingId &&
|
|
208
|
+
((orientation === 'horizontal' && closestEdge === 'right') ||
|
|
209
|
+
(orientation === 'vertical' && closestEdge === 'bottom'));
|
|
210
|
+
if (isLeadingEdgeOfNextSibling) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return true;
|
|
215
|
+
};
|
|
216
|
+
|
|
153
217
|
return (
|
|
154
|
-
<StackItemContext.Provider value={{ selfDragHandleRef, size, setSize }}>
|
|
218
|
+
<StackItemContext.Provider value={{ selfDragHandleRef, size, setSize, state: dragState, setState: setDragState }}>
|
|
155
219
|
<Root
|
|
156
220
|
{...props}
|
|
157
221
|
tabIndex={0}
|
|
@@ -179,13 +243,24 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
|
|
|
179
243
|
ref={composedItemRef}
|
|
180
244
|
>
|
|
181
245
|
{children}
|
|
182
|
-
{
|
|
246
|
+
{shouldShowDropIndicator() && closestEdge && (
|
|
247
|
+
<ListItem.DropIndicator lineInset={8} terminalInset={-8} edge={closestEdge} />
|
|
248
|
+
)}
|
|
183
249
|
</Root>
|
|
184
250
|
</StackItemContext.Provider>
|
|
185
251
|
);
|
|
186
252
|
},
|
|
187
253
|
);
|
|
188
254
|
|
|
255
|
+
type StackItemDragPreviewProps = {
|
|
256
|
+
children: ({ item }: { item: any }) => ReactNode;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const StackItemDragPreview = ({ children }: StackItemDragPreviewProps) => {
|
|
260
|
+
const { state } = useStackItem();
|
|
261
|
+
return state?.type === 'preview' ? createPortal(children({ item: state.item }), state.container) : null;
|
|
262
|
+
};
|
|
263
|
+
|
|
189
264
|
export const StackItem = {
|
|
190
265
|
Root: StackItemRoot,
|
|
191
266
|
Content: StackItemContent,
|
|
@@ -195,6 +270,7 @@ export const StackItem = {
|
|
|
195
270
|
DragHandle: StackItemDragHandle,
|
|
196
271
|
Sigil: StackItemSigil,
|
|
197
272
|
SigilButton: StackItemSigilButton,
|
|
273
|
+
DragPreview: StackItemDragPreview,
|
|
198
274
|
};
|
|
199
275
|
|
|
200
276
|
export type {
|
|
@@ -207,4 +283,5 @@ export type {
|
|
|
207
283
|
StackItemSigilProps,
|
|
208
284
|
StackItemSigilButtonProps,
|
|
209
285
|
StackItemSigilAction,
|
|
286
|
+
StackItemDragPreviewProps,
|
|
210
287
|
};
|
|
@@ -24,8 +24,9 @@ export const StackItemHeading = ({ children, classNames, ...props }: StackItemHe
|
|
|
24
24
|
tabIndex={0}
|
|
25
25
|
{...focusableGroupAttrs}
|
|
26
26
|
className={mx(
|
|
27
|
-
'flex items-center dx-focus-ring-inset-over-all relative !border-is-0 bg-headerSurface',
|
|
27
|
+
'flex items-center dx-focus-ring-inset-over-all relative !border-is-0 bg-headerSurface border-transparent [[data-scroll-separator="true"]_&]:border-subduedSeparator',
|
|
28
28
|
orientation === 'horizontal' ? 'bs-[--rail-size]' : 'is-[--rail-size] flex-col',
|
|
29
|
+
orientation === 'horizontal' ? 'border-be' : 'border-ie',
|
|
29
30
|
classNames,
|
|
30
31
|
)}
|
|
31
32
|
>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, { Fragment, type PropsWithChildren, forwardRef,
|
|
5
|
+
import React, { Fragment, type PropsWithChildren, forwardRef, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type ActionLike } from '@dxos/app-graph';
|
|
8
8
|
import { keySymbols } from '@dxos/keyboard';
|
|
@@ -62,7 +62,6 @@ export type StackItemSigilProps = PropsWithChildren<
|
|
|
62
62
|
export const StackItemSigil = forwardRef<HTMLButtonElement, StackItemSigilProps>(
|
|
63
63
|
({ actions: actionGroups, onAction, triggerLabel, attendableId, icon, related, children }, forwardedRef) => {
|
|
64
64
|
const { t } = useTranslation(translationKey);
|
|
65
|
-
const suppressNextTooltip = useRef(false);
|
|
66
65
|
|
|
67
66
|
const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
|
|
68
67
|
|
|
@@ -87,17 +86,7 @@ export const StackItemSigil = forwardRef<HTMLButtonElement, StackItemSigilProps>
|
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
return (
|
|
90
|
-
<DropdownMenu.Root
|
|
91
|
-
{...{
|
|
92
|
-
open: optionsMenuOpen,
|
|
93
|
-
onOpenChange: (nextOpen: boolean) => {
|
|
94
|
-
if (!nextOpen) {
|
|
95
|
-
suppressNextTooltip.current = true;
|
|
96
|
-
}
|
|
97
|
-
return setOptionsMenuOpen(nextOpen);
|
|
98
|
-
},
|
|
99
|
-
}}
|
|
100
|
-
>
|
|
89
|
+
<DropdownMenu.Root open={optionsMenuOpen} onOpenChange={setOptionsMenuOpen}>
|
|
101
90
|
<DropdownMenu.Trigger asChild ref={forwardedRef}>
|
|
102
91
|
{button}
|
|
103
92
|
</DropdownMenu.Trigger>
|
|
@@ -127,7 +116,6 @@ export const StackItemSigil = forwardRef<HTMLButtonElement, StackItemSigilProps>
|
|
|
127
116
|
}
|
|
128
117
|
event.stopPropagation();
|
|
129
118
|
// TODO(thure): Why does Dialog’s modal-ness cause issues if we don’t explicitly close the menu here?
|
|
130
|
-
suppressNextTooltip.current = true;
|
|
131
119
|
setOptionsMenuOpen(false);
|
|
132
120
|
onAction?.(action);
|
|
133
121
|
}}
|
|
@@ -11,7 +11,7 @@ export class StackManager {
|
|
|
11
11
|
this._page = locator.page();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
sections() {
|
|
14
|
+
sections(): Locator {
|
|
15
15
|
return this.locator.locator('section');
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ export class StackManager {
|
|
|
19
19
|
return this.locator.locator('section').evaluateAll((els) => els.map((el) => el.getAttribute('id')));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
section(index: number) {
|
|
22
|
+
section(index: number): SectionManager {
|
|
23
23
|
return new SectionManager(this.locator.locator('section').nth(index));
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -31,21 +31,21 @@ export class SectionManager {
|
|
|
31
31
|
this._page = locator.page();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
async id() {
|
|
34
|
+
async id(): Promise<string | null> {
|
|
35
35
|
return this.locator.getAttribute('id');
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
async remove() {
|
|
38
|
+
async remove(): Promise<void> {
|
|
39
39
|
await this.locator.getByTestId('section.drag-handle-menu-trigger').click();
|
|
40
40
|
await this._page.getByTestId('section.remove').click();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async navigateTo() {
|
|
43
|
+
async navigateTo(): Promise<void> {
|
|
44
44
|
await this.locator.getByTestId('section.drag-handle-menu-trigger').click();
|
|
45
45
|
await this._page.getByTestId('section.navigate-to').click();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
async dragTo(target: Locator, offset: { x: number; y: number } = { x: 0, y: 0 }) {
|
|
48
|
+
async dragTo(target: Locator, offset: { x: number; y: number } = { x: 0, y: 0 }): Promise<void> {
|
|
49
49
|
const active = this.locator.getByTestId('section.drag-handle-menu-trigger');
|
|
50
50
|
const box = await target.boundingBox();
|
|
51
51
|
if (box) {
|