Skip to content

Commit

Permalink
Introduce disabled prop on SortableContext
Browse files Browse the repository at this point in the history
The `<SortableContext>` component now optionally accepts a `disabled` prop to globally disable `useSortable` hooks rendered within it.

The `disabled` prop accepts either a boolean or an object of shape `{draggable?: boolean, droppable?: boolean}.

The `useSortable` hook has now been updated to also optionally accept the `disabled` configuration object to conditionally disable the `useDraggable` and/or `useDroppable` hooks used internally.

Like the `strategy` prop, the `disabled` prop defined on the `useSortable` hook takes precedence over the `disabled` prop defined on the parent `<SortableContext>`.
  • Loading branch information
clauderic committed May 20, 2022
1 parent 86c8444 commit 224201a
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 20 deletions.
18 changes: 18 additions & 0 deletions .changeset/sortable-disabled-prop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@dnd-kit/sortable': minor
---

The `<SortableContext>` component now optionally accepts a `disabled` prop to globally disable `useSortable` hooks rendered within it.

The `disabled` prop accepts either a boolean or an object with the following shape:

```ts
interface Disabled {
draggable?: boolean;
droppable?: boolean;
}
```

The `useSortable` hook has now been updated to also optionally accept the `disabled` configuration object to conditionally disable the `useDraggable` and/or `useDroppable` hooks used internally.

Like the `strategy` prop, the `disabled` prop defined on the `useSortable` hook takes precedence over the `disabled` prop defined on the parent `<SortableContext>`.
40 changes: 24 additions & 16 deletions packages/sortable/src/components/SortableContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ import React, {useEffect, useMemo, useRef} from 'react';
import {useDndContext, ClientRect, UniqueIdentifier} from '@dnd-kit/core';
import {useIsomorphicLayoutEffect, useUniqueId} from '@dnd-kit/utilities';

import type {SortingStrategy} from '../types';
import {getSortedRects} from '../utilities';
import type {Disabled, SortingStrategy} from '../types';
import {getSortedRects, itemsEqual, normalizeDisabled} from '../utilities';
import {rectSortingStrategy} from '../strategies';

export interface Props {
children: React.ReactNode;
items: (UniqueIdentifier | {id: UniqueIdentifier})[];
strategy?: SortingStrategy;
id?: string;
disabled?: boolean | Disabled;
}

const ID_PREFIX = 'Sortable';

interface ContextDescriptor {
activeIndex: number;
containerId: string;
disabled: Disabled;
disableTransforms: boolean;
items: UniqueIdentifier[];
overIndex: number;
Expand All @@ -35,13 +37,18 @@ export const Context = React.createContext<ContextDescriptor>({
useDragOverlay: false,
sortedRects: [],
strategy: rectSortingStrategy,
disabled: {
draggable: false,
droppable: false,
},
});

export function SortableContext({
children,
id,
items: userDefinedItems,
strategy = rectSortingStrategy,
disabled: disabledProp = false,
}: Props) {
const {
active,
Expand All @@ -60,18 +67,26 @@ export function SortableContext({
),
[userDefinedItems]
);
const isDragging = active != null;
const activeIndex = active ? items.indexOf(active.id) : -1;
const overIndex = over ? items.indexOf(over.id) : -1;
const previousItemsRef = useRef(items);
const itemsHaveChanged = !isEqual(items, previousItemsRef.current);
const itemsHaveChanged = !itemsEqual(items, previousItemsRef.current);
const disableTransforms =
(overIndex !== -1 && activeIndex === -1) || itemsHaveChanged;
const disabled = normalizeDisabled(disabledProp);

useIsomorphicLayoutEffect(() => {
if (itemsHaveChanged && !measuringScheduled) {
if (itemsHaveChanged && isDragging && !measuringScheduled) {
measureDroppableContainers(items);
}
}, [itemsHaveChanged, items, measureDroppableContainers, measuringScheduled]);
}, [
itemsHaveChanged,
items,
isDragging,
measureDroppableContainers,
measuringScheduled,
]);

useEffect(() => {
previousItemsRef.current = items;
Expand All @@ -81,16 +96,20 @@ export function SortableContext({
(): ContextDescriptor => ({
activeIndex,
containerId,
disabled,
disableTransforms,
items,
overIndex,
useDragOverlay,
sortedRects: getSortedRects(items, droppableRects),
strategy,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[
activeIndex,
containerId,
disabled.draggable,
disabled.droppable,
disableTransforms,
items,
overIndex,
Expand All @@ -102,14 +121,3 @@ export function SortableContext({

return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

function isEqual(arr1: string[], arr2: string[]) {
if (arr1 === arr2) return true;
if (arr1.length !== arr2.length) return false;
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
33 changes: 29 additions & 4 deletions packages/sortable/src/hooks/useSortable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {Data} from '@dnd-kit/core';
import {CSS, isKeyboardEvent, useCombinedRefs} from '@dnd-kit/utilities';

import {Context} from '../components';
import type {SortableData, SortingStrategy} from '../types';
import type {Disabled, SortableData, SortingStrategy} from '../types';
import {isValidIndex} from '../utilities';
import {
defaultAnimateLayoutChanges,
Expand All @@ -27,9 +27,10 @@ import type {
import {useDerivedTransform} from './utilities';

export interface Arguments
extends UseDraggableArguments,
extends Omit<UseDraggableArguments, 'disabled'>,
Pick<UseDroppableArguments, 'resizeObserverConfig'> {
animateLayoutChanges?: AnimateLayoutChanges;
disabled?: boolean | Disabled;
getNewIndex?: NewIndexGetter;
strategy?: SortingStrategy;
transition?: SortableTransition | null;
Expand All @@ -38,7 +39,7 @@ export interface Arguments
export function useSortable({
animateLayoutChanges = defaultAnimateLayoutChanges,
attributes: userDefinedAttributes,
disabled,
disabled: localDisabled,
data: customData,
getNewIndex = defaultNewIndexGetter,
id,
Expand All @@ -50,12 +51,17 @@ export function useSortable({
items,
containerId,
activeIndex,
disabled: globalDisabled,
disableTransforms,
sortedRects,
overIndex,
useDragOverlay,
strategy: globalStrategy,
} = useContext(Context);
const disabled: Disabled = normalizeLocalDisabled(
localDisabled,
globalDisabled
);
const index = items.indexOf(id);
const data = useMemo<SortableData & Data>(
() => ({sortable: {containerId, index, items}, ...customData}),
Expand All @@ -68,6 +74,7 @@ export function useSortable({
const {rect, node, isOver, setNodeRef: setDroppableNodeRef} = useDroppable({
id,
data,
disabled: disabled.droppable,
resizeObserverConfig: {
updateMeasurementsFor: itemsAfterCurrentSortable,
...resizeObserverConfig,
Expand All @@ -91,7 +98,7 @@ export function useSortable({
...defaultAttributes,
...userDefinedAttributes,
},
disabled,
disabled: disabled.draggable,
});
const setNodeRef = useCombinedRefs(setDroppableNodeRef, setDraggableNodeRef);
const isSorting = Boolean(active);
Expand Down Expand Up @@ -216,3 +223,21 @@ export function useSortable({
return undefined;
}
}

function normalizeLocalDisabled(
localDisabled: Arguments['disabled'],
globalDisabled: Disabled
) {
if (typeof localDisabled === 'boolean') {
return {
draggable: localDisabled,
// Backwards compatibility
droppable: false,
};
}

return {
draggable: localDisabled?.draggable ?? globalDisabled.draggable,
droppable: localDisabled?.droppable ?? globalDisabled.droppable,
};
}
4 changes: 4 additions & 0 deletions packages/sortable/src/types/disabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Disabled {
draggable?: boolean;
droppable?: boolean;
}
1 change: 1 addition & 0 deletions packages/sortable/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type {Disabled} from './disabled';
export type {SortableData} from './data';
export type {SortingStrategy} from './strategies';
export {hasSortableData} from './type-guard';
2 changes: 2 additions & 0 deletions packages/sortable/src/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export {arrayMove} from './arrayMove';
export {arraySwap} from './arraySwap';
export {getSortedRects} from './getSortedRects';
export {isValidIndex} from './isValidIndex';
export {itemsEqual} from './itemsEqual';
export {normalizeDisabled} from './normalizeDisabled';
19 changes: 19 additions & 0 deletions packages/sortable/src/utilities/itemsEqual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type {UniqueIdentifier} from '@dnd-kit/core';

export function itemsEqual(a: UniqueIdentifier[], b: UniqueIdentifier[]) {
if (a === b) {
return true;
}

if (a.length !== b.length) {
return false;
}

for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}

return true;
}
12 changes: 12 additions & 0 deletions packages/sortable/src/utilities/normalizeDisabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type {Disabled} from '../types';

export function normalizeDisabled(disabled: boolean | Disabled): Disabled {
if (typeof disabled === 'boolean') {
return {
draggable: disabled,
droppable: disabled,
};
}

return disabled;
}

0 comments on commit 224201a

Please sign in to comment.