antd-mobile
Version:
<img src="https://gw.alipayobjects.com/mdn/rms_ee68a8/afts/img/A*hjjDS5Yy-ooAAAAAAAAAAAAAARQnAQ" alt="logo" width="100%" />
172 lines (160 loc) • 5.08 kB
JavaScript
import React, { memo, useRef } from 'react';
import { useSpring, animated } from '@react-spring/web';
import { useDrag } from '@use-gesture/react';
import { rubberbandIfOutOfBounds } from '../../utils/rubberband';
import { bound } from '../../utils/bound';
import isEqual from 'lodash/isEqual';
import { useIsomorphicLayoutEffect } from 'ahooks';
import { measureCSSLength } from '../../utils/measure-css-length';
const classPrefix = `adm-picker-view`;
export const Wheel = memo(props => {
const {
value,
column
} = props;
function onSelect(val) {
props.onSelect(val, props.index);
}
const [{
y
}, api] = useSpring(() => ({
from: {
y: 0
},
config: {
tension: 400,
mass: 0.8
}
}));
const draggingRef = useRef(false);
const rootRef = useRef(null);
const itemHeight = useRef(34);
useIsomorphicLayoutEffect(() => {
const root = rootRef.current;
if (!root) return;
itemHeight.current = measureCSSLength(window.getComputedStyle(root).getPropertyValue('--item-height'));
});
useIsomorphicLayoutEffect(() => {
if (draggingRef.current) return;
if (!value) return;
const targetIndex = column.findIndex(item => item.value === value);
if (targetIndex < 0) return;
const finalPosition = targetIndex * -itemHeight.current;
api.start({
y: finalPosition,
immediate: y.goal !== finalPosition
});
}, [value, column]);
useIsomorphicLayoutEffect(() => {
if (column.length === 0) {
if (value !== null) {
onSelect(null);
}
} else {
if (!column.some(item => item.value === value)) {
const firstItem = column[0];
onSelect(firstItem.value);
}
}
}, [column, value]);
function scrollSelect(index) {
const finalPosition = index * -itemHeight.current;
api.start({
y: finalPosition
});
const item = column[index];
if (!item) return;
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
}
});
let selectedIndex = null;
function renderAccessible() {
if (selectedIndex === null) {
return null;
}
const current = column[selectedIndex];
const previousIndex = selectedIndex - 1;
const nextIndex = selectedIndex + 1;
const previous = column[previousIndex];
const next = column[nextIndex];
return React.createElement("div", {
className: 'adm-picker-view-column-accessible'
}, React.createElement("div", {
className: 'adm-picker-view-column-accessible-current',
role: 'button',
"aria-label": current ? `当前选择的是:${current.label}` : '当前未选择'
}, "-"), React.createElement("div", null, previous && React.createElement("div", {
className: 'adm-picker-view-column-accessible-button',
onClick: () => {
scrollSelect(previousIndex);
},
role: 'button',
"aria-label": `选择上一项:${previous.label}`
}, "-")), React.createElement("div", null, next && React.createElement("div", {
className: 'adm-picker-view-column-accessible-button',
onClick: () => {
scrollSelect(nextIndex);
},
role: 'button',
"aria-label": `选择下一项:${next.label}`
}, "-")));
}
return React.createElement("div", Object.assign({
ref: rootRef,
className: `${classPrefix}-column`
}, bind()), React.createElement(animated.div, {
style: {
translateY: y
},
className: `${classPrefix}-column-wheel`,
"aria-hidden": true
}, column.map((item, index) => {
const selected = props.value === item.value;
if (selected) selectedIndex = index;
function handleClick() {
draggingRef.current = false;
scrollSelect(index);
}
return React.createElement("div", {
key: item.value,
"data-selected": item.value === value,
className: `${classPrefix}-column-item`,
onClick: handleClick,
"aria-hidden": !selected,
"aria-label": selected ? 'active' : ''
}, React.createElement("div", {
className: `${classPrefix}-column-item-label`
}, item.label));
})), renderAccessible());
}, (prev, next) => {
if (prev.index !== next.index) return false;
if (prev.value !== next.value) return false;
if (prev.onSelect !== next.onSelect) return false;
if (!isEqual(prev.column, next.column)) {
return false;
}
return true;
});
Wheel.displayName = 'Wheel';