@dxos/react-ui-stack 0.8.2-main.fbd8ed0 → 0.8.2-staging.42af850

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/lib/browser/index.mjs +454 -322
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/testing/index.mjs.map +3 -3
  5. package/dist/lib/node/index.cjs +452 -319
  6. package/dist/lib/node/index.cjs.map +3 -3
  7. package/dist/lib/node/meta.json +1 -1
  8. package/dist/lib/node/testing/index.cjs.map +3 -3
  9. package/dist/lib/node-esm/index.mjs +454 -322
  10. package/dist/lib/node-esm/index.mjs.map +3 -3
  11. package/dist/lib/node-esm/meta.json +1 -1
  12. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  13. package/dist/types/src/components/Stack/Stack.d.ts +2 -0
  14. package/dist/types/src/components/Stack/Stack.d.ts.map +1 -1
  15. package/dist/types/src/components/Stack/Stack.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/StackContext.d.ts +13 -0
  17. package/dist/types/src/components/StackContext.d.ts.map +1 -1
  18. package/dist/types/src/components/StackItem/StackItem.d.ts +12 -3
  19. package/dist/types/src/components/StackItem/StackItem.d.ts.map +1 -1
  20. package/dist/types/src/components/StackItem/StackItemHeading.d.ts.map +1 -1
  21. package/dist/types/src/components/StackItem/StackItemSigil.d.ts.map +1 -1
  22. package/dist/types/src/testing/stack-manager.d.ts.map +1 -1
  23. package/package.json +21 -20
  24. package/src/components/Stack/Stack.stories.tsx +14 -4
  25. package/src/components/Stack/Stack.tsx +34 -3
  26. package/src/components/StackContext.tsx +20 -0
  27. package/src/components/StackItem/StackItem.tsx +87 -10
  28. package/src/components/StackItem/StackItemHeading.tsx +2 -1
  29. package/src/components/StackItem/StackItemSigil.tsx +2 -14
  30. 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-main.fbd8ed0",
3
+ "version": "0.8.2-staging.42af850",
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/keyboard": "0.8.2-main.fbd8ed0",
51
- "@dxos/live-object": "0.8.2-main.fbd8ed0",
52
- "@dxos/react-ui-attention": "0.8.2-main.fbd8ed0",
53
- "@dxos/echo-schema": "0.8.2-main.fbd8ed0",
54
- "@dxos/react-ui-dnd": "0.8.2-main.fbd8ed0",
55
- "@dxos/util": "0.8.2-main.fbd8ed0"
51
+ "@dxos/echo-schema": "0.8.2-staging.42af850",
52
+ "@dxos/live-object": "0.8.2-staging.42af850",
53
+ "@dxos/keyboard": "0.8.2-staging.42af850",
54
+ "@dxos/react-ui-attention": "0.8.2-staging.42af850",
55
+ "@dxos/react-ui-dnd": "0.8.2-staging.42af850",
56
+ "@dxos/util": "0.8.2-staging.42af850"
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-main.fbd8ed0",
65
- "@dxos/echo-schema": "0.8.2-main.fbd8ed0",
66
- "@dxos/client": "0.8.2-main.fbd8ed0",
67
- "@dxos/random": "0.8.2-main.fbd8ed0",
68
- "@dxos/react-ui-editor": "0.8.2-main.fbd8ed0",
69
- "@dxos/react-ui": "0.8.2-main.fbd8ed0",
70
- "@dxos/react-ui-theme": "0.8.2-main.fbd8ed0",
71
- "@dxos/storybook-utils": "0.8.2-main.fbd8ed0",
72
- "@dxos/test-utils": "0.8.2-main.fbd8ed0"
65
+ "@dxos/app-graph": "0.8.2-staging.42af850",
66
+ "@dxos/client": "0.8.2-staging.42af850",
67
+ "@dxos/random": "0.8.2-staging.42af850",
68
+ "@dxos/react-ui": "0.8.2-staging.42af850",
69
+ "@dxos/react-ui-theme": "0.8.2-staging.42af850",
70
+ "@dxos/storybook-utils": "0.8.2-staging.42af850",
71
+ "@dxos/react-ui-editor": "0.8.2-staging.42af850",
72
+ "@dxos/echo-schema": "0.8.2-staging.42af850",
73
+ "@dxos/test-utils": "0.8.2-staging.42af850"
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/random": "0.8.2-main.fbd8ed0",
79
- "@dxos/react-ui": "0.8.2-main.fbd8ed0",
80
- "@dxos/client": "0.8.2-main.fbd8ed0",
81
- "@dxos/react-ui-theme": "0.8.2-main.fbd8ed0"
79
+ "@dxos/client": "0.8.2-staging.42af850",
80
+ "@dxos/random": "0.8.2-staging.42af850",
81
+ "@dxos/react-ui": "0.8.2-staging.42af850",
82
+ "@dxos/react-ui-theme": "0.8.2-staging.42af850"
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 key={column.id} item={column}>
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 key={card.id} item={card}>
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, { Children, type CSSProperties, type ComponentPropsWithRef, forwardRef, useState, useMemo } from '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> & { itemsCount?: number };
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 { scrollJustEnoughIntoView } from '@atlaskit/pragmatic-drag-and-drop/element/scroll-just-enough-into-view';
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, { forwardRef, useLayoutEffect, useState, type ComponentPropsWithRef, useCallback } from '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
- scrollJustEnoughIntoView({ element: source.element });
109
- const { x, y } = preserveOffsetOnSource({ element: source.element, input: location.current.input })({
110
- container: (source.element.offsetParent ?? document.body) as HTMLElement,
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: () => setEdge(null),
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
- {closestEdge && <ListItem.DropIndicator lineInset={8} terminalInset={-8} edge={closestEdge} />}
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, useRef, useState } from 'react';
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) {