Skip to content

feat: Picker和PickerView组件增加mouseWheel事件支持 #5052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 13, 2022
6 changes: 5 additions & 1 deletion src/components/date-picker-view/date-picker-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import type {
DatePickerFilter,
} from '../date-picker/date-picker-utils'

export type DatePickerViewProps = Pick<PickerViewProps, 'style'> & {
export type DatePickerViewProps = Pick<
PickerViewProps,
'style' | 'mouseWheel'
> & {
value?: Date
defaultValue?: Date
onChange?: (value: Date) => void
Expand Down Expand Up @@ -73,6 +76,7 @@ export const DatePickerView: FC<DatePickerViewProps> = p => {
)
}
value={pickerValue}
mouseWheel={props.mouseWheel}
onChange={onChange}
/>
)
Expand Down
2 changes: 2 additions & 0 deletions src/components/date-picker/date-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type DatePickerProps = Pick<
| 'title'
| 'stopPropagation'
| 'style'
| 'mouseWheel'
> & {
value?: Date | null
defaultValue?: Date | null
Expand Down Expand Up @@ -117,6 +118,7 @@ export const DatePicker: FC<DatePickerProps> = p => {
onClick={props.onClick}
title={props.title}
stopPropagation={props.stopPropagation}
mouseWheel={props.mouseWheel}
>
{() => props.children?.(value)}
</Picker>
Expand Down
4 changes: 4 additions & 0 deletions src/components/picker-view/demos/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export default () => {
}}
/>
</DemoBlock>

<DemoBlock title='通过鼠标滚轮进行选择' padding='0'>
<PickerView columns={basicColumns} mouseWheel={true} />
</DemoBlock>
</>
)
}
2 changes: 2 additions & 0 deletions src/components/picker-view/index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ PickerView is the content area of [Picker](./picker/#picker).
| defaultValue | Default selected options | `PickerValue[]` | `[]` |
| onChange | Triggered when the options are changed | `(value: PickerValue[], extend: PickerValueExtend) => void` | - |
| renderLabel | The function to custom rendering the label shown on a column | `(item: PickerColumnItem) => ReactNode` | `(item) => item.label` |
| mouseWheel | Whether to allow interact with mouse wheel | `boolean` | `false` |

For the type definition of `PickerColumnItem` `PickerColumn` `PickerValue` `PickerValueExtend`, please refer to the document of [Picker](./picker).

Expand Down Expand Up @@ -70,6 +71,7 @@ DatePickerView is the content area of [DatePicker](./picker/#datepicker).
| precision | Precision | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day'` | `'day'` |
| renderLabel | The function to custom rendering the label shown on a column. `type` means any value in `precision`, `data` means the default number | `(type: string, data: number) => ReactNode` | - |
| filter | Filter available time | `DatePickerFilter` | - |
| mouseWheel | Whether to allow interact with mouse wheel | `boolean` | `false` |

For the type definition and usage of `DatePickerFilter`, please refer to the document of [DatePicker](./picker#datepicker).

Expand Down
16 changes: 9 additions & 7 deletions src/components/picker-view/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ PickerView 是 [Picker](./picker/#picker) 的内容区域。

### 属性

| 属性 | 说明 | 类型 | 默认值 |
| ------------ | ------------------------ | -------------------------------------------------------------- | ---------------------- |
| columns | 配置每一列的选项 | `PickerColumn[] \| ((value: PickerValue[]) => PickerColumn[])` | - |
| value | 选中项 | `PickerValue[]` | - |
| defaultValue | 默认选中项 | `PickerValue[]` | `[]` |
| onChange | 选项改变时触发 | `(value: PickerValue[], extend: PickerValueExtend) => void` | - |
| renderLabel | 自定义渲染每列展示的内容 | `(item: PickerColumnItem) => ReactNode` | `(item) => item.label` |
| 属性 | 说明 | 类型 | 默认值 |
| ------------ | ---------------------------- | -------------------------------------------------------------- | ---------------------- |
| columns | 配置每一列的选项 | `PickerColumn[] \| ((value: PickerValue[]) => PickerColumn[])` | - |
| value | 选中项 | `PickerValue[]` | - |
| defaultValue | 默认选中项 | `PickerValue[]` | `[]` |
| onChange | 选项改变时触发 | `(value: PickerValue[], extend: PickerValueExtend) => void` | - |
| renderLabel | 自定义渲染每列展示的内容 | `(item: PickerColumnItem) => ReactNode` | `(item) => item.label` |
| mouseWheel | 是否允许通过鼠标滚轮进行选择 | `boolean` | `false` |

关于 `PickerColumnItem` `PickerColumn` `PickerValue` `PickerValueExtend` 的类型定义,请参考 [Picker](./picker) 的文档。

Expand Down Expand Up @@ -70,6 +71,7 @@ DatePickerView 是 [DatePicker](./picker/#datepicker) 的内容区域。
| precision | 精度 | `'year' \| 'month' \| 'day' \| 'hour' \| 'minute' \| 'second' \| 'week' \| 'week-day'` | `'day'` |
| renderLabel | 自定义渲染每列展示的内容。其中 `type` 参数为 `precision` 中的任意值,`data` 参数为默认渲染的数字 | `(type: string, data: number) => ReactNode` | - |
| filter | 过滤可供选择的时间 | `DatePickerFilter` | - |
| mouseWheel | 是否允许通过鼠标滚轮进行选择 | `boolean` | `false` |

关于 `DatePickerFilter` 的类型定义和使用,请参考 [DatePicker](./picker#datepicker) 的文档。

Expand Down
3 changes: 3 additions & 0 deletions src/components/picker-view/picker-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export type PickerViewProps = {
columns: PickerColumn[] | ((value: PickerValue[]) => PickerColumn[])
value?: PickerValue[]
defaultValue?: PickerValue[]
mouseWheel?: boolean
onChange?: (value: PickerValue[], extend: PickerValueExtend) => void
} & Pick<PickerProps, 'renderLabel'> &
NativeProps<'--height' | '--item-height' | '--item-font-size'>

const defaultProps = {
defaultValue: [],
renderLabel: defaultRenderLabel,
mouseWheel: false,
}

export const PickerView = memo<PickerViewProps>(p => {
Expand Down Expand Up @@ -98,6 +100,7 @@ export const PickerView = memo<PickerViewProps>(p => {
value={innerValue[index]}
onSelect={handleSelect}
renderLabel={props.renderLabel}
mouseWheel={props.mouseWheel}
/>
))}
<div className={`${classPrefix}-mask`}>
Expand Down
82 changes: 55 additions & 27 deletions src/components/picker-view/wheel.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { memo, ReactNode, useRef } from 'react'
import { useSpring, animated } from '@react-spring/web'
import { useDrag } from '@use-gesture/react'
import { useDrag, useWheel } from '@use-gesture/react'
import { rubberbandIfOutOfBounds } from '../../utils/rubberband'
import { bound } from '../../utils/bound'
import { PickerColumnItem, PickerValue } from './index'
import isEqual from 'lodash/isEqual'
import { useIsomorphicLayoutEffect } from 'ahooks'
import { measureCSSLength } from '../../utils/measure-css-length'
import { supportsPassive } from '../../utils/supports-passive'
import { FullGestureState } from '@use-gesture/core/src/types/state'

const classPrefix = `adm-picker-view`

Expand All @@ -16,6 +18,7 @@ type Props = {
value: PickerValue
onSelect: (value: PickerValue, index: number) => void
renderLabel: (item: PickerColumnItem) => ReactNode
mouseWheel: boolean
}

export const Wheel = memo<Props>(
Expand Down Expand Up @@ -76,38 +79,63 @@ export const Wheel = memo<Props>(
onSelect(item.value)
}

const bind = useDrag(
const handleDrag = (
state: Pick<
FullGestureState<'drag'>,
'last' | 'offset' | 'velocity' | 'direction'
>
) => {
draggingRef.current = true
const min = -((column.length - 1) * itemHeight.current)
const max = 0
if (state.last) {
draggingRef.current = false
const position =
state.offset[1] + state.velocity[1] * state.direction[1] * 50
const targetIndex =
min < max
? -Math.round(bound(position, min, max) / itemHeight.current)
: 0
scrollSelect(targetIndex)
} else {
const position = state.offset[1]
api.start({
y: rubberbandIfOutOfBounds(
position,
min,
max,
itemHeight.current * 50,
0.2
),
})
}
}

useDrag(
state => {
draggingRef.current = true
const min = -((column.length - 1) * itemHeight.current)
const max = 0
if (state.last) {
draggingRef.current = false
const position =
state.offset[1] + state.velocity[1] * state.direction[1] * 50
const targetIndex =
min < max
? -Math.round(bound(position, min, max) / itemHeight.current)
: 0
scrollSelect(targetIndex)
} else {
const position = state.offset[1]
api.start({
y: rubberbandIfOutOfBounds(
position,
min,
max,
itemHeight.current * 50,
0.2
),
})
}
state.event.stopPropagation()
handleDrag(state)
},
{
axis: 'y',
from: () => [0, y.get()],
filterTaps: true,
pointer: { touch: true },
target: rootRef,
}
)

useWheel(
state => {
state.event.stopPropagation()
handleDrag(state)
},
{
axis: 'y',
from: () => [0, y.get()],
preventDefault: true,
target: props.mouseWheel ? rootRef : undefined,
eventOptions: supportsPassive ? { passive: false } : false,
}
)

Expand Down Expand Up @@ -166,7 +194,7 @@ export const Wheel = memo<Props>(
}

return (
<div ref={rootRef} className={`${classPrefix}-column`} {...bind()}>
<div ref={rootRef} className={`${classPrefix}-column`}>
<animated.div
style={{ translateY: y }}
className={`${classPrefix}-column-wheel`}
Expand Down
2 changes: 2 additions & 0 deletions src/components/picker/index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type PickerValueExtend = {
| cancelText | Text of the cancel button | `ReactNode` | `'取消'` |
| children | Render function of the selected options | `(items: PickerColumnItem[]) => ReactNode` | - |
| renderLabel | The function to custom rendering the label shown on a column | `(item: PickerColumnItem) => ReactNode` | `(item) => item.label` |
| mouseWheel | Whether to allow interact with mouse wheel | `boolean` | `false` |

In addition, the following attributes of [Popup](./popup) are supported: `getContainer` `afterShow` `afterClose` `onClick` `stopPropagation`

Expand Down Expand Up @@ -96,6 +97,7 @@ Same as `Picker`.
| children | The rendering function of the selected items | `(value: Date) => ReactNode` | - |
| renderLabel | The function to custom rendering the label shown on a column. `type` means any value in `precision`, `data` means the default number | `(type: string, data: number) => ReactNode` | - |
| filter | Filter available time | `DatePickerFilter` | - |
| mouseWheel | Whether to allow interact with mouse wheel | `boolean` | `false` |

```typescript | pure
type DatePickerFilter = Partial<
Expand Down
2 changes: 2 additions & 0 deletions src/components/picker/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type PickerValueExtend = {
| cancelText | 取消按钮的文字 | `ReactNode` | `'取消'` |
| children | 所选项的渲染函数 | `(items: PickerColumnItem[]) => ReactNode` | - |
| renderLabel | 自定义渲染每列展示的内容 | `(item: PickerColumnItem) => ReactNode` | `(item) => item.label` |
| mouseWheel | 是否允许通过鼠标滚轮进行选择 | `boolean` | `false` |

此外还支持 [Popup](./popup) 的以下属性:`getContainer` `afterShow` `afterClose` `onClick` `stopPropagation`

Expand Down Expand Up @@ -122,6 +123,7 @@ type CascadePickerOption = {
| children | 所选项的渲染函数 | `(value: Date) => ReactNode` | - |
| renderLabel | 自定义渲染每列展示的内容。其中 `type` 参数为 `precision` 中的任意值,`data` 参数为默认渲染的数字 | `(type: string, data: number) => ReactNode` | - |
| filter | 过滤可供选择的时间 | `DatePickerFilter` | - |
| mouseWheel | 是否允许通过鼠标滚轮进行选择 | `boolean` | `false` |

```typescript | pure
type DatePickerFilter = Partial<
Expand Down
2 changes: 2 additions & 0 deletions src/components/picker/picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type PickerProps = {
cancelText?: ReactNode
children?: (items: (PickerColumnItem | null)[]) => ReactNode
renderLabel?: (item: PickerColumnItem) => ReactNode
mouseWheel?: boolean
} & Pick<
PopupProps,
'getContainer' | 'afterShow' | 'afterClose' | 'onClick' | 'stopPropagation'
Expand Down Expand Up @@ -122,6 +123,7 @@ export const Picker = memo<PickerProps>(p => {
columns={props.columns}
renderLabel={props.renderLabel}
value={innerValue}
mouseWheel={props.mouseWheel}
onChange={onChange}
/>
</div>
Expand Down