Skip to content

Commit

Permalink
feat: 🎸 antd sortable impl by dnd, replace react-sort-hoc (#3855)
Browse files Browse the repository at this point in the history
  • Loading branch information
charlzyx authored Jun 28, 2023
1 parent 061ad21 commit b3e270f
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 60 deletions.
3 changes: 2 additions & 1 deletion packages/antd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"@formily/reactive-react": "2.2.26",
"@formily/shared": "2.2.26",
"classnames": "^2.2.6",
"react-sortable-hoc": "^1.11.0",
"@dnd-kit/core": "^6.0.0",
"@dnd-kit/sortable": "^7.0.0",
"react-sticky-box": "^0.9.3"
},
"publishConfig": {
Expand Down
1 change: 1 addition & 0 deletions packages/antd/src/__builtins__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './hooks'
export * from './portal'
export * from './loading'
export * from './pickDataProps'
export * from './sort'
143 changes: 143 additions & 0 deletions packages/antd/src/__builtins__/sort.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { DndContext, DragEndEvent, DragStartEvent } from '@dnd-kit/core'
import {
SortableContext,
useSortable,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { ReactFC } from '@formily/reactive-react'
import React, { createContext, useContext, useMemo } from 'react'

export interface ISortableContainerProps {
list: any[]
start?: number
accessibility?: {
container?: Element
}
onSortStart?: (event: DragStartEvent) => void
onSortEnd?: (event: { oldIndex: number; newIndex: number }) => void
}

export function SortableContainer<T extends React.HTMLAttributes<HTMLElement>>(
Component: ReactFC<T>
): ReactFC<ISortableContainerProps & T> {
return ({
list,
start = 0,
accessibility,
onSortStart,
onSortEnd,
...props
}) => {
const _onSortEnd = (event: DragEndEvent) => {
const { active, over } = event
const oldIndex = (active.id as number) - 1
const newIndex = (over?.id as number) - 1
onSortEnd?.({
oldIndex,
newIndex,
})
}

return (
<DndContext
accessibility={accessibility}
onDragStart={onSortStart}
onDragEnd={_onSortEnd}
>
<SortableContext
items={list.map((_, index) => index + start + 1)}
strategy={verticalListSortingStrategy}
>
<Component {...(props as unknown as T)}>{props.children}</Component>
</SortableContext>
</DndContext>
)
}
}

export const useSortableItem = () => {
return useContext(SortableItemContext)
}

export const SortableItemContext = createContext<
Partial<ReturnType<typeof useSortable>>
>({})

export interface ISortableElementProps {
index?: number
lockAxis?: 'x' | 'y'
}

export function SortableElement<T extends React.HTMLAttributes<HTMLElement>>(
Component: ReactFC<T>
): ReactFC<T & ISortableElementProps> {
return ({ index = 0, lockAxis, ...props }) => {
const sortable = useSortable({
id: index + 1,
})
const { setNodeRef, transform, transition, isDragging } = sortable
if (transform) {
switch (lockAxis) {
case 'x':
transform.y = 0
break
case 'y':
transform.x = 0
break
default:
break
}
}

const style = useMemo(() => {
const itemStyle: React.CSSProperties = {
position: 'relative',
touchAction: 'none',
zIndex: 1,
transform: `translate3d(${transform?.x || 0}px, ${
transform?.y || 0
}px, 0)`,
transition: `${transform ? 'all 200ms ease' : ''}`,
}
const dragStyle = {
transition,
opacity: '0.8',
transform: `translate3d(${transform?.x || 0}px, ${
transform?.y || 0
}px, 0)`,
}

const computedStyle = isDragging
? {
...itemStyle,
...dragStyle,
...props.style,
}
: {
...itemStyle,
...props.style,
}

return computedStyle
}, [isDragging, transform, transition, props.style])

return (
<SortableItemContext.Provider value={sortable}>
{Component({
...props,
style,
ref: setNodeRef,
} as unknown as T)}
</SortableItemContext.Provider>
)
}
}

export function SortableHandle<T extends React.HTMLAttributes<HTMLElement>>(
Component: ReactFC<T>
): ReactFC<T> {
return (props: T) => {
const { attributes, listeners } = useSortableItem()
return <Component {...props} {...attributes} {...listeners} />
}
}
3 changes: 1 addition & 2 deletions packages/antd/src/array-base/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import { ButtonProps } from 'antd/lib/button'
import { ArrayField } from '@formily/core'
import { useField, useFieldSchema, Schema, JSXComponent } from '@formily/react'
import { isValid, clone, isUndef } from '@formily/shared'
import { SortableHandle } from 'react-sortable-hoc'
import { usePrefixCls } from '../__builtins__'
import { usePrefixCls, SortableHandle } from '../__builtins__'
import cls from 'classnames'

export interface IArrayBaseAdditionProps extends ButtonProps {
Expand Down
31 changes: 10 additions & 21 deletions packages/antd/src/array-items/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import {
observer,
useFieldSchema,
RecursionField,
ReactFC,
} from '@formily/react'
import cls from 'classnames'
import { ISchema } from '@formily/json-schema'
import {
usePrefixCls,
SortableContainer,
SortableElement,
SortableContainerProps,
SortableElementProps,
} from 'react-sortable-hoc'
import { ISchema } from '@formily/json-schema'
import { usePrefixCls } from '../__builtins__'
} from '../__builtins__'
import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base'

type ComposedArrayItems = React.FC<
Expand All @@ -31,9 +28,7 @@ type ComposedArrayItems = React.FC<
>
}

const SortableItem: ReactFC<
React.HTMLAttributes<HTMLDivElement> & SortableElementProps
> = SortableElement(
const SortableItem = SortableElement(
(props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => {
const prefixCls = usePrefixCls('formily-array-items')
return (
Expand All @@ -42,11 +37,9 @@ const SortableItem: ReactFC<
</div>
)
}
) as any
)

const SortableList: ReactFC<
React.HTMLAttributes<HTMLDivElement> & SortableContainerProps
> = SortableContainer(
const SortableList = SortableContainer(
(props: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => {
const prefixCls = usePrefixCls('formily-array-items')
return (
Expand All @@ -55,7 +48,7 @@ const SortableList: ReactFC<
</div>
)
}
) as any
)

const isAdditionComponent = (schema: ISchema) => {
return schema['x-component']?.indexOf('Addition') > -1
Expand Down Expand Up @@ -95,12 +88,8 @@ export const ArrayItems: ComposedArrayItems = observer((props) => {
className={cls(prefixCls, props.className)}
>
<SortableList
useDragHandle
lockAxis="y"
helperClass={`${prefixCls}-sort-helper`}
helperContainer={() =>
ref.current?.querySelector(`.${prefixCls}-list`)
}
list={dataSource.slice()}
className={`${prefixCls}-sort-helper`}
onSortEnd={({ oldIndex, newIndex }) => {
field.move(oldIndex, newIndex)
}}
Expand All @@ -115,7 +104,7 @@ export const ArrayItems: ComposedArrayItems = observer((props) => {
index={index}
record={() => field.value?.[index]}
>
<SortableItem key={`item-${index}`} index={index}>
<SortableItem key={`item-${index}`} lockAxis="y" index={index}>
<div className={`${prefixCls}-item-inner`}>
<RecursionField schema={items} name={index} />
</div>
Expand Down
90 changes: 54 additions & 36 deletions packages/antd/src/array-table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { PaginationProps } from 'antd/lib/pagination'
import { TableProps, ColumnProps } from 'antd/lib/table'
import { SelectProps } from 'antd/lib/select'
import cls from 'classnames'
import { SortableContainer, SortableElement } from 'react-sortable-hoc'
import { GeneralField, FieldDisplayTypes, ArrayField } from '@formily/core'
import {
useField,
Expand All @@ -23,7 +22,11 @@ import {
} from '@formily/react'
import { isArr, isBool, isFn } from '@formily/shared'
import { Schema } from '@formily/json-schema'
import { usePrefixCls } from '../__builtins__'
import {
usePrefixCls,
SortableContainer,
SortableElement,
} from '../__builtins__'
import { ArrayBase, ArrayBaseMixins, IArrayBaseProps } from '../array-base'

interface ObservableColumnSource {
Expand All @@ -37,7 +40,10 @@ interface IArrayTablePaginationProps extends PaginationProps {
dataSource?: any[]
children?: (
dataSource: any[],
pagination: React.ReactNode
pagination: React.ReactNode,
options: {
startIndex: number
}
) => React.ReactElement
}

Expand Down Expand Up @@ -280,15 +286,25 @@ const ArrayTablePagination: ReactFC<IArrayTablePaginationProps> = (props) => {
>
{props.children?.(
dataSource?.slice(startIndex, endIndex + 1),
renderPagination()
renderPagination(),
{ startIndex }
)}
</PaginationContext.Provider>
</Fragment>
)
}

const RowComp = (props: any) => {
return <SortableRow index={props['data-row-key'] || 0} {...props} />
const RowComp: ReactFC<React.HTMLAttributes<HTMLTableRowElement>> = (props) => {
const prefixCls = usePrefixCls('formily-array-table')
const index = props['data-row-key'] || 0
return (
<SortableRow
lockAxis="y"
{...props}
index={index}
className={cls(props.className, `${prefixCls}-row-${index + 1}`)}
/>
)
}

export const ArrayTable: ComposedArrayTable = observer((props) => {
Expand All @@ -304,42 +320,44 @@ export const ArrayTable: ComposedArrayTable = observer((props) => {
const defaultRowKey = (record: any) => {
return dataSource.indexOf(record)
}
const addTdStyles = (node: HTMLElement) => {
const addTdStyles = (id: number) => {
const node = ref.current?.querySelector(`.${prefixCls}-row-${id}`)
const helper = document.body.querySelector(`.${prefixCls}-sort-helper`)
if (helper) {
const tds = node.querySelectorAll('td')
requestAnimationFrame(() => {
helper.querySelectorAll('td').forEach((td, index) => {
if (tds[index]) {
td.style.width = getComputedStyle(tds[index]).width
}
})
if (!helper) return
const tds = node?.querySelectorAll('td')
if (!tds) return
requestAnimationFrame(() => {
helper.querySelectorAll('td').forEach((td, index) => {
if (tds[index]) {
td.style.width = getComputedStyle(tds[index]).width
}
})
}
})
}
const WrapperComp = useCallback(
(props: any) => (
<SortableBody
useDragHandle
lockAxis="y"
helperClass={`${prefixCls}-sort-helper`}
helperContainer={() => {
return ref.current?.querySelector('tbody')
}}
onSortStart={({ node }) => {
addTdStyles(node as HTMLElement)
}}
onSortEnd={({ oldIndex, newIndex }) => {
field.move(oldIndex, newIndex)
}}
{...props}
/>
),
const getWrapperComp = useCallback(
(dataSource: any[], start: number) => (props: any) =>
(
<SortableBody
{...props}
start={start}
list={dataSource.slice()}
accessibility={{
container: ref.current || undefined,
}}
onSortStart={(event) => {
addTdStyles(event.active.id as number)
}}
onSortEnd={({ oldIndex, newIndex }) => {
field.move(oldIndex, newIndex)
}}
className={cls(`${prefixCls}-sort-helper`, props.className)}
/>
),
[field]
)
return (
<ArrayTablePagination {...pagination} dataSource={dataSource}>
{(dataSource, pager) => (
{(dataSource, pager, { startIndex }) => (
<div ref={ref} className={prefixCls}>
<ArrayBase
onAdd={onAdd}
Expand All @@ -359,7 +377,7 @@ export const ArrayTable: ComposedArrayTable = observer((props) => {
dataSource={dataSource}
components={{
body: {
wrapper: WrapperComp,
wrapper: getWrapperComp(dataSource, startIndex),
row: RowComp,
},
}}
Expand Down
Loading

0 comments on commit b3e270f

Please sign in to comment.