diff --git a/.jest.config.js b/.jest.config.js index e5e3dbe45..9f9bed91f 100644 --- a/.jest.config.js +++ b/.jest.config.js @@ -8,7 +8,6 @@ const transformPackages = [ 'react-native-collapsible', '@bang88/react-native-ultimate-listview', '@react-native-community', - '@react-native-picker/picker', 'react-native-gesture-handler' ]; diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index beba87ad3..940c7eab2 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -14,6 +14,22 @@ toc: false --- +### 5.1.0 +`2024-02-20` +- Refactor **Picker** & **PickerView** + - 🔥 Remove dependence `@react-native-picker/picker` + - 💄 Refactor extends by `ScrollView {snapToInterval}` to support web + - 🆕 Refactor `itemStyle` prop, make styles more flexible [#1311](https://github.com/ant-design/ant-design-mobile-rn/issues/1311) [#1316](https://github.com/ant-design/ant-design-mobile-rn/issues/1316) + - 🆕 Picker support (`visible`) new prop +- Refactor **DatePicker** & **DatePickerView** + - 💄 **Style** 和 **Base Props** extends by Picker & PickerView + - 🆕 Support (`precision` `filter` ) new props + - ⚡️ Deprecated (`mode`)prop; date format by [Day.js](https://day.js.org/docs/en/parse/string-format) +- ❗️Delete **ImagePicker** and remove dependence `@react-native-camera-roll/camera-roll` +- **Switch** + - fix: `checked` prop support controlled mode [#1325](https://github.com/ant-design/ant-design-mobile-rn/issues/1325) + - feat: `onChange` prop when the Promise is returned, the loading status will be displayed automatically + ### 5.0.5 `2023-11-08` - fix: Picker support `numberOfLines` property [#1311](https://github.com/ant-design/ant-design-mobile-rn/issues/1311) diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index a70ea6fd7..4557ee017 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -14,6 +14,22 @@ toc: false --- +### 5.1.0 +`2024-02-23` +- 重构 **Picker** & **PickerView** + - 🔥 重构开发并移除 `@react-native-picker/picker` 依赖 + - 💄 基于 `ScrollView {snapToInterval}` 开发并支持`web`端 + - 🆕 重构 `itemStyle` 样式,显示更灵活 [#1311](https://github.com/ant-design/ant-design-mobile-rn/issues/1311) [#1316](https://github.com/ant-design/ant-design-mobile-rn/issues/1316) + - 🆕 Picker 新增 (`visible`) 属性支持 +- 重构 **DatePicker** & **DatePickerView** + - 💄 **样式** 和 **基础属性** 继承 Picker & PickerView + - 🆕 新增 (`precision` `filter` ) 属性支持 + - ⚡️ 废弃(`mode`)属性;时间格式引用[Day.js](https://day.js.org/docs/zh-CN/parse/string-format) +- ❗️删除 **ImagePicker** 并移除 `@react-native-camera-roll/camera-roll` 依赖 +- **Switch** + - fix: `checked`属性支持全受控模式 [#1325](https://github.com/ant-design/ant-design-mobile-rn/issues/1325) + - feat: `onChange`属性当返回 Promise 时,会自动显示加载状态 + ### 5.0.5 `2023-11-08` - fix: Picker support `numberOfLines` property [#1311](https://github.com/ant-design/ant-design-mobile-rn/issues/1311) diff --git a/README.md b/README.md index 35fce1f28..01e55fe01 100644 --- a/README.md +++ b/README.md @@ -93,13 +93,13 @@ yarn add @ant-design/react-native ### Installing peer dependencies ```bash -npm install @react-native-camera-roll/camera-roll @react-native-picker/picker @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +npm install @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler ``` or ```bash -yarn add @react-native-camera-roll/camera-roll @react-native-picker/picker @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +yarn add @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler ``` > You need go to ios folder and run `pod install` (auto linking),Android will handle it by itself. diff --git a/README.zh-CN.md b/README.zh-CN.md index 131d61e2e..cc5ed069d 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -93,13 +93,13 @@ yarn add @ant-design/react-native ### 安装peer依赖 ```bash -npm install @react-native-camera-roll/camera-roll @react-native-picker/picker @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +npm install @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler ``` or ```bash -yarn add @react-native-camera-roll/camera-roll @react-native-picker/picker @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +yarn add @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler ``` > 安装完依赖后需要到 iOS 目录 `pod install`(auto linking),Android 不需要手动处理 diff --git a/components/_util/hooks/useAnimations.tsx b/components/_util/hooks/useAnimations.tsx index b48d974a9..3189c5386 100644 --- a/components/_util/hooks/useAnimations.tsx +++ b/components/_util/hooks/useAnimations.tsx @@ -45,6 +45,7 @@ export function useAnimatedTiming(): [Animated.Value, animateType] { useNativeDriver, }).start() }, + // @ts-ignore [animatedValue], ) var [animatedValue] = useAnimate({ diff --git a/components/_util/isPromise.ts b/components/_util/isPromise.ts new file mode 100644 index 000000000..d7013d76a --- /dev/null +++ b/components/_util/isPromise.ts @@ -0,0 +1,5 @@ +export function isPromise(obj: unknown): obj is Promise { + return ( + !!obj && typeof obj === 'object' && typeof (obj as any).then === 'function' + ) +} diff --git a/components/_util/with-default-props.tsx b/components/_util/with-default-props.tsx new file mode 100644 index 000000000..d209b3010 --- /dev/null +++ b/components/_util/with-default-props.tsx @@ -0,0 +1,15 @@ +const assignWith = require('lodash.assignwith') + +export function mergeProps(a: A, b: B): B & A +export function mergeProps(a: A, b: B, c: C): C & B & A +export function mergeProps(...items: any[]) { + function customizer(objValue: any, srcValue: any) { + return srcValue === undefined ? objValue : srcValue + } + + let ret = { ...items[0] } + for (let i = 1; i < items.length; i++) { + ret = assignWith(ret, items[i], customizer) + } + return ret +} diff --git a/components/carousel/__tests__/__snapshots__/demo.test.js.snap b/components/carousel/__tests__/__snapshots__/demo.test.js.snap index 5c2855905..ec5fcae89 100644 --- a/components/carousel/__tests__/__snapshots__/demo.test.js.snap +++ b/components/carousel/__tests__/__snapshots__/demo.test.js.snap @@ -27,6 +27,7 @@ exports[`renders ./components/carousel/demo/basic.tsx correctly 1`] = ` automaticallyAdjustContentInsets={false} autoplay={true} autoplayInterval={3000} + collapsable={false} contentOffset={ Object { "x": 0, @@ -39,6 +40,8 @@ exports[`renders ./components/carousel/demo/basic.tsx correctly 1`] = ` dots={true} horizontal={true} infinite={true} + onGestureHandlerEvent={[Function]} + onGestureHandlerStateChange={[Function]} onMomentumScrollEnd={[Function]} onScrollBeginDrag={[Function]} onScrollEndDrag={[Function]} @@ -492,6 +495,7 @@ exports[`renders ./components/carousel/demo/basic.tsx correctly 1`] = ` automaticallyAdjustContentInsets={false} autoplay={true} autoplayInterval={3000} + collapsable={false} contentOffset={ Object { "x": 0, @@ -504,6 +508,8 @@ exports[`renders ./components/carousel/demo/basic.tsx correctly 1`] = ` dots={true} horizontal={false} infinite={true} + onGestureHandlerEvent={[Function]} + onGestureHandlerStateChange={[Function]} onMomentumScrollEnd={[Function]} onScrollBeginDrag={[Function]} onScrollEndDrag={[Function]} diff --git a/components/carousel/index.tsx b/components/carousel/index.tsx index f358c9bed..708db7716 100644 --- a/components/carousel/index.tsx +++ b/components/carousel/index.tsx @@ -4,14 +4,14 @@ import { NativeScrollEvent, NativeSyntheticEvent, Platform, - ScrollView, ScrollViewProps, StyleProp, View, ViewStyle, } from 'react-native' -import { WithTheme, WithThemeStyles } from '../style' +import { ScrollView } from 'react-native-gesture-handler' import devWarning from '../_util/devWarning' +import { WithTheme, WithThemeStyles } from '../style' import CarouselStyles, { CarouselStyle } from './style/index' export interface CarouselPropsType diff --git a/components/date-picker-view/PropsType.tsx b/components/date-picker-view/PropsType.tsx index 3dd010355..84d8cddbe 100644 --- a/components/date-picker-view/PropsType.tsx +++ b/components/date-picker-view/PropsType.tsx @@ -1,7 +1,39 @@ -import { DatePickerPropsType } from '../date-picker/PropsType' +import type { ReactNode } from 'react' +import { DatePickerFilter, Precision } from '../date-picker/date-picker-utils' +import { PickerDate } from '../date-picker/util' +import { + PickerValueExtend, + PickerViewPropsType, +} from '../picker-view/PropsType' -export interface DatePickerProps extends DatePickerPropsType { - onScrollChange?: (newValue: any, vals: any, index: number) => void - triggerTypes?: string - styles?: any +export type RenderLabel = (type: Precision | 'now', data: number) => ReactNode + +export interface DatePickerViewPropsType + extends Pick< + PickerViewPropsType, + | 'style' + | 'itemStyle' + | 'numberOfLines' + | 'renderMaskTop' + | 'renderMaskBottom' + > { + value?: PickerDate + defaultValue?: PickerDate + mode?: Precision | 'date' + minDate?: Date + maxDate?: Date + onChange?: (value: Date, extend?: PickerValueExtend) => void + onValueChange?: (vals: any, index: number) => void + precision?: Precision + renderLabel?: RenderLabel + locale?: { + year?: string + month?: string + day?: string + hour?: string + minute?: string + am?: string + pm?: string + } + filter?: DatePickerFilter } diff --git a/components/date-picker-view/date-picker-view.tsx b/components/date-picker-view/date-picker-view.tsx index 95325e612..1835b687a 100644 --- a/components/date-picker-view/date-picker-view.tsx +++ b/components/date-picker-view/date-picker-view.tsx @@ -1,39 +1,102 @@ -import React from 'react' -import RCDatePicker from '../date-picker/datepicker' +import useMergedState from 'rc-util/lib/hooks/useMergedState' +import React, { + FunctionComponent, + memo, + useCallback, + useContext, + useMemo, +} from 'react' + import { getComponentLocale } from '../_util/getLocale' -import { DatePickerProps } from './PropsType' +import { mergeProps } from '../_util/with-default-props' +import { getValueExtend, usePickerValue } from '../date-picker/columns-extend' +import { generateDatePickerColumns } from '../date-picker/date-picker-utils' import { LocaleContext } from '../locale-provider' -export default class DatePickerView extends React.Component< - DatePickerProps, - any -> { - static defaultProps = { - mode: 'datetime', - // extra: '请选择', - minuteStep: 1, - use12Hours: false, - } - - static contextType = LocaleContext - - render() { - // tslint:disable-next-line:no-this-assignment - const { props, context } = this - const locale = getComponentLocale(props, context, 'DatePickerView', () => - require('./locale/zh_CN'), - ) +import RMCPickerView from '../picker-view/picker-view' +import { PickerViewStyle } from '../picker-view/style' +import { WithThemeStyles } from '../style' +import { DatePickerViewPropsType } from './PropsType' +import useRenderLabel from './useRenderLabel' - // DatePicker use `defaultDate`, maybe because there are PopupDatePicker inside? @yiminghe - // Here Use `date` instead of `defaultDate`, make it controlled fully. - return ( - - ) - } +export interface DatePickerViewProps + extends DatePickerViewPropsType, + WithThemeStyles {} + +const defaultProps = { + minDate: new Date('2000-1-1'), + maxDate: new Date('2030-1-1'), + mode: 'date', } + +const DatePickerView: FunctionComponent = memo((props) => { + const p = mergeProps(defaultProps, props) + const { + value, + defaultValue, + minDate, + maxDate, + mode, + precision, + renderLabel, + filter, + ...restProps + } = p + + const _precision = precision || (mode === 'date' ? 'day' : mode) || 'day' + + const [innerValue, setInnerValue] = useMergedState(new Date(), { + value: value, + defaultValue: defaultValue, + }) + + const _locale = getComponentLocale( + p, + useContext(LocaleContext), + 'DatePickerView', + () => require('./locale/zh_CN'), + ) + + const mergedRenderLabel = useRenderLabel(renderLabel, _locale) + + const pickerValue = usePickerValue(innerValue, minDate, maxDate, _precision) + + const columns = useMemo(() => { + return generateDatePickerColumns( + pickerValue, + minDate, + maxDate, + _precision, + mergedRenderLabel, + filter, + false, + ) + }, [maxDate, mergedRenderLabel, minDate, _precision, filter, pickerValue]) + + const handleSelect = useCallback( + (val: string, i: number) => { + const pvalue = pickerValue.slice(0) + pvalue[i] = val + const { dateValue, extend } = getValueExtend(columns, pvalue, _precision) + setInnerValue(dateValue) + p.onChange?.(dateValue, { + items: extend, + columns, + }) + p.onValueChange?.(dateValue, i) + }, + [columns, _precision, p, pickerValue, setInnerValue], + ) + + return ( + + ) +}) + +DatePickerView.displayName = 'Picker' + +export default DatePickerView diff --git a/components/date-picker-view/demo/basic.md b/components/date-picker-view/demo/basic.md index 5f0eafa0f..52b19b9dc 100644 --- a/components/date-picker-view/demo/basic.md +++ b/components/date-picker-view/demo/basic.md @@ -9,41 +9,118 @@ title: ```jsx import React from 'react'; -import { Text, View } from 'react-native'; -import { DatePickerView, Provider } from '@ant-design/react-native'; -export default class DatePickerViewExample extends React.Component { - constructor() { - super(...arguments); - this.state = { - value: undefined, - }; - this.onChange = value => { - console.log(value); - this.setState({ value }); - }; - this.onValueChange = (...args) => { - console.log(args); - }; +import { ScrollView, Text } from 'react-native' +import { DatePickerView } from '@ant-design/react-native'; +import { DatePickerFilter } from '@ant-design/react-native/lib/date-picker/date-picker-utils' + +const now = new Date() + +export default () => { + const [value, setValue] = useState(now) + + return ( + + 基础用法 + + + 受控模式 + { + setValue(val) + console.log('onChange', val) + }} + /> + + 自定义每列的渲染内容 + + + 周选择器 + console.log('onChange', val)} + precision="week-day" + defaultValue={now} + renderLabel={weekdayLabelRenderer} + /> + + 过滤可供选择的时间 + + + ) +} + +const labelRenderer = (type: string, data: number) => { + switch (type) { + case 'year': + return data + '年' + case 'month': + return data + '月' + case 'day': + return data + '日' + case 'hour': + return data + '时' + case 'minute': + return data + '分' + case 'second': + return data + '秒' + default: + return data + } +} + +const weekdayLabelRenderer = (type: string, data: number) => { + switch (type) { + case 'year': + return data + '年' + case 'week': + return data + '周' + case 'week-day': + return weekdayToZh(data) + default: + return data } - render() { - return ( - - - Start DateTime - - End DateTime - - - - ); +} + +const dateFilter: DatePickerFilter = { + day: (val, { date }) => { + // 去除所有周末 + if (date.getDay() > 5 || date.getDay() === 0) { + return false + } + return true + }, + hour: (val: number) => { + // 只保留每天的14点到18点 + if (val < 14 || val > 18) { + return false + } + return true + }, +} + +const weekdayToZh = (weekday: number) => { + switch (weekday) { + case 1: + return '周一' + case 2: + return '周二' + case 3: + return '周三' + case 4: + return '周四' + case 5: + return '周五' + case 6: + return '周六' + case 7: + return '周日' + default: + return weekday } } ``` diff --git a/components/date-picker-view/demo/basic.tsx b/components/date-picker-view/demo/basic.tsx index deb73095a..15f020807 100644 --- a/components/date-picker-view/demo/basic.tsx +++ b/components/date-picker-view/demo/basic.tsx @@ -1,42 +1,115 @@ -/* tslint:disable:no-console */ -import React from 'react' -import { Text, View } from 'react-native' +import React, { useState } from 'react' +import { ScrollView, Text } from 'react-native' import { DatePickerView } from '../../' +import { DatePickerFilter } from '../../date-picker/date-picker-utils' -export default class DatePickerViewExample extends React.Component { - state = { - value: undefined, - value12hours: undefined, - } - onChange = (value: any) => { - console.log(value) - this.setState({ value }) +const now = new Date() + +export default () => { + const [value, setValue] = useState(now) + + return ( + + 基础用法 + + + 受控模式 + { + setValue(val) + console.log('onChange', val) + }} + /> + + 自定义每列的渲染内容 + + + 周选择器 + console.log('onChange', val)} + precision="week-day" + defaultValue={now} + renderLabel={weekdayLabelRenderer} + /> + + 过滤可供选择的时间 + + + ) +} + +const labelRenderer = (type: string, data: number) => { + switch (type) { + case 'year': + return data + '年' + case 'month': + return data + '月' + case 'day': + return data + '日' + case 'hour': + return data + '时' + case 'minute': + return data + '分' + case 'second': + return data + '秒' + default: + return data } - onValueChange = (...args: any[]) => { - console.log(args) +} + +const weekdayLabelRenderer = (type: string, data: number) => { + switch (type) { + case 'year': + return data + '年' + case 'week': + return data + '周' + case 'week-day': + return weekdayToZh(data) + default: + return data } - render() { - return ( - - use12Hours - this.setState({ value12hours: v })} - use12Hours - /> - Start DateTime - - End DateTime - - - ) +} + +const dateFilter: DatePickerFilter = { + day: (_val, { date }) => { + // 去除所有周末 + if (date.getDay() > 5 || date.getDay() === 0) { + return false + } + return true + }, + hour: (val: number) => { + // 只保留每天的14点到18点 + if (val < 14 || val > 18) { + return false + } + return true + }, +} + +const weekdayToZh = (weekday: number) => { + switch (weekday) { + case 1: + return '周一' + case 2: + return '周二' + case 3: + return '周三' + case 4: + return '周四' + case 5: + return '周五' + case 6: + return '周六' + case 7: + return '周日' + default: + return weekday } } diff --git a/components/date-picker-view/index.en-US.md b/components/date-picker-view/index.en-US.md index 88ea7ab1f..d332ec604 100644 --- a/components/date-picker-view/index.en-US.md +++ b/components/date-picker-view/index.en-US.md @@ -8,15 +8,41 @@ DatePickerView's functions like DatePicker, but it is rendered directly in the a ## API -Properties | Descrition | Type | Default ------------|------------|------|-------- -| mode | mode value, can be a `date` or `time` or `datetime` or `year` or `month` | String | `date` | +```ts +type Precision = + | 'week' + | 'week-day' + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + +type DatePickerFilter = Partial< + Record< + Precision, + ( + val: number, + extend: { + date: Date + } + ) => boolean + > +> +``` + +Properties | Descrition | Type | Default | Version +-----------|------------|------|---------|--------- +| precision | Precision | `Precision` | `day` |`5.1.0`| | value | the currently selected value | Date | - | +| defaultValue | the default selected value | Date | - || | minDate | minimum date | Date | 2000-1-1 | | maxDate | maximum date | Date | 2030-1-1 | -| minuteStep | The amount of time, in minutes, between each minute item. | Number | 1 | -| locale | international, can override the configuration of the global `[LocaleProvider](https://mobile.ant.design/components/locale-provider)` | Object: {DatePickerLocale: {year, month, day, hour, minute, am?, pm?}, okText, dismissText} | - | -| disabled | disabled | Boolean | false | -| use12Hours | 12 hours format | Boolean | false | -| onChange | change handler | (date: Object): void | - | -| onValueChange | fire when picker col change | (vals: any, index: number) => void | - | +| onChange | change handler | `(value: Date) => void` | - || +| onValueChange | fire when picker col change | `(value: Date, index: number) => void` | - || +| renderLabel | The function to custom rendering the label shown on a column. `type` means any value in `precision`, `data` means the default number | `(type:Precision / 'now', data: number) => ReactNode` | - || +| filter | Filter available time | `DatePickerFilter` | - | `5.1.0` | + + +In addition, the following attributes of `PickerView` are supported: `style` `styles` `itemStyle` `itemHeight` `numberOfLines` `renderMaskTop` `renderMaskBottom` diff --git a/components/date-picker-view/index.zh-CN.md b/components/date-picker-view/index.zh-CN.md index 92a437ef1..b2f984298 100644 --- a/components/date-picker-view/index.zh-CN.md +++ b/components/date-picker-view/index.zh-CN.md @@ -2,22 +2,48 @@ category: Components type: Data Entry title: DatePickerView -subtitle: 选择器 +subtitle: 日期选择器 --- DatePickerView 的功能类似于 DatePicker ,但它是直接渲染在区域中,而不是弹出窗口。 ## API -属性 | 说明 | 类型 | 默认值 -----|-----|------|------ -| mode | 日期选择的类型, 可以是日期`date`,时间`time`,日期+时间`datetime`,年`year`,月`month` | String | `date` | -| value | 当前选中时间 | Date | 无 | -| minDate | 最小可选日期 | Date | 2000-1-1 | -| maxDate | 最大可选日期 | Date | 2030-1-1 | -| minuteStep | 分钟数递增步长设置 | Number | 1 | -| locale | 国际化,可覆盖全局`[LocaleProvider](https://mobile.ant.design/components/locale-provider)`的配置 | Object: {DatePickerLocale: {year, month, day, hour, minute, am?, pm?}, okText, dismissText } | - | -| disabled | 是否不可用 | Boolean | false | -| use12Hours | 12小时制 | Boolean | false | -| onChange | 时间发生变化的回调函数 | (date: Object): void | - | -| onValueChange | 每列 picker 改变时的回调 | (vals: any, index: number) => void | - | +```ts +type Precision = + | 'week' + | 'week-day' + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + +type DatePickerFilter = Partial< + Record< + Precision, + ( + val: number, + extend: { + date: Date + } + ) => boolean + > +> +``` + +属性 | 说明 | 类型 | 默认值 | 版本 +----|-----|------|------|------ +| precision | 精度 | `Precision` | `day` |`5.1.0`| +| value | 当前选中时间 | Date | 无 || +| defaultValue | 默认选中时间 | Date | 无 || +| minDate | 最小可选日期 | Date | 2000-1-1 || +| maxDate | 最大可选日期 | Date | 2030-1-1 || +| onChange | 时间发生变化的回调函数 | `(value: Date) => void` | - || +| onValueChange | 每列 picker 改变时的回调 | `(value: Date, index: number) => void` | - || +| renderLabel | 自定义渲染每列展示的内容。其中 `type` 参数为 `precision` 中的任意值或 `now`,`data` 参数为默认渲染的数字 | `(type:Precision / 'now', data: number) => ReactNode` | - || +| filter | 过滤可供选择的时间 | `DatePickerFilter` | - | `5.1.0` | + + +此外还支持 PickerView 的以下属性:`style` `styles` `itemStyle` `itemHeight` `numberOfLines` `renderMaskTop` `renderMaskBottom` \ No newline at end of file diff --git a/components/date-picker-view/locale/en_US.tsx b/components/date-picker-view/locale/en_US.tsx index 86b41df13..42c9b02df 100644 --- a/components/date-picker-view/locale/en_US.tsx +++ b/components/date-picker-view/locale/en_US.tsx @@ -1,3 +1,9 @@ -import DatePickerLocale from '../../date-picker/datepicker/locale/en_US' - -export default DatePickerLocale +export default { + year: '', + month: '', + day: '', + hour: '', + minute: '', + am: 'AM', + pm: 'PM', +} diff --git a/components/date-picker-view/locale/es_ES.tsx b/components/date-picker-view/locale/es_ES.tsx index 098313f1d..42c9b02df 100644 --- a/components/date-picker-view/locale/es_ES.tsx +++ b/components/date-picker-view/locale/es_ES.tsx @@ -1,3 +1,9 @@ -import DatePickerLocale from '../../date-picker/datepicker/locale/es_ES' - -export default DatePickerLocale +export default { + year: '', + month: '', + day: '', + hour: '', + minute: '', + am: 'AM', + pm: 'PM', +} diff --git a/components/date-picker-view/locale/fa_IR.tsx b/components/date-picker-view/locale/fa_IR.tsx index d4f41445f..9809570b1 100644 --- a/components/date-picker-view/locale/fa_IR.tsx +++ b/components/date-picker-view/locale/fa_IR.tsx @@ -1,3 +1,9 @@ -import DatePickerLocale from '../../date-picker/datepicker/locale/fa_IR' - -export default DatePickerLocale +export default { + year: 'سال', + month: 'ماه', + day: 'روز', + hour: 'ساعت', + minute: 'دقیقه', + am: 'صبح', + pm: 'بعد از ظهر', +} diff --git a/components/date-picker/datepicker/locale/id_ID.tsx b/components/date-picker-view/locale/id_ID.tsx similarity index 100% rename from components/date-picker/datepicker/locale/id_ID.tsx rename to components/date-picker-view/locale/id_ID.tsx diff --git a/components/date-picker-view/locale/ko_KR.tsx b/components/date-picker-view/locale/ko_KR.tsx new file mode 100644 index 000000000..668fd5683 --- /dev/null +++ b/components/date-picker-view/locale/ko_KR.tsx @@ -0,0 +1,9 @@ +export default { + year: '년', + month: '월', + day: '일', + hour: '시간', + minute: '분', + am: '오전', + pm: '오후', +} diff --git a/components/date-picker-view/locale/pt_BR.tsx b/components/date-picker-view/locale/pt_BR.tsx index 25989b657..9266e1468 100644 --- a/components/date-picker-view/locale/pt_BR.tsx +++ b/components/date-picker-view/locale/pt_BR.tsx @@ -1,3 +1,9 @@ -import DatePickerLocale from '../../date-picker/datepicker/locale/pt_BR' - -export default DatePickerLocale +export default { + year: 'Ano', + month: 'Mês', + day: 'Dia', + hour: 'Hora', + minute: 'Minuto', + am: 'AM', + pm: 'PM', +} diff --git a/components/date-picker-view/locale/ru_RU.tsx b/components/date-picker-view/locale/ru_RU.tsx index 78b51a6c9..42c9b02df 100644 --- a/components/date-picker-view/locale/ru_RU.tsx +++ b/components/date-picker-view/locale/ru_RU.tsx @@ -1,3 +1,9 @@ -import DatePickerLocale from '../../date-picker/datepicker/locale/ru_RU' - -export default DatePickerLocale +export default { + year: '', + month: '', + day: '', + hour: '', + minute: '', + am: 'AM', + pm: 'PM', +} diff --git a/components/date-picker-view/locale/sv_SE.tsx b/components/date-picker-view/locale/sv_SE.tsx index 0b7a64999..42c9b02df 100644 --- a/components/date-picker-view/locale/sv_SE.tsx +++ b/components/date-picker-view/locale/sv_SE.tsx @@ -1,3 +1,9 @@ -import DatePickerLocale from '../../date-picker/datepicker/locale/sv_SE' - -export default DatePickerLocale +export default { + year: '', + month: '', + day: '', + hour: '', + minute: '', + am: 'AM', + pm: 'PM', +} diff --git a/components/date-picker-view/locale/zh_CN.tsx b/components/date-picker-view/locale/zh_CN.tsx index 3a2b4892e..988abfa5e 100644 --- a/components/date-picker-view/locale/zh_CN.tsx +++ b/components/date-picker-view/locale/zh_CN.tsx @@ -1,3 +1,10 @@ -import DatePickerLocale from '../../date-picker/datepicker/locale/zh_CN' - -export default DatePickerLocale +export default { + year: '年', + month: '月', + day: '日', + hour: '时', + minute: '分', + second: '秒', + am: '上午', + pm: '下午', +} diff --git a/components/date-picker-view/useRenderLabel.ts b/components/date-picker-view/useRenderLabel.ts new file mode 100644 index 000000000..18fa708e2 --- /dev/null +++ b/components/date-picker-view/useRenderLabel.ts @@ -0,0 +1,32 @@ +import { useCallback } from 'react' +import { RenderLabel } from './PropsType' + +export default function useRenderLabel( + renderLabel?: RenderLabel, + locale?: any, +): RenderLabel { + return useCallback( + (type, data) => { + if (renderLabel) { + return renderLabel(type, data) + } + + // Default render + switch (type) { + case 'year': + case 'month': + case 'day': + return data.toString() + (locale?.[type] || '') + case 'minute': + case 'second': + case 'hour': + return ('0' + data.toString()).slice(-2) + (locale?.[type] || '') + case 'now': + return locale.tillNow + default: + return data.toString() + } + }, + [locale, renderLabel], + ) +} diff --git a/components/date-picker/PropsType.tsx b/components/date-picker/PropsType.tsx index 81f4ab737..30f6a3bf7 100644 --- a/components/date-picker/PropsType.tsx +++ b/components/date-picker/PropsType.tsx @@ -1,37 +1,63 @@ -import React from 'react' -import AntDatePickerProps from './datepicker/DatePickerProps' -export interface DatePickerPropsType extends AntDatePickerProps { - value?: Date - defaultDate?: Date - mode?: 'datetime' | 'date' | 'year' | 'month' | 'time' +import type { ReactNode } from 'react' +import { PickerValueExtend } from '../picker-view/PropsType' +import { PickerPropsType } from '../picker/PropsType' +import { DatePickerFilter, Precision } from './date-picker-utils' +import { PickerDate } from './util' + +export type RenderLabel = (type: Precision | 'now', data: number) => ReactNode + +export interface DatePickerPropsType + extends Pick< + PickerPropsType, + | 'onPickerChange' + | 'onVisibleChange' + | 'style' + | 'styles' + | 'itemStyle' + | 'numberOfLines' + | 'title' + | 'okText' + | 'dismissText' + | 'visible' + | 'children' + | 'renderMaskTop' + | 'renderMaskBottom' + > { + value?: PickerDate + defaultValue?: PickerDate + /** + * Please use `defaultValue`. + * Although it is also compatible with history, it will be removed in the future. + * @deprecated + */ + defaultDate?: PickerDate + precision?: Precision + /** + * Please use `precision`. + * Although it is also compatible with history, it will be removed in the future. + * @deprecated + */ + mode?: Precision | 'date' minDate?: Date maxDate?: Date - onChange?: (value: Date) => void - onValueChange?: (vals: any, index: number) => void - visible?: boolean - use12Hours?: boolean - onDismiss?: () => void - onOk?: () => void + onChange?: (value: Date, extend: PickerValueExtend) => void + onValueChange?: (value: PickerDate, index: number) => void + onOk?: (value: Date, extend: PickerValueExtend) => void + renderLabel?: RenderLabel locale?: { - okText: string - dismissText: string - extra: string - DatePickerLocale: { + okText?: string + dismissText?: string + extra?: string + DatePickerLocale?: { year: string month: string day: string hour: string minute: string - am?: string - pm?: string + am: string + pm: string } } - minuteStep?: number - disabled?: boolean + filter?: DatePickerFilter // @5.0.1 format?: string | ((value: Date) => string) - extra?: string - children?: React.ReactNode - dismissText?: React.ReactNode - okText?: React.ReactNode - title?: React.ReactNode } diff --git a/components/date-picker/columns-extend.tsx b/components/date-picker/columns-extend.tsx new file mode 100644 index 000000000..0a3554e7a --- /dev/null +++ b/components/date-picker/columns-extend.tsx @@ -0,0 +1,44 @@ +import { useMemo } from 'react' +import { PickerColumn, PickerValue } from '../picker-view/PropsType' +import { + Precision, + convertDateToStringArray, + convertStringArrayToDate, +} from './date-picker-utils' + +export function getValueExtend( + d: PickerColumn[], + val: PickerValue[], + mode: Precision, +) { + const extend = d.map( + (column: PickerColumn, index: number) => + column.find((item) => item?.value === val[index]) ?? + (val[index] === undefined ? column[0] : column.slice(-1)[0]), + ) as PickerColumn + return { + dateValue: convertStringArrayToDate( + extend.map((item) => item?.value), + mode, + ), + extend, + } +} + +// date2array +export function usePickerValue( + val: Date | undefined, + minDate: Date, + maxDate: Date, + mode: Precision, +) { + return useMemo(() => { + let value = new Date(val || '') + if (isNaN(value.getTime()) || value.getTime() < minDate.getTime()) { + value = minDate + } else if (value.getTime() > maxDate.getTime()) { + value = maxDate + } + return convertDateToStringArray(value, mode) + }, [val, minDate, maxDate, mode]) +} diff --git a/components/date-picker/date-picker-date-utils.ts b/components/date-picker/date-picker-date-utils.ts new file mode 100644 index 000000000..e16b26220 --- /dev/null +++ b/components/date-picker/date-picker-date-utils.ts @@ -0,0 +1,219 @@ +import dayjs from 'dayjs' +import isLeapYear from 'dayjs/plugin/isLeapYear' +import isoWeek from 'dayjs/plugin/isoWeek' +import isoWeeksInYear from 'dayjs/plugin/isoWeeksInYear' +import { RenderLabel } from '../date-picker-view/PropsType' +import { PickerColumn } from '../picker-view/PropsType' +import type { DatePickerFilter } from './date-picker-utils' +import { TILL_NOW } from './util' + +dayjs.extend(isoWeek) +dayjs.extend(isoWeeksInYear) +dayjs.extend(isLeapYear) + +export type DatePrecision = + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + +const precisionRankRecord: Record = { + year: 0, + month: 1, + day: 2, + hour: 3, + minute: 4, + second: 5, +} + +export function generateDatePickerColumns( + selected: string[], + min: Date, + max: Date, + precision: DatePrecision, + renderLabel: RenderLabel, + filter: DatePickerFilter | undefined, + tillNow?: boolean, +) { + const ret: PickerColumn[] = [] + + const minYear = min.getFullYear() + const minMonth = min.getMonth() + 1 + const minDay = min.getDate() + const minHour = min.getHours() + const minMinute = min.getMinutes() + const minSecond = min.getSeconds() + + const maxYear = max.getFullYear() + const maxMonth = max.getMonth() + 1 + const maxDay = max.getDate() + const maxHour = max.getHours() + const maxMinute = max.getMinutes() + const maxSecond = max.getSeconds() + + const rank = precisionRankRecord[precision] + + const selectedYear = parseInt(selected[0]) + const firstDayInSelectedMonth = dayjs( + convertStringArrayToDate([selected[0], selected[1], '1']), + ) + const selectedMonth = parseInt(selected[1]) + const selectedDay = parseInt(selected[2]) + const selectedHour = parseInt(selected[3]) + const selectedMinute = parseInt(selected[4]) + + const isInMinYear = selectedYear === minYear + const isInMaxYear = selectedYear === maxYear + const isInMinMonth = isInMinYear && selectedMonth === minMonth + const isInMaxMonth = isInMaxYear && selectedMonth === maxMonth + const isInMinDay = isInMinMonth && selectedDay === minDay + const isInMaxDay = isInMaxMonth && selectedDay === maxDay + const isInMinHour = isInMinDay && selectedHour === minHour + const isInMaxHour = isInMaxDay && selectedHour === maxHour + const isInMinMinute = isInMinHour && selectedMinute === minMinute + const isInMaxMinute = isInMaxHour && selectedMinute === maxMinute + + const generateColumn = ( + from: number, + to: number, + precision: DatePrecision, + ) => { + let column: number[] = [] + for (let i = from; i <= to; i++) { + column.push(i) + } + const prefix = selected.slice(0, precisionRankRecord[precision]) + const currentFilter = filter?.[precision] + if (currentFilter && typeof currentFilter === 'function') { + column = column.filter((i) => + currentFilter(i, { + get date() { + const stringArray = [...prefix, i.toString()] + return convertStringArrayToDate(stringArray) + }, + }), + ) + } + return column + } + + if (rank >= precisionRankRecord.year) { + const lower = minYear + const upper = maxYear + const years = generateColumn(lower, upper, 'year') + ret.push( + years.map((v) => ({ + label: renderLabel('year', v), + value: v.toString(), + })), + ) + } + + if (rank >= precisionRankRecord.month) { + const lower = isInMinYear ? minMonth : 1 + const upper = isInMaxYear ? maxMonth : 12 + const months = generateColumn(lower, upper, 'month') + ret.push( + months.map((v) => ({ + label: renderLabel('month', v), + value: v.toString(), + })), + ) + } + if (rank >= precisionRankRecord.day) { + const lower = isInMinMonth ? minDay : 1 + const upper = isInMaxMonth ? maxDay : firstDayInSelectedMonth.daysInMonth() + const days = generateColumn(lower, upper, 'day') + ret.push( + days.map((v) => ({ + label: renderLabel('day', v), + value: v.toString(), + })), + ) + } + if (rank >= precisionRankRecord.hour) { + const lower = isInMinDay ? minHour : 0 + const upper = isInMaxDay ? maxHour : 23 + const hours = generateColumn(lower, upper, 'hour') + ret.push( + hours.map((v) => ({ + label: renderLabel('hour', v), + value: v.toString(), + })), + ) + } + if (rank >= precisionRankRecord.minute) { + const lower = isInMinHour ? minMinute : 0 + const upper = isInMaxHour ? maxMinute : 59 + const minutes = generateColumn(lower, upper, 'minute') + ret.push( + minutes.map((v) => ({ + label: renderLabel('minute', v), + value: v.toString(), + })), + ) + } + if (rank >= precisionRankRecord.second) { + const lower = isInMinMinute ? minSecond : 0 + const upper = isInMaxMinute ? maxSecond : 59 + const seconds = generateColumn(lower, upper, 'second') + ret.push( + seconds.map((v) => ({ + label: renderLabel('second', v), + value: v.toString(), + })), + ) + } + + // Till Now + if (tillNow) { + ret[0].push({ + label: renderLabel('now', null!), + value: TILL_NOW, + }) + + if (TILL_NOW === selected?.[0]) { + for (let i = 1; i < ret.length; i += 1) { + ret[i] = [] + } + } + } + + return ret +} + +export function convertDateToStringArray( + date: Date | undefined | null, +): string[] { + if (!date) { + return [] + } + return [ + date.getFullYear().toString(), + (date.getMonth() + 1).toString(), + date.getDate().toString(), + date.getHours().toString(), + date.getMinutes().toString(), + date.getSeconds().toString(), + ] +} + +export function convertStringArrayToDate< + T extends string | number | null | undefined, +>(value: T[]): Date { + const yearString = value[0] ?? '1900' + const monthString = value[1] ?? '1' + const dateString = value[2] ?? '1' + const hourString = value[3] ?? '0' + const minuteString = value[4] ?? '0' + const secondString = value[5] ?? '0' + return dayjs( + `${parseInt(yearString as string)}-${parseInt( + monthString as string, + )}-${parseInt(dateString as string)} ${parseInt( + hourString as string, + )}:${parseInt(minuteString as string)}:${parseInt(secondString as string)}`, + ).toDate() +} diff --git a/components/date-picker/date-picker-utils.ts b/components/date-picker/date-picker-utils.ts new file mode 100644 index 000000000..2453afdda --- /dev/null +++ b/components/date-picker/date-picker-utils.ts @@ -0,0 +1,94 @@ +import { RenderLabel } from '../date-picker-view/PropsType' +import type { DatePrecision } from './date-picker-date-utils' +import * as dateUtils from './date-picker-date-utils' +import type { WeekPrecision } from './date-picker-week-utils' +import * as weekUtils from './date-picker-week-utils' +import type { PickerDate } from './util' +import { TILL_NOW } from './util' + +export type Precision = DatePrecision | WeekPrecision + +export type DatePickerFilter = Partial< + Record< + Precision, + ( + val: number, + extend: { + date: Date + }, + ) => boolean + > +> + +const precisionLengthRecord: Record = { + year: 1, + month: 2, + day: 3, + hour: 4, + minute: 5, + second: 6, +} + +export const convertDateToStringArray = ( + date: Date | undefined | null, + precision: Precision, +) => { + if (precision.includes('week')) { + return weekUtils.convertDateToStringArray(date) + } else { + const datePrecision = precision as DatePrecision + const stringArray = dateUtils.convertDateToStringArray(date) + return stringArray.slice(0, precisionLengthRecord[datePrecision]) + } +} + +export const convertStringArrayToDate = < + T extends string | number | null | undefined, +>( + value: T[], + precision: Precision, +) => { + // Special case for DATE_NOW + if (value?.[0] === TILL_NOW) { + const now: PickerDate = new Date() + now.tillNow = true + return now + } + + if (precision.includes('week')) { + return weekUtils.convertStringArrayToDate(value) + } else { + return dateUtils.convertStringArrayToDate(value) + } +} + +export const generateDatePickerColumns = ( + selected: string[], + min: Date, + max: Date, + precision: Precision, + renderLabel: RenderLabel, + filter: DatePickerFilter | undefined, + tillNow?: boolean, +) => { + if (precision.startsWith('week')) { + return weekUtils.generateDatePickerColumns( + selected, + min, + max, + precision as WeekPrecision, + renderLabel, + filter, + ) + } else { + return dateUtils.generateDatePickerColumns( + selected, + min, + max, + precision as DatePrecision, + renderLabel, + filter, + tillNow, + ) + } +} diff --git a/components/date-picker/date-picker-week-utils.ts b/components/date-picker/date-picker-week-utils.ts new file mode 100644 index 000000000..79788a763 --- /dev/null +++ b/components/date-picker/date-picker-week-utils.ts @@ -0,0 +1,141 @@ +import dayjs from 'dayjs' +import isLeapYear from 'dayjs/plugin/isLeapYear' +import isoWeek from 'dayjs/plugin/isoWeek' +import isoWeeksInYear from 'dayjs/plugin/isoWeeksInYear' +import type { ReactNode } from 'react' +import { PickerColumn } from '../picker-view/PropsType' +import type { DatePickerFilter } from './date-picker-utils' + +dayjs.extend(isoWeek) +dayjs.extend(isoWeeksInYear) +dayjs.extend(isLeapYear) + +export type WeekPrecision = 'year' | 'week' | 'week-day' + +const precisionRankRecord: Record = { + year: 0, + week: 1, + 'week-day': 2, +} + +export function generateDatePickerColumns( + selected: string[], + min: Date, + max: Date, + precision: WeekPrecision, + renderLabel: (type: WeekPrecision, data: number) => ReactNode, + filter: DatePickerFilter | undefined, +) { + const ret: PickerColumn[] = [] + + const minYear = min.getFullYear() + const maxYear = max.getFullYear() + + const rank = precisionRankRecord[precision] + + const selectedYear = parseInt(selected[0]) + const isInMinYear = selectedYear === minYear + const isInMaxYear = selectedYear === maxYear + + const minDay = dayjs(min) + const maxDay = dayjs(max) + const minWeek = minDay.isoWeek() + const maxWeek = maxDay.isoWeek() + const minWeekday = minDay.isoWeekday() + const maxWeekday = maxDay.isoWeekday() + const selectedWeek = parseInt(selected[1]) + const isInMinWeek = isInMinYear && selectedWeek === minWeek + const isInMaxWeek = isInMaxYear && selectedWeek === maxWeek + const selectedYearWeeks = dayjs(`${selectedYear}-01-01`).isoWeeksInYear() + + const generateColumn = ( + from: number, + to: number, + precision: WeekPrecision, + ) => { + let column: number[] = [] + for (let i = from; i <= to; i++) { + column.push(i) + } + const prefix = selected.slice(0, precisionRankRecord[precision]) + const currentFilter = filter?.[precision] + if (currentFilter && typeof currentFilter === 'function') { + column = column.filter((i) => + currentFilter(i, { + get date() { + const stringArray = [...prefix, i.toString()] + return convertStringArrayToDate(stringArray) + }, + }), + ) + } + return column + } + + if (rank >= precisionRankRecord.year) { + const lower = minYear + const upper = maxYear + const years = generateColumn(lower, upper, 'year') + ret.push( + years.map((v) => ({ + label: renderLabel('year', v), + value: v.toString(), + })), + ) + } + + if (rank >= precisionRankRecord.week) { + const lower = isInMinYear ? minWeek : 1 + const upper = isInMaxYear ? maxWeek : selectedYearWeeks + const weeks = generateColumn(lower, upper, 'week') + ret.push( + weeks.map((v) => ({ + label: renderLabel('week', v), + value: v.toString(), + })), + ) + } + if (rank >= precisionRankRecord['week-day']) { + const lower = isInMinWeek ? minWeekday : 1 + const upper = isInMaxWeek ? maxWeekday : 7 + const weeks = generateColumn(lower, upper, 'week-day') + ret.push( + weeks.map((v) => ({ + label: renderLabel('week-day', v), + value: v.toString(), + })), + ) + } + + return ret +} + +export function convertDateToStringArray( + date: Date | undefined | null, +): string[] { + if (!date) { + return [] + } + const day = dayjs(date) + return [ + day.isoWeekYear().toString(), + day.isoWeek().toString(), + day.isoWeekday().toString(), + ] +} + +export function convertStringArrayToDate< + T extends string | number | null | undefined, +>(value: T[]): Date { + const yearString = value[0] ?? '1900' + const weekString = value[1] ?? '1' + const weekdayString = value[2] ?? '1' + const day = dayjs() + .year(parseInt(yearString as string)) + .isoWeek(parseInt(weekString as string)) + .isoWeekday(parseInt(weekdayString as string)) + .hour(0) + .minute(0) + .second(0) + return day.toDate() +} diff --git a/components/date-picker/date-picker.tsx b/components/date-picker/date-picker.tsx new file mode 100644 index 000000000..64f4355c2 --- /dev/null +++ b/components/date-picker/date-picker.tsx @@ -0,0 +1,179 @@ +import dayjs from 'dayjs' +import React, { + forwardRef, + useCallback, + useContext, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from 'react' +import { getComponentLocale } from '../_util/getLocale' +import { mergeProps } from '../_util/with-default-props' +import useRenderLabel from '../date-picker-view/useRenderLabel' +import { LocaleContext } from '../locale-provider' +import RMCPicker, { PickerRef } from '../picker/Picker' +import { DatePickerPropsType } from './PropsType' +import { getValueExtend, usePickerValue } from './columns-extend' +import { + convertStringArrayToDate, + generateDatePickerColumns, +} from './date-picker-utils' + +export type DatePickerRef = any + +export interface DatePickerProps extends DatePickerPropsType {} + +const defaultProps = { + defaultDate: new Date(), + minDate: new Date('2000-1-1'), + maxDate: new Date('2030-1-1'), + precision: 'day', +} + +const DatePicker = forwardRef((props, ref) => { + const p = mergeProps(defaultProps, props) + const { + value, + defaultValue, + defaultDate, + minDate, + maxDate, + mode, + precision, + renderLabel, + filter, + ...restProps + } = p + + const _precision = precision || (mode === 'date' ? 'day' : mode) || 'day' + + const [innerValue, setInnerValue] = useState( + value === undefined ? defaultValue || defaultDate : value, + ) + + const _locale = getComponentLocale( + p, + useContext(LocaleContext), + 'DatePicker', + () => require('./locale/zh_CN'), + ) + + const mergedRenderLabel = useRenderLabel( + renderLabel, + _locale.DatePickerLocale, + ) + + const pickerRef = React.useRef(null) + useImperativeHandle(ref, () => pickerRef.current as PickerRef) + + const pickerValue = usePickerValue(innerValue, minDate, maxDate, _precision) + + const columns = useMemo(() => { + return generateDatePickerColumns( + pickerValue, + minDate, + maxDate, + _precision, + mergedRenderLabel, + filter, + false, + ) + }, [maxDate, mergedRenderLabel, minDate, _precision, filter, pickerValue]) + + const handleSelect = useCallback( + (val: any, i: number) => { + const pvalue = pickerValue.slice(0) + pvalue[i] = val + const { dateValue } = getValueExtend(columns, pvalue, _precision) + setInnerValue(dateValue) + p.onValueChange?.(dateValue, i) + }, + [columns, _precision, p, pickerValue], + ) + + const handleOk = useCallback( + (val, ext) => { + p.onOk?.(convertStringArrayToDate(val, _precision), ext) + }, + [_precision, p], + ) + + const handleChange = useCallback( + (val, ext) => { + p.onChange?.(convertStringArrayToDate(val, _precision), ext) + }, + [_precision, p], + ) + + // extra format + const format = useCallback( + (labels: string[]) => { + const date = convertStringArrayToDate(labels, _precision) + if (typeof p.format === 'function') { + return p.format(date) + } + + return dayjs(date).format( + typeof p.format === 'string' + ? p.format + : _precision === 'day' + ? 'YYYY-MM-DD' + : 'YYYY-MM-DD HH:mm:ss', + ) + }, + [_precision, p], + ) + + // 记录value是否变化过 + const isValueChanged = useRef(false) + + const onVisibleChange = useCallback( + (visible) => { + p.onVisibleChange?.(visible) + if (!visible && value !== innerValue && isValueChanged.current) { + // 关闭时,如果选中值不同步,恢复为原选中值 + setInnerValue(value) + } + }, + [innerValue, p, value], + ) + + // for useEffect only on update + const isInitialMount = useRef(true) + + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false + // extra update initial + pickerRef.current?._updateExtra() + } else { + isValueChanged.current = true + setInnerValue(value) + // extra update after value update + setTimeout(() => { + pickerRef.current?._updateExtra() + }) + } + }, [value]) + + return ( + + ) +}) + +DatePicker.displayName = 'Picker' + +export default DatePicker diff --git a/components/date-picker/datepicker/DatePicker.tsx b/components/date-picker/datepicker/DatePicker.tsx deleted file mode 100644 index 4cc08d59b..000000000 --- a/components/date-picker/datepicker/DatePicker.tsx +++ /dev/null @@ -1,529 +0,0 @@ -import React from 'react' -import MultiPicker from '../../picker/MultiPicker' -import Picker from '../../picker/Picker' -import DatePickerProps from './DatePickerProps' -import defaultLocale from './locale/en_US' - -function getDaysInMonth(date: any) { - return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate() -} - -function pad(n: any) { - return n < 10 ? `0${n}` : n + '' -} - -function cloneDate(date: any) { - return new Date(+date) -} - -function setMonth(date: Date, month: number) { - date.setDate( - Math.min( - date.getDate(), - getDaysInMonth(new Date(date.getFullYear(), month)), - ), - ) - date.setMonth(month) -} - -const DATETIME = 'datetime' -const DATE = 'date' -const TIME = 'time' -const MONTH = 'month' -const YEAR = 'year' -const ONE_DAY = 24 * 60 * 60 * 1000 - -class DatePicker extends React.Component { - static defaultProps = { - prefixCls: 'rmc-date-picker', - pickerPrefixCls: 'rmc-picker', - locale: defaultLocale, - mode: DATE, - disabled: false, - minuteStep: 1, - onDateChange() {}, - use12Hours: false, - } - - state = { - date: this.props.date || this.props.defaultDate, - } - - defaultMinDate: any - defaultMaxDate: any - - UNSAFE_componentWillReceiveProps(nextProps: { date: any; defaultDate: any }) { - if ('date' in nextProps) { - this.setState({ - date: nextProps.date || nextProps.defaultDate, - }) - } - } - - getNewDate = (values: any, index: any) => { - const value = parseInt(values[index], 10) - const props = this.props - const { mode } = props - const newValue = cloneDate(this.getDate()) - if (mode === DATETIME || mode === DATE || mode === YEAR || mode === MONTH) { - switch (index) { - case 0: - newValue.setFullYear(value) - break - case 1: - // Note: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setMonth - // e.g. from 2017-03-31 to 2017-02-28 - setMonth(newValue, value) - break - case 2: - newValue.setDate(value) - break - case 3: - this.setHours(newValue, value) - break - case 4: - newValue.setMinutes(value) - break - case 5: - this.setAmPm(newValue, value) - break - default: - break - } - } else if (mode === TIME) { - switch (index) { - case 0: - this.setHours(newValue, value) - break - case 1: - newValue.setMinutes(value) - break - case 2: - this.setAmPm(newValue, value) - break - default: - break - } - } - return this.clipDate(newValue) - } - - onValueChange = (values: any, index: any) => { - const props = this.props - const newValue = this.getNewDate(values, index) - if (!('date' in props)) { - this.setState({ - date: newValue, - }) - } - if (props.onDateChange) { - props.onDateChange(newValue) - } - if (props.onValueChange) { - props.onValueChange(values, index) - } - } - - onScrollChange = (values: any, index: any) => { - const props = this.props - if (props.onScrollChange) { - const newValue = this.getNewDate(values, index) - props.onScrollChange(newValue, values, index) - } - } - - setHours(date: Date, hour: number) { - if (this.props.use12Hours) { - const dh = date.getHours() - let nhour = hour - nhour = dh >= 12 ? hour + 12 : hour - nhour = nhour >= 24 ? 0 : nhour // Make sure no more than one day - date.setHours(nhour) - } else { - date.setHours(hour) - } - } - - setAmPm(date: any, index: any) { - if (index === 0) { - date.setTime(+date - ONE_DAY / 2) - } else { - date.setTime(+date + ONE_DAY / 2) - } - } - - getDefaultMinDate() { - if (!this.defaultMinDate) { - this.defaultMinDate = new Date(2000, 0, 1, 0, 0, 0) - } - return this.defaultMinDate - } - - getDefaultMaxDate() { - if (!this.defaultMaxDate) { - this.defaultMaxDate = new Date(2030, 0, 1, 23, 59, 59) - } - return this.defaultMaxDate - } - - getDate() { - return this.clipDate( - this.state.date || this.props.defaultDate || this.getDefaultMinDate(), - ) - } - - // used by rmc-picker/lib/PopupMixin.js - getValue() { - return this.getDate() - } - - getMinYear() { - return this.getMinDate().getFullYear() - } - - getMaxYear() { - return this.getMaxDate().getFullYear() - } - - getMinMonth() { - return this.getMinDate().getMonth() - } - - getMaxMonth() { - return this.getMaxDate().getMonth() - } - - getMinDay() { - return this.getMinDate().getDate() - } - - getMaxDay() { - return this.getMaxDate().getDate() - } - - getMinHour() { - return this.getMinDate().getHours() - } - - getMaxHour() { - return this.getMaxDate().getHours() - } - - getMinMinute() { - return this.getMinDate().getMinutes() - } - - getMaxMinute() { - return this.getMaxDate().getMinutes() - } - - getMinDate() { - return this.props.minDate || this.getDefaultMinDate() - } - - getMaxDate() { - return this.props.maxDate || this.getDefaultMaxDate() - } - - getDateData() { - const { locale, formatMonth, formatDay, mode } = this.props - const date = this.getDate() - const selYear = date.getFullYear() - const selMonth = date.getMonth() - const minDateYear = this.getMinYear() - const maxDateYear = this.getMaxYear() - const minDateMonth = this.getMinMonth() - const maxDateMonth = this.getMaxMonth() - const minDateDay = this.getMinDay() - const maxDateDay = this.getMaxDay() - const years: any[] = [] - for (let i = minDateYear; i <= maxDateYear; i++) { - years.push({ - value: i + '', - label: i + locale.year + '', - }) - } - const yearCol = { key: 'year', props: { children: years } } - if (mode === YEAR) { - return [yearCol] - } - - const months: any[] = [] - let minMonth = 0 - let maxMonth = 11 - if (minDateYear === selYear) { - minMonth = minDateMonth - } - if (maxDateYear === selYear) { - maxMonth = maxDateMonth - } - for (let i = minMonth; i <= maxMonth; i++) { - const label = formatMonth - ? formatMonth(i, date) - : i + 1 + locale.month + '' - months.push({ - value: i + '', - label, - }) - } - const monthCol = { key: 'month', props: { children: months } } - if (mode === MONTH) { - return [yearCol, monthCol] - } - - const days: any[] = [] - let minDay = 1 - let maxDay = getDaysInMonth(date) - - if (minDateYear === selYear && minDateMonth === selMonth) { - minDay = minDateDay - } - if (maxDateYear === selYear && maxDateMonth === selMonth) { - maxDay = maxDateDay - } - for (let i = minDay; i <= maxDay; i++) { - const label = formatDay ? formatDay(i, date) : i + locale.day + '' - days.push({ - value: i + '', - label, - }) - } - return [yearCol, monthCol, { key: 'day', props: { children: days } }] - } - - getDisplayHour(rawHour: number) { - // 12 hour am (midnight 00:00) -> 12 hour pm (noon 12:00) -> 12 hour am (midnight 00:00) - if (this.props.use12Hours) { - if (rawHour === 0) { - rawHour = 12 - } - if (rawHour > 12) { - rawHour -= 12 - } - return rawHour - } - return rawHour - } - - getTimeData(date: any) { - let minHour = 0 - let maxHour = 23 - let minMinute = 0 - let maxMinute = 59 - const { mode, locale, minuteStep, use12Hours } = this.props - const minDateMinute = this.getMinMinute() - const maxDateMinute = this.getMaxMinute() - const minDateHour = this.getMinHour() - const maxDateHour = this.getMaxHour() - const hour = date.getHours() - if (mode === DATETIME) { - const year = date.getFullYear() - const month = date.getMonth() - const day = date.getDate() - const minDateYear = this.getMinYear() - const maxDateYear = this.getMaxYear() - const minDateMonth = this.getMinMonth() - const maxDateMonth = this.getMaxMonth() - const minDateDay = this.getMinDay() - const maxDateDay = this.getMaxDay() - if ( - minDateYear === year && - minDateMonth === month && - minDateDay === day - ) { - minHour = minDateHour - if (minDateHour === hour) { - minMinute = minDateMinute - } - } - if ( - maxDateYear === year && - maxDateMonth === month && - maxDateDay === day - ) { - maxHour = maxDateHour - if (maxDateHour === hour) { - maxMinute = maxDateMinute - } - } - } else { - minHour = minDateHour - if (minDateHour === hour) { - minMinute = minDateMinute - } - maxHour = maxDateHour - if (maxDateHour === hour) { - maxMinute = maxDateMinute - } - } - - const hours: any[] = [] - if ((minHour === 0 && maxHour === 0) || (minHour !== 0 && maxHour !== 0)) { - minHour = this.getDisplayHour(minHour) - } else if (minHour === 0 && use12Hours) { - minHour = 1 - hours.push({ - value: '0', - label: locale.hour ? '12' + locale.hour : '12', - }) - } - maxHour = this.getDisplayHour(maxHour) - for (let i = minHour; i <= maxHour; i++) { - hours.push({ - value: i + '', - label: locale.hour ? i + locale.hour + '' : pad(i), - }) - } - - const minutes: any[] = [] - const selMinute = date.getMinutes() - for (let i = minMinute; i <= maxMinute; i += minuteStep!) { - minutes.push({ - value: i + '', - label: locale.minute ? i + locale.minute + '' : pad(i), - }) - if (selMinute > i && selMinute < i + minuteStep!) { - minutes.push({ - value: selMinute + '', - label: locale.minute - ? selMinute + locale.minute + '' - : pad(selMinute), - }) - } - } - const cols = [ - { key: 'hours', props: { children: hours } }, - { key: 'minutes', props: { children: minutes } }, - ].concat( - use12Hours - ? [ - { - key: 'ampm', - props: { - children: [ - { value: '0', label: locale.am }, - { value: '1', label: locale.pm }, - ], - }, - }, - ] - : [], - ) - return { cols, selMinute } - } - - clipDate(date: any) { - const { mode } = this.props - const minDate = this.getMinDate() - const maxDate = this.getMaxDate() - if (mode === DATETIME) { - if (date < minDate) { - return cloneDate(minDate) - } - if (date > maxDate) { - return cloneDate(maxDate) - } - } else if (mode === DATE || mode === YEAR || mode === MONTH) { - // compare-two-dates: https://stackoverflow.com/a/14629978/2190503 - if (+date + ONE_DAY <= minDate) { - return cloneDate(minDate) - } - if (date >= +maxDate + ONE_DAY) { - return cloneDate(maxDate) - } - } else if (mode === TIME) { - const maxHour = maxDate.getHours() - const maxMinutes = maxDate.getMinutes() - const minHour = minDate.getHours() - const minMinutes = minDate.getMinutes() - const hour = date.getHours() - const minutes = date.getMinutes() - if (hour < minHour || (hour === minHour && minutes < minMinutes)) { - return cloneDate(minDate) - } - if (hour > maxHour || (hour === maxHour && minutes > maxMinutes)) { - return cloneDate(maxDate) - } - } - return date - } - - getValueCols() { - const { mode, use12Hours } = this.props - const date = this.getDate() - let cols: any[] = [] - let value: any[] = [] - - if (mode === YEAR) { - return { - cols: this.getDateData(), - value: [date.getFullYear() + ''], - } - } - - if (mode === MONTH) { - return { - cols: this.getDateData(), - value: [date.getFullYear() + '', date.getMonth() + ''], - } - } - - if (mode === DATETIME || mode === DATE) { - cols = this.getDateData() - value = [ - date.getFullYear() + '', - date.getMonth() + '', - date.getDate() + '', - ] - } - - if (mode === DATETIME || mode === TIME) { - const time = this.getTimeData(date) - cols = cols.concat(time.cols) - const hour = date.getHours() - let dtValue = [hour + '', time.selMinute + ''] - let nhour = hour - if (use12Hours) { - nhour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour - dtValue = [nhour + '', time.selMinute + '', (hour >= 12 ? 1 : 0) + ''] - } - value = value.concat(dtValue) - } - return { - value, - cols, - } - } - - render() { - const { value, cols } = this.getValueCols() - const { disabled, rootNativeProps, style, itemStyle } = this.props - - return ( - - {cols.map((p) => ( - - {p.props.children.map((item: any) => { - return ( - - {item.label} - - ) - })} - - ))} - - ) - } -} - -export default DatePicker diff --git a/components/date-picker/datepicker/DatePickerProps.tsx b/components/date-picker/datepicker/DatePickerProps.tsx deleted file mode 100644 index afdc136bc..000000000 --- a/components/date-picker/datepicker/DatePickerProps.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { StyleProp, TextStyle, ViewStyle } from 'react-native' - -interface DatePickerProps { - date?: any - defaultDate?: any - minDate?: any - maxDate?: any - mode?: string - disabled?: boolean - locale?: any - minuteStep?: number - formatMonth?: (month: number, date?: any) => any - formatDay?: (day: number, date?: any) => any - onDateChange?: (date: any) => void - onValueChange?: (vals: any, index: number) => void - itemStyle?: StyleProp - style?: StyleProp - onScrollChange?: (date: any, vals: any, index: number) => void - rootNativeProps?: {} - use12Hours?: boolean -} - -export default DatePickerProps diff --git a/components/date-picker/datepicker/Popup.tsx b/components/date-picker/datepicker/Popup.tsx deleted file mode 100644 index c27a95fa7..000000000 --- a/components/date-picker/datepicker/Popup.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import PopupPicker from '../../picker/Popup' -import { PopupPickerProps } from '../../picker/PopupPickerTypes' -import DatePickerProps from './DatePickerProps' - -export interface PopupDatePickerProps extends PopupPickerProps { - datePicker: React.ReactElement - onChange?: (date?: any) => void - date?: any -} - -class PopupDatePicker extends React.Component { - static defaultProps = { - pickerValueProp: 'date', - pickerValueChangeProp: 'onDateChange', - } - - onOk = (v: any) => { - const { onChange, onOk } = this.props - if (onChange) { - onChange(v) - } - if (onOk) { - onOk(v) - } - } - - render() { - return ( - - ) - } -} - -export default PopupDatePicker diff --git a/components/date-picker/datepicker/PopupStyles.tsx b/components/date-picker/datepicker/PopupStyles.tsx deleted file mode 100644 index 702919b45..000000000 --- a/components/date-picker/datepicker/PopupStyles.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '../../picker/PopupStyles' diff --git a/components/date-picker/datepicker/index.tsx b/components/date-picker/datepicker/index.tsx deleted file mode 100644 index 1827e7506..000000000 --- a/components/date-picker/datepicker/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DatePicker' diff --git a/components/date-picker/datepicker/locale/en_US.tsx b/components/date-picker/datepicker/locale/en_US.tsx deleted file mode 100644 index 42c9b02df..000000000 --- a/components/date-picker/datepicker/locale/en_US.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default { - year: '', - month: '', - day: '', - hour: '', - minute: '', - am: 'AM', - pm: 'PM', -} diff --git a/components/date-picker/datepicker/locale/es_ES.tsx b/components/date-picker/datepicker/locale/es_ES.tsx deleted file mode 100644 index 42c9b02df..000000000 --- a/components/date-picker/datepicker/locale/es_ES.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default { - year: '', - month: '', - day: '', - hour: '', - minute: '', - am: 'AM', - pm: 'PM', -} diff --git a/components/date-picker/datepicker/locale/fa_IR.tsx b/components/date-picker/datepicker/locale/fa_IR.tsx deleted file mode 100644 index 9809570b1..000000000 --- a/components/date-picker/datepicker/locale/fa_IR.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default { - year: 'سال', - month: 'ماه', - day: 'روز', - hour: 'ساعت', - minute: 'دقیقه', - am: 'صبح', - pm: 'بعد از ظهر', -} diff --git a/components/date-picker/datepicker/locale/ko_KR.tsx b/components/date-picker/datepicker/locale/ko_KR.tsx new file mode 100644 index 000000000..668fd5683 --- /dev/null +++ b/components/date-picker/datepicker/locale/ko_KR.tsx @@ -0,0 +1,9 @@ +export default { + year: '년', + month: '월', + day: '일', + hour: '시간', + minute: '분', + am: '오전', + pm: '오후', +} diff --git a/components/date-picker/datepicker/locale/pt_BR.tsx b/components/date-picker/datepicker/locale/pt_BR.tsx deleted file mode 100644 index 9266e1468..000000000 --- a/components/date-picker/datepicker/locale/pt_BR.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default { - year: 'Ano', - month: 'Mês', - day: 'Dia', - hour: 'Hora', - minute: 'Minuto', - am: 'AM', - pm: 'PM', -} diff --git a/components/date-picker/datepicker/locale/ru_RU.tsx b/components/date-picker/datepicker/locale/ru_RU.tsx deleted file mode 100644 index 42c9b02df..000000000 --- a/components/date-picker/datepicker/locale/ru_RU.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default { - year: '', - month: '', - day: '', - hour: '', - minute: '', - am: 'AM', - pm: 'PM', -} diff --git a/components/date-picker/datepicker/locale/sv_SE.tsx b/components/date-picker/datepicker/locale/sv_SE.tsx deleted file mode 100644 index 42c9b02df..000000000 --- a/components/date-picker/datepicker/locale/sv_SE.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default { - year: '', - month: '', - day: '', - hour: '', - minute: '', - am: 'AM', - pm: 'PM', -} diff --git a/components/date-picker/datepicker/locale/zh_CN.tsx b/components/date-picker/datepicker/locale/zh_CN.tsx deleted file mode 100644 index 442b57afa..000000000 --- a/components/date-picker/datepicker/locale/zh_CN.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default { - year: '年', - month: '月', - day: '日', - hour: '时', - minute: '分', - am: '上午', - pm: '下午', -} diff --git a/components/date-picker/index.en-US.md b/components/date-picker/index.en-US.md index 17cfa8b90..a3d2353a5 100644 --- a/components/date-picker/index.en-US.md +++ b/components/date-picker/index.en-US.md @@ -7,31 +7,57 @@ title: DatePicker Used to select a date or time. ### Rules -- A maximum of five independent rollers are shown, each of which represents a different value. +- At most accurate to seconds. ## API +```ts +type Precision = + | 'week' + | 'week-day' + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + +type DatePickerFilter = Partial< + Record< + Precision, + ( + val: number, + extend: { + date: Date + } + ) => boolean + > +> +``` + +Properties | Descrition | Type | Default | Version +-----------|------------|------|--------|-------- +| precision | Precision | `Precision` | `day` |`5.1.0`| +| value | the currently selected value | Date | - || +| defaultValue | the default selected value | Date | - || +| minDate | minimum date | Date | 2000-1-1 || +| maxDate | maximum date | Date | 2030-1-1 || +| onChange | change handler | `(value: Date) => void` | - || +| onValueChange | fire when picker col change | `(value: Date, index: number) => void` | - || +| renderLabel | The function to custom rendering the label shown on a column. `type` means any value in `precision`, `data` means the default number | `(type:Precision / 'now', data: number) => ReactNode` | - || +| locale | international, can override the configuration of the global [LocaleProvider](/components/locale-provider) | Object: Object: {okText, dismissText, extra, `DatePickerLocale:{ year,month,day,hour,minute,am,pm }`} | - | +| filter | Filter available time | `DatePickerFilter` | - | `5.1.0` | + + +In addition, the following attributes of `Picker` are supported: `onPickerChange` `onVisibleChange` `style` `styles` `itemStyle` `itemHeight` `numberOfLines` `title` `okText` `dismissText` `visible` `children` `renderMaskTop` `renderMaskBottom` + +### Children +Same as [Picker](/components/picker/#Children), except type `format` is different: + Properties | Descrition | Type | Default ------------|------------|------|-------- -| mode | mode value, can be a `date` or `time` or `datetime` or `year` or `month` | String | `date` | -| value | the currently selected value | Date | - | -| defaultDate | the default selected value | Date | - | -| minDate | minimum date | Date | 2000-1-1 | -| maxDate | maximum date | Date | 2030-1-1 | -| minuteStep | The amount of time, in minutes, between each minute item. | Number | 1 | -| locale | international, can override the configuration of the global `[LocaleProvider](https://mobile.ant.design/components/locale-provider)` | Object: {DatePickerLocale: {year, month, day, hour, minute, am?, pm?}, okText, dismissText} | - | -| disabled | set disabled | Boolean | false | -| onChange | change handler | (date: Object): void | - | -| onValueChange | fire when picker col change | (vals: any, index: number) => void | - | -| format | format the selected value | `(value: Date) => date string` / `format string`(corresponding mode under the format are: `YYYY-MM-DD` or `HH:mm` or `YYYY-MM-DD HH:mm`) | - | -| title | title | string/React.ReactElement | - | -| itemStyle | itemStyle | StyleProp; - | - | -| extra | the display text | String | `请选择` | -| onOk | handler called when click ok | (val): void | - | -| onDismiss | handler called when click cancel | (): void | - | - -Note: The date strings have different implementations in different browsers. For example, `new Date ('2017-1-1')` is an Invalid Date on Safari but is parsed properly on Chrome. - -Note: We suggest DatePicker's children to be `List.Item`, if not, you need to be a custom component which accept and handle `onClick` / `extra` / `chidlren` props, see [demo](/components/date-picker) +----|-----|------|------ +| format | format the selected value |`(value: Date) => date string` | import [Day.js Format](https://day.js.org/docs/en/parse/string-format), precision:`YYYY-MM-DD`,`YYYY-MM-DD HH:mm:ss`| + +### Ref +Same as `Picker` \ No newline at end of file diff --git a/components/date-picker/index.tsx b/components/date-picker/index.tsx index 83edcffa7..ae7f1b350 100644 --- a/components/date-picker/index.tsx +++ b/components/date-picker/index.tsx @@ -1,71 +1,3 @@ -import React from 'react' -import PickerStyles, { PickerStyle } from '../picker/style/index' -import { WithTheme, WithThemeStyles } from '../style' -import { getComponentLocale } from '../_util/getLocale' -import AntDatePicker from './datepicker' -import PopupDatePicker from './datepicker/Popup' -import { DatePickerPropsType } from './PropsType' -import { formatProps } from './utils' -import { LocaleContext } from '../locale-provider' +import DatePicker from './date-picker' -export interface DatePickerProps - extends DatePickerPropsType, - WithThemeStyles { - triggerTypes?: string -} - -export default class DatePicker extends React.Component { - static defaultProps = { - mode: 'datetime', - triggerType: 'onPress', - minuteStep: 1, - } - static contextType = LocaleContext - render() { - const { children, value, defaultDate, itemStyle, ...restProps } = this.props - const locale = getComponentLocale( - this.props, - (this as any).context, - 'DatePicker', - () => require('./locale/zh_CN'), - ) - - const { okText, dismissText, extra, DatePickerLocale } = locale - - const dataPicker = ( - - ) - - return ( - - {(styles) => ( - - {children && - React.isValidElement(children) && - React.cloneElement(children as any, { - extra: value - ? formatProps(this.props, value) - : this.props.extra || extra, - })} - - )} - - ) - } -} +export default DatePicker diff --git a/components/date-picker/index.zh-CN.md b/components/date-picker/index.zh-CN.md index 7cb049bfd..f62255e00 100644 --- a/components/date-picker/index.zh-CN.md +++ b/components/date-picker/index.zh-CN.md @@ -8,31 +8,57 @@ subtitle: 日期选择 用于选择日期或者时间。 ### 规则 -- 最多展示 5 个独立滚轮,每个滚轮表示一个不同的值。 +- 最多精确到秒。 ## API +```ts +type Precision = + | 'week' + | 'week-day' + | 'year' + | 'month' + | 'day' + | 'hour' + | 'minute' + | 'second' + +type DatePickerFilter = Partial< + Record< + Precision, + ( + val: number, + extend: { + date: Date + } + ) => boolean + > +> +``` + +属性 | 说明 | 类型 | 默认值 | 版本 +----|-----|------|------|------ +| precision | 精度 | `Precision` | `day` |`5.1.0`| +| value | 当前选中时间 | Date | 无 || +| defaultValue | 默认选中时间 | Date | 无 || +| minDate | 最小可选日期 | Date | 2000-1-1 || +| maxDate | 最大可选日期 | Date | 2030-1-1 || +| onChange | 时间发生变化的回调函数 | `(value: Date) => void` | - || +| onValueChange | 每列 picker 改变时的回调 | `(value: Date, index: number) => void` | - || +| renderLabel | 自定义渲染每列展示的内容。其中 `type` 参数为 `precision` 中的任意值或 `now`,`data` 参数为默认渲染的数字 | `(type:Precision / 'now', data: number) => ReactNode` | - || +| locale | 国际化,可覆盖全局[LocaleProvider](/components/locale-provider-cn)的配置 | Object: {okText, dismissText, extra, `DatePickerLocale:{ year,month,day,hour,minute,am,pm }`} | - | +| filter | 过滤可供选择的时间 | `DatePickerFilter` | - | `5.1.0` | + + +此外还支持 Picker 的以下属性:`onPickerChange` `onVisibleChange` `style` `styles` `itemStyle` `itemHeight` `numberOfLines` `title` `okText` `dismissText` `visible` `children` `renderMaskTop` `renderMaskBottom` + +### Children +同 [Picker](/components/picker-cn/#Children),其中`format`类型不同: + 属性 | 说明 | 类型 | 默认值 ----|-----|------|------ -| mode | 日期选择的类型, 可以是日期`date`,时间`time`,日期+时间`datetime`,年`year`,月`month` | String | `date` | -| value | 当前选中时间 | Date | 无 | -| defaultDate | 默认选中时间 | Date | 无 | -| minDate | 最小可选日期 | Date | 2000-1-1 | -| maxDate | 最大可选日期 | Date | 2030-1-1 | -| minuteStep | 分钟数递增步长设置 | Number | 1 | -| locale | 国际化,可覆盖全局`[LocaleProvider](https://mobile.ant.design/components/locale-provider)`的配置 | Object: {DatePickerLocale: {year, month, day, hour, minute, am?, pm?}, okText, dismissText } | - | -| disabled | 是否不可用 | Boolean | false | -| onChange | 时间发生变化的回调函数 | (date: Object): void | - | -| onValueChange | 每列 picker 改变时的回调 | (vals: any, index: number) => void | - | -| format | 格式化选中的值 | `(value: Date) => date string` / `format string`(对应 mode 下格式分别为:`YYYY-MM-DD`,`HH:mm`,`YYYY-MM-DD HH:mm`) | - | -| title | 弹框的标题 | string/React.ReactElement | 无 | -| itemStyle | itemStyle | StyleProp; - | - | -| extra | 显示文案 | String | `请选择` | -| onOk | 点击选中时执行的回调 | (val): void | 无 | -| onDismiss | 点击取消时执行的回调 | (): void | 无 | - -注意:日期字符串在不同浏览器有不同的实现,例如 `new Date('2017-1-1')` 在 Safari 上是 Invalid Date,而在 Chrome 上是能正常解析的。 - -注意:`DatePicker` children 建议是 `List.Item`, 如果不是,需要是自定义组件(组件内需处理 `onClick` / `extra` / `children` 属性,详情请看 [demo](/components/date-picker-cn#demoTitle) +| format | 格式化选中的值 |`(value: Date) => date string` | 引用 [Day.js Format](https://day.js.org/docs/en/parse/string-format),参数对应精度:`YYYY-MM-DD`,`YYYY-MM-DD HH:mm:ss`| + +### Ref +同 `Picker`。 diff --git a/components/date-picker/locale/en_US.tsx b/components/date-picker/locale/en_US.tsx index 68106d368..9fb4189d0 100644 --- a/components/date-picker/locale/en_US.tsx +++ b/components/date-picker/locale/en_US.tsx @@ -1,8 +1,7 @@ -import DatePickerLocale from '../datepicker/locale/en_US' +import DatePickerLocale from '../../date-picker-view/locale/en_US' +import PickerLocale from '../../picker/locale/en_US' export default { - okText: 'OK', - dismissText: 'Cancel', - extra: 'please select', + ...PickerLocale, DatePickerLocale, } diff --git a/components/date-picker/locale/es_ES.tsx b/components/date-picker/locale/es_ES.tsx index 8d8551d16..0c092bfb8 100644 --- a/components/date-picker/locale/es_ES.tsx +++ b/components/date-picker/locale/es_ES.tsx @@ -1,8 +1,7 @@ -import DatePickerLocale from '../datepicker/locale/es_ES' +import DatePickerLocale from '../../date-picker-view/locale/es_ES' +import PickerLocale from '../../picker/locale/es_ES' export default { - okText: 'OK', - dismissText: 'Cancelar', - extra: 'Seleccione', + ...PickerLocale, DatePickerLocale, } diff --git a/components/date-picker/locale/fa_IR.tsx b/components/date-picker/locale/fa_IR.tsx index 516e2692a..2f8dd4308 100644 --- a/components/date-picker/locale/fa_IR.tsx +++ b/components/date-picker/locale/fa_IR.tsx @@ -1,8 +1,7 @@ -import DatePickerLocale from '../datepicker/locale/fa_IR' +import DatePickerLocale from '../../date-picker-view/locale/fa_IR' +import PickerLocale from '../../picker/locale/fa_IR' export default { - okText: 'تایید', - dismissText: 'لغو', - extra: 'لطفا انتخاب کنید', + ...PickerLocale, DatePickerLocale, } diff --git a/components/date-picker/locale/id_ID.tsx b/components/date-picker/locale/id_ID.tsx new file mode 100644 index 000000000..76d1c06a1 --- /dev/null +++ b/components/date-picker/locale/id_ID.tsx @@ -0,0 +1,7 @@ +import DatePickerLocale from '../../date-picker-view/locale/id_ID' +import PickerLocale from '../../picker/locale/id_ID' + +export default { + ...PickerLocale, + DatePickerLocale, +} diff --git a/components/date-picker/locale/ko_KR.tsx b/components/date-picker/locale/ko_KR.tsx new file mode 100644 index 000000000..69a87e0de --- /dev/null +++ b/components/date-picker/locale/ko_KR.tsx @@ -0,0 +1,7 @@ +import DatePickerLocale from '../../date-picker-view/locale/ko_KR' +import PickerLocale from '../../picker/locale/ko_KR' + +export default { + ...PickerLocale, + DatePickerLocale, +} diff --git a/components/date-picker/locale/pt_BR.tsx b/components/date-picker/locale/pt_BR.tsx index 4161e555e..6f0c4d4a6 100644 --- a/components/date-picker/locale/pt_BR.tsx +++ b/components/date-picker/locale/pt_BR.tsx @@ -1,8 +1,7 @@ -import DatePickerLocale from '../datepicker/locale/pt_BR' +import DatePickerLocale from '../../date-picker-view/locale/pt_BR' +import PickerLocale from '../../picker/locale/pt_BR' export default { - okText: 'OK', - dismissText: 'Cancelar', - extra: 'Selecione', + ...PickerLocale, DatePickerLocale, } diff --git a/components/date-picker/locale/ru_RU.tsx b/components/date-picker/locale/ru_RU.tsx index 9df3bd696..c0d8425ff 100644 --- a/components/date-picker/locale/ru_RU.tsx +++ b/components/date-picker/locale/ru_RU.tsx @@ -1,8 +1,7 @@ -import DatePickerLocale from '../datepicker/locale/en_US' +import DatePickerLocale from '../../date-picker-view/locale/ru_RU' +import PickerLocale from '../../picker/locale/ru_RU' export default { - okText: 'Ок', - dismissText: 'Отмена', - extra: '', + ...PickerLocale, DatePickerLocale, } diff --git a/components/date-picker/locale/sv_SE.tsx b/components/date-picker/locale/sv_SE.tsx index 2ee1f27d3..fcd011120 100644 --- a/components/date-picker/locale/sv_SE.tsx +++ b/components/date-picker/locale/sv_SE.tsx @@ -1,8 +1,7 @@ -import DatePickerLocale from '../datepicker/locale/en_US' +import DatePickerLocale from '../../date-picker-view/locale/sv_SE' +import PickerLocale from '../../picker/locale/sv_SE' export default { - okText: 'Ok', - dismissText: 'Avbryt', - extra: 'vänligen välj', + ...PickerLocale, DatePickerLocale, } diff --git a/components/date-picker/locale/zh_CN.tsx b/components/date-picker/locale/zh_CN.tsx index 76c0b536b..ca81ee9e0 100644 --- a/components/date-picker/locale/zh_CN.tsx +++ b/components/date-picker/locale/zh_CN.tsx @@ -1,8 +1,7 @@ -import DatePickerLocale from '../datepicker/locale/zh_CN' +import DatePickerLocale from '../../date-picker-view/locale/zh_CN' +import PickerLocale from '../../picker/locale/zh_CN' export default { - okText: '确定', - dismissText: '取消', - extra: '请选择', + ...PickerLocale, DatePickerLocale, } diff --git a/components/date-picker/util.ts b/components/date-picker/util.ts new file mode 100644 index 000000000..7a209410a --- /dev/null +++ b/components/date-picker/util.ts @@ -0,0 +1,5 @@ +export const TILL_NOW = 'TILL_NOW' + +export type PickerDate = Date & { + tillNow?: boolean +} diff --git a/components/date-picker/utils.tsx b/components/date-picker/utils.tsx deleted file mode 100644 index 2bfe17bc8..000000000 --- a/components/date-picker/utils.tsx +++ /dev/null @@ -1,48 +0,0 @@ -function formatIt(date: Date, form: string) { - const pad = (n: number) => (n < 10 ? `0${n}` : n) - const dateStr = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad( - date.getDate(), - )}` - const timeStr = `${pad(date.getHours())}:${pad(date.getMinutes())}` - if (form === 'YYYY-MM-DD') { - return dateStr - } - if (form === 'HH:mm') { - return timeStr - } - return `${dateStr} ${timeStr}` -} - -export function formatFn(instance: any, value: Date) { - const formatsEnum = { - date: 'YYYY-MM-DD', - time: 'HH:mm', - datetime: 'YYYY-MM-DD HH:mm', - } - const { format } = instance.props - const type = typeof format - if (type === 'string') { - return formatIt(value, format) - } - if (type === 'function') { - return format(value) - } - return formatIt(value, (formatsEnum as any)[instance.props.mode]) -} - -export function formatProps(props: any, value: Date) { - const formatsEnum = { - date: 'YYYY-MM-DD', - time: 'HH:mm', - datetime: 'YYYY-MM-DD HH:mm', - } - const { format } = props - const type = typeof format - if (type === 'string') { - return formatIt(value, format) - } - if (type === 'function') { - return format(value) - } - return formatIt(value, (formatsEnum as any)[props.mode]) -} diff --git a/components/grid/__tests__/__snapshots__/demo.test.js.snap b/components/grid/__tests__/__snapshots__/demo.test.js.snap index 7f80f6d0b..01ff63f62 100644 --- a/components/grid/__tests__/__snapshots__/demo.test.js.snap +++ b/components/grid/__tests__/__snapshots__/demo.test.js.snap @@ -919,6 +919,7 @@ exports[`renders ./components/grid/demo/basic.tsx correctly 1`] = ` automaticallyAdjustContentInsets={false} autoplay={false} autoplayInterval={3000} + collapsable={false} contentOffset={ Object { "x": 0, @@ -931,6 +932,8 @@ exports[`renders ./components/grid/demo/basic.tsx correctly 1`] = ` dots={true} horizontal={true} infinite={false} + onGestureHandlerEvent={[Function]} + onGestureHandlerStateChange={[Function]} onMomentumScrollEnd={[Function]} onScrollBeginDrag={[Function]} onScrollEndDrag={[Function]} diff --git a/components/image-picker/CameraRollPicker.tsx b/components/image-picker/CameraRollPicker.tsx deleted file mode 100644 index 07929ee83..000000000 --- a/components/image-picker/CameraRollPicker.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { CameraRoll } from '@react-native-camera-roll/camera-roll' -import React, { Component } from 'react' -import { - GetPhotosParamType, - Platform, - StyleSheet, - View, - ViewStyle, -} from 'react-native' -import ListView from '../list-view' -import ImageItem from './ImageItem' - -export interface CameraRollPickerStyle { - wrapper: ViewStyle - row: ViewStyle - marker: ViewStyle - spinner: ViewStyle -} -const styles = StyleSheet.create({ - wrapper: { - flex: 1, - }, - row: { - flexDirection: 'row', - flex: 1, - }, - marker: { - position: 'absolute', - top: 5, - backgroundColor: 'transparent', - }, - spinner: {}, -}) - -export interface CameraRollPickerProps extends GetPhotosParamType { - maximum: number - selectSingleItem?: boolean - imagesPerRow: number - imageMargin: number - containerWidth?: number - callback?: (...args: any[]) => any - selected?: any[] - selectedMarker?: React.ReactElement - backgroundColor?: string -} -export type CameraRollPickerState = { - selected: any - images: any[] -} -class CameraRollPicker extends Component< - CameraRollPickerProps, - CameraRollPickerState -> { - static defaultProps = { - groupTypes: 'SavedPhotos', - maximum: 15, - imagesPerRow: 6, - imageMargin: 4, - first: 50, - selectSingleItem: false, - assetType: 'Photos', - backgroundColor: 'white', - selected: [], - callback: function (selectedImages: any, currentImage: any) { - // tslint:disable-next-line:no-console - console.log(currentImage) - // tslint:disable-next-line:no-console - console.log(selectedImages) - }, - } - after: string | undefined - constructor(props: CameraRollPickerProps) { - super(props) - this.state = { - images: [], - selected: this.props.selected, - } - } - - UNSAFE_componentWillReceiveProps(nextProps: CameraRollPickerProps) { - this.setState({ - selected: nextProps.selected, - }) - } - - onFetch = async (_ = 1, startFetch: any, abortFetch: () => void) => { - try { - const { assetType, groupTypes, first, groupName, mimeTypes } = this.props - - const params: GetPhotosParamType = { - first, - after: this.after, - assetType: assetType, - groupName, - mimeTypes, - } - if (Platform.OS !== 'android') { - params.groupTypes = groupTypes - } - const res = await CameraRoll.getPhotos(params) - if (res) { - const data = res.edges - if (res.page_info) { - this.after = res.page_info.has_next_page - ? res.page_info.end_cursor - : '' - } - startFetch(data, first) - } - } catch (err) { - if (__DEV__) { - // tslint:disable-next-line:no-console - console.error(err) - } - abortFetch() // manually stop the refresh or pagination if it encounters network error - } - } - render() { - const { imageMargin, backgroundColor, imagesPerRow } = this.props - - return ( - - this._renderImage(item)} - /> - - ) - } - _renderImage = (item: any) => { - const { selected } = this.state - const { imageMargin, selectedMarker, imagesPerRow, containerWidth } = - this.props - const uri = item.node.image.uri - const isSelected = - this._arrayObjectIndexOf(selected, 'uri', uri) >= 0 ? true : false - return ( - - ) - } - - _selectImage(image: { uri: any }) { - const { maximum, callback, selectSingleItem } = this.props - const selected = this.state.selected - const index = this._arrayObjectIndexOf(selected, 'uri', image.uri) - if (index >= 0) { - selected.splice(index, 1) - } else { - if (selectSingleItem) { - selected.splice(0, selected.length) - } - if (selected.length < maximum!) { - selected.push(image) - } - } - this.setState({ - selected: selected, - }) - callback!(selected, image) - } - _nEveryRow(data: any, n: number) { - const result = [] - let temp = [] - for (let i = 0; i < data.length; ++i) { - if (i > 0 && i % n === 0) { - result.push(temp) - temp = [] - } - temp.push(data[i]) - } - if (temp.length > 0) { - while (temp.length !== n) { - temp.push(null) - } - result.push(temp) - } - return result - } - _arrayObjectIndexOf(array: any, property: string, value: any) { - return array - .map((o: any) => { - return o[property] - }) - .indexOf(value) - } -} - -export default CameraRollPicker diff --git a/components/image-picker/ImageItem.tsx b/components/image-picker/ImageItem.tsx deleted file mode 100644 index c60652c18..000000000 --- a/components/image-picker/ImageItem.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { Component } from 'react' -import { - Dimensions, - Image, - ImageStyle, - StyleSheet, - TouchableOpacity, -} from 'react-native' -import Icon from '../icon' -export type ImageItemProps = { - item?: any - selected?: boolean - selectedMarker?: React.ReactElement - imageMargin: number - containerWidth?: number - imagesPerRow: number - onPress?: (...args: any[]) => any -} -class ImageItem extends Component { - static defaultProps = { - item: {}, - selected: false, - } - _imageSize: number - constructor(props: ImageItemProps) { - super(props) - } - UNSAFE_componentWillMount() { - let { width } = Dimensions.get('window') - const { imageMargin, imagesPerRow, containerWidth } = this.props - if (typeof containerWidth !== 'undefined') { - width = containerWidth - } - this._imageSize = (width - (imagesPerRow + 1) * imageMargin) / imagesPerRow - } - render() { - const { item, selected, selectedMarker, imageMargin } = this.props - if (!item) { - return null - } - const marker = selectedMarker ? ( - selectedMarker - ) : ( - - ) - const image = item.node.image - return ( - this._handleClick(image)}> - - {selected ? marker : null} - - ) - } - _handleClick(item: any) { - if (this.props.onPress) { - this.props.onPress(item) - } - } -} -const styles = StyleSheet.create<{ - marker: ImageStyle -}>({ - marker: { - position: 'absolute', - top: 5, - right: 5, - backgroundColor: 'transparent', - }, -}) - -export default ImageItem diff --git a/components/image-picker/ImageRoll.tsx b/components/image-picker/ImageRoll.tsx deleted file mode 100644 index d7bbd1b48..000000000 --- a/components/image-picker/ImageRoll.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react' -import { - Modal, - StatusBar, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native' -import varibles from '../style/themes/default' -import CameraRollPicker, { CameraRollPickerProps } from './CameraRollPicker' - -export interface ImageRollProps extends ImageRollTexts { - onCancel: () => void - onSelected: (imgObj: {}) => void - cameraPickerProps?: CameraRollPickerProps -} - -export interface ImageRollTexts { - title?: React.ReactNode - cancelText?: React.ReactNode -} - -const styles = StyleSheet.create({ - statusBarBg: { - height: 5 * 4, - }, - naviBar: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - borderBottomWidth: 1, - borderBottomColor: '#d9d9d9', - height: 11 * 4, - }, - barTitle: { - flex: 1, - textAlign: 'center', - fontWeight: '500', - marginLeft: 7 * 4, - fontSize: 16, - }, - rightBtn: { - width: 14 * 4, - color: varibles.brand_primary, - fontSize: 16, - }, -}) - -export default class ImageRoll extends React.Component { - static defaultProps = { - title: '图片', - cancelText: '取消', - cameraPickerProps: {}, - } - onSelected = (images: any[], _: any) => { - this.props.onSelected(images[0]) - this.props.onCancel() - } - render() { - const { title, cancelText, cameraPickerProps } = this.props - - return ( - {}} - transparent={false}> - - - - - {title} - - {cancelText} - - - - - - ) - } -} diff --git a/components/image-picker/PropsType.tsx b/components/image-picker/PropsType.tsx deleted file mode 100644 index db1801407..000000000 --- a/components/image-picker/PropsType.tsx +++ /dev/null @@ -1,11 +0,0 @@ -// import React from 'react'; - -export interface ImagePickerPropTypes { - styles?: {} - files?: Array<{}> - onChange?: (files: Array<{}>, operationType: string, index?: number) => void - onImageClick?: (index?: number, files?: Array<{}>) => void - onAddImageClick?: () => void - onFail?: (msg: string) => void - selectable?: boolean -} diff --git a/components/image-picker/__tests__/__snapshots__/demo.test.js.snap b/components/image-picker/__tests__/__snapshots__/demo.test.js.snap deleted file mode 100644 index 913b3579e..000000000 --- a/components/image-picker/__tests__/__snapshots__/demo.test.js.snap +++ /dev/null @@ -1,697 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders ./components/image-picker/demo/basic.tsx correctly 1`] = ` - - - - - - - - - × - - - - - - - - - - × - - - - - - - - - - × - - - - - - - - - - × - - - - - - - - - - × - - - - - - - - - - × - - - - - - + - - - - - - - - + - - - - -`; diff --git a/components/image-picker/__tests__/demo.test.js b/components/image-picker/__tests__/demo.test.js deleted file mode 100644 index 27ee9039b..000000000 --- a/components/image-picker/__tests__/demo.test.js +++ /dev/null @@ -1,3 +0,0 @@ -import rnDemoTest from '../../../tests/shared/demoTest' - -rnDemoTest('image-picker') diff --git a/components/image-picker/demo/basic.md b/components/image-picker/demo/basic.md deleted file mode 100644 index d0ef454c3..000000000 --- a/components/image-picker/demo/basic.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -order: 0 -title: - zh-CN: 基本 - en-US: Basic ---- - -[Demo Source Code](https://github.com/ant-design/ant-design-mobile-rn/blob/master/components/image-picker/demo/basic.tsx) - -````jsx -// todos: include('./basic.tsx') -```` diff --git a/components/image-picker/demo/basic.tsx b/components/image-picker/demo/basic.tsx deleted file mode 100644 index 8332c6adb..000000000 --- a/components/image-picker/demo/basic.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react' -import { PermissionsAndroid, Platform, Text, View } from 'react-native' -import { ImagePicker, WhiteSpace } from '../../' - -export default class ImagePickerExample extends React.Component { - constructor(props: any) { - super(props) - this.state = { - files: [ - { - url: 'https://zos.alipayobjects.com/rmsportal/WCxfiPKoDDHwLBM.png', - id: '2121', - }, - { - url: 'https://zos.alipayobjects.com/rmsportal/WCxfiPKoDDHwLBM.png', - id: '2122', - }, - { - url: 'https://zos.alipayobjects.com/rmsportal/WCxfiPKoDDHwLBM.png', - id: '2123', - }, - { - url: 'https://zos.alipayobjects.com/rmsportal/WCxfiPKoDDHwLBM.png', - id: '2124', - }, - { - url: 'https://zos.alipayobjects.com/rmsportal/WCxfiPKoDDHwLBM.png', - id: '2125', - }, - { - url: 'https://zos.alipayobjects.com/rmsportal/WCxfiPKoDDHwLBM.png', - id: '2126', - }, - ], - files2: [], - } - } - - handleFileChange = (files: any) => { - this.setState({ - files, - }) - } - - handleFile2Change = (files2: any) => { - this.setState({ - files2, - }) - } - async requestCameraPermission() { - try { - const granted = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, - { - title: '需要访问相册', - message: '需要访问相册', - } as any, - ) - if (granted === PermissionsAndroid.RESULTS.GRANTED) { - this.setState({ - granted: true, - }) - } else { - this.setState({ - granted: false, - }) - } - } catch (err) { - console.warn(err) - } - } - async componentDidMount() { - if (Platform.OS === 'android') { - await this.requestCameraPermission() - } - } - - render() { - if (Platform.OS === 'android' && !this.state.granted) { - return 需要访问相册的权限 - } - return ( - - - - - - ) - } -} diff --git a/components/image-picker/index.en-US.md b/components/image-picker/index.en-US.md deleted file mode 100644 index 7451d6ab0..000000000 --- a/components/image-picker/index.en-US.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -category: Components -type: Data Entry -title: ImagePicker ---- - -Note: Just for selecting picture. Generally `ImagePicker` is used to select picture before uploading, but without the feature of uploading. - -If you have permission issues Please checkout https://github.com/ant-design/ant-design-mobile-rn/issues/90 - -## API - -| Properties | Descrition | Type | Default | -| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------- | -| styles | Styles object for the various elements of the ImagePicker | Object | See `/components/image-picker/style/index.tsx` for the defaults | -| files | Picture files array which includes `url`(required) in each object | Array | [] | -| onChange | Callback is called when the value of `files` is changed. The `operationType` is one of `add` or `remove`(the third argument is the removed index). | (files: Object, operationType: string, index: number): void | | -| onImageClick | Callback is called when the user clicks the selected picture | (index: number, files: Array): void | | -| onAddImageClick | Callback is called when the selector button is clicked | (): void | | -| onFail | Callback is called when the image selection is cancelled | (msg: string): void | | -| selectable | whether to show selector button | boolean | true | -| title | ImageRoll'title | string | 'Photos' | -| cancelText | Cancel text | string | 'Cancel' | -| cameraPickerProps | CameraPickerProps | CameraPickerProps | - | - -> Note: Only return assets-library type for RN, if you want to upload files, see https://github.com/facebook/react-native/issues/201 diff --git a/components/image-picker/index.tsx b/components/image-picker/index.tsx deleted file mode 100644 index f987f3de7..000000000 --- a/components/image-picker/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import React from 'react' -import { - Image, - Text, - TouchableOpacity, - TouchableWithoutFeedback, - View, -} from 'react-native' -import { WithTheme, WithThemeStyles } from '../style' -import { CameraRollPickerProps } from './CameraRollPicker' -import ImageRoll, { ImageRollTexts } from './ImageRoll' -import { ImagePickerPropTypes } from './PropsType' -import ImagePickerStyles, { ImagePickerStyle } from './style/index' - -export interface ImagePickerProps - extends ImagePickerPropTypes, - WithThemeStyles, - ImageRollTexts { - cameraPickerProps?: CameraRollPickerProps -} - -export default class ImagePicker extends React.Component< - ImagePickerProps, - any -> { - static defaultProps = { - onChange() {}, - onFail() {}, - files: [], - selectable: true, - } - - plusText: any - plusWrap: any - - constructor(props: ImagePickerProps) { - super(props) - this.state = { - visible: false, - } - } - - onPressIn = (styles: ReturnType) => () => { - this.plusWrap.setNativeProps({ - style: [styles.item, styles.size, styles.plusWrapHighlight], - }) - } - - onPressOut = (styles: ReturnType) => () => { - this.plusWrap.setNativeProps({ - style: [styles.item, styles.size, styles.plusWrapNormal], - }) - } - - showPicker = () => { - if (this.props.onAddImageClick) { - this.props.onAddImageClick() - return - } - this.setState({ - visible: true, - }) - } - - addImage(imageObj: any) { - if (!imageObj.url) { - imageObj.url = imageObj.uri - delete imageObj.uri - } - const { files = [] } = this.props - const newImages = files.concat(imageObj) - if (this.props.onChange) { - this.props.onChange(newImages, 'add') - } - } - - removeImage(idx: number): void { - const newImages: any[] = [] - const { files = [] } = this.props - files.forEach((image, index) => { - if (index !== idx) { - newImages.push(image) - } - }) - if (this.props.onChange) { - this.props.onChange(newImages, 'remove', idx) - } - } - - hideImageRoll = () => { - this.setState({ - visible: false, - }) - if (this.props.onFail) { - this.props.onFail('cancel image selection') - } - } - - onImageClick(index: number) { - if (this.props.onImageClick) { - this.props.onImageClick(index, this.props.files) - } - } - - render() { - const { files = [], selectable, cameraPickerProps } = this.props - return ( - - {(styles) => { - const filesView = files.map((item: any, index) => ( - - this.onImageClick(index)} - activeOpacity={0.6}> - - - this.removeImage(index)} - style={styles.closeWrap} - activeOpacity={0.6}> - × - - - )) - - const imageRollEl = ( - this.addImage(imgObj)} - title={this.props.title} - cancelText={this.props.cancelText} - cameraPickerProps={cameraPickerProps} - /> - ) - return ( - - {filesView} - {selectable && ( - - (this.plusWrap = conponent)} - style={[ - styles.item, - styles.size, - styles.plusWrap, - styles.plusWrapNormal, - ]}> - + - - - )} - {this.state.visible ? imageRollEl : null} - - ) - }} - - ) - } -} diff --git a/components/image-picker/index.zh-CN.md b/components/image-picker/index.zh-CN.md deleted file mode 100644 index 920519716..000000000 --- a/components/image-picker/index.zh-CN.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -category: Components -type: Data Entry -title: ImagePicker -subtitle: 图片选择器 ---- - -注意:只是图片选择器,一般用于上传图片前的文件选择操作,但不包括图片上传的功能 - - -权限相关的问题可以移步至:https://github.com/ant-design/ant-design-mobile-rn/issues/90 查看 - -## API - -| 属性 | 说明 | 类型 | 默认值 | -| ----------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -------- | -| files | 图片文件数组,元素为对象,包含属性 url(必选, 可能还有id, orientation, 以及业务需要的其它属性 | Array | [] | -| onChange | files 值发生变化触发的回调函数, operationType 操作类型有添加,移除,如果是移除操作,则第三个参数代表的是移除图片的索引 | (files: Object, operationType: string, index: number): void | | -| onImageClick | 点击图片触发的回调 | (index: number, files: Object): void | | -| onAddImageClick | 自定义选择图片的方法 | (): void | | -| onFail | 取消回调 | (msg: string): void | | -| selectable | 是否显示添加按钮 | boolean | true | -| title | ImageRoll 标题 | string | 'Photos' | -| cancelText | 取消按钮 | string | 'Cancel' | -| cameraPickerProps | CameraPickerProps | CameraPickerProps | - | - - -> 注: RN 版本回传 assets-library (性能考虑),需要使用 native 模块进行上传,可参考 https://github.com/facebook/react-native/issues/201 diff --git a/components/image-picker/style/index.tsx b/components/image-picker/style/index.tsx deleted file mode 100644 index 4cbbc3924..000000000 --- a/components/image-picker/style/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { ImageStyle, StyleSheet, TextStyle, ViewStyle } from 'react-native' -import varibles from '../../style/themes/default' - -export interface ImagePickerStyle { - container: ViewStyle - size: ImageStyle - item: ViewStyle - image: ImageStyle - closeWrap: ViewStyle - closeText: TextStyle - plusWrap: ViewStyle - plusWrapNormal: ViewStyle - plusWrapHighlight: ViewStyle - plusText: TextStyle -} - -export default () => - StyleSheet.create({ - container: { - flexWrap: 'wrap', - flexDirection: 'row', - }, - size: { - width: 80, - height: 80, - }, - item: { - marginRight: varibles.h_spacing_sm, - marginBottom: varibles.v_spacing_sm, - overflow: 'hidden', - }, - image: { - overflow: 'hidden', - borderRadius: varibles.radius_sm, - }, - closeWrap: { - width: 16, - height: 16, - backgroundColor: '#999', - borderRadius: 8, - position: 'absolute', - top: 4, - right: 4, - justifyContent: 'center', - alignItems: 'center', - overflow: 'hidden', - }, - closeText: { - color: varibles.color_text_base_inverse, - backgroundColor: 'transparent', - fontSize: 20, - height: 20, - marginTop: -8, - fontWeight: '300', - }, - plusWrap: { - borderRadius: varibles.radius_sm, - borderWidth: 1, - justifyContent: 'center', - alignItems: 'center', - }, - plusWrapNormal: { - backgroundColor: varibles.fill_base, - borderColor: varibles.border_color_base, - }, - plusWrapHighlight: { - backgroundColor: varibles.fill_tap, - borderColor: varibles.border_color_base, - }, - plusText: { - fontSize: 64, - backgroundColor: 'transparent', - fontWeight: '100', - color: varibles.color_text_caption, - }, - }) diff --git a/components/index.tsx b/components/index.tsx index 9173c2962..9a4954ffc 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -12,7 +12,6 @@ export { default as Drawer } from './drawer/index' export { default as Flex } from './flex/index' export { default as Grid } from './grid/index' export { default as Icon } from './icon/index' -export { default as ImagePicker } from './image-picker/index' export { default as InputItem } from './input-item/index' export { default as ListView } from './list-view/index' export { default as List } from './list/index' diff --git a/components/input-item/locale/ko_KR.tsx b/components/input-item/locale/ko_KR.tsx new file mode 100644 index 000000000..7382b0e18 --- /dev/null +++ b/components/input-item/locale/ko_KR.tsx @@ -0,0 +1,5 @@ +export default { + confirmLabel: '확인', + backspaceLabel: '지우기', + cancelKeyboardLabel: '키보드 숨기기', +} diff --git a/components/list-view/locale/ko_KR.tsx b/components/list-view/locale/ko_KR.tsx new file mode 100644 index 000000000..53b1fe21a --- /dev/null +++ b/components/list-view/locale/ko_KR.tsx @@ -0,0 +1,8 @@ +export default { + done: '완료', + loading: '로딩중', + refreshableTitlePull: '당겨서 새로고침', + refreshableTitleRelease: '놓아서 새로고침', + refreshableTitleRefreshing: '로딩중...', + noData: '데이터 없음', +} diff --git a/components/locale-provider/__tests__/__snapshots__/demo.test.js.snap b/components/locale-provider/__tests__/__snapshots__/demo.test.js.snap index 3651321d0..ed4c93550 100644 --- a/components/locale-provider/__tests__/__snapshots__/demo.test.js.snap +++ b/components/locale-provider/__tests__/__snapshots__/demo.test.js.snap @@ -12,29 +12,46 @@ exports[`renders ./components/locale-provider/demo/basic.tsx correctly 1`] = ` ] } > - + @@ -42,91 +59,72 @@ exports[`renders ./components/locale-provider/demo/basic.tsx correctly 1`] = ` style={ Array [ Object { - "alignItems": "center", - "borderBottomColor": "#dddddd", - "borderBottomWidth": 0.5, "flex": 1, - "flexDirection": "row", - "minHeight": 44, - "paddingRight": 15, - "paddingVertical": 6, + "flexDirection": "column", }, - false, - false, ] } > - - - Choose language - - - - - English - - + Choose language + + + -  + 请选择 + +  + @@ -397,29 +395,46 @@ exports[`renders ./components/locale-provider/demo/basic.tsx correctly 1`] = ` } } > - + @@ -427,117 +442,115 @@ exports[`renders ./components/locale-provider/demo/basic.tsx correctly 1`] = ` style={ Array [ Object { - "alignItems": "center", - "borderBottomColor": "#dddddd", - "borderBottomWidth": 0.5, "flex": 1, - "flexDirection": "row", - "minHeight": 44, - "paddingRight": 15, - "paddingVertical": 6, + "flexDirection": "column", }, - false, - false, ] } > - - - DatePicker - - - - - please select - - + DatePicker + + + -  + please select + +  + - + @@ -545,91 +558,72 @@ exports[`renders ./components/locale-provider/demo/basic.tsx correctly 1`] = ` style={ Array [ Object { - "alignItems": "center", - "borderBottomColor": "#dddddd", - "borderBottomWidth": 0.5, "flex": 1, - "flexDirection": "row", - "minHeight": 44, - "paddingRight": 15, - "paddingVertical": 6, + "flexDirection": "column", }, - false, - false, ] } > - - - Picker - - - - - please select - - + Picker + + + -  + please select + +  + diff --git a/components/locale-provider/demo/basic.tsx b/components/locale-provider/demo/basic.tsx index 76f8db5a1..4d8bc360f 100644 --- a/components/locale-provider/demo/basic.tsx +++ b/components/locale-provider/demo/basic.tsx @@ -12,8 +12,11 @@ import { } from '../../' import enUS from '../en_US' import esES from '../es_ES' +import faIR from '../fa_IR' +import koKR from '../ko_KR' import ptBR from '../pt_BR' import ruRU from '../ru_RU' +import svSE from '../sv_SE' import zhCN from '../zh_CN' const maxDate = new Date(2018, 11, 3, 22, 0) @@ -105,6 +108,21 @@ export default class LocaleProviderExample extends React.Component { label: 'Português - BR', language: ptBR, }, + { + value: 'Sverige', + label: 'Sverige', + language: svSE, + }, + { + value: 'Persian', + label: 'Persian', + language: faIR, + }, + { + value: '한국', + label: '한국', + language: koKR, + }, ] const currentLocale = languages.find( (item) => item.value === locale, diff --git a/components/locale-provider/ko_KR.tsx b/components/locale-provider/ko_KR.tsx new file mode 100644 index 000000000..89ecc7e75 --- /dev/null +++ b/components/locale-provider/ko_KR.tsx @@ -0,0 +1,20 @@ +import DatePickerView from '../date-picker-view/locale/ko_KR' +import DatePicker from '../date-picker/locale/ko_KR' +import InputItem from '../input-item/locale/ko_KR' +import ListView from '../list-view/locale/ko_KR' +import Modal from '../modal/locale/ko_KR' +import Pagination from '../pagination/locale/ko_KR' +import Picker from '../picker/locale/ko_KR' +import SearchBar from '../search-bar/locale/ko_KR' + +export default { + locale: 'ko_KR', + DatePicker, + DatePickerView, + InputItem, + Modal, + Pagination, + Picker, + SearchBar, + ListView, +} diff --git a/components/locale-provider/sv_SE.tsx b/components/locale-provider/sv_SE.tsx index c92ce4ae9..b5fcc856d 100644 --- a/components/locale-provider/sv_SE.tsx +++ b/components/locale-provider/sv_SE.tsx @@ -1,11 +1,11 @@ -import DatePickerView from '../date-picker-view/locale/sv_Se' -import DatePicker from '../date-picker/locale/sv_Se' -import InputItem from '../input-item/locale/sv_Se' -import ListView from '../list-view/locale/sv_Se' -import Modal from '../modal/locale/sv_Se' -import Pagination from '../pagination/locale/sv_Se' -import Picker from '../picker/locale/sv_Se' -import SearchBar from '../search-bar/locale/sv_Se' +import DatePickerView from '../date-picker-view/locale/sv_SE' +import DatePicker from '../date-picker/locale/sv_SE' +import InputItem from '../input-item/locale/sv_SE' +import ListView from '../list-view/locale/sv_SE' +import Modal from '../modal/locale/sv_SE' +import Pagination from '../pagination/locale/sv_SE' +import Picker from '../picker/locale/sv_SE' +import SearchBar from '../search-bar/locale/sv_SE' export default { locale: 'sv', diff --git a/components/modal/locale/ko_KR.tsx b/components/modal/locale/ko_KR.tsx new file mode 100644 index 000000000..ab43dfe84 --- /dev/null +++ b/components/modal/locale/ko_KR.tsx @@ -0,0 +1,5 @@ +export default { + okText: '확인', + cancelText: '취소', + buttonText: '버튼', +} diff --git a/components/pagination/locale/ko_KR.tsx b/components/pagination/locale/ko_KR.tsx new file mode 100644 index 000000000..4a999d31e --- /dev/null +++ b/components/pagination/locale/ko_KR.tsx @@ -0,0 +1,4 @@ +export default { + prevText: '이전', + nextText: '다음', +} diff --git a/components/picker-view/PickerView.tsx b/components/picker-view/PickerView.tsx deleted file mode 100644 index 173195b83..000000000 --- a/components/picker-view/PickerView.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react' -import { StyleProp, TextStyle, ViewStyle } from 'react-native' -import RMCCascader from '../picker/cascader' -import MultiPicker from '../picker/MultiPicker' -import RMCPicker from '../picker/Picker' -import { PickerData } from '../picker/PropsType' - -function getDefaultProps() { - return { - cols: 3, - cascade: true, - value: [], - onChange() {}, - } -} - -export interface PickerViewProps { - cols?: number - cascade?: boolean - value?: any[] - data?: PickerData[] | PickerData[][] - styles?: any - onChange?: (value?: any) => void - onScrollChange?: (value?: any) => void - indicatorStyle?: StyleProp - itemStyle?: StyleProp -} - -export default class PickerView extends React.Component { - static defaultProps = getDefaultProps() - - getCol = () => { - const { data, indicatorStyle, itemStyle } = this.props - return (data as PickerData[][]).map((col, index) => { - return ( - - {col.map((item) => { - return ( - - {item.label} - - ) - })} - - ) - }) - } - render() { - // tslint:disable-next-line:no-this-assignment - const { props } = this - let picker - if (props.cascade) { - picker = ( - - ) - } else { - picker = ( - - {this.getCol()} - - ) - } - return picker - } -} diff --git a/components/picker-view/PropsType.tsx b/components/picker-view/PropsType.tsx new file mode 100644 index 000000000..e6dd6e73a --- /dev/null +++ b/components/picker-view/PropsType.tsx @@ -0,0 +1,38 @@ +import type { ReactNode } from 'react' +import { StyleProp, TextStyle, ViewStyle } from 'react-native' + +export type PickerValue = string | number + +export type PickerValueExtend = { + columns: PickerColumn[] + items: (PickerColumnItem | undefined)[] +} + +export type PickerColumnItem = { + label: string | ReactNode + value: string | number + key?: string | number + children?: PickerColumnItem[] +} + +export type PickerColumn = PickerColumnItem[] +// type PickerColumn = (string | PickerColumnItem)[] // TODO: support string type + +export interface PickerViewPropsType { + value?: PickerValue[] + defaultValue?: PickerValue[] + data: PickerColumn | PickerColumn[] + cols?: number + cascade?: boolean + loading?: boolean + loadingContent?: ReactNode + numberOfLines?: number + style?: StyleProp + itemStyle?: StyleProp + itemHeight?: number + indicatorStyle?: StyleProp + onChange?: (value: PickerValue[], extend: PickerValueExtend) => void + renderLabel?: (item: PickerColumnItem, index: number) => ReactNode + renderMaskTop?: () => ReactNode + renderMaskBottom?: () => ReactNode +} diff --git a/components/picker-view/Wheel.tsx b/components/picker-view/Wheel.tsx new file mode 100644 index 000000000..17a6e0af3 --- /dev/null +++ b/components/picker-view/Wheel.tsx @@ -0,0 +1,200 @@ +// 需要重构成translate3d +import type { ReactNode } from 'react' +import React from 'react' +import { + NativeScrollEvent, + NativeSyntheticEvent, + Platform, + View, +} from 'react-native' +import { ScrollView } from 'react-native-gesture-handler' + +type ColumnItem = { + label: string | ReactNode + value: string | number +} +type Value = string | number + +type Props = { + index: number + column: ColumnItem[] + value?: Value + onSelect: (value: Value, index: number) => void + renderLabel: (item: ColumnItem, index: number) => ReactNode + itemHeight: number + wheelHeight: number +} + +class Wheel extends React.Component { + scrollerRef: any + + scrollBuffer: number + + selectValue: any + isScrolling: boolean + + componentDidMount() { + const { value, column, itemHeight } = this.props + setTimeout(() => { + this.scrollTo(this.getSelectIndex(column, value) * itemHeight) + }) + } + + shouldComponentUpdate(nextProps: Props) { + const { value, column, itemHeight, wheelHeight } = nextProps + if ( + value !== this.props.value || + itemHeight !== this.props.itemHeight || + wheelHeight !== this.props.wheelHeight + ) { + this.scrollTo(this.getSelectIndex(column, value) * itemHeight) + return true + } + return column !== this.props.column + } + + componentDidUpdate(prevProps: Props) { + const { column } = prevProps + if (column !== this.props.column) { + this.scrollTo( + this.getSelectIndex(this.props.column, this.props.value) * + this.props.itemHeight, + ) + } + } + + scrollTo = (y: any) => { + this.scrollerRef?.scrollTo?.({ + y, + animated: false, + }) + } + + getSelectIndex(column: ColumnItem[], value?: Value) { + return Math.max( + column.findIndex((item) => item.value === value), + 0, + ) + } + + handleSelect = () => { + if (this.props.value !== this.selectValue && this.props.onSelect) { + this.props.onSelect(this.selectValue, this.props.index) + } + + setTimeout(() => { + this.scrollTo( + this.getSelectIndex(this.props.column, this.props.value) * + this.props.itemHeight, + ) + }) + } + + onScrollEndForWeb = (e: NativeSyntheticEvent) => { + e.persist?.() + let contentOffset = e.nativeEvent.contentOffset + // android/web hack + if (!contentOffset) { + //@ts-ignore + const { position } = e.nativeEvent + contentOffset = { + x: 0, + y: position * this.props.itemHeight, + } + } + + const selectIndex = Math.round(contentOffset.y / this.props.itemHeight) + if (this.props.column[selectIndex]) { + this.selectValue = this.props.column[selectIndex].value + if (!this.isScrolling) { + this.updateSelectThrottle() + } + } + } + + /*** for web event ***/ + onTouchStartForWeb = () => { + this.isScrolling = true + } + onTouchEndForWeb = () => { + this.isScrolling = false + this.updateSelectThrottle() + } + onScrollForWeb = (e: NativeSyntheticEvent) => { + this.onScrollEndForWeb(e) + } + updateSelectThrottle = () => { + clearTimeout(this.scrollBuffer) + this.scrollBuffer = setTimeout(() => { + this.handleSelect() + }, 100) //idle time + } + + onMomentumScrollBegin = () => { + this.isScrolling = true + } + onMomentumScrollEnd = (e: NativeSyntheticEvent) => { + e.persist?.() + if (this.isScrolling) { + let contentOffset = e.nativeEvent.contentOffset + // android/web hack + if (!contentOffset) { + //@ts-ignore + const { position } = e.nativeEvent + contentOffset = { + x: 0, + y: position * this.props.itemHeight, + } + } + const selectIndex = Math.round(contentOffset.y / this.props.itemHeight) + if (this.props.column[selectIndex]) { + this.selectValue = this.props.column[selectIndex].value + this.handleSelect() + } + } + this.isScrolling = false + } + + renderItems = () => { + const { column, renderLabel, itemHeight, wheelHeight } = this.props + return ( + + {column.map((item, index) => renderLabel(item, index))} + + ) + } + + render() { + return ( + (this.scrollerRef = el)} + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} + pagingEnabled={false} + automaticallyAdjustContentInsets={false} + directionalLockEnabled={true} + decelerationRate="fast" + snapToInterval={this.props.itemHeight} + {...(Platform.OS === 'web' + ? { + onTouchStart: this.onTouchStartForWeb, + onTouchEnd: this.onTouchEndForWeb, + onScroll: this.onScrollForWeb, + scrollEventThrottle: 16, + } + : { + onMomentumScrollBegin: this.onMomentumScrollBegin, + onMomentumScrollEnd: this.onMomentumScrollEnd, + })}> + {this.renderItems()} + + ) + } +} + +export default Wheel diff --git a/components/picker-view/columns-extend.tsx b/components/picker-view/columns-extend.tsx new file mode 100644 index 000000000..cb8f1f5a2 --- /dev/null +++ b/components/picker-view/columns-extend.tsx @@ -0,0 +1,86 @@ +import { PickerColumn, PickerValue } from './PropsType' + +export function getColumns( + d: PickerColumn | PickerColumn[], + val: PickerValue[], + cols: number, + cascade: boolean, +): PickerColumn[] { + if (!d || d.length === 0) { + return [] + } + + if (!cascade) { + // when d is PickerColumn + if (!Array.isArray(d[0])) { + return [d as PickerColumn] + } + // when d is PickerColumn[] + return (d as PickerColumn[]).slice(0, cols!) + } + + // cascade data + const columns = [] + let currentOptions = (d as PickerColumn).slice() + let selected = val || [] + let i = 0 + while (i < cols!) { + columns.push( + currentOptions.map((option: any) => ({ + label: option.label, + value: option.value, + })), + ) + const x = selected[i] + const targetOptions = + currentOptions.find((option: any) => option.value === x) || + currentOptions[0] + if (!targetOptions.children) { + break + } + currentOptions = targetOptions.children + i++ + } + return columns +} + +export function getValueExtend( + d: PickerColumn | PickerColumn[], + val: PickerValue[], + cols: number, + cascade: boolean, +) { + if (!d || d.length === 0) { + return { nextValue: [], extend: [] } + } + + if (!cascade) { + const columns = getColumns(d, val, cols, false).map( + (column: PickerColumn, index: number) => + column.find((item) => item.value === val[index]) ?? column[0], + ) + + return { nextValue: columns.map((item) => item.value), extend: columns } + } + + // cascade data + let currentOptions = (d as PickerColumn).slice() + const nextValue = [] + const extend = [] + let selected = val || [] + let i = 0 + while (i < cols!) { + const x = selected[i] + const targetOptions = + currentOptions.find((option: any) => option.value === x) || + currentOptions[0] + nextValue[i] = targetOptions.value + extend[i] = targetOptions + if (!targetOptions.children) { + break + } + currentOptions = targetOptions.children + i++ + } + return { nextValue, extend } +} diff --git a/components/picker-view/demo/basic.tsx b/components/picker-view/demo/basic.tsx index 42ab146ba..6f2515745 100644 --- a/components/picker-view/demo/basic.tsx +++ b/components/picker-view/demo/basic.tsx @@ -1,26 +1,18 @@ import React from 'react' +import { ScrollView, Text } from 'react-native' import { PickerView } from '../../' -const seasons = [ +const basicColumns = [ [ - { - label: '2013', - value: '2013', - }, - { - label: '2014', - value: '2014', - }, + { label: '周一', value: 'Mon' }, + { label: '周二', value: 'Tues' }, + { label: '周三', value: 'Wed' }, + { label: '周四', value: 'Thur' }, + { label: '周五', value: 'Fri' }, ], [ - { - label: '春', - value: '春', - }, - { - label: '夏', - value: '夏', - }, + { label: '上午', value: 'am' }, + { label: '下午', value: 'pm' }, ], ] @@ -35,12 +27,31 @@ export default class PickerViewExample extends React.Component { } render() { return ( - + + 基础用法 + + + 自定义高度 + + + 受控模式 + + + + ) } } diff --git a/components/picker-view/index.en-US.md b/components/picker-view/index.en-US.md index 6a79818b5..99f805932 100644 --- a/components/picker-view/index.en-US.md +++ b/components/picker-view/index.en-US.md @@ -8,13 +8,38 @@ PickerView's functions like Picker, but it is rendered directly in the area inst ## API +### Props + +Properties | Descrition | Type | Default +-----------|------------|------|-------- +| data | data source | `PickerColumn` / `PickerColumn[]` | - | +| value | Selected options | `PickerValue[]` | - | +| defaultValue | Default selected options | `PickerValue[]` | - | +| cascade | whether cascade
child cascade get from `data[].children` | Boolean | `true` | +| cols | col numbers | Number | `3` | +| onChange | selected callback function, can use [rc-form](https://github.com/react-component/form) | `(value: PickerValue[], extend: PickerValueExtend) => void` | - | +| renderLabel | The function to custom rendering the label shown on a column | `(item: PickerColumnItem, index: number) => ReactNode` | `(item) => item.label` | +| loading | Should the Picker displays as loading state | Boolean | - | +| loadingContent | The loading content displayed in loading state | ReactNode | `` | +| indicatorStyle | style of default Indicator | Object | - | + +For the type definition of `PickerColumnItem` `PickerColumn` `PickerValue` `PickerValueExtend`, please refer to the document of [Picker](/components/picker/). + +### Custom Style + +Properties | Descrition | Type | Default +-----------|------------|------|-------- +| style | style | `StyleProp` | - | +| styles | inner component styles | interface `PickerViewStyle` | - | +| itemStyle| style to apply to each of the item labels | `StyleProp` | - | +| itemHeight | Height of option item, calculated by `numberOfLines` when without value; `itemStyle` was not allowed to set `{height}` | Number | - | +| numberOfLines | Used to truncate the text with an ellipsis after computing the text layout, including line wrapping, such that the total number of lines does not exceed this number | Number | `1` | + +#### Mask View + +Support custom mask style, such as using the gradient component ``. Default is white translucency. + Properties | Descrition | Type | Default -----------|------------|------|-------- -| data | data source | `Array<{value, label}>` / `Array>` | - | -| value | the value, the format is `[value1, value2, value3]`, corresponds to the level value of the data source | Array | - | -| cascade | whether cascade | Boolean | true | -| cols | col numbers | Number | `3` | -| onChange | selected callback function, can use [rc-form](https://github.com/react-component/form) | (val): void | - | -| cascade | whether is cascade mode | Boolean | true | -| itemStyle | style to apply to each of the item labels | Object | - | -| indicatorStyle | style of indicator | Object | - | +| renderMaskTop | The function to custom rendering the mask top half view | `()=> ReactNode` | `` | +| renderMaskBottom | The function to custom rendering the mask bottom half view | `()=> ReactNode` | `` | \ No newline at end of file diff --git a/components/picker-view/index.tsx b/components/picker-view/index.tsx index c259ec5be..fbd10f687 100644 --- a/components/picker-view/index.tsx +++ b/components/picker-view/index.tsx @@ -1,3 +1,69 @@ -import PickerView from './PickerView' +import useMergedState from 'rc-util/lib/hooks/useMergedState' +import React, { + forwardRef, + useCallback, + useImperativeHandle, + useMemo, +} from 'react' +import { mergeProps } from '../_util/with-default-props' +import { WithThemeStyles } from '../style' +import { PickerValue, PickerViewPropsType } from './PropsType' +import { getColumns, getValueExtend } from './columns-extend' +import RMCPickerView from './picker-view' +import { PickerViewStyle } from './style' + +export interface PickerViewProps + extends PickerViewPropsType, + WithThemeStyles {} + +const defaultProps = { + cols: 3, + cascade: true, +} + +const PickerView = forwardRef((props, ref) => { + const p = mergeProps(defaultProps, props) + + const [innerValue, setInnerValue] = useMergedState([], { + value: p.value, + defaultValue: p.defaultValue, + }) + + useImperativeHandle(ref, () => ({ + value: innerValue, + })) + + const columns = useMemo( + () => getColumns(p.data, innerValue, p.cols, p.cascade), + [p.data, innerValue, p.cols, p.cascade], + ) + + const handleSelect = useCallback( + (val: PickerValue, index: number) => { + const value = innerValue?.slice?.(0) || [] + value[index] = val + const { nextValue, extend } = getValueExtend( + p.data, + value, + p.cols, + p.cascade, + ) + setInnerValue(nextValue) + p.onChange?.(nextValue, { items: extend, columns }) + }, + [p, columns, innerValue, setInnerValue], + ) + + return ( + + ) +}) + +PickerView.displayName = 'AntmPickerView' export default PickerView diff --git a/components/picker-view/index.zh-CN.md b/components/picker-view/index.zh-CN.md index 622b0d95c..ff5cfe1bc 100644 --- a/components/picker-view/index.zh-CN.md +++ b/components/picker-view/index.zh-CN.md @@ -9,12 +9,38 @@ PickerView 的功能类似于 Picker ,但它是直接渲染在区域中,而 ## API +### 属性 + 属性 | 说明 | 类型 | 默认值 ----|-----|------|------ -| data | 数据源 | `Array<{value, label}>` / `Array>` | - | -| value | 值, 格式是`[value1, value2, value3]`, 对应数据源的相应级层 value | Array | - | -| cascade | 是否级联 | Boolean| true| +| data | 数据源 | `PickerColumn` / `PickerColumn[]` | - | +| value | 选中项 | `PickerValue[]` | - | +| defaultValue | 默认选中项 | `PickerValue[]` | - | +| cascade | 是否级联。
子级来自`data`参数内的`children`属性 | Boolean | `true` | | cols | 列数 | Number | `3` | -| onChange | 选中后的回调,可使用[rc-form](https://github.com/react-component/form) | (val): void | - | -| itemStyle| 每列样式 | Object | - | -| indicatorStyle | indicator 样式 | Object | - | +| onChange | 选中后的回调,可使用[rc-form](https://github.com/react-component/form) | `(value: PickerValue[], extend: PickerValueExtend) => void` | - | +| renderLabel | 自定义渲染每列展示的内容 | `(item: PickerColumnItem, index: number) => ReactNode` | `(item) => item.label` | +| loading | 是否处于加载状态 | Boolean | - | +| loadingContent | 加载状态下展示的内容 | ReactNode | `` | +| indicatorStyle | 默认Indicator的样式 | Object | - | + +关于 `PickerColumnItem` `PickerColumn` `PickerValue` `PickerValueExtend` 的类型定义,请参考 [Picker](/components/picker-cn/) 的文档。 + +### 自定义样式 + +属性 | 说明 | 类型 | 默认值 +----|-----|------|------ +| style | 外部样式 | `StyleProp` | - | +| styles | 内部组件样式集 | `PickerViewStyle` | - | +| itemStyle| 每列样式 | `StyleProp` | - | +| itemHeight | 每列固定高度,未设值时会根据`numberOfLines`动态计算;`itemStyle`属性设置`{height}`值是无效的 | Number | - | +| numberOfLines | 允许每列显示行数 | Number | `1` | + +#### 遮罩层 + +还支持自定义遮罩样式,如使用渐变组件``。当前默认为白色半透明。 + +属性 | 说明 | 类型 | 默认值 +----|-----|------|------ +| renderMaskTop | 自定义渲染上半部分遮罩层 | `()=> ReactNode` | `` | +| renderMaskBottom | 自定义渲染下半部分遮罩层 | `()=> ReactNode` | `` | \ No newline at end of file diff --git a/components/picker-view/picker-view.tsx b/components/picker-view/picker-view.tsx new file mode 100644 index 000000000..6996fe750 --- /dev/null +++ b/components/picker-view/picker-view.tsx @@ -0,0 +1,167 @@ +import React from 'react' +import { ActivityIndicator, LayoutChangeEvent, Text, View } from 'react-native' +import { WithTheme, WithThemeStyles } from '../style' +import { + PickerColumn, + PickerColumnItem, + PickerValue, + PickerViewPropsType, +} from './PropsType' +import Wheel from './Wheel' +import pickerViewStyles, { PickerViewStyle } from './style/index' + +export type RMCPickerViewProps = Omit< + PickerViewPropsType, + 'data' | 'cols' | 'cascade' | 'onChange' +> & + WithThemeStyles & { + columns: PickerColumn[] + handleSelect: (value: PickerValue, index: number) => void + } +export default class RMCPickerView extends React.Component< + RMCPickerViewProps, + any +> { + static defaultProps = { + value: [], + itemHeight: 0, + numberOfLines: 1, + renderMaskTop: () => ( + + ), + renderMaskBottom: () => ( + + ), + } + + constructor(props: RMCPickerViewProps) { + super(props) + this.state = { + itemHeight: 0, + wheelHeight: 0, + } + } + + wrapperMeasure = (e: LayoutChangeEvent) => { + const { height } = e.nativeEvent.layout + this.setState({ wheelHeight: height }) + } + + itemHeightMeasure = (e: LayoutChangeEvent) => { + const { height } = e.nativeEvent.layout + this.setState({ itemHeight: height }) + } + + renderLabel = (item: PickerColumnItem, index: number) => { + return ( + + {this.props.renderLabel?.(item, index) || ( + + {item.label} + + )} + + ) + } + + renderMask = (s: PickerViewStyle) => ( + + {this.props.renderMaskTop?.()} + + {this.props.renderMaskBottom?.()} + + ) + + render() { + const { wheelHeight } = this.state + const { + style, + styles, + columns, + value, + loading, + indicatorStyle, + numberOfLines, + handleSelect, + loadingContent, + } = this.props + const itemHeight = this.props.itemHeight || this.state.itemHeight + return ( + + {(s) => + // {/* 计算中文占位符换行的高度后,items统一这个高度 */} + itemHeight === 0 ? ( + + {this.renderLabel( + { + value: 'layout', + label: <>{Array(numberOfLines).join('\n')} , + }, + 0, + )} + + ) : ( + + + {(loading || columns?.length === 0) && loading !== false + ? loadingContent || ( + // TODO-luokun: loading样式优化 + + + + ) + : itemHeight > 0 && + wheelHeight > 0 && + columns.map((column, index) => ( + + ))} + + {/* mask */} + {this.renderMask(s)} + + ) + } + + ) + } +} diff --git a/components/picker-view/style/index.tsx b/components/picker-view/style/index.tsx new file mode 100644 index 000000000..20d4bef47 --- /dev/null +++ b/components/picker-view/style/index.tsx @@ -0,0 +1,51 @@ +import { StyleSheet, ViewStyle } from 'react-native' + +export interface PickerViewStyle { + wrappper: ViewStyle + wheelWrapper: ViewStyle + mask: ViewStyle + maskTop: ViewStyle + maskMiddle: ViewStyle + maskBottom: ViewStyle +} + +export default () => + StyleSheet.create({ + wrappper: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + overflow: 'hidden', + }, + wheelWrapper: { + display: 'flex', + flexDirection: 'row', + backgroundColor: '#fff', + }, + mask: { + position: 'absolute', + zIndex: 10000, + left: 0, + top: 0, + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + }, + + maskTop: { + flex: 1, + overflow: 'hidden', + }, + + maskMiddle: { + borderColor: '#eee', + borderTopWidth: 1, + borderBottomWidth: 1, + }, + + maskBottom: { + flex: 1, + overflow: 'hidden', + }, + }) diff --git a/components/picker/MultiPicker.tsx b/components/picker/MultiPicker.tsx deleted file mode 100644 index d7394306a..000000000 --- a/components/picker/MultiPicker.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import { View } from 'react-native' -import MultiPickerMixin from './MultiPickerMixin' -import MultiPickerProps from './MultiPickerProps' - -export interface MultiPickerProp { - getValue: Function -} - -const MultiPicker = (props: MultiPickerProp & MultiPickerProps) => { - const { children, style } = props - const selectedValue = props.getValue() - const colElements = React.Children.map(children, (col: any, i) => { - return React.cloneElement(col, { - selectedValue: selectedValue[i], - onValueChange: (...args: any[]) => props.onValueChange!(i, ...args), - }) - }) - return {colElements} -} - -export default MultiPickerMixin(MultiPicker) diff --git a/components/picker/MultiPickerMixin.tsx b/components/picker/MultiPickerMixin.tsx deleted file mode 100644 index b89d75250..000000000 --- a/components/picker/MultiPickerMixin.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react' -import MultiPickerProps from './MultiPickerProps' - -export default function (ComposedComponent: any) { - return class extends React.Component { - static defaultProps = { - prefixCls: 'rmc-multi-picker', - onValueChange() {}, - } - - getValue = () => { - const { children, selectedValue } = this.props - if (selectedValue && selectedValue.length) { - return selectedValue - } else { - if (!children) { - return [] - } - return React.Children.map(children, (c: any) => { - const cc: any = React.Children.toArray(c.children || c.props.children) - return cc && cc[0] && cc[0].props.value - }) - } - } - - onChange = (i: any, v: any, cb: any) => { - const value = this.getValue().concat() - value[i] = v - if (cb) { - cb(value, i) - } - } - - onValueChange = (i: any, v: any) => { - this.onChange(i, v, this.props.onValueChange) - } - - onScrollChange = (i: any, v: any) => { - this.onChange(i, v, this.props.onScrollChange) - } - - render() { - return ( - - ) - } - } -} diff --git a/components/picker/MultiPickerProps.tsx b/components/picker/MultiPickerProps.tsx deleted file mode 100644 index e8ecfc5bf..000000000 --- a/components/picker/MultiPickerProps.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { StyleProp, ViewStyle } from 'react-native' - -export interface PickerCol { - key?: string - props?: any -} - -interface MultiPickerProps { - selectedValue?: any[] - rootNativeProps?: any - onValueChange?: (v?: any, i?: number) => void - children?: any - style?: StyleProp - onScrollChange?: (v?: any, i?: number) => void -} - -export default MultiPickerProps diff --git a/components/picker/NativePicker.android.tsx b/components/picker/NativePicker.android.tsx deleted file mode 100644 index bbc2f3f67..000000000 --- a/components/picker/NativePicker.android.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React from 'react' -import { PixelRatio, ScrollView, StyleSheet, Text, View } from 'react-native' -import PickerMixin from './PickerMixin' -import { PickerProps } from './PickerTypes' - -const ratio = PixelRatio.get() -const styles = StyleSheet.create({ - indicator: { - position: 'absolute', - left: 0, - width: '100%', - borderColor: '#aaa', - borderTopWidth: 1 / ratio, - borderBottomWidth: 1 / ratio, - } as any, - - selectedItemText: { - fontSize: 20, - fontWeight: 'bold', - color: '#000', - } as any, - - itemText: { - fontSize: 20, - color: '#aaa', - textAlign: 'center', - } as any, -}) - -export interface IPickerProp { - select: Function - doScrollingComplete: Function -} - -class Picker extends React.Component { - scrollBuffer: number - scrollerRef: ScrollView | null - - state = { - itemHeight: 0, - } - - onItemLayout = (e: any) => { - const { height } = e.nativeEvent.layout - this.setState({ itemHeight: height }, () => { - this.props.select( - this.props.selectedValue, - this.state.itemHeight, - this.scrollTo, - ) - }) - } - - componentDidUpdate() { - this.props.select( - this.props.selectedValue, - this.state.itemHeight, - this.scrollTo, - ) - } - - componentWillUnmount() { - this.clearScrollBuffer() - } - - clearScrollBuffer() { - if (this.scrollBuffer) { - clearTimeout(this.scrollBuffer) - } - } - - scrollTo = (y: any) => { - if (this.scrollerRef) { - this.scrollerRef.scrollTo({ - y, - animated: false, - }) - } - } - - fireValueChange = (selectedValue: any) => { - if ( - this.props.selectedValue !== selectedValue && - this.props.onValueChange - ) { - this.props.onValueChange(selectedValue) - } - } - - onScroll = (e: any) => { - const { y } = e.nativeEvent.contentOffset - this.clearScrollBuffer() - this.scrollBuffer = setTimeout(() => { - this.clearScrollBuffer() - this.props.doScrollingComplete( - y, - this.state.itemHeight, - this.fireValueChange, - ) - }, 50) as any - } - - render() { - const { itemHeight } = this.state - const { - children, - itemStyle, - selectedValue, - style, - numberOfLines = 1, - } = this.props - const items = React.Children.map(children, (item: any, index) => { - const totalStyle = [styles.itemText] - if (selectedValue === item.props.value) { - totalStyle.push(styles.selectedItemText) - } - return ( - - - {item.props.label} - - - ) - }) - return ( - - - {/* 计算中文占位符换行的高度后,items统一这个高度 */} - {itemHeight === 0 && ( - - {Array(numberOfLines).join('\n')}  - - )} - (this.scrollerRef = el)} - onScroll={this.onScroll} - showsVerticalScrollIndicator={false} - overScrollMode="never" - renderToHardwareTextureAndroid - scrollEventThrottle={10} - needsOffscreenAlphaCompositing - collapsable - horizontal={false} - removeClippedSubviews> - - {items} - - - - ) - } -} - -export default PickerMixin(Picker) diff --git a/components/picker/NativePicker.ios.tsx b/components/picker/NativePicker.ios.tsx deleted file mode 100644 index d9eb7606e..000000000 --- a/components/picker/NativePicker.ios.tsx +++ /dev/null @@ -1 +0,0 @@ -export { Picker as default } from '@react-native-picker/picker' diff --git a/components/picker/NativePicker.tsx b/components/picker/NativePicker.tsx deleted file mode 100644 index dae5f0123..000000000 --- a/components/picker/NativePicker.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// hack for ts module resolution -export { default } from './NativePicker.ios' diff --git a/components/picker/Picker.tsx b/components/picker/Picker.tsx index 02ffb13da..acb4be903 100644 --- a/components/picker/Picker.tsx +++ b/components/picker/Picker.tsx @@ -1,39 +1,180 @@ -import React from 'react' -import NativePicker from './NativePicker' -import { PickerProps } from './PickerTypes' +import useMergedState from 'rc-util/lib/hooks/useMergedState' +import React, { + forwardRef, + useCallback, + useContext, + useImperativeHandle, + useMemo, + useState, +} from 'react' +import { GestureHandlerRootView } from 'react-native-gesture-handler' +import { Omit } from 'utility-types' -const Item: any = NativePicker.Item +import { getComponentLocale } from '../_util/getLocale' +import { LocaleContext } from '../locale-provider' +import { PickerColumn, PickerValue } from '../picker-view/PropsType' +import RMCPickerView from '../picker-view/picker-view' +import { useTheme } from '../style' +import PopupPicker from './Popup' +import { PickerPropsType } from './PropsType' +import PickerStyles from './style' -class Picker extends React.Component { - static defaultProps = { - children: [], - } +export type PickerActions = { + open: () => void + close: () => void + toggle: () => void + _updateExtra: () => void +} +export type PickerRef = PickerActions - static Item: any = () => {} +export interface RMCPickerProps + extends Omit { + value: PickerValue[] + columns: PickerColumn[] + handleSelect: (value: PickerValue, index: number) => void +} - getValue() { - if ('selectedValue' in this.props) { - return this.props.selectedValue - } - const children: any = React.Children.toArray(this.props.children) - return children && children[0] && children[0].props.value - } +const RMCPicker = forwardRef((props, ref) => { + const { + onVisibleChange, + visible, + children, + okText, + dismissText, + onChange, + onOk, + onDismiss, + onClose, + title, + extra, + disabled, + format, + value, + columns, + handleSelect, + ...restProps + } = props + + const [innerVisible, setInnerVisible] = useMergedState(false, { + value: visible, + onChange: (v) => onVisibleChange?.(v), + }) + const [innerExtra, setInnerExtra] = useState(extra) - shouldComponentUpdate(nextProps: any) { - return ( - this.props.selectedValue !== nextProps.selectedValue || - this.props.children !== nextProps.children + const actions = useMemo( + () => ({ + toggle: () => { + setInnerVisible(!innerVisible) + }, + open: () => { + setInnerVisible(true) + }, + close: () => { + setInnerVisible(false) + onClose?.() + }, + // TODO: remove hack + _updateExtra: () => { + setInnerExtra( + format?.( + columns.reduce((cur: any[], next, index) => { + const labelValue = next.find( + (item) => item.value === value?.[index], + ) + if (labelValue?.label) { + cur.push(labelValue.label) + } + return cur + }, []), + ), + ) + }, + }), + [columns, format, innerVisible, onClose, setInnerVisible, value], + ) + useImperativeHandle(ref, () => actions) + + const handleOk = useCallback(() => { + const extend = columns.map( + (column, index) => + column.find((item) => item.value === value?.[index]) ?? column[0], ) - } + const nextValue = extend.map((item) => item.value) + onOk?.(nextValue, { items: extend, columns }) + onChange?.(nextValue, { items: extend, columns }) + actions.close() + }, [actions, columns, onChange, onOk, value]) - render() { - const children = React.Children.map(this.props.children, (c: any) => { - return ( - - ) - }) - return {children} + const handleDismiss = useCallback(() => { + onDismiss?.() + actions.close() + }, [actions, onDismiss]) + + const _locale = getComponentLocale( + props, + useContext(LocaleContext), + 'Picker', + () => require('./locale/zh_CN'), + ) + + const renderChildren = () => { + if (!children) { + return null + } + const child = children as any + const newChildProps = { + extra: innerExtra || extra || _locale.extra, + disabled, + } as any + if (!disabled) { + newChildProps[props.triggerType!] = (e: any) => { + if (child.props[props.triggerType!]) { + child.props[props.triggerType!](e) + } + actions.toggle() + } + } + return React.cloneElement(child as any, newChildProps) } + + const styles = useTheme({ + styles: props.styles, + themeStyles: PickerStyles, + }) + + return ( + <> + + {/* TODO: 组件卸载是在visible更新fasle之后,需要前置 */} + {/* 否则会无效执行onPickerChange */} + {innerVisible && ( + + + + )} + + {renderChildren()} + + ) +}) + +RMCPicker.displayName = 'Picker' +RMCPicker.defaultProps = { + triggerType: 'onPress', + format: (labels: string[]) => labels.join(','), } -export default Picker +export default RMCPicker diff --git a/components/picker/PickerMixin.tsx b/components/picker/PickerMixin.tsx deleted file mode 100644 index 0cb3cb04d..000000000 --- a/components/picker/PickerMixin.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* tslint:disable:no-console */ -import React from 'react' -import { PickerProps } from './PickerTypes' - -type ItemProps = { - value: any -} - -const Item = (_props: ItemProps) => null - -export default function (ComposedComponent: any) { - return class extends React.Component { - static Item = Item - - select = (value: any, itemHeight: any, scrollTo: any) => { - const children: any = React.Children.toArray(this.props.children) - for (let i = 0, len = children.length; i < len; i++) { - if (children[i].props.value === value) { - this.selectByIndex(i, itemHeight, scrollTo) - return - } - } - this.selectByIndex(0, itemHeight, scrollTo) - } - - selectByIndex(index: number, itemHeight: any, zscrollTo: any) { - if ( - index < 0 || - index >= React.Children.count(this.props.children) || - !itemHeight - ) { - return - } - zscrollTo(index * itemHeight) - } - - computeChildIndex(top: any, itemHeight: any, childrenLength: number) { - const index = Math.round(top / itemHeight) - return Math.min(index, childrenLength - 1) - } - - doScrollingComplete = (top: any, itemHeight: any, fireValueChange: any) => { - const children = React.Children.toArray(this.props.children) - const index = this.computeChildIndex(top, itemHeight, children.length) - const child: any = children[index] - if (child) { - fireValueChange(child.props.value) - } else if (console.warn) { - console.warn('child not found', children, index) - } - } - - render() { - return ( - - ) - } - } -} diff --git a/components/picker/PickerTypes.tsx b/components/picker/PickerTypes.tsx deleted file mode 100644 index 9c441471f..000000000 --- a/components/picker/PickerTypes.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { StyleProp, TextStyle, ViewStyle } from 'react-native' - -export type PickerProps = { - disabled?: boolean - selectedValue?: any - onValueChange?: (value: any) => void - itemStyle?: StyleProp - indicatorStyle?: StyleProp - indicatorClassName?: string - defaultSelectedValue?: any - style?: StyleProp - onScrollChange?: (value: any) => void - noAnimate?: boolean - numberOfLines?: number -} diff --git a/components/picker/Popup.tsx b/components/picker/Popup.tsx index d15e7f481..f67e00f28 100644 --- a/components/picker/Popup.tsx +++ b/components/picker/Popup.tsx @@ -1,14 +1,20 @@ -import React from 'react' +import React, { memo } from 'react' import { Text, TouchableHighlight, View } from 'react-native' import Modal from '../modal/ModalView' -import PopupMixin from './PopupMixin' +import { PopupPickerProps } from './PopupPickerTypes' -const getModal = ( - props: any, - visible: any, - { getContent, hide, onDismiss, onOk }: any, -) => { - const { styles, title, okText, dismissText } = props +const PopupPicker = memo((props: PopupPickerProps) => { + const { + styles, + title, + okText = 'Ok', + dismissText = 'Dismiss', + visible, + onDismiss, + onOk, + onClose, + children, + } = props const titleEl = typeof title === 'string' ? ( @@ -35,7 +41,7 @@ const getModal = ( wrapStyle={styles.modal} style={styles.container} visible={visible} - onClose={hide}> + onClose={onClose}> - {getContent()} + {children} ) -} - -export default PopupMixin(getModal, { - actionTextUnderlayColor: '#ddd', - actionTextActiveOpacity: 1, - triggerType: 'onPress', - styles: {}, - WrapComponent: View as any, }) +export default PopupPicker diff --git a/components/picker/PopupMixin.tsx b/components/picker/PopupMixin.tsx deleted file mode 100644 index e463a072c..000000000 --- a/components/picker/PopupMixin.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import React from 'react' -import { View } from 'react-native' -import { PopupPickerProps } from './PopupPickerTypes' - -interface Args { - getContent: any - hide: any - onDismiss: any - onOk: any -} - -export default function PopupMixin( - getModal: (props: any, visible: any, args: Args) => React.ReactNode, - platformProps: { - actionTextUnderlayColor: string - actionTextActiveOpacity: number - triggerType: string - styles: {} - WrapComponent: View - }, -) { - return class extends React.Component { - static defaultProps = { - onVisibleChange(_: any) {}, - okText: 'Ok', - dismissText: 'Dismiss', - title: '', - onOk(_: any) {}, - onDismiss() {}, - ...platformProps, - } - - picker: any - - constructor(props: Readonly) { - super(props) - - this.state = { - pickerValue: 'value' in this.props ? this.props.value : null, - visible: this.props.visible || false, - } - } - - UNSAFE_componentWillReceiveProps(nextProps: { value: any; visible: any }) { - if ('value' in nextProps) { - this.setState({ - pickerValue: nextProps.value, - }) - } - if ('visible' in nextProps) { - this.setVisibleState(nextProps.visible) - } - } - - onPickerChange = (pickerValue: any) => { - if (this.state.pickerValue !== pickerValue) { - this.setState({ - pickerValue, - }) - const { picker, pickerValueChangeProp } = this.props - if (picker && picker.props[pickerValueChangeProp!]) { - picker.props[pickerValueChangeProp!](pickerValue) - } - } - } - - saveRef = (picker: any) => { - this.picker = picker - } - - setVisibleState(visible: any) { - this.setState({ - visible, - }) - if (!visible) { - this.setState({ - pickerValue: null, - }) - } - } - - fireVisibleChange(visible: boolean) { - if (this.state.visible !== visible) { - if (!('visible' in this.props)) { - this.setVisibleState(visible) - } - this.props.onVisibleChange!(visible) - } - } - - getRender() { - const props = this.props - const children = props.children - if (!children) { - return getModal(props, this.state.visible, { - getContent: this.getContent, - onOk: this.onOk, - hide: this.hide, - onDismiss: this.onDismiss, - }) - } - const { WrapComponent, disabled } = this.props - const child = children - const newChildProps = {} - if (!disabled) { - ;(newChildProps as any)[props.triggerType!] = this.onTriggerClick - } - return ( - - {React.cloneElement(child as any, newChildProps)} - {getModal(props, this.state.visible, { - getContent: this.getContent, - onOk: this.onOk, - hide: this.hide, - onDismiss: this.onDismiss, - })} - - ) - } - - onTriggerClick = (e: any) => { - const child: any = this.props.children - const childProps = child.props || {} - if (childProps[this.props.triggerType!]) { - childProps[this.props.triggerType!](e) - } - this.fireVisibleChange(!this.state.visible) - } - - onOk = () => { - this.props.onOk!(this.picker && this.picker.getValue()) - this.fireVisibleChange(false) - } - - getContent = () => { - if (this.props.picker) { - let { pickerValue } = this.state - if (pickerValue === null) { - pickerValue = this.props.value - } - return React.cloneElement(this.props.picker, { - [this.props.pickerValueProp!]: pickerValue, - [this.props.pickerValueChangeProp!]: this.onPickerChange, - ref: this.saveRef, - }) - } else { - return this.props.content - } - } - - onDismiss = () => { - this.props.onDismiss!() - this.fireVisibleChange(false) - } - - hide = () => { - this.fireVisibleChange(false) - } - - render() { - return this.getRender() - } - } -} diff --git a/components/picker/PopupPickerTypes.tsx b/components/picker/PopupPickerTypes.tsx index 3920286f6..aaaa4a24b 100644 --- a/components/picker/PopupPickerTypes.tsx +++ b/components/picker/PopupPickerTypes.tsx @@ -1,27 +1,15 @@ -import React from 'react' +import type { ReactNode } from 'react' export type PopupPickerProps = { - picker?: any - value?: any - triggerType?: string - WrapComponent?: any - dismissText?: string | React.ReactElement // React.ReactElement only for web - okText?: string | React.ReactElement // React.ReactElement only for web - title?: string | React.ReactElement // React.ReactElement only for web - visible?: boolean - disabled?: boolean - onOk?: (value?: any) => void - style?: any - onVisibleChange?: (visible: boolean) => void - content?: React.ReactElement | string + onOk?: () => void onDismiss?: () => void - styles?: any + onClose?: () => void + title?: ReactNode + visible?: boolean + okText?: ReactNode + dismissText?: ReactNode + children?: ReactNode + styles: any actionTextUnderlayColor?: string actionTextActiveOpacity?: number - wrapStyle?: React.CSSProperties - pickerValueProp?: string - pickerValueChangeProp?: string - transitionName?: string - popupTransitionName?: string - maskTransitionName?: string } diff --git a/components/picker/PopupStyles.tsx b/components/picker/PopupStyles.tsx deleted file mode 100644 index de67f97da..000000000 --- a/components/picker/PopupStyles.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { StyleSheet } from 'react-native' - -const textStyle = { - color: '#0ae', - fontSize: 18, - textAlign: 'center', -} - -const styles = StyleSheet.create({ - modal: { - flexDirection: 'column', - justifyContent: 'flex-end', - }, - container: {}, - header: { - // flex:1, 0.39.0 needs to remove - height: 44, - alignItems: 'center', - flexDirection: 'row', - justifyContent: 'center', - borderBottomWidth: 1, - borderBottomColor: '#e7e7e7', - }, - headerItem: { - height: 44, - alignItems: 'center', - justifyContent: 'center', - flex: 1, - }, - actionText: textStyle, - okText: {}, - dismissText: {}, - title: { - ...textStyle, - color: '#666', - }, -}) - -export default styles diff --git a/components/picker/PropsType.tsx b/components/picker/PropsType.tsx index 377de595f..e3e912c9b 100644 --- a/components/picker/PropsType.tsx +++ b/components/picker/PropsType.tsx @@ -1,22 +1,26 @@ -import { StyleProp, TextStyle, ViewStyle } from 'react-native' +import type { ReactNode } from 'react' import { Omit } from 'utility-types' +import { + PickerValue, + PickerValueExtend, + PickerViewPropsType, +} from '../picker-view/PropsType' +import { PickerViewStyle } from '../picker-view/style' +import { WithThemeStyles } from '../style' import { PopupPickerProps } from './PopupPickerTypes' -import { CascaderValue } from './cascader/CascaderTypes' -export interface PickerData { - value: string | number - label: string - children?: PickerData[] -} -export interface PickerPropsType extends Omit { - data: PickerData[] | PickerData[][] - cascade?: boolean - value?: Array - format?: (values: string[]) => string - cols?: number +import { PickerStyle } from './style' + +export interface PickerPropsType + extends PickerViewPropsType, + WithThemeStyles, + Omit { + onOk?: (value: PickerValue[], extend: PickerValueExtend) => void + onPickerChange?: (value: PickerValue[], index: number) => void + onVisibleChange?: (visible: boolean) => void + format?: (labels: string[]) => string extra?: string - onChange?: (date?: CascaderValue) => void - onPickerChange?: (value: CascaderValue) => void - itemStyle?: StyleProp - indicatorStyle?: StyleProp - numberOfLines?: number + triggerType?: string + disabled?: boolean + children?: ReactNode + locale?: { okText?: string; dismissText?: string; extra?: string } } diff --git a/components/picker/__tests__/__snapshots__/demo.test.js.snap b/components/picker/__tests__/__snapshots__/demo.test.js.snap index a58301127..c6e28f8d5 100644 --- a/components/picker/__tests__/__snapshots__/demo.test.js.snap +++ b/components/picker/__tests__/__snapshots__/demo.test.js.snap @@ -1,13 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders ./components/picker/demo/basic.tsx correctly 1`] = ` - + + > + List Children + - + @@ -48,117 +68,115 @@ exports[`renders ./components/picker/demo/basic.tsx correctly 1`] = ` style={ Array [ Object { - "alignItems": "center", - "borderBottomColor": "#dddddd", - "borderBottomWidth": 0.5, "flex": 1, - "flexDirection": "row", - "minHeight": 44, - "paddingRight": 15, - "paddingVertical": 6, + "flexDirection": "column", }, - false, - false, ] } > - - - 省市选择 - - - - - 请选择 - - + 省市选择 + + + -  + 请选择 + +  + - + @@ -166,142 +184,121 @@ exports[`renders ./components/picker/demo/basic.tsx correctly 1`] = ` style={ Array [ Object { - "alignItems": "center", - "borderBottomColor": "#dddddd", - "borderBottomWidth": 0.5, "flex": 1, - "flexDirection": "row", - "minHeight": 44, - "paddingRight": 15, - "paddingVertical": 6, + "flexDirection": "column", }, - false, - false, ] } > - - - 省市选择(异步加载) - - - - - 请选择 - - + 省市选择(异步加载) + + + -  + 请选择 + +  + - + - - - Customized children - - + - 请选择 - - + } + > + 请选择 + + + visible 控制显示/隐藏 + + + + + + 选择 + + + + + 未选择 + + `; diff --git a/components/picker/__tests__/__snapshots__/index.test.js.snap b/components/picker/__tests__/__snapshots__/index.test.js.snap index 6f0218b8d..23f73dac3 100644 --- a/components/picker/__tests__/__snapshots__/index.test.js.snap +++ b/components/picker/__tests__/__snapshots__/index.test.js.snap @@ -10,15 +10,13 @@ Array [ } } > - - - 省市选择 - - + + 省市选择 + , - - - - + /> - - - - + - - - - + > + + +   + + diff --git a/components/picker/cascader/Cascader.tsx b/components/picker/cascader/Cascader.tsx deleted file mode 100644 index 653e30f35..000000000 --- a/components/picker/cascader/Cascader.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import arrayTreeFilter from 'array-tree-filter' -import React from 'react' -import MultiPicker from '../MultiPicker' -import Picker from '../Picker' -import { CascaderDataItem, CascaderProps } from './CascaderTypes' - -class Cascader extends React.Component { - static defaultProps = { - cols: 3, - data: [], - disabled: false, - } - - state = { - value: this.getValue( - this.props.data, - this.props.defaultValue || this.props.value, - ), - } - - UNSAFE_componentWillReceiveProps(nextProps: { data: any; value: any }) { - if ('value' in nextProps) { - this.setState({ - value: this.getValue(nextProps.data, nextProps.value), - }) - } - } - - onValueChange = (value: any, index: any) => { - const children = arrayTreeFilter(this.props.data, (c, level) => { - return level <= index && c.value === value[level] - }) - let data = children[index] - let i - for ( - i = index + 1; - data && data.children && data.children.length && i < this.props.cols!; - i++ - ) { - data = data.children[0] - value[i] = data.value - } - value.length = i - if (!('value' in this.props)) { - this.setState({ - value, - }) - } - if (this.props.onChange) { - this.props.onChange(value) - } - } - - getValue(d: any, val: any) { - let data = d || this.props.data - const value = val || this.props.value || this.props.defaultValue - let level = 0 - const nextValue = [] - - if (value && value.length) { - do { - const index = (data as CascaderDataItem[]).findIndex( - (item) => item.value === value[level], - ) - - if (index < 0) { - break - } - - nextValue[level] = value[level] - level += 1 - data = data[index].children || [] - } while (data.length > 0) - } - - for (let i = level; i < this.props.cols!; i++) { - if (data && data.length) { - nextValue[i] = data[0].value - data = data[0].children - } else { - break - } - } - - return nextValue - } - - getCols() { - const { - data, - cols, - disabled, - pickerItemStyle, - indicatorStyle, - numberOfLines, - } = this.props - const value = this.state.value - const childrenTree = arrayTreeFilter(data, (c, level) => { - return c.value === value[level] - }).map((c) => c.children) - - // in case the users data is async get when select change - const needPad = cols! - childrenTree.length - if (needPad > 0) { - for (let i = 0; i < needPad; i++) { - childrenTree.push([]) - } - } - childrenTree.length = cols! - 1 - childrenTree.unshift(data) - return childrenTree.map((children: any[] = [], level) => ( - - {children.map((item) => ( - - {item.label} - - ))} - - )) - } - - render() { - const props = this.props - const { rootNativeProps, style } = props - const cols = this.getCols() - - return ( - - {cols} - - ) - } -} - -export default Cascader diff --git a/components/picker/cascader/CascaderTypes.tsx b/components/picker/cascader/CascaderTypes.tsx deleted file mode 100644 index 2f8948840..000000000 --- a/components/picker/cascader/CascaderTypes.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react' -import { StyleProp, TextStyle, ViewStyle } from 'react-native' - -export type CascaderOneValue = string | number -export type CascaderValue = CascaderOneValue[] - -export interface CascaderDataItem { - label: React.ReactNode - value: CascaderOneValue - children?: CascaderDataItem[] -} - -export interface CascaderProps { - defaultValue?: CascaderValue - value?: CascaderValue - onChange?: (value: CascaderValue) => void - data: CascaderDataItem[] - cols?: number - disabled?: boolean - rootNativeProps?: {} - pickerItemStyle?: StyleProp - indicatorStyle?: StyleProp - style?: StyleProp - onScrollChange?: (value: CascaderValue) => void - numberOfLines?: number -} diff --git a/components/picker/cascader/Popup.tsx b/components/picker/cascader/Popup.tsx deleted file mode 100644 index 9e98f3acc..000000000 --- a/components/picker/cascader/Popup.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react' -import PopupPicker from '../Popup' -import { PopupPickerProps } from '../PopupPickerTypes' -import { CascaderProps, CascaderValue } from './CascaderTypes' - -export interface IPopupCascaderProps extends PopupPickerProps { - cascader: React.ReactElement - onChange?: (date?: CascaderValue) => void - children?: React.ReactNode -} - -class PopupCascader extends React.Component { - static defaultProps = { - pickerValueProp: 'value', - pickerValueChangeProp: 'onChange', - } - - onOk = (v: any) => { - const { onChange, onOk } = this.props - if (onChange) { - onChange(v) - } - if (onOk) { - onOk(v) - } - } - - render() { - return ( - - ) - } -} - -export default PopupCascader diff --git a/components/picker/cascader/PopupStyles.tsx b/components/picker/cascader/PopupStyles.tsx deleted file mode 100644 index 9dac90392..000000000 --- a/components/picker/cascader/PopupStyles.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '../PopupStyles' diff --git a/components/picker/cascader/index.tsx b/components/picker/cascader/index.tsx deleted file mode 100644 index 6eaa35f2f..000000000 --- a/components/picker/cascader/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Cascader' diff --git a/components/picker/demo/basic.tsx b/components/picker/demo/basic.tsx index 9fbc77ef1..5b1de50d9 100644 --- a/components/picker/demo/basic.tsx +++ b/components/picker/demo/basic.tsx @@ -1,8 +1,9 @@ const data = require('./data.json') import { district } from 'antd-mobile-demo-data' -import React from 'react' +import React, { useState } from 'react' import { Text, TouchableOpacity, View } from 'react-native' -import { List, Picker } from '../../' +import { Button, List, Picker } from '../../' +import { PickerValue, PickerValueExtend } from '../../picker-view/PropsType' const CustomChildren = (props: any) => ( @@ -21,6 +22,52 @@ const CustomChildren = (props: any) => ( ) +// visible用法 +function BasicDemo() { + const [visible, setVisible] = useState(false) + const [value, setValue] = useState([]) + const [extend, setExtend] = useState() + return ( + + + + {/* extend渲染所选值 */} + + {extend?.items?.map((item: any) => item.label).join(',') || ' 未选择'} + + + {/* visible控制显示/隐藏 */} + { + setVisible(false) + }} + visible={visible} + value={value} + onOk={(v: PickerValue[], ext: PickerValueExtend) => { + setValue(v) + setExtend(ext) + }} + /> + + ) +} + export default class PopupExample extends React.Component { constructor(props: any) { super(props) @@ -40,11 +87,14 @@ export default class PopupExample extends React.Component { onChange = (value: any) => { this.setState({ value }) } + render() { return ( - + + List Children { Customized children + visible 控制显示/隐藏 + ) } diff --git a/components/picker/index.en-US.md b/components/picker/index.en-US.md index bf8879b31..d2c36408a 100644 --- a/components/picker/index.en-US.md +++ b/components/picker/index.en-US.md @@ -12,24 +12,85 @@ Choose from a set of data, e.g. Country choice. ## API +### Props + +```ts +type PickerColumnItem = { + label: string | ReactNode + value: string | number + key?: string | number + children?: PickerColumnItem[] +} + +type PickerColumn = PickerColumnItem[] + +type PickerValue = string | number + +type PickerValueExtend = { + columns: PickerColumn[] + items: (PickerColumnItem | undefined)[] +} +``` + Properties | Descrition | Type | Default -----------|------------|------|-------- -| data | data source | `Array<{value, label, children: Array}>` | - | -| value | the value, the format is `[value1, value2, value3]`, corresponds to the level value of the data source | Array | - | -| format | a function that formats the selected value | (labels: string[]): any | `(labels) => { return labels.join(','); } ` | -| cols | col numbers | Number | `3` | -| onChange | selected callback function, can use [rc-form](https://github.com/react-component/form) | (val): void | - | -| onPickerChange | trigger on each column of selected data is changed | (val): void | - | -| onVisibleChange | visible state change callback | (visible: bool): void | - | -| itemStyle | style to apply to each of the item labels | Object | -| -| numberOfLines | Used to truncate the text with an ellipsis after computing the text layout, including line wrapping, such that the total number of lines does not exceed this number | Number | 1 | -| indicatorStyle | style of indicator | Object | - | -| children| usually `List.Item` | Object | `List.Item` | -| okText | ok text | String | `确定` | +| data | data source | `PickerColumn` / `PickerColumn[]` | - | +| value | Selected options | `PickerValue[]` | - | +| defaultValue | Default selected options | `PickerValue[]` | - | +| cascade | whether cascade
child cascade get from `data[].children` | Boolean | `true` | +| cols | col numbers | Number | `3` | +| onChange | selected callback function, can use [rc-form](https://github.com/react-component/form) | `(value: PickerValue[], extend: PickerValueExtend) => void` | - | +| onPickerChange | trigger on each column of selected data is changed | `(value: PickerValue[], index: number) => void` | - | +| onVisibleChange | visible state change callback | `(visible: bool): void` | - | +| renderLabel | The function to custom rendering the label shown on a column | `(item: PickerColumnItem, index: number) => ReactNode` | `(item) => item.label` | +| locale | international, can override the configuration of the global [LocaleProvider](/components/locale-provider) | Object: Object: {okText, dismissText, extra} | - | +| title | title | ReactNode | - | +| okText | ok text | String | `确定` | | dismissText | dismiss text | String | `取消` | -| onOk | handler called when click ok | (val): void | - | +| onOk | handler called when click ok | `(value: PickerValue[], extend: PickerValueExtend) => void` | - | | onDismiss | handler called when click cancel | (): void | - | -| title | title | String | - | -| extra | Picker's children is best to `List.Item`, if not, need to be a custom component (the `onClick`/`extra` props need to be handled in the component) | String | `请选择` | -| disabled | set disabled | Boolean | false | -| cascade | whether is cascade mode | Boolean | true | +| visible | Whether to show or hide the Picker | Boolean | - | +| loading | Should the Picker displays as loading state | Boolean | - | +| loadingContent | The loading content displayed in loading state | ReactNode | - | +| indicatorStyle | style of default Indicator | Object | - | + +### Custom Style + +Properties | Descrition | Type | Default +-----------|------------|------|-------- +| style | style | `StyleProp` | - | +| styles | inner component styles | interface `PickerViewStyle` | - | +| itemStyle| style to apply to each of the item labels | `StyleProp` | - | +| itemHeight | Height of option item, calculated by `numberOfLines` when without value; `itemStyle` was not allowed to set `{height}` | Number | - | +| numberOfLines | Used to truncate the text with an ellipsis after computing the text layout, including line wrapping, such that the total number of lines does not exceed this number | Number | `1` | + +#### Mask View + +Support custom mask style, such as using the gradient component ``. Default is white translucency. + +Properties | Descrition | Type | Default +-----------|------------|------|-------- +| renderMaskTop | The function to custom rendering the mask top half view | `()=> ReactNode` | `` | +| renderMaskBottom | The function to custom rendering the mask bottom half view | `()=> ReactNode` | `` | + + +### Children +Picker's children is best to [List.Item](/components/list/#List.Item), if not, need to be a custom component (the `onClick`/`extra` props need to be handled in the component): + +Properties | Descrition | Type | Default +-----------|------------|------|-------- +| children| usually `List.Item` | ReactNode | `List.Item` | +| extra | Picker's children `extra` prop, display when no `value` | String | `请选择` | +| format | a function that formats the selected value | (labels: string[]): any | `(labels) => { return labels.join(','); } ` | +| triggerType | Press event name | String | `onPress` | +| disabled | set disabled | Boolean | `false` | + +### PickerActions +Properties | Descrition | Type +-----------|------------|----- +| close | Close Picker | `() => void` | +| open | Open Picker | `() => void` | +| toggle | Toggle the visible state of Picker | `() => void` | + +### Ref +Same as PickerActions diff --git a/components/picker/index.tsx b/components/picker/index.tsx index d7ddf925d..de7ec2466 100644 --- a/components/picker/index.tsx +++ b/components/picker/index.tsx @@ -1,240 +1,97 @@ -import treeFilter from 'array-tree-filter' -import React from 'react' -import { getComponentLocale } from '../_util/getLocale' -import { LocaleContext } from '../locale-provider' -import { WithTheme, WithThemeStyles } from '../style' -import MultiPicker from './MultiPicker' -import RMCPicker from './Picker' -import { PickerData, PickerPropsType } from './PropsType' -import RMCCascader from './cascader' -import RMCPopupCascader from './cascader/Popup' -import PickerStyles, { PickerStyle } from './style' +import React, { + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from 'react' +import { mergeProps } from '../_util/with-default-props' +import { PickerValue } from '../picker-view/PropsType' +import { getColumns, getValueExtend } from '../picker-view/columns-extend' +import RMCPicker, { PickerRef } from './Picker' +import { PickerPropsType } from './PropsType' -export interface PickerProps - extends PickerPropsType, - WithThemeStyles { - pickerPrefixCls?: string - popupPrefixCls?: string - children: React.ReactNode -} +export interface PickerProps extends PickerPropsType {} -export function getDefaultProps() { - const defaultFormat = (values: string[]) => { - return values.join(',') - } - return { - triggerType: 'onPress', - prefixCls: 'am-picker', - pickerPrefixCls: 'am-picker-col', - popupPrefixCls: 'am-picker-popup', - format: defaultFormat, - cols: 3, - cascade: true, - title: '', - } +const defaultProps = { + defaultValue: [], + cols: 3, + cascade: true, } -export default class Picker extends React.Component { - static contextType = LocaleContext - static defaultProps = getDefaultProps() - protected popupProps: {} - private scrollValue: any - getSel = () => { - const value = this.props.value || [] - let treeChildren: PickerData[] - const { data } = this.props - if (this.props.cascade) { - treeChildren = treeFilter(data as PickerData[], (c: any, level: any) => { - return c.value === value[level] - }) - } else { - treeChildren = value.map((v, i) => { - return (data as PickerData[][])[i].filter((d) => d.value === v)[0] - }) - } - return ( - this.props.format && - this.props.format( - treeChildren.map((v) => { - return v.label - }), - ) - ) - } +const Picker = forwardRef((props, ref) => { + const p = mergeProps(defaultProps, props) - getPickerCol = () => { - const { data, itemStyle, indicatorStyle, numberOfLines } = this.props + const [innerValue, setInnerValue] = useState( + p.value === undefined ? p.defaultValue : p.value, + ) - return ((Array.isArray(data[0]) ? data : [data]) as PickerData[][]).map( - (col, index) => { - return ( - - {col.map((item) => { - return ( - - {item.label} - - ) - })} - - ) - }, - ) - } + const pickerRef = React.useRef(null) - onOk = (v: any) => { - if (this.scrollValue !== undefined) { - v = this.scrollValue - } - if (this.props.onChange) { - this.props.onChange(v) - } - if (this.props.onOk) { - this.props.onOk(v) - } - } + useImperativeHandle(ref, () => pickerRef.current as PickerRef) - setScrollValue = (v: any) => { - this.scrollValue = v - } + const columns = useMemo( + () => getColumns(p.data, innerValue, p.cols, p.cascade), + [p.data, innerValue, p.cols, p.cascade], + ) - setCasecadeScrollValue = (v: any) => { - // 级联情况下保证数据正确性,滚动过程中只有当最后一级变化时才变更数据 - if (v && this.scrollValue) { - const length = this.scrollValue.length - if ( - length === v.length && - this.scrollValue[length - 1] === v[length - 1] - ) { - return - } - } - this.setScrollValue(v) - } + const handleSelect = useCallback( + (val: PickerValue, index: number) => { + const value = innerValue?.slice?.(0) || [] + value[index] = val + const { nextValue } = getValueExtend(p.data, value, p.cols, p.cascade) + setInnerValue(nextValue) + p.onPickerChange?.(nextValue, index) + }, + [p, innerValue, setInnerValue], + ) - fixOnOk = (cascader: any) => { - if (cascader && cascader.onOk !== this.onOk) { - cascader.onOk = this.onOk - cascader.forceUpdate() - } - } + // 记录value是否变化过 + const isValueChanged = useRef(false) - onPickerChange = (v: any) => { - this.setScrollValue(v) - if (this.props.onPickerChange) { - this.props.onPickerChange(v) - } - } + const onVisibleChange = useCallback( + (visible) => { + p.onVisibleChange?.(visible) + if (!visible && p.value !== innerValue && isValueChanged.current) { + // 关闭时,如果选中值不同步,恢复为原选中值 + setInnerValue(p.value || []) + } + }, + [innerValue, p, setInnerValue], + ) - onVisibleChange = (visible: boolean) => { - this.setScrollValue(undefined) - if (this.props.onVisibleChange) { - this.props.onVisibleChange(visible) - } - } + // for useEffect only on update + const isInitialMount = useRef(true) - render() { - const { - children, - value = [], - popupPrefixCls, - itemStyle, - indicatorStyle, - numberOfLines, - okText, - dismissText, - extra, - cascade, - data, - cols, - onOk, - ...restProps - } = this.props + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false + // extra update initial + pickerRef.current?._updateExtra() + } else { + isValueChanged.current = true + setInnerValue(p.value || []) + // extra update after value update + setTimeout(() => { + pickerRef.current?._updateExtra() + }) + } + }, [p.value]) - // tslint:disable-next-line:variable-name - const _locale = getComponentLocale( - this.props, - (this as any).context, - 'Picker', - () => require('./locale/zh_CN'), - ) + return ( + + ) +}) - const { cascader, popupMoreProps }: { cascader: any; popupMoreProps: {} } = - this.getCascade( - cascade, - data, - cols, - itemStyle, - indicatorStyle, - numberOfLines, - ) - return ( - - {(styles) => ( - - {children && - typeof children !== 'string' && - React.isValidElement(children) && - React.cloneElement(children as any, { - extra: this.getSel() || extra || _locale.extra, - })} - - )} - - ) - } +Picker.displayName = 'AntmPicker' - getCascade = ( - cascade: boolean | undefined, - data: PickerData[] | PickerData[][], - cols: number | undefined, - itemStyle: any, - indicatorStyle: any, - numberOfLines: number | undefined, - ) => { - let cascader: React.ReactNode - let popupMoreProps = {} - if (cascade) { - cascader = ( - - ) - } else { - cascader = ( - - {this.getPickerCol()} - - ) - popupMoreProps = { - pickerValueProp: 'selectedValue', - pickerValueChangeProp: 'onValueChange', - } - } - return { cascader, popupMoreProps } - } -} +export default Picker diff --git a/components/picker/index.zh-CN.md b/components/picker/index.zh-CN.md index 18dfdeccf..2f1793e07 100644 --- a/components/picker/index.zh-CN.md +++ b/components/picker/index.zh-CN.md @@ -13,25 +13,83 @@ subtitle: 选择器 ## API +### 属性 + +```ts +type PickerColumnItem = { + label: string | ReactNode + value: string | number + key?: string | number + children?: PickerColumnItem[] +} + +type PickerColumn = PickerColumnItem[] + +type PickerValue = string | number + +type PickerValueExtend = { + columns: PickerColumn[] + items: (PickerColumnItem | undefined)[] +} +``` + 属性 | 说明 | 类型 | 默认值 ----|-----|------|------ -| data | 数据源 | `Array<{value, label, children: Array}>` | - | -| value | 值, 格式是`[value1, value2, value3]`, 对应数据源的相应级层value | Array | - | -| format | 格式化选中值的函数 | (labels: string[]): any | `(labels) => { return labels.join(','); } ` | -| cols | 列数 | Number | `3` | -| onChange | 选中后的回调,可使用[rc-form](https://github.com/react-component/form) | (val): void | - | -| onPickerChange | 每列数据选择变化后的回调函数 | (val): void | - | -| onVisibleChange | 当显隐状态变化时回调函数 | (visible: bool): void | - | -| itemStyle | 每列样式 | Object | - | -| numberOfLines | 允许每列显示行数 | Number | 1 | -| indicatorStyle | indicator 样式 | Object | - | -| children| 通常是 `List.Item` | Object | `List.Item` | -| okText | 选中的文案 | String | `确定` | +| data | 数据源 | `PickerColumn` / `PickerColumn[]` | - | +| value | 选中项 | `PickerValue[]` | - | +| defaultValue | 默认选中项 | `PickerValue[]` | - | +| cascade | 是否级联。
子级来自`data`参数内的`children` | Boolean | `true` | +| cols | 列数 | Number | `3` | +| onChange | 选中后的回调,可使用[rc-form](https://github.com/react-component/form) | `(value: PickerValue[], extend: PickerValueExtend) => void` | - | +| onPickerChange | 每列数据选择变化后的回调函数 | `(value: PickerValue[], index: number) => void` | - | +| onVisibleChange | 当显隐状态变化时回调函数 | `(visible: bool): void` | - | +| renderLabel | 自定义渲染每列展示的内容 | `(item: PickerColumnItem, index: number) => ReactNode` | `(item) => item.label` | +| locale | 国际化,可覆盖全局[LocaleProvider](/components/locale-provider-cn)的配置 | Object: {okText, dismissText, extra} | - | +| title | 大标题 | ReactNode | - | +| okText | 选中的文案 | String | `确定` | | dismissText | 取消选中的文案 | String | `取消` | -| onOk | 点击选中时执行的回调 | (val): void | 无 | -| onDismiss | 点击取消时执行的回调 | (): void | 无 | -| title | 大标题 | String | - | -| extra | Picker children 建议是 `List.Item`, 如果不是,需要是自定义组件(组件内需处理`onClick`/`extra`属性) | String | `请选择` | -| disabled | 是否不可用 | Boolean | false | -| cascade | 是否联动 | Boolean | true | +| onOk | 点击选中时执行的回调 | `(value: PickerValue[], extend: PickerValueExtend) => void` | - | +| onDismiss | 点击取消时执行的回调 | (): void | - | +| visible | 是否显示选择器 | Boolean | - | +| loading | 是否处于加载状态 | Boolean | - | +| loadingContent | 加载状态下展示的内容 | ReactNode | - | +| indicatorStyle | 默认Indicator的样式 | Object | - | + +### 自定义样式 + +属性 | 说明 | 类型 | 默认值 +----|-----|------|------ +| style | 外部样式 | `StyleProp` | - | +| styles | 内部组件样式集 | `PickerViewStyle` | - | +| itemStyle| 每列样式 | `StyleProp` | - | +| itemHeight | 每列固定高度,未设值时会根据`numberOfLines`动态计算;`itemStyle`属性设置`{height}`值是无效的 | Number | - | +| numberOfLines | 允许每列显示行数 | Number | `1` | + +#### 遮罩层 +还支持自定义遮罩样式,如使用渐变组件``。当前默认为白色半透明。 + +属性 | 说明 | 类型 | 默认值 +----|-----|------|------ +| renderMaskTop | 自定义渲染上半部分遮罩层 | `()=> ReactNode` | `` | +| renderMaskBottom | 自定义渲染下半部分遮罩层 | `()=> ReactNode` | `` | + +### Children +通常是 [List.Item](/components/list-cn/#List.Item) ,以下属性也是围绕着`List.Item`展开: + +属性 | 说明 | 类型 | 默认值 +----|-----|------|------ +| children| Picker占位组件,通常是`List.Item` | ReactNode | `List.Item` | +| extra | Picker children的`extra`属性,无选中项时展示 | String | `请选择` | +| format | 格式化选中值的函数,用于回显在`extra`属性上 | (labels: string[]): any | `(labels) => { return labels.join(','); } ` | +| triggerType | 按钮事件名称 | String | `onPress` | +| disabled | 是否不可用 | Boolean | `false` | + +### PickerActions +属性 | 说明 | 类型 +----|-----|------ +| close |关闭 Picker|`() => void`| +| open |显示 Picker|`() => void`| +| toggle|切换 Picker 的显示和隐藏状态|`() => void`| +### Ref +同 PickerActions diff --git a/components/picker/locale/id_ID.tsx b/components/picker/locale/id_ID.tsx new file mode 100644 index 000000000..db99d7ea2 --- /dev/null +++ b/components/picker/locale/id_ID.tsx @@ -0,0 +1,5 @@ +export default { + okText: 'Ok', + dismissText: 'Cancel', + extra: 'please select', +} diff --git a/components/picker/locale/ko_KR.tsx b/components/picker/locale/ko_KR.tsx new file mode 100644 index 000000000..21c6edf98 --- /dev/null +++ b/components/picker/locale/ko_KR.tsx @@ -0,0 +1,5 @@ +export default { + okText: '확인', + dismissText: '취소', + extra: '선택해주세요', +} diff --git a/components/search-bar/locale/ko_KR.tsx b/components/search-bar/locale/ko_KR.tsx new file mode 100644 index 000000000..771b2f8e1 --- /dev/null +++ b/components/search-bar/locale/ko_KR.tsx @@ -0,0 +1,3 @@ +export default { + cancelText: '취소', +} diff --git a/components/style/index.tsx b/components/style/index.tsx index 1a8523bb3..2676cbec1 100644 --- a/components/style/index.tsx +++ b/components/style/index.tsx @@ -17,9 +17,33 @@ export const ThemeProvider = (props: ThemeProviderProps) => { export interface UseThemeContextProps { theme?: PartialTheme } -export const useTheme = (props: UseThemeContextProps = {}) => { +export const useTheme = (props: any) => { const theme = React.useContext(ThemeContext) - return { ...theme, ...props.theme } + const { themeStyles, styles } = props + + const stylesRef = React.useRef(undefined) + const cache = React.useRef(undefined) + + const getStyles = React.useCallback(() => { + if (themeStyles && cache.current === undefined) { + cache.current = themeStyles(theme) + } + + // TODO: check these styles has changed + if (styles && !shallowequal(stylesRef.current, styles)) { + stylesRef.current = styles + // merge styles from user defined + styles && + Object.keys(styles).forEach((key) => { + if (cache.current[key]) { + cache.current[key] = [cache.current[key], styles[key]] + } + }) + } + + return cache.current || {} + }, [theme, themeStyles, styles]) + return getStyles() } export interface WithThemeProps { diff --git a/components/switch/PropsType.tsx b/components/switch/PropsType.tsx index 1709ea1a7..c75252e3f 100644 --- a/components/switch/PropsType.tsx +++ b/components/switch/PropsType.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import React from 'react' import { ColorValue } from 'react-native' @@ -15,6 +14,9 @@ export interface SwitchPropsType { title?: string checkedChildren?: string | React.ReactNode unCheckedChildren?: string | React.ReactNode - onChange?: (checked: boolean) => void + onChange?: (checked: boolean) => void | Promise + /** + * @deprecated + */ onPress?: (checked: boolean) => void } diff --git a/components/switch/Switch.tsx b/components/switch/Switch.tsx index 88bf4053e..d88f844bd 100644 --- a/components/switch/Switch.tsx +++ b/components/switch/Switch.tsx @@ -1,13 +1,20 @@ import classNames from 'classnames' import useMergedState from 'rc-util/lib/hooks/useMergedState' import * as React from 'react' -import { Animated, Easing, StyleProp, View, ViewStyle } from 'react-native' +import { + Animated, + Easing, + Pressable, + StyleProp, + View, + ViewStyle, +} from 'react-native' +import devWarning from '../_util/devWarning' +import { useAnimatedTiming } from '../_util/hooks/useAnimations' +import { isPromise } from '../_util/isPromise' import RNActivityIndicator from '../activity-indicator' -import ButtonWave from '../button/ButtonWave' import { WithTheme, WithThemeStyles } from '../style' import AntmView from '../view/index' -import devWarning from '../_util/devWarning' -import { useAnimatedTiming } from '../_util/hooks/useAnimations' import { SwitchPropsType } from './PropsType' import SwitchStyles, { SwitchStyle } from './style/index' @@ -21,6 +28,7 @@ export interface SwitchProps const AnimatedView = Animated.createAnimatedComponent(AntmView) const AntmSwitch = ({ prefixCls = 'switch', + style, checked, defaultChecked, disabled, @@ -40,29 +48,38 @@ const AntmSwitch = ({ 'Switch', '`value` is not a valid prop, do you mean `checked`?', ) - // Compatible with old code : checked without onChange was alse onControlled - const checkedRef = React.useRef() - if (checkedRef.current === undefined) { - checkedRef.current = checked ?? defaultChecked - } const [innerChecked, setInnerChecked] = useMergedState(false, { - value: checkedRef.current, + value: checked, defaultValue: defaultChecked, + onChange: onChange, }) + const [innerLoading, setInnerLoading] = useMergedState(false, { + value: loading, + }) + + const PADDING = 11 // switch旁白最低宽度 + const TRACK_PADDING = 5 // switch轨道按压变形宽度 + const BORDER_WIDTH = 2 // switch轨道边框宽度 + + // switch height measure + const [itemHeight, setHeight] = React.useState(31) + const wrapperMeasure = React.useCallback((e) => { + setHeight(e.nativeEvent.layout.height) + }, []) //disabled when loading - disabled = disabled || loading + disabled = disabled || innerLoading // animate1 const [animatedValue, animate] = useAnimatedTiming() const transitionMargin = { marginLeft: animatedValue.interpolate({ inputRange: [0, 1], - outputRange: [25, 7], + outputRange: [itemHeight - BORDER_WIDTH, BORDER_WIDTH], }), marginRight: animatedValue.interpolate({ inputRange: [0, 1], - outputRange: [7, 25], + outputRange: [BORDER_WIDTH, itemHeight - BORDER_WIDTH], }), } @@ -71,48 +88,59 @@ const AntmSwitch = ({ const transitionWidth = { width: animatedValue2.interpolate({ inputRange: [0, 1], - outputRange: [22, 28], + outputRange: [ + itemHeight - BORDER_WIDTH * 2, + itemHeight - BORDER_WIDTH * 2 + TRACK_PADDING, + ], }), left: !innerChecked ? animatedValue.interpolate({ inputRange: [0, 1], - outputRange: [0, 10], + outputRange: [BORDER_WIDTH, PADDING], }) : undefined, right: innerChecked ? animatedValue.interpolate({ inputRange: [0, 1], - outputRange: [10, 0], + outputRange: [PADDING, BORDER_WIDTH], }) : 0, } //initial animate React.useEffect(() => { - if (checkedRef.current) { + if (innerChecked) { animate({}) animate2({ toValue: 0 }) } else { animate({ toValue: 0 }) } - }, [animate, animate2, checkedRef]) + }, [animate, animate2, innerChecked, itemHeight]) - function triggerChange(newChecked: boolean) { + async function triggerChange(newChecked: boolean) { if (!disabled) { - checkedRef.current = newChecked setInnerChecked(newChecked) - onChange?.(newChecked) + const result = onChange?.(newChecked) + if (isPromise(result)) { + setInnerLoading(true) + try { + await result + setInnerLoading(false) + } catch (e) { + setInnerLoading(false) + throw e + } + } return newChecked } return innerChecked } - function onInternalClick() { - const ret = triggerChange(!innerChecked) + async function onInternalClick() { + const ret = await triggerChange(!innerChecked) // [Legacy] trigger onClick with value onPress?.(ret) - animate({ toValue: ret ? 1 : 0 }) } function onPressIn() { @@ -134,6 +162,7 @@ const AntmSwitch = ({ }) .split(' ') .map((a) => styles[a]) + .concat([style]) const ant_switch_inner = classNames(`${prefixCls}_inner`, { [`${prefixCls}_inner_checked`]: innerChecked, @@ -149,6 +178,13 @@ const AntmSwitch = ({ }) .split(' ') .map((a) => styles[a]) + .concat([ + { + width: itemHeight - BORDER_WIDTH * 2, + height: itemHeight - BORDER_WIDTH * 2, + borderRadius: itemHeight - BORDER_WIDTH * 2, + }, + ]) // color props const Color = innerChecked @@ -170,51 +206,44 @@ const AntmSwitch = ({ const accessibilityState = { checked: innerChecked, disabled, - busy: loading, + busy: innerLoading, } return ( - - - - - {loading && ( - - )} - - - {innerChecked ? checkedChildren : unCheckedChildren} - - - - + {...restProps} + disabled={disabled} + onPressIn={onPressIn} + onPressOut={onPressOut} + onPress={onInternalClick}> + + + {innerLoading && ( + + )} + + + {innerChecked ? checkedChildren : unCheckedChildren} + + + ) }} diff --git a/components/switch/__tests__/__snapshots__/demo.test.js.snap b/components/switch/__tests__/__snapshots__/demo.test.js.snap index fc8c9bfde..6d6f09cc5 100644 --- a/components/switch/__tests__/__snapshots__/demo.test.js.snap +++ b/components/switch/__tests__/__snapshots__/demo.test.js.snap @@ -101,103 +101,90 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": false, - "disabled": undefined, + "disabled": false, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > - - + - + } + /> @@ -319,107 +306,94 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": false, "disabled": true, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > - - + - + } + /> @@ -626,105 +600,92 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": true, - "disabled": undefined, + "disabled": false, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > - - + - 开启 - - + } + > + 开 + @@ -800,105 +761,92 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": false, - "disabled": undefined, + "disabled": false, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > - - + - 0 - - + } + > + 0 + @@ -974,119 +922,106 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": true, - "disabled": undefined, + "disabled": false, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > - + - + - -  - - +  + @@ -1214,125 +1149,111 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` "disabled": true, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > + - - - - + - + @@ -1413,125 +1334,111 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` "disabled": true, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > + - - - - + - + @@ -1653,107 +1560,299 @@ exports[`renders ./components/switch/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": true, - "disabled": undefined, + "disabled": false, } } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + > + + + + + + + + + + + + + + + 异步 + + + + + + + - - + + + - + - + - + } + /> diff --git a/components/switch/demo/basic.tsx b/components/switch/demo/basic.tsx index a3d1b1b88..c803266d9 100644 --- a/components/switch/demo/basic.tsx +++ b/components/switch/demo/basic.tsx @@ -7,6 +7,7 @@ export default class SwitchExample extends React.Component { super(props) this.state = { disabled: true, + checked: false, } } @@ -15,6 +16,20 @@ export default class SwitchExample extends React.Component { disabled: !this.state.disabled, }) } + + sleep1s = () => { + return new Promise((resolve) => { + setTimeout(resolve, 1000) + }) + } + + onChangeAsync = async (val: boolean) => { + await this.sleep1s() + this.setState({ + checked: val, + }) + } + render() { return ( @@ -36,8 +51,8 @@ export default class SwitchExample extends React.Component { } @@ -66,6 +81,17 @@ export default class SwitchExample extends React.Component { color="red" + + + }> + onChange 返回 Promise + + ) } diff --git a/components/switch/index.en-US.md b/components/switch/index.en-US.md index 10c96be68..29e1833a6 100644 --- a/components/switch/index.en-US.md +++ b/components/switch/index.en-US.md @@ -7,14 +7,17 @@ title: Switch Select between two status, e.g. Select On or Off. ### Rules -- Used in `List` only. -- There is no need to add extra text to describe the value of `Switch` . +- This is a **controlled component** that requires an `onChange` callback that updates the `checked` prop in order for the component to reflect user actions. ## API Properties | Descrition | Type | Default -----------|------------|------|-------- | checked | Whether is checked by default | Boolean | false | +| defaultChecked | Whether to open initially | Boolean | false | | disabled | whether is disabled | Boolean | false | +| loading | Loading status | Boolean | false | +| onChange | The callback function when changing, when the Promise is returned, the loading status will be displayed automatically | `(val: boolean) => void \| Promise` | - | | color | Background color when the switch is turned on. | String | #4dd865 | -| onChange | The callback function that is triggered when the selected state changes. | (checked: bool): void | - | +| checkedChildren | Selected content | ReactNode | - | +| unCheckedChildren | Non-selected content | ReactNode | - | \ No newline at end of file diff --git a/components/switch/index.zh-CN.md b/components/switch/index.zh-CN.md index d6327d0a6..e4997856b 100644 --- a/components/switch/index.zh-CN.md +++ b/components/switch/index.zh-CN.md @@ -8,18 +8,17 @@ subtitle: 滑动开关 在两个互斥对象进行选择,eg:选择开或关。 ### 规则 -- 只在 List 中使用。 -- 避免增加额外的文案来描述当前 Switch 的值。 +- 这是一个“受控组件”。你必须使用`onChange`回调来更新`checked`属性以响应用户的操作。 ## API 属性 | 说明 | 类型 | 默认值 ----|-----|------|------ | checked | 是否默认选中 | Boolean | false | -| checkedChildren | 选中时的内容 | String \| ReactNode | 无 | +| defaultChecked | 初始是否打开 | Boolean | false | | disabled | 是否不可修改 | Boolean | false | -| loading | 加载中的开关 -| unCheckedChildren | 非选中时的内容 | String \| ReactNode | 无 | -| onChange | change 事件触发的回调函数 | (checked: bool): void | 无 | -| color | 开关打开后的颜色 | String | #4dd865 | -| onPress | click事件触发的回调函数,当switch为disabled时,入参的值始终是默认传入的checked值。 | (checked: bool): void | 无 | +| loading | 加载中的开关 | Boolean | false | +| onChange | 变化时的回调函数,当返回 Promise 时,会自动显示加载状态 | `(val: boolean) => void \| Promise` | 无 | +| color | 开关打开后的颜色 | String | `#4dd865` | +| checkedChildren | 选中时的内容 | ReactNode | 无 | +| unCheckedChildren | 非选中时的内容 | ReactNode | 无 | \ No newline at end of file diff --git a/components/switch/style/index.tsx b/components/switch/style/index.tsx index 3f95edaee..923b55805 100644 --- a/components/switch/style/index.tsx +++ b/components/switch/style/index.tsx @@ -20,36 +20,48 @@ export default (theme: Theme) => StyleSheet.create({ switch: { position: 'relative', - minWidth: 50, - minHeight: 25, + width: 55, + height: 31, display: 'flex', flexDirection: 'row', alignItems: 'center', padding: 0, - borderRadius: 20, - borderWidth: 1, - borderColor: 'transparent', - overflow: 'hidden', + borderRadius: 31, }, // handle switch_handle: { position: 'absolute', - width: 22, - height: 22, - borderRadius: 999, + width: 27, + height: 27, + borderRadius: 27, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', backgroundColor: '#ffffff', + shadowColor: 'rgb(0, 35, 11)', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.2, + shadowRadius: 10, + elevation: 10, }, // inner switch_inner: { color: '#fff', fontSize: 12, + flex: 1, + textAlign: 'center', + alignItems: 'center', + justifyContent: 'center', }, switch_inner_checked: { marginLeft: 7, - marginRight: 25, + marginRight: 27, }, switch_inner_unchecked: { - marginLeft: 25, + marginLeft: 27, marginRight: 7, }, // checked diff --git a/components/toast/__tests__/__snapshots__/demo.test.js.snap b/components/toast/__tests__/__snapshots__/demo.test.js.snap index 76c790638..1af00ead5 100644 --- a/components/toast/__tests__/__snapshots__/demo.test.js.snap +++ b/components/toast/__tests__/__snapshots__/demo.test.js.snap @@ -96,103 +96,90 @@ exports[`renders ./components/toast/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": true, - "disabled": undefined, + "disabled": false, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > - - + - + } + /> @@ -270,103 +257,90 @@ exports[`renders ./components/toast/demo/basic.tsx correctly 1`] = ` accessibilityRole="switch" accessibilityState={ Object { - "busy": undefined, + "busy": false, "checked": true, - "disabled": undefined, + "disabled": false, } } - style={ - Array [ - Object { - "alignItems": "center", - "borderColor": "transparent", - "borderRadius": 20, - "borderWidth": 1, - "display": "flex", - "flexDirection": "row", - "minHeight": 25, - "minWidth": 50, - "overflow": "hidden", - "padding": 0, - "position": "relative", - }, - Object { - "padding": 1, - }, - ] - } + accessible={true} + collapsable={false} + focusable={true} + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} > - - + - + } + /> diff --git a/components/view/index.tsx b/components/view/index.tsx index 237ae5305..972e33bc3 100644 --- a/components/view/index.tsx +++ b/components/view/index.tsx @@ -43,6 +43,22 @@ class AntmView extends React.PureComponent { } } + if ( + React.isValidElement(children) && + String(children.type) === 'Symbol(react.fragment)' + ) { + return ( + + {React.Children.map(children.props.children, (child) => { + if (React.isValidElement(child)) { + return child + } + return {child} + })} + + ) + } + return } } diff --git a/docs/react/upgrade-notes.en-US.md b/docs/react/upgrade-notes.en-US.md index 7f22a0853..c7bf502a2 100644 --- a/docs/react/upgrade-notes.en-US.md +++ b/docs/react/upgrade-notes.en-US.md @@ -5,6 +5,26 @@ title: Upgrade Here list some of main incompatible changes and recommended changes in the upgrade. See [Changelog](/changelog) for all changes. +### 5.1.0 + +> 安装 peer 依赖 + +```bash +npm install @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +``` + +or + +```bash +yarn add @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +``` +
+> On the root of the Project, the `App.js/ App.tsx` file probably need import + +```js +import 'react-native-gesture-handler'; +``` + ### 5.0.3 > Installing peer dependencies diff --git a/docs/react/upgrade-notes.zh-CN.md b/docs/react/upgrade-notes.zh-CN.md index 9b428be56..1a9711e68 100644 --- a/docs/react/upgrade-notes.zh-CN.md +++ b/docs/react/upgrade-notes.zh-CN.md @@ -5,6 +5,26 @@ title: 升级指南 此处着重列出升级中的不兼容变化和推荐改动。所有变动请见 [Changelog](/changelog)。 +### 5.1.0 + +> 安装 peer 依赖 + +```bash +npm install @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +``` + +or + +```bash +yarn add @react-native-community/segmented-control @react-native-community/slider react-native-gesture-handler +``` +
+> 在项目的根目录下,入口文件(通常是App.js)文件中需要引入这句: + +```js +import 'react-native-gesture-handler'; +``` + ### 5.0.3 > 安装 peer 依赖 diff --git a/example/.nvmrc b/example/.nvmrc new file mode 100644 index 000000000..25bf17fc5 --- /dev/null +++ b/example/.nvmrc @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/example/App.js b/example/App.js index 6d1b5a66f..67af603bf 100644 --- a/example/App.js +++ b/example/App.js @@ -3,6 +3,7 @@ import { useFonts } from 'expo-font' import * as SplashScreen from 'expo-splash-screen' import React, { useCallback } from 'react' import { View } from 'react-native' +import 'react-native-gesture-handler' SplashScreen.preventAutoHideAsync() diff --git a/example/app.json b/example/app.json index 830b4857f..656ad6ec9 100644 --- a/example/app.json +++ b/example/app.json @@ -5,7 +5,7 @@ "name": "@ant-design/react-native", "slug": "ant-design-mobile-rn", "description": "基于蚂蚁金服移动设计规范的 React Native 组件库", - "sdkVersion": "47.0.0", + "sdkVersion": "49.0.0", "icon": "../rn-kitchen-sink/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png", "splash": { "image": "../rn-kitchen-sink/ios/KitchenSink/Images.xcassets/AppIcon.appiconset/ios-marketing-1024@1x.png" diff --git a/example/index.js b/example/index.js index 40906434b..a37ba27c6 100644 --- a/example/index.js +++ b/example/index.js @@ -1,3 +1,4 @@ +import '@expo/metro-runtime' import { registerRootComponent } from 'expo' import App from './App' diff --git a/example/package.json b/example/package.json index 837c796b9..4183ff649 100644 --- a/example/package.json +++ b/example/package.json @@ -19,16 +19,18 @@ "dependencies": { "@ant-design/icons-react-native": "^2.3.2", "@ant-design/react-native": "*", - "expo": "^47.0.0", + "expo": "49", "expo-font": "~11.0.1", "expo-splash-screen": "~0.17.4", "expo-updates": "~0.15.6", "react": "18.1.0", "react-dom": "18.1.0", - "react-native": "0.70.5", + "react-native": "0.70.8", + "react-native-gesture-handler": "~2.12.0", "react-native-reanimated": "~2.12.0" }, "devDependencies": { - "babel-preset-expo": "9.0.2" + "@expo/metro-runtime": "^2.2.15", + "babel-preset-expo": "~9.2.1" } } diff --git a/example/tsconfig.json b/example/tsconfig.json new file mode 100644 index 000000000..0e6371f6f --- /dev/null +++ b/example/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": {}, + "extends": "expo/tsconfig.base" +} diff --git a/package.json b/package.json index d04e92ff8..637544b3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ant-design/react-native", - "version": "5.0.5", + "version": "5.1.0", "description": "基于蚂蚁金服移动设计规范的 React Native 组件库", "keywords": [ "ant", @@ -34,13 +34,15 @@ "@ant-design/icons-react-native": "^2.3.1", "@bang88/react-native-ultimate-listview": "^4.1.0", "@types/shallowequal": "^1.1.1", - "array-tree-filter": "~2.1.0", "babel-runtime": "^6.x", "classnames": "^2.2.1", + "dayjs": "^1.11.7", + "lodash.assignwith": "^4.2.0", "normalize-css-color": "^1.0.2", "rc-util": "^4.21.1", "react-native-codegen": "^0.0.7", "react-native-collapsible": "^1.6.0", + "react-native-gesture-handler": "~2.2.1", "react-native-modal-popover": "^2.0.1", "shallowequal": "^1.1.0", "utility-types": "^3.10.0" @@ -49,12 +51,9 @@ "@ant-design/tools": "^13.4.1-beta.0", "@babel/core": "^7.12.9", "@babel/runtime": "^7.12.5", - "@react-native-camera-roll/camera-roll": "^5.1.0", "@react-native-community/eslint-config": "^2.0.0", - "@react-native-community/masked-view": "^0.1.9", "@react-native-community/segmented-control": "^2.2.2", "@react-native-community/slider": "^3.0.3", - "@react-native-picker/picker": "^2.4.8", "@react-navigation/native": "^6.1.1", "@react-navigation/stack": "^6.3.10", "@testing-library/jest-native": "^4.0.1", @@ -88,7 +87,6 @@ "react-github-button": "^0.1.9", "react-intl": "^2.2.3", "react-native": "0.64.2", - "react-native-gesture-handler": "~2.2.1", "react-native-mocker": "^0.0.12", "react-native-reanimated": "^2.2.0", "react-native-safe-area-context": "4.2.4", @@ -101,13 +99,11 @@ "typescript": "^4.3.2" }, "peerDependencies": { - "react": ">=17.0.1", - "react-native": ">=0.64.1", - "@react-native-camera-roll/camera-roll": ">= 5.0.0", "@react-native-community/segmented-control": ">= 1.4.0", "@react-native-community/slider": ">= 2.0.0", - "@react-native-picker/picker": "^2.4.8", - "react-native-gesture-handler": "^2.2.1" + "react": ">=17.0.1", + "react-native": ">=0.64.1", + "react-native-gesture-handler": ">=2.2.1" }, "scripts": { "lint": "npm run tslint && npm run srclint && npm run applint", diff --git a/rn-kitchen-sink/App.js b/rn-kitchen-sink/App.js index f80ef4d14..2fb856a9d 100644 --- a/rn-kitchen-sink/App.js +++ b/rn-kitchen-sink/App.js @@ -4,8 +4,8 @@ import React from 'react' import { AppRegistry } from 'react-native' import 'react-native-gesture-handler' import Provider from '../components/provider' -import RnIndex from './components/index' import Theme from './components/Theme' +import RnIndex from './components/index' import { OTHERS, UIBARS, UICONTROLS, UIVIEWS } from './demoList' const getOptions = (title) => ({ @@ -78,9 +78,11 @@ AppRegistry.registerComponent('KitchenSink', () => App) export default App // global catch error to avoid crash -global.ErrorUtils?.setGlobalHandler((e, isFatal) => { - if (isFatal) { - // eslint-disable-next-line no-alert - alert(`${e.name}: ${e.message}`) - } -}) +if (!__DEV__) { + global.ErrorUtils?.setGlobalHandler((e, isFatal) => { + if (isFatal) { + // eslint-disable-next-line no-alert + alert(`${e.name}: ${e.message}`) + } + }) +} diff --git a/rn-kitchen-sink/demoList.js b/rn-kitchen-sink/demoList.js index ac268fb56..796aa7c99 100644 --- a/rn-kitchen-sink/demoList.js +++ b/rn-kitchen-sink/demoList.js @@ -137,12 +137,6 @@ module.exports = { icon: 'https://os.alipayobjects.com/rmsportal/IQtMSWmYwLEuqln.png', module: require('../components/date-picker-view/demo/basic'), }, - { - title: 'ImagePicker', - description: '图片选择', - icon: 'https://os.alipayobjects.com/rmsportal/NDsSvklLUeodsHK.png', - module: require('../components/image-picker/demo/basic'), - }, { title: 'InputItem', description: '文本输入', diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap index 8f627242c..7ceff5e7a 100644 --- a/tests/__snapshots__/index.test.js.snap +++ b/tests/__snapshots__/index.test.js.snap @@ -16,7 +16,6 @@ Array [ "Flex", "Grid", "Icon", - "ImagePicker", "InputItem", "ListView", "List", diff --git a/yarn.lock b/yarn.lock index 4352582b3..ec492db04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -116,13 +116,6 @@ dependencies: "@babel/highlight" "^7.12.13" -"@babel/code-frame@~7.10.4": - version "7.10.4" - resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" - integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== - dependencies: - "@babel/highlight" "^7.10.4" - "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.4": version "7.14.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.4.tgz#45720fe0cecf3fd42019e1d12cc3d27fadc98d58" @@ -1220,72 +1213,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@expo/config-plugins@~5.0.3": - version "5.0.4" - resolved "https://registry.npmmirror.com/@expo/config-plugins/-/config-plugins-5.0.4.tgz#216fea6558fe66615af1370de55193f4181cb23e" - integrity sha512-vzUcVpqOMs3h+hyRdhGwk+eGIOhXa5xYdd92yO17RMNHav3v/+ekMbs7XA2c3lepMO8Yd4/5hqmRw9ZTL6jGzg== - dependencies: - "@expo/config-types" "^47.0.0" - "@expo/json-file" "8.2.36" - "@expo/plist" "0.0.18" - "@expo/sdk-runtime-versions" "^1.0.0" - "@react-native/normalize-color" "^2.0.0" - chalk "^4.1.2" - debug "^4.3.1" - find-up "~5.0.0" - getenv "^1.0.0" - glob "7.1.6" - resolve-from "^5.0.0" - semver "^7.3.5" - slash "^3.0.0" - xcode "^3.0.1" - xml2js "0.4.23" - -"@expo/config-types@^47.0.0": - version "47.0.0" - resolved "https://registry.npmmirror.com/@expo/config-types/-/config-types-47.0.0.tgz#99eeabe0bba7a776e0f252b78beb0c574692c38d" - integrity sha512-r0pWfuhkv7KIcXMUiNACJmJKKwlTBGMw9VZHNdppS8/0Nve8HZMTkNRFQzTHW1uH3pBj8jEXpyw/2vSWDHex9g== - -"@expo/config@~7.0.0": - version "7.0.3" - resolved "https://registry.npmmirror.com/@expo/config/-/config-7.0.3.tgz#c9c634e76186de25e296485e51418f1e52966e6e" - integrity sha512-joVtB5o+NF40Tmsdp65UzryRtbnCuMbXkVO4wJnNJO4aaK0EYLdHCYSewORVqNcDfGN0LphQr8VTG2npbd9CJA== - dependencies: - "@babel/code-frame" "~7.10.4" - "@expo/config-plugins" "~5.0.3" - "@expo/config-types" "^47.0.0" - "@expo/json-file" "8.2.36" - getenv "^1.0.0" - glob "7.1.6" - require-from-string "^2.0.2" - resolve-from "^5.0.0" - semver "7.3.2" - slugify "^1.3.4" - sucrase "^3.20.0" - -"@expo/json-file@8.2.36": - version "8.2.36" - resolved "https://registry.npmmirror.com/@expo/json-file/-/json-file-8.2.36.tgz#62a505cb7f30a34d097386476794680a3f7385ff" - integrity sha512-tOZfTiIFA5KmMpdW9KF7bc6CFiGjb0xnbieJhTGlHrLL+ps2G0OkqmuZ3pFEXBOMnJYUVpnSy++52LFxvpa5ZQ== - dependencies: - "@babel/code-frame" "~7.10.4" - json5 "^1.0.1" - write-file-atomic "^2.3.0" - -"@expo/plist@0.0.18": - version "0.0.18" - resolved "https://registry.npmmirror.com/@expo/plist/-/plist-0.0.18.tgz#9abcde78df703a88f6d9fa1a557ee2f045d178b0" - integrity sha512-+48gRqUiz65R21CZ/IXa7RNBXgAI/uPSdvJqoN9x1hfL44DNbUoWHgHiEXTx7XelcATpDwNTz6sHLfy0iNqf+w== - dependencies: - "@xmldom/xmldom" "~0.7.0" - base64-js "^1.2.3" - xmlbuilder "^14.0.0" - -"@expo/sdk-runtime-versions@^1.0.0": - version "1.0.0" - resolved "https://registry.npmmirror.com/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz#d7ebd21b19f1c6b0395e50d78da4416941c57f7c" - integrity sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ== - "@hapi/hoek@^9.0.0": version "9.2.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" @@ -1562,11 +1489,6 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@react-native-camera-roll/camera-roll@^5.1.0": - version "5.1.0" - resolved "https://registry.npmmirror.com/@react-native-camera-roll/camera-roll/-/camera-roll-5.1.0.tgz#5cfb3cf02d72ab03b3d6a0bdda392e2896c8d55f" - integrity sha512-74pavpt2T2U3V0r5d+pn4NChJbRNcydqakp3NVmosod35Lzxrt9My7kCLDdHXW2S6J6DhgXb/n36/heZQB4AUA== - "@react-native-community/cli-debugger-ui@^5.0.1-alpha.1": version "5.0.1-alpha.1" resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-5.0.1-alpha.1.tgz#09a856ccd2954cf16eea59b14dd26ae66720e4e6" @@ -1715,11 +1637,6 @@ resolved "https://registry.yarnpkg.com/@react-native-community/eslint-plugin/-/eslint-plugin-1.1.0.tgz#e42b1bef12d2415411519fd528e64b593b1363dc" integrity sha512-W/J0fNYVO01tioHjvYWQ9m6RgndVtbElzYozBq1ZPrHO/iCzlqoySHl4gO/fpCl9QEFjvJfjPgtPMTMlsoq5DQ== -"@react-native-community/masked-view@^0.1.9": - version "0.1.11" - resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.11.tgz#2f4c6e10bee0786abff4604e39a37ded6f3980ce" - integrity sha512-rQfMIGSR/1r/SyN87+VD8xHHzDYeHaJq6elOSCAD+0iLagXkSI2pfA0LmSXP21uw5i3em7GkkRjfJ8wpqWXZNw== - "@react-native-community/segmented-control@^2.2.2": version "2.2.2" resolved "https://registry.yarnpkg.com/@react-native-community/segmented-control/-/segmented-control-2.2.2.tgz#4014256819ab8f40f6bc3a3929ff14a9d149cf04" @@ -1730,11 +1647,6 @@ resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-3.0.3.tgz#830167fd757ba70ac638747ba3169b2dbae60330" integrity sha512-8IeHfDwJ9/CTUwFs6x90VlobV3BfuPgNLjTgC6dRZovfCWigaZwVNIFFJnHBakK3pW2xErAPwhdvNR4JeNoYbw== -"@react-native-picker/picker@^2.4.8": - version "2.4.8" - resolved "https://registry.npmmirror.com/@react-native-picker/picker/-/picker-2.4.8.tgz#a1a21f3d6ecadedbc3f0b691a444ddd7baa081f8" - integrity sha512-5NQ5XPo1B03YNqKFrV6h9L3CQaHlB80wd4ETHUEABRP2iLh7FHLVObX2GfziD+K/VJb8G4KZcZ23NFBFP1f7bg== - "@react-native/assets@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@react-native/assets/-/assets-1.0.0.tgz#c6f9bf63d274bafc8e970628de24986b30a55c8e" @@ -1745,11 +1657,6 @@ resolved "https://registry.yarnpkg.com/@react-native/normalize-color/-/normalize-color-1.0.0.tgz#c52a99d4fe01049102d47dc45d40cbde4f720ab6" integrity sha512-xUNRvNmCl3UGCPbbHvfyFMnpvLPoOjDCcp5bT9m2k+TF/ZBklEQwhPZlkrxRx2NhgFh1X3a5uL7mJ7ZR+8G7Qg== -"@react-native/normalize-color@^2.0.0": - version "2.1.0" - resolved "https://registry.npmmirror.com/@react-native/normalize-color/-/normalize-color-2.1.0.tgz#939b87a9849e81687d3640c5efa2a486ac266f91" - integrity sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA== - "@react-native/polyfills@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@react-native/polyfills/-/polyfills-1.0.0.tgz#05bb0031533598f9458cf65a502b8df0eecae780" @@ -2019,11 +1926,6 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== -"@types/qs@^6.5.3": - version "6.9.7" - resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - "@types/react-native@^0.64.10": version "0.64.10" resolved "https://registry.npmjs.org/@types/react-native/-/react-native-0.64.10.tgz#5eb6a72c77ce0f7e6e14b19c61a6bc585975eef5" @@ -2456,11 +2358,6 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" -"@xmldom/xmldom@~0.7.0": - version "0.7.9" - resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.7.9.tgz#7f9278a50e737920e21b297b8a35286e9942c056" - integrity sha512-yceMpm/xd4W2a85iqZyO09gTnHvXF6pyiWjD2jcOJs7hRoZtNNOO1eJlhHj1ixA+xip2hOyGn+LgcvLCMo5zXA== - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -2786,11 +2683,6 @@ any-observable@^0.3.0: resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -2965,7 +2857,7 @@ array-tree-filter@^1.0.0: resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-1.0.1.tgz#0a8ad1eefd38ce88858632f9cc0423d7634e4d5d" integrity sha1-CorR7v04zoiFhjL5zAQj12NOTV0= -array-tree-filter@^2.1.0, array-tree-filter@~2.1.0: +array-tree-filter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== @@ -3422,7 +3314,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2, base64-js@^1.1.2, base64-js@^1.2.3, base64-js@^1.5.1: +base64-js@^1.0.2, base64-js@^1.1.2, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -3452,11 +3344,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big-integer@1.6.x: - version "1.6.51" - resolved "https://registry.npmmirror.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" - integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== - big-integer@^1.6.44: version "1.6.48" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" @@ -3594,13 +3481,6 @@ bplist-creator@0.0.8: dependencies: stream-buffers "~2.2.0" -bplist-creator@0.1.0: - version "0.1.0" - resolved "https://registry.npmmirror.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" - integrity sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg== - dependencies: - stream-buffers "2.2.x" - bplist-parser@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" @@ -3608,13 +3488,6 @@ bplist-parser@0.2.0: dependencies: big-integer "^1.6.44" -bplist-parser@0.3.1: - version "0.3.1" - resolved "https://registry.npmmirror.com/bplist-parser/-/bplist-parser-0.3.1.tgz#e1c90b2ca2a9f9474cc72f6862bbf3fee8341fd1" - integrity sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA== - dependencies: - big-integer "1.6.x" - brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3992,14 +3865,6 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -4404,7 +4269,7 @@ commander@^2.12.1, commander@^2.14.1, commander@^2.18.0, commander@^2.19.0, comm resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.0, commander@^4.0.1: +commander@^4.0.1: version "4.1.1" resolved "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -5010,6 +4875,11 @@ date-fns@^1.27.2, date-fns@^1.30.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +dayjs@^1.11.7: + version "1.11.10" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + dayjs@^1.8.15: version "1.10.5" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986" @@ -6011,25 +5881,6 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" -expo-constants@~13.2.0: - version "13.2.4" - resolved "https://registry.npmmirror.com/expo-constants/-/expo-constants-13.2.4.tgz#eab4a553f074b2c60ad7a158d3b82e3484a94606" - integrity sha512-Zobau8EuTk2GgafwkfGnWM6CmSLB7X8qnQXVuXe0nd3v92hfQUmRWGhJwH88uxXj3LrfqctM6PaJ8taG1vxfBw== - dependencies: - "@expo/config" "~7.0.0" - uuid "^3.3.2" - -expo-linking@~3.2.2: - version "3.2.4" - resolved "https://registry.npmmirror.com/expo-linking/-/expo-linking-3.2.4.tgz#94d65ee957f45e2d2e12a25df7ad063a17807eee" - integrity sha512-yVH72hqridgxiknuqeVs5ZClxIaEft2xyDfC9+x8pSelGb1oMnaOYY4muE9ytph6VCXbmMI9VNf0etrfFoDLqQ== - dependencies: - "@types/qs" "^6.5.3" - expo-constants "~13.2.0" - invariant "^2.2.4" - qs "^6.9.1" - url-parse "^1.5.9" - express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -6406,14 +6257,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@~5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -6732,11 +6575,6 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= -getenv@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31" - integrity sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg== - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -6833,18 +6671,6 @@ glob-watcher@^5.0.3: normalize-path "^3.0.0" object.defaults "^1.1.0" -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmmirror.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -9245,12 +9071,10 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" +lodash.assignwith@^4.2.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz#127a97f02adc41751a954d24b0de17e100e038eb" + integrity sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g== lodash.clonedeep@^4.5.0: version "4.5.0" @@ -10152,15 +9976,6 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - nan@^2.12.1: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" @@ -10787,13 +10602,6 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -11142,14 +10950,6 @@ plist@^3.0.1: xmlbuilder "^9.0.7" xmldom "^0.5.0" -plist@^3.0.5: - version "3.0.6" - resolved "https://registry.npmmirror.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3" - integrity sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA== - dependencies: - base64-js "^1.5.1" - xmlbuilder "^15.1.1" - plugin-error@1.0.1, plugin-error@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" @@ -11914,13 +11714,6 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.9.1: - version "6.11.0" - resolved "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -13208,7 +13001,7 @@ requires-port@^1.0.0: resize-observer-polyfill@^1.5.0: version "1.5.1" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== resolve-cwd@^2.0.0: @@ -13478,7 +13271,7 @@ sass-loader@^8.0.0: schema-utils "^2.6.1" semver "^6.3.0" -sax@>=0.6.0, sax@^1.1.4, sax@^1.2.1, sax@^1.2.4, sax@~1.2.4: +sax@^1.1.4, sax@^1.2.1, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -13569,11 +13362,6 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.2: - version "7.3.2" - resolved "https://registry.npmmirror.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== - semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -13792,15 +13580,6 @@ simple-plist@^1.0.0: bplist-parser "0.2.0" plist "^3.0.1" -simple-plist@^1.1.0: - version "1.3.1" - resolved "https://registry.npmmirror.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" - integrity sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw== - dependencies: - bplist-creator "0.1.0" - bplist-parser "0.3.1" - plist "^3.0.5" - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -13846,11 +13625,6 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -slugify@^1.3.4: - version "1.6.5" - resolved "https://registry.npmmirror.com/slugify/-/slugify-1.6.5.tgz#c8f5c072bf2135b80703589b39a3d41451fbe8c8" - integrity sha512-8mo9bslnBO3tr5PEVFzMPIWwWnipGS0xVbYf65zxDqfNwmzYn1LpiKNrR6DlClusuvo+hDHd1zKpmfAe83NQSQ== - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -14157,7 +13931,7 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-buffers@2.2.x, stream-buffers@~2.2.0: +stream-buffers@~2.2.0: version "2.2.0" resolved "https://registry.npmmirror.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== @@ -14436,18 +14210,6 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -sucrase@^3.20.0: - version "3.29.0" - resolved "https://registry.npmmirror.com/sucrase/-/sucrase-3.29.0.tgz#3207c5bc1b980fdae1e539df3f8a8a518236da7d" - integrity sha512-bZPAuGA5SdFHuzqIhTAqt9fvNEo9rESqXIG3oiKdF8K4UmkQxC4KlNL3lVyAErXp+mPvUqZ5l13qx6TrDIGf3A== - dependencies: - commander "^4.0.0" - glob "7.1.6" - lines-and-columns "^1.1.6" - mz "^2.7.0" - pirates "^4.0.1" - ts-interface-checker "^0.1.9" - sudo-prompt@^9.0.0: version "9.2.1" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" @@ -14659,20 +14421,6 @@ text-table@0.2.0, text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - throat@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" @@ -14879,11 +14627,6 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - ts-jest@^25.4.0: version "25.5.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.5.1.tgz#2913afd08f28385d54f2f4e828be4d261f4337c7" @@ -15374,14 +15117,6 @@ url-parse@^1.1.8, url-parse@^1.4.3, url-parse@^1.5.1: querystringify "^2.1.1" requires-port "^1.0.0" -url-parse@^1.5.9: - version "1.5.10" - resolved "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -15451,11 +15186,6 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^7.0.3: - version "7.0.3" - resolved "https://registry.npmmirror.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== - uuid@^8.3.0: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -15692,8 +15422,10 @@ watchpack@^1.7.4: resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: + chokidar "^3.4.1" graceful-fs "^4.1.2" neo-async "^2.5.0" + watchpack-chokidar2 "^2.0.1" optionalDependencies: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" @@ -16121,14 +15853,6 @@ xcode@^2.0.0: simple-plist "^1.0.0" uuid "^3.3.2" -xcode@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/xcode/-/xcode-3.0.1.tgz#3efb62aac641ab2c702458f9a0302696146aa53c" - integrity sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA== - dependencies: - simple-plist "^1.1.0" - uuid "^7.0.3" - "xml-name-validator@>= 2.0.1 < 3.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" @@ -16139,34 +15863,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml2js@0.4.23: - version "0.4.23" - resolved "https://registry.npmmirror.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@^14.0.0: - version "14.0.0" - resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-14.0.0.tgz#876b5aec4f05ffd5feb97b0a871c855d16fbeb8c" - integrity sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg== - -xmlbuilder@^15.1.1: - version "15.1.1" - resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" - integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== - xmlbuilder@^9.0.7: version "9.0.7" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"