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 | Support mouse wheel events | `boolean` | `false` |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该修正为 Enable mouse wheel mode


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 | Support mouse wheel events | `boolean` | `false` |

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

Expand Down
2 changes: 2 additions & 0 deletions src/components/picker-view/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ PickerView 是 [Picker](./picker/#picker) 的内容区域。
| defaultValue | 默认选中项 | `PickerValue[]` | `[]` |
| onChange | 选项改变时触发 | `(value: PickerValue[], extend: PickerValueExtend) => void` | - |
| renderLabel | 自定义渲染每列展示的内容 | `(item: PickerColumnItem) => ReactNode` | `(item) => item.label` |
| mouseWheel | 鼠标滚轮模式 | `boolean` | `false` |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该修正为 启用鼠标滚轮模式

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一次滚动,直接滚动4条数据了

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一次滚动,直接滚动4条数据了

是的,目前依然有这个问题。不知道何时可以解决呢?

Copy link
Contributor Author

@shusiwei shusiwei Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一次滚动,直接滚动4条数据了

是的,目前依然有这个问题。不知道何时可以解决呢?

我刚才提交了个pr,看会不会合并吧 #6853


关于 `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
2 changes: 2 additions & 0 deletions src/components/picker-view/picker-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ 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'>
Expand Down Expand Up @@ -98,6 +99,7 @@ export const PickerView = memo<PickerViewProps>(p => {
value={innerValue[index]}
onSelect={handleSelect}
renderLabel={props.renderLabel}
mouseWheel={props.mouseWheel}
/>
))}
<div className={`${classPrefix}-mask`}>
Expand Down
105 changes: 70 additions & 35 deletions src/components/picker-view/wheel.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { memo, ReactNode, useRef } from 'react'
import { useSpring, animated } from '@react-spring/web'
import { useDrag } from '@use-gesture/react'
import { useDrag, useWheel, Handler, EventTypes } from '@use-gesture/react'
import { rubberbandIfOutOfBounds } from '../../utils/rubberband'
import { bound } from '../../utils/bound'
import { useLockScroll } from '../../utils/use-lock-scroll'
import { PickerColumnItem, PickerValue } from './index'
import isEqual from 'lodash/isEqual'
import { useIsomorphicLayoutEffect } from 'ahooks'
Expand All @@ -16,6 +17,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,40 +78,68 @@ export const Wheel = memo<Props>(
onSelect(item.value)
}

const bind = 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
),
})
}
},
{
axis: 'y',
from: () => [0, y.get()],
filterTaps: true,
pointer: { touch: true },
function handleWheel(
state: Parameters<
| Handler<'drag', EventTypes['drag']>
| Handler<'wheel', EventTypes['wheel']>
>[0]
) {
state.event.stopPropagation()

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
),
})
}
)
}

const drag = useDrag(handleWheel, {
axis: 'y',
from: () => [0, y.get()],
filterTaps: true,
pointer: { touch: true },
})

const wheel = useWheel(handleWheel, {
axis: 'y',
from: () => [0, y.get()],
preventDefault: true,
})

const { lock, unlock } = useLockScroll(rootRef, false)
const wheelProps =
props.mouseWheel === true
? {
...wheel(),
onMouseOver(evt: React.MouseEvent<HTMLDivElement>) {
evt.preventDefault()
lock()
},
onMouseOut(evt: React.MouseEvent<HTMLDivElement>) {
evt.preventDefault()
unlock()
},
}
: {}

let selectedIndex: number | null = null

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

return (
<div ref={rootRef} className={`${classPrefix}-column`} {...bind()}>
<div
ref={rootRef}
className={`${classPrefix}-column`}
{...drag()}
{...wheelProps}
>
<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 | Support mouse wheel events | `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 | Support mouse wheel events | `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
5 changes: 5 additions & 0 deletions src/utils/use-lock-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ export function useLockScroll(
}
}
}, [shouldLock])

return {
lock,
unlock,
}
}