From 58e3998d5d0cef924affb93864df7962164be2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20Schr=C3=B6ter?= Date: Fri, 21 Feb 2025 12:19:25 +0100 Subject: [PATCH 1/3] fix: forward defaultid to dnd hooks --- packages/@react-aria/dnd/src/useDroppableCollection.ts | 5 +++-- packages/@react-spectrum/list/src/ListView.tsx | 1 + packages/@react-spectrum/table/src/TableViewBase.tsx | 1 + packages/@react-types/table/src/index.d.ts | 4 ++-- packages/react-aria-components/src/GridList.tsx | 1 + packages/react-aria-components/src/ListBox.tsx | 1 + packages/react-aria-components/src/Table.tsx | 2 +- 7 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/@react-aria/dnd/src/useDroppableCollection.ts b/packages/@react-aria/dnd/src/useDroppableCollection.ts index 420d924c7c5..e12ce363125 100644 --- a/packages/@react-aria/dnd/src/useDroppableCollection.ts +++ b/packages/@react-aria/dnd/src/useDroppableCollection.ts @@ -21,6 +21,7 @@ import { } from './utils'; import { Collection, + DOMProps, DropEvent, DropOperation, DroppableCollectionDropEvent, @@ -42,7 +43,7 @@ import {useAutoScroll} from './useAutoScroll'; import {useDrop} from './useDrop'; import {useLocale} from '@react-aria/i18n'; -export interface DroppableCollectionOptions extends DroppableCollectionProps { +export interface DroppableCollectionOptions extends DOMProps, DroppableCollectionProps { /** A delegate object that implements behavior for keyboard focus movement. */ keyboardDelegate: KeyboardDelegate, /** A delegate object that provides drop targets for pointer coordinates within the collection. */ @@ -745,7 +746,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: }); }, [localState, ref, onDrop, direction]); - let id = useId(); + let id = useId(props.id); droppableCollectionMap.set(state, {id, ref}); return { collectionProps: mergeProps(dropProps, { diff --git a/packages/@react-spectrum/list/src/ListView.tsx b/packages/@react-spectrum/list/src/ListView.tsx index 3072a20e745..068d47e5b1c 100644 --- a/packages/@react-spectrum/list/src/ListView.tsx +++ b/packages/@react-spectrum/list/src/ListView.tsx @@ -167,6 +167,7 @@ export const ListView = React.forwardRef(function ListView(pro selectionManager }); droppableCollection = dragAndDropHooks.useDroppableCollection!({ + id: props.id, keyboardDelegate: new ListKeyboardDelegate({ collection, disabledKeys: dragState?.draggingKeys.size ? undefined : selectionManager.disabledKeys, diff --git a/packages/@react-spectrum/table/src/TableViewBase.tsx b/packages/@react-spectrum/table/src/TableViewBase.tsx index 7fa21fba707..c7cf7185a58 100644 --- a/packages/@react-spectrum/table/src/TableViewBase.tsx +++ b/packages/@react-spectrum/table/src/TableViewBase.tsx @@ -226,6 +226,7 @@ function TableViewBase(props: TableBaseProps, ref: DOMRef extends MultipleSelection, Sortable { +export interface TableProps extends DOMProps, MultipleSelection, Sortable { /** The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows. */ children: [ReactElement>, ReactElement>], /** A list of row keys to disable. */ @@ -35,7 +35,7 @@ export interface TableProps extends MultipleSelection, Sortable { /** * @deprecated - use SpectrumTableProps from '@adobe/react-spectrum' instead. */ -export interface SpectrumTableProps extends TableProps, SpectrumSelectionProps, DOMProps, AriaLabelingProps, StyleProps { +export interface SpectrumTableProps extends TableProps, SpectrumSelectionProps, AriaLabelingProps, StyleProps { /** * Sets the amount of vertical padding within each cell. * @default 'regular' diff --git a/packages/react-aria-components/src/GridList.tsx b/packages/react-aria-components/src/GridList.tsx index 18f76a2b36c..c5d8693301b 100644 --- a/packages/react-aria-components/src/GridList.tsx +++ b/packages/react-aria-components/src/GridList.tsx @@ -182,6 +182,7 @@ function GridListInner({props, collection, gridListRef: ref}: }); let dropTargetDelegate = dragAndDropHooks.dropTargetDelegate || ctxDropTargetDelegate || new dragAndDropHooks.ListDropTargetDelegate(collection, ref, {layout, direction}); droppableCollection = dragAndDropHooks.useDroppableCollection!({ + id: props.id, keyboardDelegate, dropTargetDelegate }, dropState, ref); diff --git a/packages/react-aria-components/src/ListBox.tsx b/packages/react-aria-components/src/ListBox.tsx index b3b02a52c79..55f045aaff0 100644 --- a/packages/react-aria-components/src/ListBox.tsx +++ b/packages/react-aria-components/src/ListBox.tsx @@ -192,6 +192,7 @@ function ListBoxInner({state: inputState, props, listBoxRef}: let dropTargetDelegate = dragAndDropHooks.dropTargetDelegate || ctxDropTargetDelegate || new dragAndDropHooks.ListDropTargetDelegate(collection, listBoxRef, {orientation, layout, direction}); droppableCollection = dragAndDropHooks.useDroppableCollection!({ + id: props.id, keyboardDelegate, dropTargetDelegate }, dropState, listBoxRef); diff --git a/packages/react-aria-components/src/Table.tsx b/packages/react-aria-components/src/Table.tsx index fc93cb493e8..5af246ae70a 100644 --- a/packages/react-aria-components/src/Table.tsx +++ b/packages/react-aria-components/src/Table.tsx @@ -365,7 +365,6 @@ interface TableInnerProps { collection: ITableCollection> } - function TableInner({props, forwardedRef: ref, selectionState, collection}: TableInnerProps) { let tableContainerContext = useContext(ResizableTableContainerContext); ref = useObjectRef(useMemo(() => mergeRefs(ref, tableContainerContext?.tableRef), [ref, tableContainerContext?.tableRef])); @@ -433,6 +432,7 @@ function TableInner({props, forwardedRef: ref, selectionState, collection}: Tabl }); let dropTargetDelegate = dragAndDropHooks.dropTargetDelegate || ctxDropTargetDelegate || new dragAndDropHooks.ListDropTargetDelegate(collection.rows, ref); droppableCollection = dragAndDropHooks.useDroppableCollection!({ + id: props.id, keyboardDelegate, dropTargetDelegate }, dropState, ref); From 9812548d1c8f833d50b6c2b170012ad262446404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20Schr=C3=B6ter?= Date: Fri, 21 Feb 2025 20:17:40 +0100 Subject: [PATCH 2/3] fix: disabled keys in dnd keyboard delegate --- .../dnd/src/useDroppableCollection.ts | 19 +++++++++++++------ .../dnd/src/useDroppableCollectionState.ts | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/@react-aria/dnd/src/useDroppableCollection.ts b/packages/@react-aria/dnd/src/useDroppableCollection.ts index e12ce363125..ad8371b9445 100644 --- a/packages/@react-aria/dnd/src/useDroppableCollection.ts +++ b/packages/@react-aria/dnd/src/useDroppableCollection.ts @@ -380,10 +380,11 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: // first try the other positions in the current key. Otherwise (e.g. in a grid layout), // jump to the same drop position in the new key. let nextCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyBefore(target.key) : localState.state.collection.getKeyAfter(target.key); - if (nextKey == null || nextKey === nextCollectionKey) { + let isNextDisabled = nextCollectionKey && localState.state.selectionManager.isDisabled(nextCollectionKey); + if (nextKey == null || nextKey === nextCollectionKey || isNextDisabled) { let positionIndex = dropPositions.indexOf(target.dropPosition); let nextDropPosition = dropPositions[positionIndex + 1]; - if (positionIndex < dropPositions.length - 1 && !(nextDropPosition === dropPositions[2] && nextKey != null)) { + if (positionIndex < dropPositions.length - 1 && (!(nextDropPosition === dropPositions[2] && nextKey != null) || isNextDisabled)) { return { type: 'item', key: target.key, @@ -393,7 +394,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: // If the last drop position was 'after', then 'before' on the next key is equivalent. // Switch to 'on' instead. - if (target.dropPosition === dropPositions[2]) { + if (target.dropPosition === dropPositions[2] && !isNextDisabled) { dropPosition = 'on'; } } else { @@ -434,10 +435,12 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: // first try the other positions in the current key. Otherwise (e.g. in a grid layout), // jump to the same drop position in the new key. let prevCollectionKey = horizontal && direction === 'rtl' ? localState.state.collection.getKeyAfter(target.key) : localState.state.collection.getKeyBefore(target.key); - if (nextKey == null || nextKey === prevCollectionKey) { + let isPrevDisabled = prevCollectionKey && localState.state.selectionManager.isDisabled(prevCollectionKey); + if (nextKey == null || nextKey === prevCollectionKey || isPrevDisabled) { let positionIndex = dropPositions.indexOf(target.dropPosition); let nextDropPosition = dropPositions[positionIndex - 1]; - if (positionIndex > 0 && nextDropPosition !== dropPositions[2]) { + console.log(target.key, target.dropPosition, isPrevDisabled); + if (positionIndex > 0 && (nextDropPosition !== dropPositions[2] || isPrevDisabled)) { return { type: 'item', key: target.key, @@ -445,9 +448,13 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: }; } + if (isPrevDisabled) { + nextKey = prevCollectionKey; + } + // If the last drop position was 'before', then 'after' on the previous key is equivalent. // Switch to 'on' instead. - if (target.dropPosition === dropPositions[0]) { + if (target.dropPosition === dropPositions[0] && nextKey !== prevCollectionKey) { dropPosition = 'on'; } } else { diff --git a/packages/@react-stately/dnd/src/useDroppableCollectionState.ts b/packages/@react-stately/dnd/src/useDroppableCollectionState.ts index 688c7315bb8..862571ba68e 100644 --- a/packages/@react-stately/dnd/src/useDroppableCollectionState.ts +++ b/packages/@react-stately/dnd/src/useDroppableCollectionState.ts @@ -99,7 +99,7 @@ export function useDroppableCollectionState(props: DroppableCollectionStateOptio // Feedback was that internal root drop was weird so preventing that from happening let isValidRootDrop = onRootDrop && target.type === 'root' && !isInternal; // Automatically prevent items (i.e. folders) from being dropped on themselves. - let isValidOnItemDrop = onItemDrop && target.type === 'item' && target.dropPosition === 'on' && !(isInternal && target.key != null && draggingKeys.has(target.key)) && (!shouldAcceptItemDrop || shouldAcceptItemDrop(target, types)); + let isValidOnItemDrop = onItemDrop && target.type === 'item' && target.dropPosition === 'on' && !(isInternal && target.key != null && draggingKeys.has(target.key)) && (!shouldAcceptItemDrop || shouldAcceptItemDrop(target, types)) && !selectionManager.isDisabled(target.key); if (onDrop || isValidInsert || isValidReorder || isValidRootDrop || isValidOnItemDrop) { if (getDropOperation) { @@ -111,7 +111,7 @@ export function useDroppableCollectionState(props: DroppableCollectionStateOptio } return 'cancel'; - }, [isDisabled, acceptedDragTypes, getDropOperation, onInsert, onRootDrop, onItemDrop, shouldAcceptItemDrop, onReorder, onDrop]); + }, [isDisabled, acceptedDragTypes, getDropOperation, onInsert, onRootDrop, onItemDrop, shouldAcceptItemDrop, onReorder, onDrop, selectionManager]); return { collection, From dec5ecf66828a9c01d97f4905d35cc465421cfaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikolas=20Schr=C3=B6ter?= Date: Fri, 21 Feb 2025 21:02:58 +0100 Subject: [PATCH 3/3] fix: test suite and remove console logs --- packages/@react-aria/dnd/src/useDroppableCollection.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/@react-aria/dnd/src/useDroppableCollection.ts b/packages/@react-aria/dnd/src/useDroppableCollection.ts index ad8371b9445..74aff972951 100644 --- a/packages/@react-aria/dnd/src/useDroppableCollection.ts +++ b/packages/@react-aria/dnd/src/useDroppableCollection.ts @@ -392,9 +392,13 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: }; } + if (isNextDisabled) { + nextKey = nextCollectionKey; + } + // If the last drop position was 'after', then 'before' on the next key is equivalent. // Switch to 'on' instead. - if (target.dropPosition === dropPositions[2] && !isNextDisabled) { + if (target.dropPosition === dropPositions[2]) { dropPosition = 'on'; } } else { @@ -439,7 +443,6 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: if (nextKey == null || nextKey === prevCollectionKey || isPrevDisabled) { let positionIndex = dropPositions.indexOf(target.dropPosition); let nextDropPosition = dropPositions[positionIndex - 1]; - console.log(target.key, target.dropPosition, isPrevDisabled); if (positionIndex > 0 && (nextDropPosition !== dropPositions[2] || isPrevDisabled)) { return { type: 'item', @@ -454,7 +457,7 @@ export function useDroppableCollection(props: DroppableCollectionOptions, state: // If the last drop position was 'before', then 'after' on the previous key is equivalent. // Switch to 'on' instead. - if (target.dropPosition === dropPositions[0] && nextKey !== prevCollectionKey) { + if (target.dropPosition === dropPositions[0]) { dropPosition = 'on'; } } else {