@dxos/react-ui-stack 0.7.5-main.9d2a38b → 0.7.5-main.b19bfc8

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 (46) hide show
  1. package/dist/lib/browser/index.mjs +159 -163
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +195 -198
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +159 -163
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/MenuSignifier.d.ts +2 -3
  11. package/dist/types/src/components/MenuSignifier.d.ts.map +1 -1
  12. package/dist/types/src/components/Stack.d.ts +6 -1
  13. package/dist/types/src/components/Stack.d.ts.map +1 -1
  14. package/dist/types/src/components/Stack.stories.d.ts +1 -2
  15. package/dist/types/src/components/Stack.stories.d.ts.map +1 -1
  16. package/dist/types/src/components/StackContext.d.ts +2 -1
  17. package/dist/types/src/components/StackContext.d.ts.map +1 -1
  18. package/dist/types/src/components/StackItem.d.ts +7 -5
  19. package/dist/types/src/components/StackItem.d.ts.map +1 -1
  20. package/dist/types/src/components/StackItemContent.d.ts +1 -1
  21. package/dist/types/src/components/StackItemContent.d.ts.map +1 -1
  22. package/dist/types/src/components/StackItemDragHandle.d.ts +2 -2
  23. package/dist/types/src/components/StackItemDragHandle.d.ts.map +1 -1
  24. package/dist/types/src/components/StackItemHeading.d.ts +1 -1
  25. package/dist/types/src/components/StackItemHeading.d.ts.map +1 -1
  26. package/dist/types/src/components/StackItemResizeHandle.d.ts +1 -2
  27. package/dist/types/src/components/StackItemResizeHandle.d.ts.map +1 -1
  28. package/dist/types/src/components/StackItemSigil.d.ts.map +1 -1
  29. package/dist/types/src/hooks/index.d.ts +2 -0
  30. package/dist/types/src/hooks/index.d.ts.map +1 -0
  31. package/dist/types/src/hooks/useStackDropForElements.d.ts +15 -0
  32. package/dist/types/src/hooks/useStackDropForElements.d.ts.map +1 -0
  33. package/package.json +29 -27
  34. package/src/components/Stack.stories.tsx +1 -1
  35. package/src/components/Stack.tsx +44 -57
  36. package/src/components/StackContext.tsx +3 -1
  37. package/src/components/StackItem.tsx +45 -14
  38. package/src/components/StackItemContent.tsx +2 -0
  39. package/src/components/StackItemHeading.tsx +1 -1
  40. package/src/components/StackItemResizeHandle.tsx +12 -98
  41. package/src/components/StackItemSigil.tsx +90 -103
  42. package/src/hooks/index.ts +5 -0
  43. package/src/hooks/useStackDropForElements.ts +73 -0
  44. package/dist/types/src/playwright/playwright.config.d.ts +0 -3
  45. package/dist/types/src/playwright/playwright.config.d.ts.map +0 -1
  46. /package/src/playwright/{playwright.config.ts → playwright.config.cts} +0 -0
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  export type StackItemResizeHandleProps = {};
3
- export declare const StackItemResizeHandle: () => React.JSX.Element;
2
+ export declare const StackItemResizeHandle: () => import("react/jsx-runtime").JSX.Element;
4
3
  //# sourceMappingURL=StackItemResizeHandle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StackItemResizeHandle.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemResizeHandle.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAkC,MAAM,OAAO,CAAC;AAoBvD,MAAM,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE5C,eAAO,MAAM,qBAAqB,yBAmEjC,CAAC"}
1
+ {"version":3,"file":"StackItemResizeHandle.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemResizeHandle.tsx"],"names":[],"mappings":"AAcA,MAAM,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE5C,eAAO,MAAM,qBAAqB,+CAajC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"StackItemSigil.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemSigil.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAY,KAAK,iBAAiB,EAAgC,MAAM,OAAO,CAAC;AAE9F,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAEL,KAAK,WAAW,EAMjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAgB,MAAM,0BAA0B,CAAC;AAOzF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;AAElF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,OAAO,CAAC;AAE9F,eAAO,MAAM,oBAAoB,kHAgBhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CACjD;IACE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,oBAAoB,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnD,GAAG,OAAO,CACZ,CAAC;AAEF,eAAO,MAAM,cAAc;mBARR,MAAM;kBACP,MAAM;cACV,oBAAoB,EAAE,EAAE;UAC5B,MAAM;eACD,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI;;;2CA6GpD,CAAC"}
1
+ {"version":3,"file":"StackItemSigil.d.ts","sourceRoot":"","sources":["../../../../src/components/StackItemSigil.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,EAAY,KAAK,iBAAiB,EAAgC,MAAM,OAAO,CAAC;AAE9F,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAU,KAAK,WAAW,EAAyD,MAAM,gBAAgB,CAAC;AACjH,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,OAAO,EAAgB,MAAM,0BAA0B,CAAC;AAOzF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;AAElF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,GAAG,YAAY,GAAG,OAAO,CAAC;AAE9F,eAAO,MAAM,oBAAoB,kHAgBhC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,CACjD;IACE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,oBAAoB,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnD,GAAG,OAAO,CACZ,CAAC;AAEF,eAAO,MAAM,cAAc;mBARR,MAAM;kBACP,MAAM;cACV,oBAAoB,EAAE,EAAE;UAC5B,MAAM;eACD,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI;;;2CAwGpD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { useStackDropForElements } from './useStackDropForElements';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type Orientation } from '../components/Stack';
2
+ import { type StackItemRearrangeHandler } from '../components/StackContext';
3
+ /**
4
+ * Hook to handle drag and drop functionality for Stack components.
5
+ */
6
+ export declare const useStackDropForElements: ({ element, selfDroppable, orientation, id, onRearrange, }: {
7
+ element: HTMLDivElement | null;
8
+ selfDroppable: boolean;
9
+ orientation: Orientation;
10
+ id?: string;
11
+ onRearrange?: StackItemRearrangeHandler;
12
+ }) => {
13
+ dropping: boolean;
14
+ };
15
+ //# sourceMappingURL=useStackDropForElements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStackDropForElements.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useStackDropForElements.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,KAAK,yBAAyB,EAAsB,MAAM,4BAA4B,CAAC;AAEhG;;GAEG;AACH,eAAO,MAAM,uBAAuB,8DAMjC;IACD,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,yBAAyB,CAAC;CACzC;;CA4CA,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-stack",
3
- "version": "0.7.5-main.9d2a38b",
3
+ "version": "0.7.5-main.b19bfc8",
4
4
  "description": "A stack component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
7
  "license": "MIT",
8
8
  "author": "DXOS.org",
9
9
  "sideEffects": true,
10
+ "type": "module",
10
11
  "exports": {
11
12
  ".": {
12
13
  "types": "./dist/types/src/index.d.ts",
@@ -36,22 +37,23 @@
36
37
  ],
37
38
  "dependencies": {
38
39
  "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
40
+ "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0",
39
41
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
40
42
  "@effect/schema": "^0.75.5",
41
- "@fluentui/react-tabster": "^9.19.0",
42
- "@radix-ui/primitive": "^1.0.0",
43
- "@radix-ui/react-compose-refs": "^1.0.0",
44
- "@radix-ui/react-context": "^1.0.0",
45
- "@radix-ui/react-menu": "^2.0.6",
46
- "@radix-ui/react-slot": "^1.0.1",
47
- "@radix-ui/react-use-controllable-state": "^1.0.0",
43
+ "@fluentui/react-tabster": "9.23.3",
44
+ "@radix-ui/primitive": "1.1.1",
45
+ "@radix-ui/react-compose-refs": "1.1.1",
46
+ "@radix-ui/react-context": "1.1.1",
47
+ "@radix-ui/react-menu": "2.1.6",
48
+ "@radix-ui/react-slot": "1.1.2",
49
+ "@radix-ui/react-use-controllable-state": "1.1.0",
48
50
  "react-resize-detector": "^11.0.1",
49
- "@dxos/echo-schema": "0.7.5-main.9d2a38b",
50
- "@dxos/keyboard": "0.7.5-main.9d2a38b",
51
- "@dxos/live-object": "0.7.5-main.9d2a38b",
52
- "@dxos/react-ui-attention": "0.7.5-main.9d2a38b",
53
- "@dxos/react-ui-mosaic": "0.7.5-main.9d2a38b",
54
- "@dxos/util": "0.7.5-main.9d2a38b"
51
+ "@dxos/keyboard": "0.7.5-main.b19bfc8",
52
+ "@dxos/echo-schema": "0.7.5-main.b19bfc8",
53
+ "@dxos/live-object": "0.7.5-main.b19bfc8",
54
+ "@dxos/react-ui-attention": "0.7.5-main.b19bfc8",
55
+ "@dxos/react-ui-dnd": "0.7.5-main.b19bfc8",
56
+ "@dxos/util": "0.7.5-main.b19bfc8"
55
57
  },
56
58
  "devDependencies": {
57
59
  "@phosphor-icons/react": "^2.1.5",
@@ -60,24 +62,24 @@
60
62
  "react": "~18.2.0",
61
63
  "react-dom": "~18.2.0",
62
64
  "vite": "5.4.7",
63
- "@dxos/app-graph": "0.7.5-main.9d2a38b",
64
- "@dxos/random": "0.7.5-main.9d2a38b",
65
- "@dxos/echo-schema": "0.7.5-main.9d2a38b",
66
- "@dxos/client": "0.7.5-main.9d2a38b",
67
- "@dxos/react-ui": "0.7.5-main.9d2a38b",
68
- "@dxos/react-ui-theme": "0.7.5-main.9d2a38b",
69
- "@dxos/react-ui-editor": "0.7.5-main.9d2a38b",
70
- "@dxos/storybook-utils": "0.7.5-main.9d2a38b",
71
- "@dxos/test-utils": "0.7.5-main.9d2a38b"
65
+ "@dxos/client": "0.7.5-main.b19bfc8",
66
+ "@dxos/app-graph": "0.7.5-main.b19bfc8",
67
+ "@dxos/echo-schema": "0.7.5-main.b19bfc8",
68
+ "@dxos/random": "0.7.5-main.b19bfc8",
69
+ "@dxos/react-ui": "0.7.5-main.b19bfc8",
70
+ "@dxos/react-ui-editor": "0.7.5-main.b19bfc8",
71
+ "@dxos/react-ui-theme": "0.7.5-main.b19bfc8",
72
+ "@dxos/storybook-utils": "0.7.5-main.b19bfc8",
73
+ "@dxos/test-utils": "0.7.5-main.b19bfc8"
72
74
  },
73
75
  "peerDependencies": {
74
76
  "@phosphor-icons/react": "^2.1.5",
75
77
  "react": "~18.2.0",
76
78
  "react-dom": "~18.2.0",
77
- "@dxos/client": "0.7.5-main.9d2a38b",
78
- "@dxos/random": "0.7.5-main.9d2a38b",
79
- "@dxos/react-ui": "0.7.5-main.9d2a38b",
80
- "@dxos/react-ui-theme": "0.7.5-main.9d2a38b"
79
+ "@dxos/client": "0.7.5-main.b19bfc8",
80
+ "@dxos/react-ui": "0.7.5-main.b19bfc8",
81
+ "@dxos/random": "0.7.5-main.b19bfc8",
82
+ "@dxos/react-ui-theme": "0.7.5-main.b19bfc8"
81
83
  },
82
84
  "publishConfig": {
83
85
  "access": "public"
@@ -126,7 +126,7 @@ export const Default: Story = {
126
126
  };
127
127
 
128
128
  const meta: Meta<typeof StorybookStack> = {
129
- title: 'ui/react-ui-stack-next/Stack',
129
+ title: 'ui/react-ui-stack/Stack',
130
130
  component: StorybookStack,
131
131
  decorators: [withTheme],
132
132
  argTypes: { orientation: { control: 'radio', options: ['horizontal', 'vertical'] } },
@@ -1,34 +1,34 @@
1
1
  //
2
- // Copyright 2024 DXOS.org
2
+ // Copyright 2025 DXOS.org
3
3
  //
4
- import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
5
- import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
4
+
6
5
  import { useArrowNavigationGroup } from '@fluentui/react-tabster';
7
6
  import { composeRefs } from '@radix-ui/react-compose-refs';
8
- import React, {
9
- Children,
10
- type CSSProperties,
11
- type ComponentPropsWithRef,
12
- forwardRef,
13
- useLayoutEffect,
14
- useState,
15
- } from 'react';
7
+ import React, { Children, type CSSProperties, type ComponentPropsWithRef, forwardRef, useState, useMemo } from 'react';
16
8
 
17
9
  import { type ThemedClassName, ListItem } from '@dxos/react-ui';
18
10
  import { mx } from '@dxos/react-ui-theme';
19
11
 
20
- import { type StackContextValue, StackContext, type StackItemData } from './StackContext';
12
+ import { type StackContextValue, StackContext } from './StackContext';
13
+ import { useStackDropForElements } from '../hooks';
21
14
 
22
15
  export type Orientation = 'horizontal' | 'vertical';
23
- export type Size = 'intrinsic' | 'contain';
16
+ export type Size = 'intrinsic' | 'contain' | 'contain-fit-content';
24
17
 
25
18
  export type StackProps = Omit<ThemedClassName<ComponentPropsWithRef<'div'>>, 'aria-orientation'> &
26
19
  Partial<StackContextValue> & { itemsCount?: number };
27
20
 
28
21
  export const railGridHorizontal = 'grid-rows-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
29
-
30
22
  export const railGridVertical = 'grid-cols-[[rail-start]_var(--rail-size)_[content-start]_1fr_[content-end]]';
31
23
 
24
+ // TODO(ZaymonFC): Magic 2px to stop overflow (tabster dummies... ask @thure).
25
+ export const railGridHorizontalContainFitContent =
26
+ 'grid-rows-[[rail-start]_var(--rail-size)_[content-start]_fit-content(calc(100%-var(--rail-size)*2+2px))_[content-end]]';
27
+ export const railGridVerticalContainFitContent =
28
+ 'grid-cols-[[rail-start]_var(--rail-size)_[content-start]_fit-content(calc(100%-var(--rail-size)*2+2px))_[content-end]]';
29
+
30
+ export const autoScrollRootAttributes = { 'data-drag-autoscroll': 'idle' };
31
+
32
32
  export const Stack = forwardRef<HTMLDivElement, StackProps>(
33
33
  (
34
34
  {
@@ -46,49 +46,34 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
46
46
  ) => {
47
47
  const [stackElement, stackRef] = useState<HTMLDivElement | null>(null);
48
48
  const composedItemRef = composeRefs<HTMLDivElement>(stackRef, forwardedRef);
49
- const [dropping, setDropping] = useState(false);
50
-
51
49
  const arrowNavigationGroup = useArrowNavigationGroup({ axis: orientation });
52
50
 
53
51
  const styles: CSSProperties = {
54
- [orientation === 'horizontal' ? 'gridTemplateColumns' : 'gridTemplateRows']: `repeat(${itemsCount}, min-content)`,
52
+ [orientation === 'horizontal' ? 'gridTemplateColumns' : 'gridTemplateRows']:
53
+ `repeat(${itemsCount}, min-content) [tabster-dummies] 0`,
55
54
  ...style,
56
55
  };
57
56
 
58
57
  const selfDroppable = !!(itemsCount < 1 && onRearrange && props.id);
59
58
 
60
- useLayoutEffect(() => {
61
- if (!stackElement || !selfDroppable) {
62
- return;
59
+ const { dropping } = useStackDropForElements({
60
+ id: props.id,
61
+ element: stackElement,
62
+ selfDroppable,
63
+ orientation,
64
+ onRearrange,
65
+ });
66
+
67
+ const gridClasses = useMemo(() => {
68
+ if (!rail) {
69
+ return orientation === 'horizontal' ? 'grid-rows-1 pli-1' : 'grid-cols-1 plb-1';
63
70
  }
64
- const acceptSourceType = orientation === 'horizontal' ? 'column' : 'card';
65
- return dropTargetForElements({
66
- element: stackElement,
67
- getData: ({ input, element }) => {
68
- return attachClosestEdge(
69
- { id: props.id, type: orientation === 'horizontal' ? 'card' : 'column' },
70
- { input, element, allowedEdges: [orientation === 'horizontal' ? 'left' : 'top'] },
71
- );
72
- },
73
- onDragEnter: ({ source }) => {
74
- if (source.data.type === acceptSourceType) {
75
- setDropping(true);
76
- }
77
- },
78
- onDrag: ({ source }) => {
79
- if (source.data.type === acceptSourceType) {
80
- setDropping(true);
81
- }
82
- },
83
- onDragLeave: () => setDropping(false),
84
- onDrop: ({ self, source }) => {
85
- setDropping(false);
86
- if (source.data.type === acceptSourceType && selfDroppable) {
87
- onRearrange(source.data as StackItemData, self.data as StackItemData, extractClosestEdge(self.data));
88
- }
89
- },
90
- });
91
- }, [stackElement, selfDroppable]);
71
+ if (orientation === 'horizontal') {
72
+ return size === 'contain-fit-content' ? railGridHorizontalContainFitContent : railGridHorizontal;
73
+ } else {
74
+ return size === 'contain-fit-content' ? railGridVerticalContainFitContent : railGridVertical;
75
+ }
76
+ }, [rail, orientation, size]);
92
77
 
93
78
  return (
94
79
  <StackContext.Provider value={{ orientation, rail, size, onRearrange }}>
@@ -97,25 +82,27 @@ export const Stack = forwardRef<HTMLDivElement, StackProps>(
97
82
  {...arrowNavigationGroup}
98
83
  className={mx(
99
84
  'grid relative',
100
- rail
101
- ? orientation === 'horizontal'
102
- ? railGridHorizontal
103
- : railGridVertical
104
- : orientation === 'horizontal'
105
- ? 'grid-rows-1'
106
- : 'grid-cols-1',
107
- size === 'contain' &&
85
+ gridClasses,
86
+ (size === 'contain' || size === 'contain-fit-content') &&
108
87
  (orientation === 'horizontal'
109
88
  ? 'overflow-x-auto min-bs-0 bs-full max-bs-full'
110
89
  : 'overflow-y-auto min-is-0 is-full max-is-full'),
111
90
  classNames,
112
91
  )}
92
+ data-rail={rail}
113
93
  aria-orientation={orientation}
114
94
  style={styles}
115
95
  ref={composedItemRef}
116
96
  >
117
97
  {children}
118
- {selfDroppable && dropping && <ListItem.DropIndicator edge={orientation === 'horizontal' ? 'left' : 'top'} />}
98
+ {selfDroppable && dropping && (
99
+ <ListItem.DropIndicator
100
+ lineInset={8}
101
+ terminalInset={-8}
102
+ gap={-8}
103
+ edge={orientation === 'horizontal' ? 'left' : 'top'}
104
+ />
105
+ )}
119
106
  </div>
120
107
  </StackContext.Provider>
121
108
  );
@@ -5,9 +5,11 @@
5
5
  import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
6
6
  import { createContext, useContext } from 'react';
7
7
 
8
+ import { type Size as DndSize } from '@dxos/react-ui-dnd';
9
+
8
10
  import { type Orientation, type Size } from './Stack';
9
11
 
10
- export type StackItemSize = number | 'min-content';
12
+ export type StackItemSize = DndSize;
11
13
 
12
14
  export type StackItemData = { id: string; type: 'column' | 'card' };
13
15
 
@@ -4,8 +4,8 @@
4
4
 
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
- import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
8
- import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
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';
9
9
  import {
10
10
  attachClosestEdge,
11
11
  extractClosestEdge,
@@ -16,6 +16,7 @@ import { composeRefs } from '@radix-ui/react-compose-refs';
16
16
  import React, { forwardRef, useLayoutEffect, useState, type ComponentPropsWithRef, useCallback } from 'react';
17
17
 
18
18
  import { type ThemedClassName, ListItem } from '@dxos/react-ui';
19
+ import { resizeAttributes, sizeStyle } from '@dxos/react-ui-dnd';
19
20
  import { mx } from '@dxos/react-ui-theme';
20
21
 
21
22
  import { useStack, StackItemContext, type StackItemSize, type StackItemData } from './StackContext';
@@ -46,10 +47,27 @@ export type StackItemRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> &
46
47
  size?: StackItemSize;
47
48
  onSizeChange?: (nextSize: StackItemSize) => void;
48
49
  role?: 'article' | 'section';
50
+ disableRearrange?: boolean;
51
+ focusIndicatorVariant?: 'over-all' | 'group';
49
52
  };
50
53
 
51
54
  const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
52
- ({ item, children, classNames, size: propsSize, onSizeChange, role, order, style, ...props }, forwardedRef) => {
55
+ (
56
+ {
57
+ item,
58
+ children,
59
+ classNames,
60
+ size: propsSize,
61
+ onSizeChange,
62
+ role,
63
+ order,
64
+ style,
65
+ disableRearrange,
66
+ focusIndicatorVariant = 'over-all',
67
+ ...props
68
+ },
69
+ forwardedRef,
70
+ ) => {
53
71
  const [itemElement, itemRef] = useState<HTMLDivElement | null>(null);
54
72
  const [selfDragHandleElement, selfDragHandleRef] = useState<HTMLDivElement | null>(null);
55
73
  const [closestEdge, setEdge] = useState<Edge | null>(null);
@@ -74,7 +92,7 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
74
92
  const type = orientation === 'horizontal' ? 'column' : 'card';
75
93
 
76
94
  useLayoutEffect(() => {
77
- if (!itemElement || !onRearrange) {
95
+ if (!itemElement || !onRearrange || disableRearrange) {
78
96
  return;
79
97
  }
80
98
  return combine(
@@ -82,10 +100,20 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
82
100
  element: itemElement,
83
101
  ...(selfDragHandleElement && { dragHandle: selfDragHandleElement }),
84
102
  getInitialData: () => ({ id: item.id, type }),
85
- // TODO(thure): tabster focus honeypots are causing the preview to render with the wrong dimensions; what do?
86
- onGenerateDragPreview: ({ nativeSetDragImage }) => {
87
- disableNativeDragPreview({ nativeSetDragImage });
88
- preventUnhandled.start();
103
+ onGenerateDragPreview: ({ nativeSetDragImage, source, location }) => {
104
+ document.body.setAttribute('data-drag-preview', 'true');
105
+ scrollJustEnoughIntoView({ element: source.element });
106
+ const { x, y } = preserveOffsetOnSource({ element: source.element, input: location.current.input })({
107
+ container: (source.element.offsetParent ?? document.body) as HTMLElement,
108
+ });
109
+ nativeSetDragImage?.(source.element, x, y);
110
+ },
111
+ onDragStart: () => {
112
+ document.body.removeAttribute('data-drag-preview');
113
+ itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'active');
114
+ },
115
+ onDrop: () => {
116
+ itemElement?.closest('[data-drag-autoscroll]')?.setAttribute('data-drag-autoscroll', 'idle');
89
117
  },
90
118
  }),
91
119
  dropTargetForElements({
@@ -126,17 +154,20 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
126
154
  tabIndex={0}
127
155
  {...focusGroupAttrs}
128
156
  className={mx(
129
- 'group/stack-item grid relative ch-focus-ring-inset-over-all',
130
- size === 'min-content' && (orientation === 'horizontal' ? 'is-min' : 'bs-min'),
157
+ 'group/stack-item grid relative',
158
+ focusIndicatorVariant === 'over-all'
159
+ ? 'dx-focus-ring-inset-over-all'
160
+ : orientation === 'horizontal'
161
+ ? 'dx-focus-ring-group-x'
162
+ : 'dx-focus-ring-group-y',
131
163
  orientation === 'horizontal' ? 'grid-rows-subgrid' : 'grid-cols-subgrid',
132
164
  rail && (orientation === 'horizontal' ? 'row-span-2' : 'col-span-2'),
133
165
  classNames,
134
166
  )}
135
167
  data-dx-stack-item
168
+ {...resizeAttributes}
136
169
  style={{
137
- ...(size !== 'min-content' && {
138
- [orientation === 'horizontal' ? 'inlineSize' : 'blockSize']: `${size}rem`,
139
- }),
170
+ ...sizeStyle(size, orientation),
140
171
  ...(Number.isFinite(order) && {
141
172
  [orientation === 'horizontal' ? 'gridColumn' : 'gridRow']: `${order}`,
142
173
  }),
@@ -145,7 +176,7 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
145
176
  ref={composedItemRef}
146
177
  >
147
178
  {children}
148
- {closestEdge && <ListItem.DropIndicator edge={closestEdge} />}
179
+ {closestEdge && <ListItem.DropIndicator lineInset={8} terminalInset={-8} edge={closestEdge} />}
149
180
  </Root>
150
181
  </StackItemContext.Provider>
151
182
  );
@@ -15,10 +15,12 @@ export type StackItemContentProps = ThemedClassName<ComponentPropsWithoutRef<'di
15
15
  * (`row-span-2`) or was buggy. See the description of the StackItem.Content component itself for more information.
16
16
  */
17
17
  toolbar: boolean;
18
+
18
19
  /**
19
20
  * Whether to provide for the layout of a statusbar after the content.
20
21
  */
21
22
  statusbar?: boolean;
23
+
22
24
  /**
23
25
  * Whether to set a certain aspect ratio on the content, including the toolbar and statusbar. This is provided for
24
26
  * convenience and consistency; it can instead be specified by the `classNames` or `style` props as needed.
@@ -23,7 +23,7 @@ export const StackItemHeading = ({ children, classNames, ...props }: StackItemHe
23
23
  tabIndex={0}
24
24
  {...focusableGroupAttrs}
25
25
  className={mx(
26
- 'flex items-center ch-focus-ring-inset-over-all relative !border-is-0',
26
+ 'flex items-center dx-focus-ring-inset-over-all relative !border-is-0',
27
27
  orientation === 'horizontal' ? 'bs-[--rail-size]' : 'is-[--rail-size] flex-col',
28
28
  classNames,
29
29
  )}
@@ -2,115 +2,29 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
- import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
7
- import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
8
- import { type DragLocationHistory } from '@atlaskit/pragmatic-drag-and-drop/types';
9
- import React, { useLayoutEffect, useRef } from 'react';
5
+ import React from 'react';
10
6
 
11
- import { mx } from '@dxos/react-ui-theme';
7
+ import { ResizeHandle } from '@dxos/react-ui-dnd';
12
8
 
13
- import { useStack, useStackItem, type StackItemSize } from './StackContext';
9
+ import { useStack, useStackItem } from './StackContext';
14
10
  import { DEFAULT_EXTRINSIC_SIZE } from './StackItem';
15
11
 
16
- const REM = parseFloat(getComputedStyle(document.documentElement).fontSize);
17
-
18
- const MIN_SIZE = 20;
19
-
20
- const measureStackItem = (element: HTMLButtonElement): { width: number; height: number } => {
21
- const stackItemElement = element.closest('[data-dx-stack-item]');
22
- return stackItemElement?.getBoundingClientRect() ?? { width: DEFAULT_EXTRINSIC_SIZE, height: DEFAULT_EXTRINSIC_SIZE };
23
- };
24
-
25
- const getNextSize = (startSize: number, location: DragLocationHistory, client: 'clientX' | 'clientY') => {
26
- return Math.max(MIN_SIZE, startSize + (location.current.input[client] - location.initial.input[client]) / REM);
27
- };
12
+ const MIN_WIDTH = 20;
13
+ const MIN_HEIGHT = 3;
28
14
 
29
15
  export type StackItemResizeHandleProps = {};
30
16
 
31
17
  export const StackItemResizeHandle = () => {
32
18
  const { orientation } = useStack();
33
19
  const { setSize, size } = useStackItem();
34
- const buttonRef = useRef<HTMLButtonElement>(null);
35
- const dragStartSize = useRef<StackItemSize>(size);
36
- const client = orientation === 'horizontal' ? 'clientX' : 'clientY';
37
-
38
- useLayoutEffect(
39
- () => {
40
- if (!buttonRef.current || buttonRef.current.hasAttribute('draggable')) {
41
- return;
42
- }
43
- // TODO(thure): This should handle StackItem state vs local state better.
44
- draggable({
45
- element: buttonRef.current,
46
- onGenerateDragPreview: ({ nativeSetDragImage }) => {
47
- // We will be moving the line to indicate a drag; we can disable the native drag preview.
48
- disableNativeDragPreview({ nativeSetDragImage });
49
- // We don't want any native drop animation for when the user does not drop on a drop target.
50
- // We want the drag to finish immediately.
51
- preventUnhandled.start();
52
- },
53
- onDragStart: () => {
54
- dragStartSize.current =
55
- dragStartSize.current === 'min-content'
56
- ? measureStackItem(buttonRef.current!)[orientation === 'horizontal' ? 'width' : 'height'] / REM
57
- : dragStartSize.current;
58
- },
59
- onDrag: ({ location }) => {
60
- if (typeof dragStartSize.current !== 'number') {
61
- return;
62
- }
63
- setSize(getNextSize(dragStartSize.current, location, client));
64
- },
65
- onDrop: ({ location }) => {
66
- if (typeof dragStartSize.current !== 'number') {
67
- return;
68
- }
69
- const nextSize = getNextSize(dragStartSize.current, location, client);
70
- setSize(nextSize, true);
71
- dragStartSize.current = nextSize;
72
- },
73
- });
74
- },
75
- [
76
- // Note that `size` should not be a dependency here since dragging this adjusts the size.
77
- ],
78
- );
79
-
80
- return (
81
- <button
82
- ref={buttonRef}
83
- className={mx(
84
- orientation === 'horizontal' ? 'cursor-col-resize' : 'cursor-row-resize',
85
- 'group absolute is-3 bs-full inline-end-[-1px] !border-lb-0',
86
- 'before:transition-opacity before:duration-100 before:ease-in-out before:opacity-0 hover:before:opacity-100 focus-visible:before:opacity-100 active:before:opacity-100',
87
- 'before:absolute before:block before:inset-block-0 before:inline-end-0 before:is-1 before:bg-accentFocusIndicator',
88
- )}
89
- >
90
- <div
91
- role='none'
92
- className='absolute block-start-0 inline-end-[1px] bs-[--rail-size] flex items-center group-hover:opacity-0 group-focus-visible:opacity-0 group-active:opacity-0'
93
- >
94
- <DragHandleSignifier />
95
- </div>
96
- </button>
97
- );
98
- };
99
20
 
100
- const DragHandleSignifier = () => {
101
21
  return (
102
- <svg
103
- xmlns='http://www.w3.org/2000/svg'
104
- viewBox='0 0 256 256'
105
- fill='currentColor'
106
- className='shrink-0 bs-[1em] is-[1em] text-unAccent'
107
- >
108
- {/* two pips: <path d='M256,120c-8.8,0-16-7.2-16-16v-56c0-8.8,7.2-16,16-16v88Z' />
109
- <path d='M256,232c-8.8,0-16-7.2-16-16v-56c0-8.8,7.2-16,16-16v88Z' /> */}
110
- <path d='M256,64c-8.8,0-16-7.2-16-16s7.2-16,16-16v32Z' />
111
- <path d='M256,120c-8.8,0-16-7.2-16-16s7.2-16,16-16v32Z' />
112
- <path d='M256,176c-8.8,0-16-7.2-16-16s7.2-16,16-16v32Z' />
113
- <path d='M256,232c-8.8,0-16-7.2-16-16s7.2-16,16-16v32Z' />
114
- </svg>
22
+ <ResizeHandle
23
+ side={orientation === 'horizontal' ? 'inline-end' : 'block-end'}
24
+ fallbackSize={DEFAULT_EXTRINSIC_SIZE}
25
+ minSize={orientation === 'horizontal' ? MIN_WIDTH : MIN_HEIGHT}
26
+ size={size}
27
+ onSizeChange={setSize}
28
+ />
115
29
  );
116
30
  };