mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
Compare commits
1 Commits
v0.23.0-be
...
xp/02-07-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adaee0ef5f |
@@ -0,0 +1,3 @@
|
||||
# Sortable
|
||||
|
||||
Migrated from https://github.com/clauderic/dnd-kit
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { ElementDragType } from '@atlaskit/pragmatic-drag-and-drop/types';
|
||||
import React, {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { rectSortingStrategy } from './strategies';
|
||||
import type {
|
||||
ClientRect,
|
||||
Disabled,
|
||||
SortingStrategy,
|
||||
UniqueIdentifier,
|
||||
} from './types';
|
||||
import {
|
||||
getSortedRects,
|
||||
itemsEqual,
|
||||
normalizeDisabled,
|
||||
useUniqueId,
|
||||
} from './utilities';
|
||||
|
||||
export interface Props {
|
||||
children: React.ReactNode;
|
||||
items: (UniqueIdentifier | { id: UniqueIdentifier })[];
|
||||
strategy?: SortingStrategy;
|
||||
disabled?: boolean | Disabled;
|
||||
}
|
||||
|
||||
const ID_PREFIX = 'Sortable';
|
||||
|
||||
interface ContextDescriptor {
|
||||
activeIndex: number;
|
||||
containerId: string;
|
||||
disableTransforms: boolean;
|
||||
items: {
|
||||
id: UniqueIdentifier;
|
||||
}[];
|
||||
overIndex: number;
|
||||
sortedRects: ClientRect[];
|
||||
strategy: SortingStrategy;
|
||||
disabled: Disabled;
|
||||
}
|
||||
|
||||
export const Context = React.createContext<ContextDescriptor>({
|
||||
activeIndex: -1,
|
||||
containerId: ID_PREFIX,
|
||||
disableTransforms: false,
|
||||
items: [],
|
||||
overIndex: -1,
|
||||
sortedRects: [],
|
||||
strategy: rectSortingStrategy,
|
||||
disabled: {
|
||||
draggable: false,
|
||||
droppable: false,
|
||||
},
|
||||
});
|
||||
|
||||
export function SortableContext({
|
||||
children,
|
||||
items: userDefinedItems,
|
||||
strategy = rectSortingStrategy,
|
||||
disabled: disabledProp = false,
|
||||
}: Props) {
|
||||
const [active, setActive] = useState<ElementDragType | null>(null);
|
||||
|
||||
const { active, droppableRects, over, measureDroppableContainers } =
|
||||
useDndContext();
|
||||
const containerId = useUniqueId(ID_PREFIX, id);
|
||||
const items = useMemo<UniqueIdentifier[]>(
|
||||
() =>
|
||||
userDefinedItems.map(item =>
|
||||
typeof item === 'object' && 'id' in item ? item.id : item
|
||||
),
|
||||
[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 = !itemsEqual(items, previousItemsRef.current);
|
||||
const disableTransforms =
|
||||
(overIndex !== -1 && activeIndex === -1) || itemsHaveChanged;
|
||||
const disabled = normalizeDisabled(disabledProp);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (itemsHaveChanged && isDragging) {
|
||||
measureDroppableContainers(items);
|
||||
}
|
||||
}, [itemsHaveChanged, items, isDragging, measureDroppableContainers]);
|
||||
|
||||
useEffect(() => {
|
||||
previousItemsRef.current = items;
|
||||
}, [items]);
|
||||
|
||||
const contextValue = useMemo(
|
||||
(): ContextDescriptor => ({
|
||||
activeIndex,
|
||||
containerId,
|
||||
disabled,
|
||||
disableTransforms,
|
||||
items,
|
||||
overIndex,
|
||||
sortedRects: getSortedRects(items, droppableRects),
|
||||
strategy,
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
activeIndex,
|
||||
containerId,
|
||||
disabled.draggable,
|
||||
disabled.droppable,
|
||||
disableTransforms,
|
||||
items,
|
||||
overIndex,
|
||||
droppableRects,
|
||||
strategy,
|
||||
]
|
||||
);
|
||||
|
||||
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { SortingStrategy } from './types';
|
||||
import { arrayMove } from './utilities';
|
||||
|
||||
export const rectSortingStrategy: SortingStrategy = ({
|
||||
rects,
|
||||
activeIndex,
|
||||
overIndex,
|
||||
index,
|
||||
}) => {
|
||||
const newRects = arrayMove(rects, overIndex, activeIndex);
|
||||
|
||||
const oldRect = rects[index];
|
||||
const newRect = newRects[index];
|
||||
|
||||
if (!newRect || !oldRect) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
x: newRect.left - oldRect.left,
|
||||
y: newRect.top - oldRect.top,
|
||||
scaleX: newRect.width / oldRect.width,
|
||||
scaleY: newRect.height / oldRect.height,
|
||||
};
|
||||
};
|
||||
32
packages/frontend/component/src/ui/dnd/sortable/types.ts
Normal file
32
packages/frontend/component/src/ui/dnd/sortable/types.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export type Transform = {
|
||||
x: number;
|
||||
y: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
};
|
||||
|
||||
export interface ClientRect {
|
||||
width: number;
|
||||
height: number;
|
||||
top: number;
|
||||
left: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export type SortingStrategy = (args: {
|
||||
activeNodeRect: ClientRect | null;
|
||||
activeIndex: number;
|
||||
index: number;
|
||||
rects: ClientRect[];
|
||||
overIndex: number;
|
||||
}) => Transform | null;
|
||||
|
||||
export type UniqueIdentifier = string | number;
|
||||
|
||||
export type RectMap = Map<UniqueIdentifier, ClientRect>;
|
||||
|
||||
export interface Disabled {
|
||||
draggable?: boolean;
|
||||
droppable?: boolean;
|
||||
}
|
||||
76
packages/frontend/component/src/ui/dnd/sortable/utilities.ts
Normal file
76
packages/frontend/component/src/ui/dnd/sortable/utilities.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import type { ClientRect, Disabled, RectMap, UniqueIdentifier } from './types';
|
||||
|
||||
let ids: Record<string, number> = {};
|
||||
|
||||
export function useUniqueId(prefix: string, value?: string) {
|
||||
return useMemo(() => {
|
||||
if (value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const id = ids[prefix] == null ? 0 : ids[prefix] + 1;
|
||||
ids[prefix] = id;
|
||||
|
||||
return `${prefix}-${id}`;
|
||||
}, [prefix, value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an array item to a different position. Returns a new array with the item moved to the new position.
|
||||
*/
|
||||
export function arrayMove<T>(array: T[], from: number, to: number): T[] {
|
||||
const newArray = array.slice();
|
||||
newArray.splice(
|
||||
to < 0 ? newArray.length + to : to,
|
||||
0,
|
||||
newArray.splice(from, 1)[0]
|
||||
);
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
export function getSortedRects(items: UniqueIdentifier[], rects: RectMap) {
|
||||
return items.reduce<ClientRect[]>(
|
||||
(accumulator, id, index) => {
|
||||
const rect = rects.get(id);
|
||||
|
||||
if (rect) {
|
||||
accumulator[index] = rect;
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
Array.from({ length: items.length })
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function normalizeDisabled(disabled: boolean | Disabled): Disabled {
|
||||
if (typeof disabled === 'boolean') {
|
||||
return {
|
||||
draggable: disabled,
|
||||
droppable: disabled,
|
||||
};
|
||||
}
|
||||
|
||||
return disabled;
|
||||
}
|
||||
Reference in New Issue
Block a user