@dxos/react-ui-stack 0.8.4-main.e098934 → 0.8.4-main.e8ec1fe

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 (59) hide show
  1. package/dist/lib/browser/{chunk-3V2YUQK5.mjs → chunk-3F2KBXLP.mjs} +208 -101
  2. package/dist/lib/browser/chunk-3F2KBXLP.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +5 -1
  4. package/dist/lib/browser/meta.json +1 -1
  5. package/dist/lib/browser/playwright/index.mjs +10 -23
  6. package/dist/lib/browser/playwright/index.mjs.map +2 -2
  7. package/dist/lib/browser/testing/index.mjs +1 -1
  8. package/dist/lib/node-esm/{chunk-HE3BRF7A.mjs → chunk-SYKFLQGK.mjs} +208 -101
  9. package/dist/lib/node-esm/chunk-SYKFLQGK.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +5 -1
  11. package/dist/lib/node-esm/meta.json +1 -1
  12. package/dist/lib/node-esm/playwright/index.mjs +10 -23
  13. package/dist/lib/node-esm/playwright/index.mjs.map +2 -2
  14. package/dist/lib/node-esm/testing/index.mjs +1 -1
  15. package/dist/types/src/components/Image/Image.d.ts +5 -2
  16. package/dist/types/src/components/Image/Image.d.ts.map +1 -1
  17. package/dist/types/src/components/Image/Image.stories.d.ts +3 -1
  18. package/dist/types/src/components/Image/Image.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/Stack/Stack.d.ts +6 -5
  20. package/dist/types/src/components/Stack/Stack.d.ts.map +1 -1
  21. package/dist/types/src/components/Stack/Stack.stories.d.ts +1 -2
  22. package/dist/types/src/components/Stack/Stack.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/StackItem/StackItem.d.ts +4 -3
  24. package/dist/types/src/components/StackItem/StackItem.d.ts.map +1 -1
  25. package/dist/types/src/components/StackItem/StackItem.stories.d.ts +0 -1
  26. package/dist/types/src/components/StackItem/StackItem.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/StackItem/StackItemContent.d.ts +20 -10
  28. package/dist/types/src/components/StackItem/StackItemContent.d.ts.map +1 -1
  29. package/dist/types/src/components/StackItem/StackItemHeading.d.ts.map +1 -1
  30. package/dist/types/src/exemplars/Card/Card.d.ts +21 -10
  31. package/dist/types/src/exemplars/Card/Card.d.ts.map +1 -1
  32. package/dist/types/src/exemplars/Card/Card.stories.d.ts +0 -23
  33. package/dist/types/src/exemplars/Card/Card.stories.d.ts.map +1 -1
  34. package/dist/types/src/exemplars/Card/fragments.d.ts +1 -1
  35. package/dist/types/src/exemplars/Card/fragments.d.ts.map +1 -1
  36. package/dist/types/src/exemplars/CardStack/CardStack.d.ts +3 -1
  37. package/dist/types/src/exemplars/CardStack/CardStack.d.ts.map +1 -1
  38. package/dist/types/src/exemplars/CardStack/CardStack.stories.d.ts +3 -1
  39. package/dist/types/src/exemplars/CardStack/CardStack.stories.d.ts.map +1 -1
  40. package/dist/types/src/hooks/useStackDropForElements.d.ts +2 -2
  41. package/dist/types/src/hooks/useStackDropForElements.d.ts.map +1 -1
  42. package/dist/types/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +27 -27
  44. package/src/components/Image/Image.stories.tsx +34 -8
  45. package/src/components/Image/Image.tsx +158 -73
  46. package/src/components/Stack/Stack.stories.tsx +2 -4
  47. package/src/components/Stack/Stack.tsx +93 -38
  48. package/src/components/StackItem/StackItem.stories.tsx +3 -5
  49. package/src/components/StackItem/StackItem.tsx +11 -4
  50. package/src/components/StackItem/StackItemContent.tsx +19 -8
  51. package/src/components/StackItem/StackItemHeading.tsx +1 -5
  52. package/src/exemplars/Card/Card.stories.tsx +1 -25
  53. package/src/exemplars/Card/Card.tsx +39 -15
  54. package/src/exemplars/Card/fragments.ts +1 -1
  55. package/src/exemplars/CardStack/CardStack.stories.tsx +5 -4
  56. package/src/exemplars/CardStack/CardStack.tsx +11 -8
  57. package/src/hooks/useStackDropForElements.ts +42 -35
  58. package/dist/lib/browser/chunk-3V2YUQK5.mjs.map +0 -7
  59. package/dist/lib/node-esm/chunk-HE3BRF7A.mjs.map +0 -7
@@ -2,19 +2,17 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
10
8
  import { DropdownMenu, Icon } from '@dxos/react-ui';
11
- import { withTheme } from '@dxos/storybook-utils';
9
+ import { withTheme } from '@dxos/react-ui/testing';
12
10
 
13
11
  import { StackItem, type StackItemRootProps } from './StackItem';
14
12
 
15
13
  const DefaultStory = (props: StackItemRootProps) => {
16
14
  return (
17
- <StackItem.Root role='section' {...props} classNames='w-[20rem] border border-separator'>
15
+ <StackItem.Root role='section' {...props} classNames='is-[20rem] border border-separator'>
18
16
  <StackItem.Heading>
19
17
  <span className='sr-only'>Title</span>
20
18
  <div role='none' className='sticky -block-start-px bg-[--sticky-bg] p-1 is-full'>
@@ -27,7 +25,7 @@ const DefaultStory = (props: StackItemRootProps) => {
27
25
  </DropdownMenu.Root>
28
26
  </div>
29
27
  </StackItem.Heading>
30
- <StackItem.Content classNames='p-2'>Content</StackItem.Content>
28
+ <StackItem.Content>Content</StackItem.Content>
31
29
  </StackItem.Root>
32
30
  );
33
31
  };
@@ -63,7 +63,7 @@ type StackItemRootProps = ThemedClassName<ComponentPropsWithRef<'div'>> & {
63
63
  onSizeChange?: (nextSize: StackItemSize) => void;
64
64
  role?: 'article' | 'section';
65
65
  disableRearrange?: boolean;
66
- focusIndicatorVariant?: 'over-all' | 'group';
66
+ focusIndicatorVariant?: 'over-all' | 'group' | 'over-all-always' | 'group-always';
67
67
  };
68
68
 
69
69
  const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
@@ -232,15 +232,22 @@ const StackItemRoot = forwardRef<HTMLDivElement, StackItemRootProps>(
232
232
  'group/stack-item grid relative',
233
233
  focusIndicatorVariant === 'over-all'
234
234
  ? 'dx-focus-ring-inset-over-all'
235
- : orientation === 'horizontal'
236
- ? 'dx-focus-ring-group-x'
237
- : 'dx-focus-ring-group-y',
235
+ : focusIndicatorVariant === 'over-all-always'
236
+ ? 'dx-focus-ring-inset-over-all-always'
237
+ : orientation === 'horizontal'
238
+ ? focusIndicatorVariant === 'group-always'
239
+ ? 'dx-focus-ring-group-x-always'
240
+ : 'dx-focus-ring-group-x'
241
+ : focusIndicatorVariant === 'group-always'
242
+ ? 'dx-focus-ring-group-y-always'
243
+ : 'dx-focus-ring-group-y',
238
244
  orientation === 'horizontal' ? 'grid-rows-subgrid' : 'grid-cols-subgrid',
239
245
  rail && (orientation === 'horizontal' ? 'row-span-2' : 'col-span-2'),
240
246
  role === 'section' && orientation !== 'horizontal' && 'border-be border-subduedSeparator',
241
247
  classNames,
242
248
  )}
243
249
  data-dx-stack-item={stackId}
250
+ data-dx-item-id={item.id}
244
251
  {...resizeAttributes}
245
252
  style={{
246
253
  ...(stackSize !== 'split' && sizeStyle(size, orientation)),
@@ -9,7 +9,8 @@ import { mx } from '@dxos/react-ui-theme';
9
9
 
10
10
  import { useStack, useStackItem } from '../StackContext';
11
11
 
12
- export type StackItemContentProps = ThemedClassName<Omit<ComponentPropsWithoutRef<'div'>, 'role'>> & {
12
+ // TODO(burdon): Add prop for container-max-width?
13
+ export type StackItemContentProps = ThemedClassName<Omit<ComponentPropsWithoutRef<'div'>, 'role' | 'scrollable'>> & {
13
14
  /**
14
15
  * This flag is required in order to clarify a developer experience that seemed like it needed extra boilerplate
15
16
  * (`row-span-2`) or was buggy. See the description of the StackItem.Content component itself for more information.
@@ -22,15 +23,21 @@ export type StackItemContentProps = ThemedClassName<Omit<ComponentPropsWithoutRe
22
23
  statusbar?: boolean;
23
24
 
24
25
  /**
25
- * Whether the consumer intends to do something custom and typical affordances should not apply
26
+ * Whether to support y-axis scrolling.
26
27
  */
27
- layoutManaged?: boolean;
28
+ scrollable?: boolean;
28
29
 
29
30
  /**
30
- * Whether to set a certain aspect ratio on the content, including the toolbar and statusbar. This is provided for
31
- * convenience and consistency; it can instead be specified by the `classNames` or `style` props as needed.
31
+ * Whether to set a certain aspect ratio on the content, including the toolbar and statusbar.
32
+ * This is provided for convenience and consistency; it can instead be specified by the `classNames` or `style` props as needed.
32
33
  */
33
34
  size?: 'intrinsic' | 'video' | 'square';
35
+
36
+ /**
37
+ * Whether the consumer intends to do something custom and typical affordances should not apply.
38
+ * @deprecated Replace with override for gridTempateRows.
39
+ */
40
+ layoutManaged?: boolean;
34
41
  };
35
42
 
36
43
  /**
@@ -38,7 +45,10 @@ export type StackItemContentProps = ThemedClassName<Omit<ComponentPropsWithoutRe
38
45
  * The `toolbar` flag must be provided since this component provides for the layout of content with the toolbar.
39
46
  */
40
47
  export const StackItemContent = forwardRef<HTMLDivElement, StackItemContentProps>(
41
- ({ children, toolbar, statusbar, layoutManaged, classNames, size = 'intrinsic', ...props }, forwardedRef) => {
48
+ (
49
+ { children, toolbar, statusbar, layoutManaged, classNames, size = 'intrinsic', scrollable, ...props },
50
+ forwardedRef,
51
+ ) => {
42
52
  const { size: stackItemSize } = useStack();
43
53
  const { role } = useStackItem();
44
54
  const style = useMemo(
@@ -61,12 +71,13 @@ export const StackItemContent = forwardRef<HTMLDivElement, StackItemContentProps
61
71
  {...props}
62
72
  className={mx(
63
73
  'group grid grid-cols-[100%] density-coarse',
64
- stackItemSize === 'contain' && 'min-bs-0 overflow-hidden',
65
74
  size === 'video' ? 'aspect-video' : size === 'square' && 'aspect-square',
66
- toolbar && '[&>.dx-toolbar]:relative [&>.dx-toolbar]:border-be [&>.dx-toolbar]:border-subduedSeparator',
75
+ stackItemSize === 'contain' && 'min-bs-0 overflow-hidden',
76
+ scrollable ? 'min-bs-0 overflow-y-auto scrollbar-thin contain-layout' : 'overflow-hidden',
67
77
  role === 'section' &&
68
78
  toolbar &&
69
79
  '[&_.dx-toolbar]:sticky [&_.dx-toolbar]:z-[1] [&_.dx-toolbar]:block-start-0 [&_.dx-toolbar]:-mbe-px [&_.dx-toolbar]:min-is-0',
80
+ toolbar && '[&>.dx-toolbar]:relative [&>.dx-toolbar]:border-be [&>.dx-toolbar]:border-subduedSeparator',
70
81
  classNames,
71
82
  )}
72
83
  style={style}
@@ -2,7 +2,6 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useFocusableGroup } from '@fluentui/react-tabster';
6
5
  import { Slot } from '@radix-ui/react-slot';
7
6
  import React, {
8
7
  type ComponentPropsWithRef,
@@ -30,7 +29,6 @@ export const StackItemHeading = ({
30
29
  ...props
31
30
  }: StackItemHeadingProps) => {
32
31
  const { orientation } = useStack();
33
- const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited' });
34
32
 
35
33
  const Root = asChild ? Slot : 'div';
36
34
 
@@ -38,10 +36,8 @@ export const StackItemHeading = ({
38
36
  <Root
39
37
  role='heading'
40
38
  {...props}
41
- tabIndex={0}
42
- {...focusableGroupAttrs}
43
39
  className={mx(
44
- 'flex items-center dx-focus-ring-inset-over-all relative !border-is-0 bg-headerSurface',
40
+ 'flex items-center !border-is-0 bg-headerSurface',
45
41
  separateOnScroll
46
42
  ? 'border-transparent [[data-scroll-separator="true"]_&]:border-subduedSeparator'
47
43
  : 'border-subduedSeparator',
@@ -2,13 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
10
8
  import { faker } from '@dxos/random';
11
- import { withTheme } from '@dxos/storybook-utils';
9
+ import { withTheme } from '@dxos/react-ui/testing';
12
10
 
13
11
  import { Card } from './Card';
14
12
 
@@ -47,28 +45,6 @@ const meta = {
47
45
  parameters: {
48
46
  layout: 'centered',
49
47
  },
50
- argTypes: {
51
- title: {
52
- control: 'text',
53
- description: 'Card title',
54
- },
55
- description: {
56
- control: 'text',
57
- description: 'Card description',
58
- },
59
- image: {
60
- control: 'text',
61
- description: 'URL for the poster image',
62
- },
63
- showImage: {
64
- control: 'boolean',
65
- description: 'Whether to show the image',
66
- },
67
- showIcon: {
68
- control: 'boolean',
69
- description: 'Whether to show an icon (when image is not shown)',
70
- },
71
- },
72
48
  } satisfies Meta<typeof DefaultStory>;
73
49
 
74
50
  export default meta;
@@ -13,21 +13,31 @@ import React, {
13
13
  } from 'react';
14
14
 
15
15
  import { Icon, IconButton, type ThemedClassName, Toolbar, type ToolbarRootProps, useTranslation } from '@dxos/react-ui';
16
- import { hoverableControls, mx } from '@dxos/react-ui-theme';
16
+ import { cardMinInlineSize, hoverableControls, mx } from '@dxos/react-ui-theme';
17
17
 
18
18
  import { Image, StackItem } from '../../components';
19
19
  import { translationKey } from '../../translations';
20
20
 
21
21
  import { cardChrome, cardHeading, cardRoot, cardSpacing, cardText } from './fragments';
22
22
 
23
+ /**
24
+ * The default width of cards. It should be no larger than 320px per WCAG 2.1 SC 1.4.10.
25
+ */
26
+ const cardDefaultInlineSize = cardMinInlineSize;
27
+
28
+ /**
29
+ * This is `cardDefaultInlineSize` plus 2 times the sum of the inner and outer spacing applied by CardStack on the inline axis.
30
+ */
31
+ const cardStackDefaultInlineSizeRem = cardDefaultInlineSize + 2.125;
32
+
23
33
  type SharedCardProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> & { asChild?: boolean };
24
34
 
25
- const CardStaticRoot = forwardRef<HTMLDivElement, SharedCardProps>(
26
- ({ children, classNames, asChild, role = 'group', ...props }, forwardedRef) => {
35
+ const CardStaticRoot = forwardRef<HTMLDivElement, SharedCardProps & { id?: string }>(
36
+ ({ children, classNames, id, asChild, role = 'group', ...props }, forwardedRef) => {
27
37
  const Root = asChild ? Slot : 'div';
28
38
  const rootProps = asChild ? { classNames: [cardRoot, classNames] } : { className: mx(cardRoot, classNames), role };
29
39
  return (
30
- <Root {...props} {...rootProps} ref={forwardedRef}>
40
+ <Root {...(id && { 'data-object-id': id })} {...props} {...rootProps} ref={forwardedRef}>
31
41
  {children}
32
42
  </Root>
33
43
  );
@@ -35,18 +45,19 @@ const CardStaticRoot = forwardRef<HTMLDivElement, SharedCardProps>(
35
45
  );
36
46
 
37
47
  /**
38
- * This should be used by Surface fulfillments in cases where the content may or may not already be encapsulated (e.g.
39
- * in a Popover) and knows this based on the `role` it receives. This will render a `Card.StaticRoot` by default, otherwise
40
- * it will render a `div` primitive with the appropriate styling for specific handled situations.
48
+ * This should be used by Surface fulfillments in cases where the content may or may not already be encapsulated (e.g., in a Popover) and knows this based on the `role` it receives.
49
+ * This will render a `Card.StaticRoot` by default, otherwise it will render a `div` primitive with the appropriate styling for specific handled situations.
41
50
  */
42
51
  const CardSurfaceRoot = ({
52
+ id,
43
53
  role = 'never',
44
54
  children,
45
55
  classNames,
46
- }: ThemedClassName<PropsWithChildren<{ role?: string }>>) => {
56
+ }: ThemedClassName<PropsWithChildren<{ id?: string; role?: string }>>) => {
47
57
  if (['card--popover', 'card--intrinsic', 'card--extrinsic'].includes(role)) {
48
58
  return (
49
59
  <div
60
+ {...(id && { 'data-object-id': id })}
50
61
  className={mx(
51
62
  role === 'card--popover'
52
63
  ? 'popover-card-width'
@@ -62,6 +73,7 @@ const CardSurfaceRoot = ({
62
73
  } else {
63
74
  return (
64
75
  <CardStaticRoot
76
+ id={id}
65
77
  classNames={[
66
78
  role === 'card--transclusion' && 'mlb-1',
67
79
  role === 'card--transclusion' && hoverableControls,
@@ -118,22 +130,26 @@ const CardDragPreview = StackItem.DragPreview;
118
130
 
119
131
  const CardMenu = Primitive.div as FC<ComponentPropsWithRef<'div'>>;
120
132
 
121
- type CardPosterProps = {
122
- alt: string;
123
- aspect?: 'video' | 'auto';
124
- } & Partial<{ image: string; icon: string }>;
133
+ type CardPosterProps = ThemedClassName<
134
+ {
135
+ alt: string;
136
+ aspect?: 'video' | 'auto';
137
+ } & Partial<{ image: string; icon: string }>
138
+ >;
125
139
 
126
140
  const CardPoster = (props: CardPosterProps) => {
127
141
  const aspect = props.aspect === 'auto' ? 'aspect-auto' : 'aspect-video';
128
142
  if (props.image) {
129
- return <Image classNames={[`dx-card__poster is-full __bs-auto`, aspect]} src={props.image} alt={props.alt} />;
143
+ return (
144
+ <Image classNames={[`dx-card__poster is-full`, aspect, props.classNames]} src={props.image} alt={props.alt} />
145
+ );
130
146
  }
131
147
 
132
148
  if (props.icon) {
133
149
  return (
134
150
  <div
135
151
  role='image'
136
- className={mx(`dx-card__poster grid place-items-center bg-inputSurface text-subdued`, aspect)}
152
+ className={mx(`dx-card__poster grid place-items-center bg-inputSurface text-subdued`, aspect, props.classNames)}
137
153
  aria-label={props.alt}
138
154
  >
139
155
  <Icon icon={props.icon} size={10} />
@@ -183,4 +199,12 @@ export const Card = {
183
199
  Text: CardText,
184
200
  };
185
201
 
186
- export { cardRoot, cardHeading, cardText, cardChrome, cardSpacing };
202
+ export {
203
+ cardRoot,
204
+ cardHeading,
205
+ cardText,
206
+ cardChrome,
207
+ cardSpacing,
208
+ cardStackDefaultInlineSizeRem,
209
+ cardDefaultInlineSize,
210
+ };
@@ -21,4 +21,4 @@ export const cardText = cardSpacing;
21
21
  export const cardHeading = 'text-lg font-medium line-clamp-2 grow';
22
22
 
23
23
  export const cardChrome =
24
- 'pli-[--dx-cardSpacingChrome] mlb-[--dx-cardSpacingChrome] [&_.dx-button]:text-start [&_.dx-button]:is-full';
24
+ 'pli-[--dx-cardSpacingChrome] mlb-[--dx-cardSpacingChrome] [&_.dx-button]:text-start [&_.dx-button]:is-full [&_.dx-button]:pis-[calc(var(--dx-cardSpacingInline)-var(--dx-cardSpacingChrome))]';
@@ -2,15 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
8
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
9
7
  import React, { useCallback, useState } from 'react';
10
8
 
11
9
  import { faker } from '@dxos/random';
12
10
  import { IconButton } from '@dxos/react-ui';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
11
+ import { withTheme } from '@dxos/react-ui/testing';
14
12
 
15
13
  import { StackItem } from '../../components';
16
14
  import { Card, CardDragPreview } from '../Card';
@@ -162,7 +160,10 @@ const CardStackStory = () => {
162
160
  const meta = {
163
161
  title: 'ui/react-ui-stack/CardStack',
164
162
  component: CardStackStory,
165
- decorators: [withTheme, withLayout({ fullscreen: true })],
163
+ decorators: [withTheme],
164
+ parameters: {
165
+ layout: 'fullscreen',
166
+ },
166
167
  } satisfies Meta<typeof CardStackStory>;
167
168
 
168
169
  export default meta;
@@ -72,17 +72,20 @@ const CardStackFooter = forwardRef<HTMLDivElement, SharedCardStackProps>(
72
72
  },
73
73
  );
74
74
 
75
- const cardStackContent = [
76
- 'shrink min-bs-0 bg-baseSurface border border-separator rounded-md grid dx-focus-ring-group-x-indicator kanban-drop',
77
- railGridHorizontalContainFitContent,
78
- ];
75
+ const cardStackContent =
76
+ 'shrink min-bs-0 bg-baseSurface border border-separator rounded-md grid dx-focus-ring-group-x-indicator kanban-drop';
79
77
 
80
- const CardStackContent = forwardRef<HTMLDivElement, SharedCardStackProps>(
81
- ({ children, classNames, asChild, role = 'none', ...props }, forwardedRef) => {
78
+ type CardStackContentProps = SharedCardStackProps & {
79
+ footer?: boolean;
80
+ };
81
+
82
+ const CardStackContent = forwardRef<HTMLDivElement, CardStackContentProps>(
83
+ ({ children, classNames, asChild, role = 'none', footer = true, ...props }, forwardedRef) => {
82
84
  const Root = asChild ? Slot : 'div';
85
+ const baseClassNames = footer ? [cardStackContent, railGridHorizontalContainFitContent] : [cardStackContent];
83
86
  const rootProps = asChild
84
- ? { classNames: [...cardStackContent, classNames] }
85
- : { className: mx(...cardStackContent, classNames), role };
87
+ ? { classNames: [...baseClassNames, classNames] }
88
+ : { className: mx(...baseClassNames, classNames), role };
86
89
  return (
87
90
  <Root {...props} {...rootProps} data-scroll-separator='false' ref={forwardedRef}>
88
91
  {children}
@@ -10,6 +10,8 @@ import { useLayoutEffect, useState } from 'react';
10
10
 
11
11
  import { type Orientation, type StackItemData, type StackItemRearrangeHandler } from '../components';
12
12
 
13
+ const noop = () => {};
14
+
13
15
  /**
14
16
  * Hook to handle drag and drop functionality for Stack components.
15
17
  */
@@ -17,59 +19,64 @@ export const useStackDropForElements = ({
17
19
  id,
18
20
  element,
19
21
  scrollElement = element,
20
- selfDroppable,
21
22
  orientation,
23
+ selfDroppable,
22
24
  onRearrange,
23
25
  }: {
24
26
  id?: string;
25
27
  element: HTMLDivElement | null;
26
28
  scrollElement?: HTMLDivElement | null;
27
- selfDroppable: boolean;
28
29
  orientation: Orientation;
30
+ selfDroppable: boolean;
29
31
  onRearrange?: StackItemRearrangeHandler;
30
32
  }) => {
31
33
  const [dropping, setDropping] = useState(false);
32
34
 
33
35
  useLayoutEffect(() => {
34
- if (!element || !selfDroppable) {
36
+ if (!element) {
35
37
  return;
36
38
  }
37
39
 
38
40
  const acceptSourceType = orientation === 'horizontal' ? 'column' : 'card';
39
41
 
40
42
  return combine(
41
- dropTargetForElements({
42
- element,
43
- getData: ({ input, element }) => {
44
- return attachClosestEdge(
45
- { id, type: orientation === 'horizontal' ? 'card' : 'column' },
46
- { input, element, allowedEdges: [orientation === 'horizontal' ? 'left' : 'top'] },
47
- );
48
- },
49
- onDragEnter: ({ source }) => {
50
- if (source.data.type === acceptSourceType) {
51
- setDropping(true);
52
- }
53
- },
54
- onDrag: ({ source }) => {
55
- if (source.data.type === acceptSourceType) {
56
- setDropping(true);
57
- }
58
- },
59
- onDragLeave: () => {
60
- return setDropping(false);
61
- },
62
- onDrop: ({ self, source }) => {
63
- setDropping(false);
64
- if (source.data.type === acceptSourceType && selfDroppable && onRearrange) {
65
- onRearrange(source.data as StackItemData, self.data as StackItemData, extractClosestEdge(self.data));
66
- }
67
- },
68
- }),
69
- autoScrollForElements({
70
- element: scrollElement as Element,
71
- getAllowedAxis: () => orientation,
72
- }),
43
+ selfDroppable
44
+ ? dropTargetForElements({
45
+ element,
46
+ getData: ({ input, element }) => {
47
+ return attachClosestEdge(
48
+ { id, type: orientation === 'horizontal' ? 'card' : 'column' },
49
+ { input, element, allowedEdges: [orientation === 'horizontal' ? 'left' : 'top'] },
50
+ );
51
+ },
52
+ onDragEnter: ({ source }) => {
53
+ if (source.data.type === acceptSourceType) {
54
+ setDropping(true);
55
+ }
56
+ },
57
+ onDrag: ({ source }) => {
58
+ if (source.data.type === acceptSourceType) {
59
+ setDropping(true);
60
+ }
61
+ },
62
+ onDragLeave: () => {
63
+ return setDropping(false);
64
+ },
65
+ onDrop: ({ self, source }) => {
66
+ setDropping(false);
67
+ if (source.data.type === acceptSourceType && selfDroppable && onRearrange) {
68
+ onRearrange(source.data as StackItemData, self.data as StackItemData, extractClosestEdge(self.data));
69
+ }
70
+ },
71
+ })
72
+ : noop,
73
+
74
+ scrollElement
75
+ ? autoScrollForElements({
76
+ element: scrollElement,
77
+ getAllowedAxis: () => orientation,
78
+ })
79
+ : noop,
73
80
  );
74
81
  }, [element, scrollElement, selfDroppable, orientation, id, onRearrange]);
75
82