diff --git a/components/range/__docs__/adaptor/index.tsx b/components/range/__docs__/adaptor/index.tsx index d60ec58d15..553c5dc638 100644 --- a/components/range/__docs__/adaptor/index.tsx +++ b/components/range/__docs__/adaptor/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Types } from '@alifd/adaptor-helper'; import { Range } from '@alifd/next'; +import type { RangeProps } from '@alifd/next/types/range'; export default { name: 'Range', @@ -70,6 +71,16 @@ export default { end, style, ...others + }: { + shape: 'basic' | 'scale'; + level: RangeProps['slider']; + type: 'basic' | 'scale'; + scalePosition: RangeProps['marksPosition']; + state: 'normal' | 'hover' | 'clicked' | 'disabled'; + width: number; + start: number; + end: number; + style: React.CSSProperties; }) => { return (
@@ -90,7 +101,7 @@ export default {
); }, - demoOptions: demo => { + demoOptions: (demo: any) => { const { node } = demo; const { props } = node; if (props.level === 'double') { diff --git a/components/range/__docs__/demo/basic/index.tsx b/components/range/__docs__/demo/basic/index.tsx index aadb5180ad..54c0f3a894 100644 --- a/components/range/__docs__/demo/basic/index.tsx +++ b/components/range/__docs__/demo/basic/index.tsx @@ -13,7 +13,6 @@ const Demo = () => {
-
Disabled:
diff --git a/components/range/__docs__/demo/change/index.tsx b/components/range/__docs__/demo/change/index.tsx index 527db6a2d3..65da74fc7e 100644 --- a/components/range/__docs__/demo/change/index.tsx +++ b/components/range/__docs__/demo/change/index.tsx @@ -1,15 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Range } from '@alifd/next'; +import type { RangeValueType } from '@alifd/next/types/range'; -const onProcess = value => { +const onProcess = (value: RangeValueType) => { // This callback will be triggered when startValue and endValue aren't equal after mousedown/mousemove. You shouldn't call setState here. console.log('onProcess: ', value); }; const Demo = () => { - const [value, onChange] = React.useState(128); - const [doubleValue, onDoubleChange] = React.useState([200, 500]); + const [value, onChange] = React.useState(128); + const [doubleValue, onDoubleChange] = React.useState([200, 500]); return (
diff --git a/components/range/__docs__/demo/control/index.tsx b/components/range/__docs__/demo/control/index.tsx index 68e6935837..74519a7f4d 100644 --- a/components/range/__docs__/demo/control/index.tsx +++ b/components/range/__docs__/demo/control/index.tsx @@ -3,9 +3,8 @@ import ReactDOM from 'react-dom'; import { Range, NumberPicker, Icon } from '@alifd/next'; const Demo = () => { - const [valueInc, setValueInc] = React.useState(30); + const [valueInc, setValueInc] = React.useState(30); - const colorCry = ``; return (
@@ -22,7 +21,10 @@ const Demo = () => {
{ + constructor(props: AppProps) { super(props); this.state = { value: [10, 300], @@ -19,13 +21,13 @@ class App extends React.Component { } //Controlled. onChange will be triggered when startValue isn't equal to endValue after sliding - onChange(value) { + onChange(value: RangeValueType) { console.log('onChange value:', value); this.setState({ value }); } // This callback will be triggered when startValue and endValue aren't equal after mousedown/mousemove. You shouldn't call setState here. - onProcess(value) { + onProcess(value: RangeValueType) { // this.setState({value}); console.log('onProcess: ', value); } diff --git a/components/range/__docs__/demo/tipRender/index.tsx b/components/range/__docs__/demo/tipRender/index.tsx index e2f82f9a2f..b060d8058d 100644 --- a/components/range/__docs__/demo/tipRender/index.tsx +++ b/components/range/__docs__/demo/tipRender/index.tsx @@ -1,9 +1,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Range } from '@alifd/next'; +import type { RangeValueType } from '@alifd/next/types/range'; -class App extends React.Component { - constructor(props) { +interface AppProps {} +class App extends React.Component { + constructor(props: AppProps) { super(props); this.state = { value: 128, @@ -11,17 +13,17 @@ class App extends React.Component { } // This callback will be triggered when startValue and endValue aren't equal after moving. - onChange(value) { + onChange(value: RangeValueType) { console.log('onChange value:', value); } // This callback will be triggered when startValue and endValue aren't equal after mousedown/mousemove. You can call setState here when using a controlled component. - onProcess(value) { + onProcess(value: RangeValueType) { console.log('onProcess'); this.setState({ value }); } - formatter(value) { + formatter(value: RangeValueType) { return `0 ~ ${value}`; } render() { diff --git a/components/range/__docs__/index.en-us.md b/components/range/__docs__/index.en-us.md index 2b02cd1c1c..33540baf98 100644 --- a/components/range/__docs__/index.en-us.md +++ b/components/range/__docs__/index.en-us.md @@ -23,30 +23,38 @@ Range Component is used to select a value in a range by dragging slider. Normall ### Range -| Param | Descripiton | Type | Default Value | -| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -------------- | -| slider | number of sliders

**option**:
'single'
'double' | Enum | 'single' | -| min | minimal value | Number | 0 | -| max | maximal value | Number | 100 | -| step | step of the range, which is positive integer and (max - min) can be divided by it | Number | 1 | -| value | current value. It's in the form of `Number` when `slider` is `single` otherwise `[Number, Number]` | Number/[Number, Number] | - | -| defaultValue | default value. It's in the form of `Number` when `slider` is `single` otherwise `[Number, Number]` | Number/[Number, Number] | - | -| marks | way to show the scale. (`false` means nothing, `array` means enum, `number` means equal division, and `object` means `key` as the mark with `value` as the value) | Boolean/Number/Array<Number>/Object | false | -| marksPosition | position for the scale

**option**:
'above', 'below' | Enum | 'above' | -| disabled | disabled | Boolean | false | -| onChange | callback triggered when value changes | Function(value: Number/[Number, Number]) => void | func.noop | -| onProcess | callback triggered when slider being dragged, and used only for special need | Function(value: Number/[Number, Number]) => void | func.noop | -| hasTip | whether to show tip | Boolean | true | -| tipRender | custom tip content

**signature**:
Function(value?: Number/String) => ReactNode
**signature**:
_value_: {Number/String} value
**returns**:
{ReactNode} content
| Function | value => value | -| reverse | reverse the selected part | Boolean | false | -| pure | pure render or not | Boolean | false | -| fixedWidth | drag a line with fixed width. It considers `slider` as `double`, and `defaultValue` must be a interval. | Boolean | false | -| tooltipVisible| tooltip always be visible or not | Boolean | false | +| Param | Description | Type | Default Value | Required | +| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------- | -------- | +| slider | Number of sliders

**option**:
'single'
'double' | 'single' \| 'double' | 'single' | | +| min | Minimal value | number | 0 | | +| max | Maximal value | number | 100 | | +| step | Step of the range, which is positive integer and (max | number | 1 | | +| value | Current value. It's in the form of `Number` when `slider` is `single` otherwise `[Number, Number]` | RangeValueType | - | | +| defaultValue | Default value. It's in the form of `Number` when `slider` is `single` otherwise `[Number, Number]` | RangeValueType | - | | +| marks | Way to show the scale. (`false` means nothing, `array` means enum, `number` means equal division, and `object` means `key` as the mark with `value` as the value) | false \| number \| Array\ \| Record\ | false | | +| marksPosition | Position for the scale

**option**:
'above', 'below' | 'above' \| 'below' | 'above' | | +| disabled | Disabled | boolean | false | | +| onChange | Callback triggered when value changes

**signature**:
**params**:
_value_: The changed value | (value: RangeValueType) => void | () =\> void | | +| onProcess | Callback triggered when slider being dragged, and used only for special need

**signature**:
**params**:
_value_: The changed value | (value: RangeValueType) => void | () =\> void | | +| hasTip | Whether to show tip | boolean | true | | +| tipRender | Custom tip content

**signature**:
**params**:
_value_: The changed value
**return**:
React.ReactNode | (value: number \| string) => React.ReactNode | (value) =\> value | | +| reverse | Reverse the selected part | boolean | false | | +| pure | Pure render or not | boolean | false | | +| fixedWidth | Drag a line with fixed width. It considers `slider` as `double`, and `defaultValue` must be a interval. | boolean | false | | +| tooltipVisible | Tooltip always be visible or not | boolean | false | | +| isPreview | Is preview or not | boolean | false | | +| renderPreview | Custom preview content

**signature**:
**params**:
_value_: The changed value
_props_: RangeProps
**return**:
React.ReactNode | (value: RangeValueType \| undefined, props: RangeProps) => React.ReactNode | - | | + +### RangeValueType + +```typescript +export type RangeValueType = number | [number, number]; +``` ## ARIA and KeyBoard -| KeyBoard | Descripiton | -| :---------- | :------------------------------ | -| Right Arrow | control the slider to move to the right | -| Left Arrow | control the slider to move to the left | -| Tab | switch to other slider | \ No newline at end of file +| KeyBoard | Descripiton | +| :---------- | :-------------------------------------- | +| Right Arrow | control the slider to move to the right | +| Left Arrow | control the slider to move to the left | +| Tab | switch to other slider | diff --git a/components/range/__docs__/index.md b/components/range/__docs__/index.md index a750d499f8..270cf5dddd 100644 --- a/components/range/__docs__/index.md +++ b/components/range/__docs__/index.md @@ -22,33 +22,38 @@ ### Range -| 参数 | 说明 | 类型 | 默认值 | -| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -------------- | -| slider | 滑块个数

**可选值**:
'single'(单个)
'double'(两个) | Enum | 'single' | -| min | 最小值 | Number | 0 | -| max | 最大值 | Number | 100 | -| step | 步长,取值必须大于 0,并且可被 (max - min) 整除。 | Number | 1 | -| value | 设置当前取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` | Number/Array<Number> | - | -| defaultValue | 设置初始取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` | Number/Array<Number> | - | -| marks | 刻度数值显示逻辑(false 代表不显示,array 枚举显示的值,number 代表按 number 平分,object 表示按 key 划分,value 值显示) | Boolean/Number/Array<Number>/Object | false | -| marksPosition | marks显示在上方('above')or下方('below')

**可选值**:
'above', 'below' | Enum | 'above' | -| disabled | 值为 `true` 时,滑块为禁用状态 | Boolean | false | -| onChange | 当 Range 的值发生改变后,会触发 onChange 事件,并把改变后的值作为参数传入, 如果设置了value, 要配合此函数做受控使用

**签名**:
Function(value: String/number) => void
**参数**:
_value_: {String/number} null | Function | func.noop | -| onProcess | 滑块拖动的时候触发的事件,不建议在这里setState, 一般情况下不需要用, 滑动时有特殊需求时使用

**签名**:
Function(value: String/number) => void
**参数**:
_value_: {String/number} null | Function | func.noop | -| hasTip | 是否显示 tip | Boolean | true | -| tipRender | 自定义 tip 显示内容

**签名**:
Function(value: Number/String) => ReactNode
**参数**:
_value_: {Number/String} 值
**返回值**:
{ReactNode} 显示内容
| Function | value => value | -| reverse | 选中态反转 | Boolean | false | -| pure | 是否pure render | Boolean | false | -| fixedWidth | 是否为拖动线段类型,默认slider为double, defaultValue必传且指定区间 | Boolean | false | -| tooltipVisible | tooltip是否默认展示 | Boolean | false | -| rtl | 是否已rtl模式展示 | Boolean | false | -| isPreview | 是否为预览态 | Boolean | false | -| renderPreview | 预览态模式下渲染的内容

**签名**:
Function(value: number) => void
**参数**:
_value_: {number} 评分值 | Function | - | +| 参数 | 说明 | 类型 | 默认值 | 是否必填 | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------- | -------- | +| slider | 滑块个数

**可选值**:
'single'(单个)
'double'(两个) | 'single' \| 'double' | 'single' | | +| min | 最小值 | number | 0 | | +| max | 最大值 | number | 100 | | +| step | 步长,取值必须大于 0,并且可被 (max - min) 整除 | number | 1 | | +| value | 设置当前取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` | RangeValueType | - | | +| defaultValue | 设置初始取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` | RangeValueType | - | | +| marks | 刻度数值显示逻辑(false 代表不显示,array 枚举显示的值,number 代表按 number 平分,object 表示按 key 划分,value 值显示) | false \| number \| Array\ \| Record\ | false | | +| marksPosition | marks显示在上方('above')or下方('below')

**可选值**:
'above', 'below' | 'above' \| 'below' | 'above' | | +| disabled | 值为 `true` 时,滑块为禁用状态 | boolean | false | | +| onChange | 当 Range 的值发生改变后,会触发 onChange 事件,并把改变后的值作为参数传入, 如果设置了value, 要配合此函数做受控使用

**签名**:
**参数**:
_value_: 改变后的值 | (value: RangeValueType) => void | () =\> void | | +| onProcess | 滑块拖动的时候触发的事件,不建议在这里setState, 一般情况下不需要用, 滑动时有特殊需求时使用

**签名**:
**参数**:
_value_: 改变后的值 | (value: RangeValueType) => void | () =\> void | | +| hasTip | 是否显示 tip | boolean | true | | +| tipRender | 自定义 tip 显示内容

**签名**:
**参数**:
_value_: 改变后的值
**返回值**:
React.ReactNode | (value: number \| string) => React.ReactNode | (value) =\> value | | +| reverse | 选中态反转 | boolean | false | | +| pure | 是否pure render | boolean | false | | +| fixedWidth | 是否为拖动线段类型,默认slider为double, defaultValue必传且指定区间 | boolean | false | | +| tooltipVisible | tooltip是否默认展示 | boolean | false | | +| isPreview | 是否为预览态 | boolean | false | | +| renderPreview | 预览态模式下渲染的内容

**签名**:
**参数**:
_value_: 改变后的值
_props_: RangeProps
**返回值**:
React.ReactNode | (value: RangeValueType \| undefined, props: RangeProps) => React.ReactNode | - | | + +### RangeValueType + +```typescript +export type RangeValueType = number | [number, number]; +``` ## 无障碍键盘操作指南 -| 按键 | 说明 | -| :---------- | :------- | +| 按键 | 说明 | +| :---------- | :--------------- | | Right Arrow | 控制滑块往右移动 | | Left Arrow | 控制滑块向左移动 | -| Tab | 切换滑动条 | +| Tab | 切换滑动条 | diff --git a/components/range/__docs__/theme/index.tsx b/components/range/__docs__/theme/index.tsx index daff79c384..fe07460c5d 100644 --- a/components/range/__docs__/theme/index.tsx +++ b/components/range/__docs__/theme/index.tsx @@ -2,9 +2,17 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import '../../../demo-helper/style'; -import { Demo, DemoGroup, initDemo } from '../../../demo-helper'; +import { Demo, DemoGroup, initDemo, type DemoFunctionDefineForObject } from '../../../demo-helper'; import '../../style'; import Range from '../../index'; +import type { RangeProps } from '../../types'; + +interface FuncDemoProps { + i18n: Record; +} +interface FuncDemoState { + demoFunction: Record; +} const i18nMap = { 'zh-cn': { @@ -15,7 +23,11 @@ const i18nMap = { }, }; -function ItemDemo({ title, marksPosition, ...others }) { +function ItemDemo({ + title, + marksPosition, + ...others +}: RangeProps & { title: string; scales?: number }) { return ( @@ -47,8 +59,8 @@ ItemDemo.propTypes = { marksPosition: PropTypes.bool, }; -class OriginalDemo extends Component { - constructor(props) { +class OriginalDemo extends Component { + constructor(props: FuncDemoProps) { super(props); this.state = { demoFunction: { @@ -66,7 +78,7 @@ class OriginalDemo extends Component { this.onFunctionChange = this.onFunctionChange.bind(this); } - onFunctionChange(demoFunction) { + onFunctionChange(demoFunction: Record) { this.setState({ demoFunction, }); @@ -74,7 +86,7 @@ class OriginalDemo extends Component { render() { const { demoFunction } = this.state; - const marksPosition = demoFunction.marksPosition.value; + const marksPosition = demoFunction.marksPosition.value as RangeProps['marksPosition']; return ( { +const render = (i18n: Record) => { ReactDOM.render(
- +
, document.getElementById('container') ); diff --git a/components/range/__tests__/a11y-spec.tsx b/components/range/__tests__/a11y-spec.tsx index 07c84b0221..a5a999abc9 100644 --- a/components/range/__tests__/a11y-spec.tsx +++ b/components/range/__tests__/a11y-spec.tsx @@ -1,34 +1,14 @@ import React from 'react'; -import ReactDOM from 'react-dom'; -import ReactTestUtils from 'react-dom/test-utils'; -import Enzyme, { mount, shallow } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import simulateEvent from 'simulate-event'; -import assert from 'power-assert'; -import sinon from 'sinon'; import Range from '../index'; -import { unmount, testReact } from '../../util/__tests__/legacy/a11y/validate'; - -/* eslint-disable react/no-multi-comp */ -Enzyme.configure({ adapter: new Adapter() }); +import { testReact } from '../../util/__tests__/a11y/validate'; describe('Range A11y', () => { - let wrapper; - - afterEach(() => { - if (wrapper) { - wrapper.unmount(); - wrapper = null; - } - unmount(); - }); it('should not have any violations for range without tips', async () => { - wrapper = await testReact( + await testReact(
); - return wrapper; }); /** @@ -36,12 +16,12 @@ describe('Range A11y', () => { * To fix this will require structural change, ignore temporarily. */ it.skip('should not have any violations for range with tips', async () => { - wrapper = await testReact( + await testReact(
{ />
); - return wrapper; }); }); diff --git a/components/range/__tests__/index-spec.tsx b/components/range/__tests__/index-spec.tsx index e9500548e7..7d331cca08 100644 --- a/components/range/__tests__/index-spec.tsx +++ b/components/range/__tests__/index-spec.tsx @@ -1,474 +1,451 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import ReactTestUtils from 'react-dom/test-utils'; -import Enzyme, { mount, shallow } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import simulateEvent from 'simulate-event'; -import assert from 'power-assert'; -import sinon from 'sinon'; +import React, { type ReactElement } from 'react'; +import type { MountReturn } from 'cypress/react'; import Range from '../index'; - -/* eslint-disable react/no-multi-comp */ - -Enzyme.configure({ adapter: new Adapter() }); +import '../style'; + +const checkRangeSelectedDomWidthAndLeft = (width: string, left: string) => { + cy.get('.next-range-selected') + .should('exist') + .then($el => { + expect({ + width: $el[0].style.width, + left: $el[0].style.left, + }).deep.equal({ + width, + left, + }); + }); +}; describe('Range ', () => { - afterEach(() => { - //清楚所有浮层 - const nodeListArr = [].slice.call(document.querySelectorAll('.next-balloon-tooltip')); - nodeListArr.forEach((node, index) => { - node.parentNode.removeChild(node); - }); + beforeEach(() => { + document.body.style.marginLeft = '0px'; }); - it('defaultValue', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); + cy.mount().as('el'); + checkRangeSelectedDomWidthAndLeft('10%', '0%'); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '10%', - left: '0%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-selected').props().style, { - width: '20%', - left: '10%', - }); - wrapperSingle.setProps({ - defaultValue: 20, - }); - wrapperDouble.setProps({ - defaultValue: [30, 40], - }); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '10%', - left: '0%', + cy.get('@el').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { defaultValue: 20 })); }); - assert.deepEqual(wrapperDouble.find('.next-range-selected').props().style, { - width: '20%', - left: '10%', + checkRangeSelectedDomWidthAndLeft('10%', '0%'); + + cy.mount().as('el'); + checkRangeSelectedDomWidthAndLeft('20%', '10%'); + + cy.get('@el').then(({ component, rerender }) => { + return rerender( + React.cloneElement(component as ReactElement, { defaultValue: [30, 40] }) + ); }); + checkRangeSelectedDomWidthAndLeft('20%', '10%'); }); it('value', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); + cy.mount().as('el'); + checkRangeSelectedDomWidthAndLeft('10%', '0%'); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '10%', - left: '0%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-selected').props().style, { - width: '20%', - left: '10%', - }); - wrapperSingle.setProps({ - value: 20, + cy.get('@el').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { value: 20 })); }); - wrapperDouble.setProps({ - value: [30, 40], - }); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '20%', - left: '0%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-selected').props().style, { - width: '10%', - left: '30%', + checkRangeSelectedDomWidthAndLeft('20%', '0%'); + + cy.mount().as('el'); + checkRangeSelectedDomWidthAndLeft('20%', '10%'); + + cy.get('@el').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { value: [30, 40] })); }); + checkRangeSelectedDomWidthAndLeft('10%', '30%'); }); it('min max', () => { - const wrapper = mount(); - assert.deepEqual(wrapper.find('.next-range-selected').props().style, { - width: '50%', - left: '0%', - }); + cy.mount(); + checkRangeSelectedDomWidthAndLeft('50%', '0%'); }); it('disabled', () => { - const wrapper = mount(); - assert(wrapper.find('.next-range.disabled').length === 1); + cy.mount(); + cy.get('.next-range.disabled').should('to.length', 1); }); it('step', () => { - const wrapper1 = mount(); - const wrapper2 = mount(); - wrapper2.find('.next-range').simulate('mousedown', { pageX: 1 }); - - // 这里模拟的pageX 无效 - simulateEvent.simulate(document, 'mousemove', { pageX: 2 }); - simulateEvent.simulate(document, 'mouseup'); - //TODO - assert(wrapper1.props().step === 10); - assert(wrapper2.props().step === 0.01); + cy.mount(); + cy.get('.next-range-slider').trigger('mousedown', { + button: 0, + pageX: 0, + pageY: 0, + }); + cy.get('.next-range-slider').trigger('mousemove', { + pageX: 30, + pageY: 0, + type: 'mousemove', + }); + cy.get('.next-range-slider').trigger('mouseup', { + pageX: 30, + pageY: 0, + }); + checkRangeSelectedDomWidthAndLeft('3%', '0%'); }); it('hasTip === true and tipRender', () => { - const tipRender = sinon.spy(); - const wrapper = mount(); - - wrapper.find('.next-range-slider').simulate('mouseenter'); - - assert(tipRender.called); + const tipRender = (value: number) => { + expect(value).equal(20); + return
{value}
; + }; + cy.mount(); + cy.get('.next-range-slider').trigger('mouseover', { + pageX: 0, + pageY: 0, + }); + cy.then(() => { + cy.get('.custom-tip').should('exist'); + }); }); - it('hasTip === false', done => { - const wrapper1 = mount(); - - wrapper1.find('.next-range-slider').simulate('mouseenter'); - - assert(document.querySelector('.next-balloon-tooltip') === null); - done(); + it('hasTip === false', () => { + const tipRender = (value: number) => { + expect(value).equal(20); + return
{value}
; + }; + cy.mount(); + cy.get('.next-range-slider').trigger('mouseover', { + pageX: 0, + pageY: 0, + }); + cy.then(() => { + cy.get('.custom-tip').should('not.exist'); + }); }); it('marks', () => { - const wrapper = mount(); - const wrapper2 = mount(); - - const wrapper3 = mount( - - ); - - assert(wrapper.find('.next-range-scale-item').length === 4); - assert(wrapper.find('.next-range-scale-item.activated').length === 2); - assert(wrapper.find('.next-range-scale-item').first().props().style.left === '3%'); - assert(wrapper.find('.next-range-scale-item').at(1).props().style.left === '26%'); - assert(wrapper.find('.next-range-scale-item').at(2).props().style.left === '37%'); - assert(wrapper2.find('.next-range-scale-item').length === 11); - assert(wrapper3.find('.next-range-scale-item').length === 4); - assert(wrapper3.find('.next-range-mark-text').first().text() === '0°C'); + cy.mount().as('el'); + cy.get('.next-range-scale-item').should('to.length', 4); + cy.get('.next-range-scale-item.activated').should('to.length', 2); + cy.get('.next-range-scale-item') + .first() + .should('to.have.attr', 'style', 'left: 3%; right: auto;'); + cy.get('.next-range-scale-item') + .eq(1) + .should('to.have.attr', 'style', 'left: 26%; right: auto;'); + cy.get('.next-range-scale-item') + .eq(2) + .should('to.have.attr', 'style', 'left: 37%; right: auto;'); + + cy.get('@el').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { marks: 10 })); + }); + cy.get('.next-range-scale-item').should('to.length', 11); + + cy.get('@el').then(({ component, rerender }) => { + return rerender( + React.cloneElement(component as ReactElement, { + marks: { 0: '0°C', 26: '26°C', 37: '37°C', 100: '100°C' }, + }) + ); + }); + cy.get('.next-range-scale-item').should('to.length', 4); + cy.get('.next-range-mark-text').first().should('to.have.text', '0°C'); }); - it('laptop dragging onChange onProcess', done => { - let changeValue = 0; - let processValue = 0; - let onChangeCall = 0; - let processCall = 0; - const wrapper = mount( + it('laptop dragging onChange onProcess', () => { + const handleChange = cy.spy().as('onChange'); + const handleProcess = cy.spy().as('onProcess'); + cy.mount( - ); - - const RangeInstance = wrapper.find('Range').at(0).instance(); - RangeInstance._onMouseDown({ - button: 0, - pageX: 10, - stopPropagation: () => {}, - preventDefault: () => {}, - }); - RangeInstance._move({ - pageX: 20, - type: 'mousemove', - stopPropagation: () => {}, - preventDefault: () => {}, - }); - RangeInstance._end(); - - assert(processValue === 20); - assert(processCall === 1); - assert(onChangeCall === 1); - done(); + ) + .get('.next-range-slider') + .trigger('mousedown', { button: 0, pageX: 10 }); + cy.get('.next-range-slider').trigger('mousemove', { type: 'mousemove', pageX: 20 }); + cy.get('.next-range-slider').trigger('mouseup', { pageX: 20 }); + cy.get('@onChange').should('have.been.calledOnce'); + cy.get('@onChange').should('have.been.calledWith', 20); + cy.get('@onProcess').should('have.been.calledOnce'); + cy.get('@onProcess').should('have.been.calledWith', 20); }); - it('mobile dragging onChange onProcess', done => { - let changeValue = 0; - let processValue = 0; - let onChangeCall = 0; - let processCall = 0; - const wrapper = mount( + it('mobile dragging onChange onProcess', () => { + const handleChange = cy.spy().as('onChange'); + const handleProcess = cy.spy().as('onProcess'); + cy.mount( - ); - - const RangeInstance = wrapper.find('Range').at(0).instance(); - RangeInstance._onTouchStart({ - targetTouches: [{ pageX: 10 }], - stopPropagation: () => {}, - preventDefault: () => {}, - }); - RangeInstance._move({ - targetTouches: [{ pageX: 20 }], + ) + .get('.next-range-slider') + .trigger('touchstart', { button: 0, targetTouches: [{ pageX: 10 }] }); + cy.get('.next-range-slider').trigger('touchmove', { type: 'touchmove', - stopPropagation: () => {}, - preventDefault: () => {}, + targetTouches: [{ pageX: 20 }], }); - RangeInstance._end(); - - assert(processValue === 20); - assert(processCall === 1); - assert(onChangeCall === 1); - done(); + cy.get('.next-range-slider').trigger('touchend', { targetTouches: [{ pageX: 20 }] }); + cy.get('@onChange').should('have.been.calledOnce'); + cy.get('@onChange').should('have.been.calledWith', 20); + cy.get('@onProcess').should('have.been.calledOnce'); + cy.get('@onProcess').should('have.been.calledWith', 20); }); - it('exchange upper and lower', done => { - let changeValue = 0; - - const wrapper = mount( + it('exchange upper and lower', () => { + const handleChange = cy.spy().as('onChange'); + cy.mount( - ); - - const RangeInstance = wrapper.find('Range').at(0).instance(); - RangeInstance._onMouseDown({ - button: 0, - pageX: 10, - stopPropagation: () => {}, - preventDefault: () => {}, - }); - RangeInstance._move({ - pageX: 30, - type: 'mousemove', - stopPropagation: () => {}, - preventDefault: () => {}, - }); - RangeInstance._end(); - - assert(changeValue[0] === 20); - assert(changeValue[1] === 30); - - RangeInstance._onMouseDown({ - button: 0, - pageX: 30, - stopPropagation: () => {}, - preventDefault: () => {}, - }); - RangeInstance._move({ - pageX: 0, - type: 'mousemove', - stopPropagation: () => {}, - preventDefault: () => {}, - }); - RangeInstance._end(); - - assert(changeValue[0] === 0); - assert(changeValue[1] === 20); - done(); + ) + .get('.next-range-slider') + .eq(1) + .trigger('mousedown', { button: 0, pageX: 20 }); + cy.get('.next-range-slider').eq(1).trigger('mousemove', { type: 'mousemove', pageX: 30 }); + cy.get('.next-range-slider').eq(1).trigger('mouseup', { pageX: 30 }); + cy.get('.next-range-slider').eq(0).trigger('mousedown', { button: 0, pageX: 10 }); + cy.get('.next-range-slider').eq(0).trigger('mousemove', { type: 'mousemove', pageX: 20 }); + cy.get('.next-range-slider').eq(0).trigger('mouseup', { pageX: 30 }); + cy.get('@onChange').should('have.been.calledWith', [20, 30]); + + cy.get('.next-range-slider').eq(0).trigger('mousedown', { button: 0, pageX: 20 }); + cy.get('.next-range-slider').eq(0).trigger('mousemove', { type: 'mousemove', pageX: 0 }); + cy.get('.next-range-slider').eq(0).trigger('mouseup', { pageX: 0 }); + cy.get('.next-range-slider').eq(1).trigger('mousedown', { button: 0, pageX: 30 }); + cy.get('.next-range-slider').eq(1).trigger('mousemove', { type: 'mousemove', pageX: 20 }); + cy.get('.next-range-slider').eq(1).trigger('mouseup', { pageX: 20 }); + cy.get('@onChange').should('have.been.calledWith', [0, 20]); }); it('value bigger max', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); - - assert.deepEqual(wrapperSingle.find('.next-range-slider').props().style, { - zIndex: 100, - left: '100%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-slider').at(0).props().style, { - zIndex: 100, - left: '100%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-slider').at(1).props().style, { - zIndex: 100, - left: '100%', - }); + cy.mount(); + cy.get('.next-range-slider') + .should('exist') + .then($el => { + expect({ + zIndex: $el[0].style.zIndex, + left: $el[0].style.left, + }).deep.equal({ + zIndex: '100', + left: '100%', + }); + }); + + cy.mount(); + cy.get('.next-range-slider') + .eq(0) + .should('exist') + .then($el => { + expect({ + zIndex: $el[0].style.zIndex, + left: $el[0].style.left, + }).deep.equal({ + zIndex: '100', + left: '100%', + }); + }); + cy.get('.next-range-slider') + .eq(1) + .should('exist') + .then($el => { + expect({ + zIndex: $el[0].style.zIndex, + left: $el[0].style.left, + }).deep.equal({ + zIndex: '100', + left: '100%', + }); + }); }); - it('set value === undefined for reset ', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); - - wrapperSingle.setProps({ - value: undefined, - }); - wrapperDouble.setProps({ - value: undefined, + it('set value === undefined for reset', () => { + cy.mount().as('el'); + checkRangeSelectedDomWidthAndLeft('8.16327%', '0%'); + cy.get('@el').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { value: undefined })); }); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '0%', - left: '0%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-selected').props().style, { - width: '0%', - left: '0%', - }); - }); - it('reverse ', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); + checkRangeSelectedDomWidthAndLeft('0%', '0%'); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '90%', - left: '10%', + cy.mount().as('el'); + checkRangeSelectedDomWidthAndLeft('20%', '10%'); + cy.get('@el').then(({ component, rerender }) => { + return rerender(React.cloneElement(component as ReactElement, { value: undefined })); }); - assert(wrapperDouble.find('.next-range-selected').length === 2); + checkRangeSelectedDomWidthAndLeft('0%', '0%'); + }); + it('reverse', () => { + cy.mount(); + checkRangeSelectedDomWidthAndLeft('90%', '10%'); + + cy.mount(); + cy.get('.next-range-selected').should('to.length', 2); + cy.get('.next-range-selected') + .eq(0) + .then($el => { + expect({ + width: $el[0].style.width, + left: $el[0].style.left, + }).deep.equal({ + width: '10%', + left: '0px', + }); + }); + cy.get('.next-range-selected') + .eq(1) + .then($el => { + expect({ + width: $el[0].style.width, + left: $el[0].style.left, + }).deep.equal({ + width: '70%', + left: '30%', + }); + }); }); - it('fixedWidth ', () => { - const wrapperSingle = mount(); - - assert.deepEqual(wrapperSingle.find('.next-range-frag').props().style, { - left: '20%', - right: '60%', - }); + it('fixedWidth', () => { + cy.mount() + .get('.next-range-frag') + .then($el => { + expect({ + right: $el[0].style.right, + left: $el[0].style.left, + }).deep.equal({ + right: '60%', + left: '20%', + }); + }); }); it('fixedWidth no tip', () => { - const wrapper = mount(); - - const RangeInstance = wrapper.find('Range').at(0).instance(); - - assert(RangeInstance.dom.querySelector('.next-balloon-tooltip') === null); + let ref: React.ComponentRef | null = null; + cy.mount( + { + ref = e; + }} + fixedWidth + hasTip={false} + defaultValue={[20, 40]} + /> + ).then(() => { + const RangeInstance = ref?.getInstance(); + if (RangeInstance) { + expect(RangeInstance.dom.querySelector('.next-balloon-tooltip')).to.null; + } + }); }); it('fixedWidth dragging should have active class', () => { - const wrapper = mount( + cy.mount( - ); - - const RangeInstance = wrapper.find('Range').at(0).instance(); - - RangeInstance._onMouseDown({ - button: 0, - pageX: 10, - stopPropagation: () => {}, - preventDefault: () => {}, - }); - assert(RangeInstance.dom.querySelector('.next-range-active') !== null); + ) + .get('.next-range-slider') + .eq(0) + .trigger('mousedown', { button: 0, pageX: 20, pageY: 0 }); + cy.get('.next-range-active').should('to.exist'); }); - it(' fixedWidth tooltip enter+down+leave+up', () => { - const wrapper = mount(); - let parent = document.createElement('div'); - document.body.appendChild(parent); - - ReactDOM.render(, parent); - - assert(document.querySelector('.next-balloon-tooltip') === null); - // wrapper.find('.next-range-frag').simulate('mousedown'); - ReactTestUtils.Simulate.mouseDown(document.querySelectorAll('.next-range-frag')[0], { - button: 0, - }); - assert(document.querySelector('.next-balloon-tooltip') !== null); - simulateEvent.simulate(document, 'mouseup'); - - // enzyme simulate 不支持 addEventListener 添加的事件冒泡(an anti-pattern in React),导致 mouseup 的case很难测 - // https://github.com/airbnb/enzyme/issues/426 - // assert(document.querySelector('.next-balloon-tooltip') === null); - - wrapper.unmount(); - - document.body.removeChild(parent); - parent = null; + it('fixedWidth tooltip enter+down+leave+up', () => { + cy.mount(); + cy.get('.next-balloon-tooltip').should('to.not.exist'); + cy.get('.next-range-frag').trigger('mousedown', { button: 0, pageX: 0, pageY: 0 }); + cy.get('.next-range-frag').trigger('mousemove', { + type: 'mousemove', + pageX: 100, + pageY: 0, + }); + cy.get('.next-balloon-tooltip').should('to.exist'); + cy.get('.next-range-frag').trigger('mouseleave', { pageX: 200, pageY: 100 }); + cy.get('.next-balloon-tooltip').should('to.exist'); + cy.get('.next-range-frag').trigger('mouseup', { pageX: 100, pageY: 0 }); + cy.get('.next-balloon-tooltip').should('to.not.exist'); }); - it(' fixedWidth tooltipVisible === true, always has tooltip', () => { - const wrapper = mount(); - - const RangeInstance = wrapper.find('Range').at(0).instance(); - assert(RangeInstance.dom.querySelector('.next-balloon-tooltip') !== null); - - wrapper.find('.next-range-frag').simulate('mouseenter'); - wrapper.find('.next-range-frag').simulate('mouseleave'); - wrapper.find('.next-range-frag').simulate('mousedown'); - wrapper.find('.next-range-frag').simulate('mouseenter'); - wrapper.find('.next-range-frag').simulate('mouseleave'); - wrapper.find('.next-range-frag').simulate('mouseup'); - - assert(RangeInstance.dom.querySelector('.next-balloon-tooltip') !== null); + it('fixedWidth tooltipVisible === true, always has tooltip', () => { + cy.mount(); + cy.get('.next-balloon-tooltip').should('to.exist'); + cy.get('.next-range-frag').trigger('mousedown', { button: 0, pageX: 0, pageY: 0 }); + cy.get('.next-range-frag').trigger('mousemove', { + type: 'mousemove', + pageX: 100, + pageY: 0, + }); + cy.get('.next-balloon-tooltip').should('to.exist'); + cy.get('.next-range-frag').trigger('mouseleave', { pageX: 200, pageY: 100 }); + cy.get('.next-balloon-tooltip').should('to.exist'); + cy.get('.next-range-frag').trigger('mouseup', { pageX: 100, pageY: 0 }); + cy.get('.next-balloon-tooltip').should('to.exist'); }); it('keymove right', () => { - const aSpy = sinon.spy(); - const wrapper = mount(); - - wrapper.find('.next-range-slider').simulate('keyDown', { keyCode: 39 }); - assert(aSpy.called); + const onChange = cy.spy().as('onChange'); + cy.mount(); + cy.get('.next-range-slider').trigger('keydown', { keyCode: 39 }); + cy.get('@onChange').should('have.been.calledOnce'); + cy.get('@onChange').should('be.calledWith', 3); }); it('keymove left', () => { - const aSpy = sinon.spy(); - const wrapper = mount(); - - wrapper.find('.next-range-slider').simulate('keyDown', { keyCode: 37 }); - assert(aSpy.called); + const onChange = cy.spy().as('onChange'); + cy.mount(); + cy.get('.next-range-slider').trigger('keydown', { keyCode: 37 }); + cy.get('@onChange').should('have.been.calledOnce'); + cy.get('@onChange').should('be.calledWith', 1); }); it('keymove right at rightmost', () => { - const aSpy = sinon.spy(); - const wrapper = mount( - - ); - - wrapper.find('.next-range-slider').simulate('keyDown', { keyCode: 39 }); - assert(!aSpy.called); + const onChange = cy.spy().as('onChange'); + cy.mount(); + cy.get('.next-range-slider').trigger('keydown', { keyCode: 39 }); + cy.get('@onChange').should('not.have.been.called'); + checkRangeSelectedDomWidthAndLeft('100%', '0%'); }); it('rtl', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '10%', - left: '90%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-selected').props().style, { - width: '20%', - left: '70%', - }); + cy.mount(); + checkRangeSelectedDomWidthAndLeft('10%', '90%'); + + cy.mount(); + checkRangeSelectedDomWidthAndLeft('20%', '70%'); }); it('rtl & reverse', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); - assert.deepEqual(wrapperSingle.find('.next-range-selected').props().style, { - width: '90%', - left: '0%', - }); - assert.deepEqual(wrapperDouble.find('.next-range-selected').at(0).props().style, { - width: '70%', - left: 0, - }); + cy.mount(); + checkRangeSelectedDomWidthAndLeft('90%', '0%'); + + cy.mount(); + checkRangeSelectedDomWidthAndLeft('70%', '0px'); }); it('should isPreview', () => { - const wrapperSingle = mount(); - const wrapperDouble = mount(); - - assert(wrapperSingle.getDOMNode().innerText === '30'); - assert(wrapperDouble.getDOMNode().innerText === '10~40'); + cy.mount(); + cy.get('.next-form-preview').then($el => { + expect($el[0].innerText).to.equal('30'); + }); + cy.mount(); + cy.get('.next-form-preview').then($el => { + expect($el[0].innerText).to.equal('10~40'); + }); }); it('should renderPreview', () => { - const wrapperSingle = mount( 'single'} value={30} />); - const wrapperDouble = mount( - 'double'} value={[10, 40]} /> - ); - - assert(wrapperSingle.getDOMNode().innerText === 'single'); - assert(wrapperDouble.getDOMNode().innerText === 'double'); + cy.mount( 'single'} value={30} />); + cy.get('.next-form-preview').then($el => { + expect($el[0].innerText).to.equal('single'); + }); + cy.mount( 'double'} value={[10, 40]} />); + cy.get('.next-form-preview').then($el => { + expect($el[0].innerText).to.equal('double'); + }); }); }); diff --git a/components/range/index.tsx b/components/range/index.tsx index f4f8010b48..d2e97c1191 100644 --- a/components/range/index.tsx +++ b/components/range/index.tsx @@ -1,5 +1,8 @@ import ConfigProvider from '../config-provider'; import Range from './view/range'; +import type { RangeProps, RangeValueType } from './types'; + +export type { RangeProps, RangeValueType }; export default ConfigProvider.config(Range, { transform: /* istanbul ignore next */ (props, deprecated) => { diff --git a/components/range/types.ts b/components/range/types.ts index 421291c1f2..ad530c6f5e 100644 --- a/components/range/types.ts +++ b/components/range/types.ts @@ -1,122 +1,252 @@ -/// - -import React from 'react'; -import { CommonProps } from '../util'; - -interface HTMLAttributesWeak extends React.HTMLAttributes { - defaultValue?: any; - onChange?: any; - /** - * for form item - */ - name?: string; +import type { CommonProps } from '../util'; +import type { PopupProps } from '../overlay'; + +interface HTMLAttributesWeak + extends Omit, 'defaultValue' | 'onChange'> {} + +/** + * @api RangeValueType + * @order 1 + */ +export type RangeValueType = number | [number, number]; + +export interface RangeState { + value: RangeValueType | undefined; + tempValue: RangeValueType | undefined; + hasMovingClass: boolean; + lowerTooltipVisible: boolean; + upperTooltipVisible: boolean; + tooltipAnimation: boolean; } +/** + * @api Range + * @order 0 + */ export interface RangeProps extends HTMLAttributesWeak, CommonProps { /** - * 样式类名的品牌前缀 + * 表单项 name + * @en Form item name + * @skip */ - prefix?: string; + name?: string; /** * 自定义类名 + * @en className + * @skip */ className?: string; /** * 自定义内敛样式 + * @en style + * @skip */ style?: React.CSSProperties; /** - * 滑块个数 + * 滑块个数

**可选值**:
'single'(单个)
'double'(两个) + * @en number of sliders

**option**:
'single'
'double' + * @defaultValue 'single' */ slider?: 'single' | 'double'; /** * 最小值 + * @en minimal value + * @defaultValue 0 */ min?: number; /** * 最大值 + * @en maximal value + * @defaultValue 100 */ max?: number; /** - * 步长,取值必须大于 0,并且可被 (max - min) 整除。 + * 步长,取值必须大于 0,并且可被 (max - min) 整除 + * @en step of the range, which is positive integer and (max - min) can be divided by it + * @defaultValue 1 */ step?: number; /** * 设置当前取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` + * @en current value. It's in the form of `Number` when `slider` is `single` otherwise `[Number, Number]` */ - value?: number | [number, number]; + value?: RangeValueType; /** * 设置初始取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` + * @en default value. It's in the form of `Number` when `slider` is `single` otherwise `[Number, Number]` */ - defaultValue?: number | [number, number]; + defaultValue?: RangeValueType; /** * 刻度数值显示逻辑(false 代表不显示,array 枚举显示的值,number 代表按 number 平分,object 表示按 key 划分,value 值显示) + * @en way to show the scale. (`false` means nothing, `array` means enum, `number` means equal division, and `object` means `key` as the mark with `value` as the value) + * @defaultValue false */ - marks?: boolean | number | Array | {}; + marks?: false | number | Array | Record; /** - * marks显示在上方('above')or下方('below') + * marks显示在上方('above')or下方('below')

**可选值**:
'above', 'below' + * @en position for the scale

**option**:
'above', 'below' + * @defaultValue 'above' */ marksPosition?: 'above' | 'below'; /** * 值为 `true` 时,滑块为禁用状态 + * @en disabled + * @defaultValue false */ disabled?: boolean; /** * 当 Range 的值发生改变后,会触发 onChange 事件,并把改变后的值作为参数传入, 如果设置了value, 要配合此函数做受控使用 + * @en callback triggered when value changes + * @param value - 改变后的值 - the changed value + * @defaultValue () =\> void */ - onChange?: (value: number | [number, number]) => void; + onChange?: (value: RangeValueType) => void; /** * 滑块拖动的时候触发的事件,不建议在这里setState, 一般情况下不需要用, 滑动时有特殊需求时使用 + * @en callback triggered when slider being dragged, and used only for special need + * @param value - 改变后的值 - the changed value + * @defaultValue () =\> void */ - onProcess?: (value: number | [number, number]) => void; + onProcess?: (value: RangeValueType) => void; /** * 是否显示 tip + * @en whether to show tip + * @defaultValue true */ hasTip?: boolean; + /** + * 是否显示 tip + * @en whether to show tip + * @deprecated use hasTip + * @skip + */ + hasTips?: boolean; + /** * 自定义 tip 显示内容 + * @en custom tip content + * @param value - 改变后的值 - the changed value + * @returns React.ReactNode - React.ReactNode + * @defaultValue (value) =\> value */ tipRender?: (value: number | string) => React.ReactNode; + /** + * 自定义 tip 显示内容 + * @deprecated use tipRender + * @skip + */ + tipFormatter?: (value: number | string) => string; + /** * 选中态反转 + * @en reverse the selected part + * @defaultValue false */ reverse?: boolean; /** * 是否pure render + * @en pure render or not + * @defaultValue false */ pure?: boolean; /** - * 是否为拖动线段类型,默认slider为double, defaultValue必传且指定区间 + * 是否为拖动线段类型,默认slider为double, defaultValue必传且指定区间 + * @en drag a line with fixed width. It considers `slider` as `double`, and `defaultValue` must be a interval. + * @defaultValue false */ fixedWidth?: boolean; /** * tooltip是否默认展示 + * @en tooltip always be visible or not + * @defaultValue false */ tooltipVisible?: boolean; /** * 是否已rtl模式展示 + * @defaultValue false + * @skip */ rtl?: boolean; + + /** + * 是否为预览态 + * @en is preview or not + * @defaultValue false + */ + isPreview?: boolean; + + /** + * 预览态模式下渲染的内容 + * @en custom preview content + * @param value - 改变后的值 - the changed value + * @param props - RangeProps - RangeProps + * @returns React.ReactNode - React.ReactNode + */ + renderPreview?: (value: RangeValueType | undefined, props: RangeProps) => React.ReactNode; +} + +export interface RangeMarkProps + extends Pick, 'min' | 'max' | 'prefix' | 'marksPosition' | 'rtl'> { + value: RangeValueType; + marks: Record; +} + +export interface RangeSliderProps + extends Pick< + Required, + 'prefix' | 'hasTip' | 'slider' | 'tipRender' | 'min' | 'max' | 'tooltipVisible' | 'rtl' + > { + value: number; + hasMovingClass: RangeState['hasMovingClass'] | null; + tooltipAnimation?: PopupProps['animation']; + onTooltipVisibleChange?: PopupProps['onVisibleChange']; + onKeyDown?: (e: React.KeyboardEvent) => void; } -export default class Range extends React.Component {} +export interface RangeScaleProps + extends Pick, 'min' | 'max' | 'prefix' | 'rtl'> { + value: RangeValueType; + scales: number[]; +} + +export interface RangeSelectedProps + extends Pick, 'min' | 'max' | 'prefix' | 'slider' | 'reverse' | 'rtl'> { + value: RangeValueType; +} + +export interface RangeFixedSliderProps + extends Pick< + Required, + 'min' | 'max' | 'prefix' | 'hasTip' | 'tooltipVisible' | 'tipRender' | 'disabled' | 'rtl' + > { + value: [number, number]; + hasMovingClass: RangeState['hasMovingClass']; + tooltipAnimation?: PopupProps['animation']; + onTooltipVisibleChange?: PopupProps['onVisibleChange']; +} + +export interface RangeFixedSliderState { + hasMovingClass: boolean; + onTooltipVisibleChange: boolean; + tooltipAnimation: boolean; +} diff --git a/components/range/utils.tsx b/components/range/utils.tsx index e48c5a0b5a..e9e5b78e9a 100644 --- a/components/range/utils.tsx +++ b/components/range/utils.tsx @@ -1,4 +1,6 @@ -export function inRange(value, range, min) { +import type { RangeValueType } from './types'; + +export function inRange(value: number, range: RangeValueType, min: number) { if (!Array.isArray(range)) { range = [min, range]; } @@ -6,11 +8,11 @@ export function inRange(value, range, min) { return value >= range[0] && value <= range[1]; } -export function getPercent(min, max, value) { +export function getPercent(min: number, max: number, value: number) { return ((value - min) * 100) / (max - min); } -export function getPrecision(step) { +export function getPrecision(step: number) { let precision = 0; const stepString = step.toString(); if (stepString.indexOf('.') !== -1) { @@ -19,16 +21,17 @@ export function getPrecision(step) { return precision; } -export function isEqual(left, right) { +export function isEqual(left: RangeValueType, right: RangeValueType) { if (Array.isArray(left)) { + // @ts-expect-error 增加对 right 前置判断 return left[0] === right[0] && left[1] === right[1]; } else { return left === right; } } -export function getDragging(current, preValue) { - let dragging = 'upper'; +export function getDragging(current: number, preValue: [number, number]) { + let dragging: 'lower' | 'upper' = 'upper'; if (current > preValue[1]) { dragging = 'upper'; diff --git a/components/range/view/fixedSlider.tsx b/components/range/view/fixedSlider.tsx index 5b6a097c6d..d1291fcc08 100644 --- a/components/range/view/fixedSlider.tsx +++ b/components/range/view/fixedSlider.tsx @@ -3,11 +3,12 @@ import PropTypes from 'prop-types'; import { events, func } from '../../util'; import Balloon from '../../balloon'; import { getPercent } from '../utils'; +import type { RangeFixedSliderProps, RangeFixedSliderState } from '../types'; const Tooltip = Balloon.Tooltip; const { noop } = func; -function _getStyle(min, max, value, rtl) { +function _getStyle(min: number, max: number, value: [number, number], rtl: boolean) { if (rtl) { return { left: `${getPercent(min, max, max + min - value[1])}%`, @@ -20,7 +21,13 @@ function _getStyle(min, max, value, rtl) { }; } -function sliderFrag(props) { +function sliderFrag( + props: RangeFixedSliderProps & { + onMouseEnter: () => void; + onMouseLeave: () => void; + onMouseDown: () => void; + } +) { const { prefix, min, max, value, disabled, onMouseEnter, onMouseLeave, onMouseDown, rtl } = props; @@ -59,7 +66,10 @@ sliderFrag.propTypes = { rtl: PropTypes.bool, }; -export default class FixedSlider extends React.Component { +export default class FixedSlider extends React.Component< + RangeFixedSliderProps, + RangeFixedSliderState +> { static propTypes = { hasTip: PropTypes.bool, tooltipVisible: PropTypes.bool, @@ -77,12 +87,15 @@ export default class FixedSlider extends React.Component { hasTip: true, onChange: noop, onProcess: noop, - tipRender: value => value, + tipRender: (value: number | string) => value, reverse: false, rtl: false, }; - constructor(props) { + keyState: 'down' | 'enter' | ''; + _onMouseUpListener: ReturnType | null; + + constructor(props: RangeFixedSliderProps) { super(props); this.state = { @@ -150,7 +163,7 @@ export default class FixedSlider extends React.Component { return hasTip ? ( target.parentNode} + popupContainer={(target: HTMLElement) => target.parentNode} popupProps={{ visible: tooltipVisible || hasMovingClass, animation: this.state.tooltipAnimation diff --git a/components/range/view/mark.tsx b/components/range/view/mark.tsx index a35d1578c2..e6fd0b5654 100644 --- a/components/range/view/mark.tsx +++ b/components/range/view/mark.tsx @@ -1,9 +1,10 @@ -import classNames from 'classnames'; import React from 'react'; import PropTypes from 'prop-types'; +import classNames from 'classnames'; import { inRange, getPercent } from '../utils'; +import type { RangeMarkProps } from '../types'; -export default class Mark extends React.Component { +export default class Mark extends React.Component { static propTypes = { min: PropTypes.number, max: PropTypes.number, @@ -25,21 +26,24 @@ export default class Mark extends React.Component { _renderItems() { const { min, max, value, prefix, marks, rtl } = this.props; - const items = []; + const items: React.ReactNode[] = []; Object.keys(marks).forEach((mark, i) => { const classes = classNames({ [`${prefix}range-mark-text`]: true, + // @ts-expect-error mark 为 string 类型,应该转换为 number activated: inRange(mark, value, min), }); let style; if (rtl) { style = { + // @ts-expect-error mark 为 string 类型,应该转换为 number right: `${getPercent(min, max, mark)}%`, left: 'auto', }; } else { style = { + // @ts-expect-error mark 为 string 类型,应该转换为 number left: `${getPercent(min, max, mark)}%`, right: 'auto', }; @@ -48,6 +52,7 @@ export default class Mark extends React.Component { items.push( // "key" is for https://fb.me/react-warning-keys + {/* @ts-expect-error mark 为 string 类型,应该转换为 number */} {marks[mark]} ); diff --git a/components/range/view/range.tsx b/components/range/view/range.tsx index 097f3b7362..8e85ac15b9 100644 --- a/components/range/view/range.tsx +++ b/components/range/view/range.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import React from 'react'; import PropTypes from 'prop-types'; import { polyfill } from 'react-lifecycles-compat'; -import { events, func, KEYCODE, dom, obj } from '../../util'; +import { events, func, KEYCODE, dom, obj, type ClassPropsWithDefault } from '../../util'; import Balloon from '../../balloon'; import { getPercent, getPrecision, isEqual, getDragging } from '../utils'; import Scale from './scale'; @@ -11,16 +11,21 @@ import Selected from './selected'; import Mark from './mark'; import Slider from './slider'; import FixedSlider from './fixedSlider'; +import type { RangeProps, RangeState, RangeValueType, RangeSliderProps } from '../types'; + +type RangePropsWithDefault = ClassPropsWithDefault; +type EventOnReturnType = ReturnType; +type LowerAndUpperSliderProps = Omit & { value?: RangeValueType }; const Tooltip = Balloon.Tooltip; const { noop, bindCtx } = func; const { pickOthers } = obj; -function _isMultiple(slider, isFixedWidth) { +function _isMultiple(slider: RangeProps['slider'], isFixedWidth?: boolean) { return isFixedWidth || slider === 'double'; } -function LowerSlider(props) { +function LowerSlider(props: LowerAndUpperSliderProps) { const { hasTip, value, @@ -32,22 +37,24 @@ function LowerSlider(props) { } = props; if (_isMultiple(slider)) { + // FIXME 对 value 增加类型守卫 + const arrayValue = value as [number, number]; return hasTip ? ( target.parentNode} + popupContainer={(target: HTMLElement) => target.parentNode} popupProps={{ visible: tooltipVisible, onVisibleChange: onTooltipVisibleChange, animation: tooltipAnimation, needAdjust: false, }} - trigger={Slider({ ...props, value: value[0] })} + trigger={Slider({ ...props, value: arrayValue[0] })} align="t" > - {tipRender(`${value[0]}`)} + {tipRender(`${arrayValue[0]}`)} ) : ( - Slider({ ...props, value: value[0] }) + Slider({ ...props, value: arrayValue[0] }) ); } return null; @@ -63,7 +70,7 @@ LowerSlider.propTypes = { slider: PropTypes.oneOf(['single', 'double']), }; -function UpperSlider(props) { +function UpperSlider(props: LowerAndUpperSliderProps) { const newprop = Object.assign({}, props); const { hasTip, @@ -75,45 +82,48 @@ function UpperSlider(props) { tooltipAnimation, } = newprop; if (_isMultiple(slider)) { + // FIXME 对 value 增加类型守卫 + const arrayValue = value as [number, number]; delete newprop.onKeyDown; return hasTip ? ( target.parentNode} + popupContainer={(target: HTMLElement) => target.parentNode} popupProps={{ visible: tooltipVisible, onVisibleChange: onTooltipVisibleChange, animation: tooltipAnimation, needAdjust: false, }} - trigger={Slider({ ...newprop, value: value[1] })} + trigger={Slider({ ...newprop, value: arrayValue[1] })} align="t" > - {tipRender(value[1])} + {tipRender(arrayValue[1])} ) : ( - Slider({ ...newprop, value: value[1] }) + Slider({ ...newprop, value: arrayValue[1] }) ); } return hasTip ? ( target.parentNode} + popupContainer={(target: HTMLElement) => target.parentNode} popupProps={{ visible: tooltipVisible, onVisibleChange: onTooltipVisibleChange, animation: tooltipAnimation, needAdjust: false, }} + // @ts-expect-error Tooltip 组件不存在 animation 属性 animation={{ in: 'fadeInUp', out: 'fadeOutDown', }} - trigger={Slider(newprop)} + trigger={Slider(newprop as RangeSliderProps)} align="t" > - {tipRender(value)} + {tipRender(value as number)} ) : ( - Slider(newprop) + Slider(newprop as RangeSliderProps) ); } @@ -127,121 +137,45 @@ UpperSlider.propTypes = { slider: PropTypes.oneOf(['single', 'double']), }; -function pauseEvent(e) { +function pauseEvent(e: React.SyntheticEvent) { e.stopPropagation(); e.preventDefault(); } -/** Range */ -class Range extends React.Component { +class Range extends React.Component { static contextTypes = { prefix: PropTypes.string, }; static propTypes = { - /** - * 样式类名的品牌前缀 - */ prefix: PropTypes.string, - /** - * 自定义类名 - */ className: PropTypes.string, - /** - * 自定义内敛样式 - */ style: PropTypes.object, - /** - * 滑块个数 - * @enumdesc 单个, 两个 - */ slider: PropTypes.oneOf(['single', 'double']), - /** - * 最小值 - */ min: PropTypes.number, - /** - * 最大值 - */ max: PropTypes.number, - /** - * 步长,取值必须大于 0,并且可被 (max - min) 整除。 - */ step: PropTypes.number, - /** - * 设置当前取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` - */ value: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]), tempValue: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]), - /** - * 设置初始取值。当 `slider` 为 `single` 时,使用 `Number`,否则用 `[Number, Number]` - */ defaultValue: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]), - /** - * 刻度数值显示逻辑(false 代表不显示,array 枚举显示的值,number 代表按 number 平分,object 表示按 key 划分,value 值显示) - */ marks: PropTypes.oneOfType([ PropTypes.bool, PropTypes.number, PropTypes.arrayOf(PropTypes.number), PropTypes.object, ]), - /** - * marks显示在上方('above')or下方('below') - */ marksPosition: PropTypes.oneOf(['above', 'below']), - /** - * 值为 `true` 时,滑块为禁用状态 - */ disabled: PropTypes.bool, - /** - * 当 Range 的值发生改变后,会触发 onChange 事件,并把改变后的值作为参数传入, 如果设置了value, 要配合此函数做受控使用 - * @param {String/number} value - */ onChange: PropTypes.func, - /** - * 滑块拖动的时候触发的事件,不建议在这里setState, 一般情况下不需要用, 滑动时有特殊需求时使用 - * @param {String/number} value - */ onProcess: PropTypes.func, - /** - * 是否显示 tip - */ hasTip: PropTypes.bool, - /** - * 自定义 tip 显示内容 - * @param {Number|String} value 值 - * @return {ReactNode} 显示内容 - */ tipRender: PropTypes.func, id: PropTypes.string, - /** - * 选中态反转 - */ reverse: PropTypes.bool, - /** - * 是否pure render - */ pure: PropTypes.bool, - /** - * 是否为拖动线段类型,默认slider为double, defaultValue必传且指定区间 - */ fixedWidth: PropTypes.bool, - /** - * tooltip是否默认展示 - */ tooltipVisible: PropTypes.bool, - /** - * 是否已rtl模式展示 - */ rtl: PropTypes.bool, - /** - * 是否为预览态 - */ isPreview: PropTypes.bool, - /** - * 预览态模式下渲染的内容 - * @param {number} value 评分值 - */ renderPreview: PropTypes.func, }; @@ -258,18 +192,35 @@ class Range extends React.Component { hasTip: true, onChange: noop, onProcess: noop, - tipRender: value => value, + tipRender: (value: RangeValueType) => value, reverse: false, pure: false, marksPosition: 'above', rtl: false, isPreview: false, }; - - constructor(props) { + _moving: { + start: number; + end: number; + startValue: RangeState['tempValue']; + dragging?: 'lower' | 'upper'; + } | null; + dom: HTMLDivElement; + isFixedWidth: boolean; + lastPosition: number; + oldDragging?: 'lower' | 'upper'; + _onMouseMoveListener: EventOnReturnType | null; + _onMouseUpListener: EventOnReturnType | null; + _onContextMenuListener: EventOnReturnType | null; + _onTouchMoveListener: EventOnReturnType | null; + _onTouchEndListener: EventOnReturnType | null; + + readonly props: RangePropsWithDefault; + + constructor(props: RangeProps) { super(props); const { min } = props; - const initialValue = _isMultiple(props.slider) ? [min, min] : min; + const initialValue = _isMultiple(props.slider) ? ([min, min] as [number, number]) : min; const defaultValue = 'defaultValue' in props ? props.defaultValue : initialValue; const value = props.value !== undefined ? props.value : defaultValue; @@ -289,11 +240,11 @@ class Range extends React.Component { ]); } - static getDerivedStateFromProps(props, state) { + static getDerivedStateFromProps(props: RangePropsWithDefault, state: RangeState) { if ('value' in props) { const { min, slider, value } = props; const { hasMovingClass } = state; - const newState = { + const newState: Partial = { value, }; @@ -310,15 +261,16 @@ class Range extends React.Component { return null; } - _marksToScales(marks) { - let result = []; + _marksToScales(marks: RangePropsWithDefault['marks']) { + let result: number | number[] | false = []; if (Object.prototype.toString.call(marks) === '[object Object]') { - for (const key in marks) { + for (const key in marks as Record) { if (Object.hasOwnProperty.call(marks, key)) { result.push(parseInt(key)); } } } else { + // @ts-expect-error 需要对 marks 增加类型守卫 result = marks; } return result; @@ -350,7 +302,7 @@ class Range extends React.Component { _calcMarks() { const { min, max, marks } = this.props; - let result = {}; + let result: Record = {}; if (Array.isArray(marks)) { marks.forEach(m => { @@ -358,25 +310,28 @@ class Range extends React.Component { }); } else if (typeof marks === 'number') { const pace = (max - min) / marks; - + // @ts-expect-error result 对象的 value 为 string 类型 result[min] = min; for (let i = 1; i < marks; i++) { - let mark = min + i * pace; + let mark: string | number = min + i * pace; let precision = getPrecision(mark); if (precision > 2) { precision = 2; } + mark = mark.toFixed(precision); + // @ts-expect-error result 对象的 key 为 number 类型 result[mark] = mark; } + // @ts-expect-error result 对象的 value 为 string 类型 result[max] = max; } else { - result = marks; + result = marks as Record; } return result; } - _onMouseDown(e) { + _onMouseDown(e: React.MouseEvent) { if (e.button === 0) { this._start(e.pageX); this._addDocumentMouseEvents(); @@ -384,13 +339,13 @@ class Range extends React.Component { } } - _onTouchStart(e) { + _onTouchStart(e: React.TouchEvent) { this._start(e.targetTouches[0].pageX); this._addDocumentTouchEvents(); e.stopPropagation(); // preventDefault() will be ignored: https://www.chromestatus.com/features/5093566007214080 } - onKeyDown(e) { + onKeyDown(e: React.KeyboardEvent) { if (this.props.disabled) return; if (e.keyCode === KEYCODE.LEFT_ARROW || e.keyCode === KEYCODE.RIGHT_ARROW) { @@ -398,9 +353,9 @@ class Range extends React.Component { e.preventDefault(); let newValue; if (e.keyCode === KEYCODE.LEFT_ARROW) { - newValue = this.state.value - this.props.step; + newValue = (this.state.value as number) - this.props.step; } else { - newValue = this.state.value + this.props.step; + newValue = (this.state.value as number) + this.props.step; } if (newValue > this.props.max) { newValue = this.props.max; @@ -417,11 +372,11 @@ class Range extends React.Component { } } - _onContextMenu(e) { + _onContextMenu(e: React.MouseEvent) { pauseEvent(e); } - _start(position) { + _start(position: number) { this.setState({ hasMovingClass: true, }); @@ -451,7 +406,7 @@ class Range extends React.Component { } _end() { - const { startValue } = this._moving; + const { startValue } = this._moving!; const { tempValue, value } = this.state; this._moving = null; this._removeDocumentEvents(); @@ -462,7 +417,7 @@ class Range extends React.Component { tooltipAnimation: true, }); - if (!isEqual(tempValue, startValue)) { + if (!isEqual(tempValue!, startValue!)) { // Not Controlled if (!('value' in this.props)) { this.setState({ @@ -475,16 +430,19 @@ class Range extends React.Component { value, }); } - this.props.onChange(tempValue); + this.props.onChange(tempValue!); } } - _move(e) { - const position = e.type === 'mousemove' ? e.pageX : e.targetTouches[0].pageX; + _move(e: React.MouseEvent | React.TouchEvent) { + const position = + e.type === 'mousemove' + ? (e as React.MouseEvent).pageX + : (e as React.TouchEvent).targetTouches[0].pageX; this._onProcess(position); } - _onProcess(position, start) { + _onProcess(position: number, start?: boolean) { const { tempValue } = this.state; const current = this._positionToCurrent(position); //current 为当前click的value @@ -495,43 +453,43 @@ class Range extends React.Component { } else if (start) { this.lastPosition = current; if (_isMultiple(this.props.slider)) { - this._moving.dragging = getDragging(current, tempValue); + this._moving!.dragging = getDragging(current, tempValue as [number, number]); } else { - this._moving.dragging = 'upper'; + this._moving!.dragging = 'upper'; } this.setState({ - lowerTooltipVisible: this._moving.dragging === 'lower', - upperTooltipVisible: this._moving.dragging === 'upper', + lowerTooltipVisible: this._moving!.dragging === 'lower', + upperTooltipVisible: this._moving!.dragging === 'upper', tooltipAnimation: false, }); - } else if (this.oldDragging === 'lower' && this._moving.dragging === 'upper') { + } else if (this.oldDragging === 'lower' && this._moving!.dragging === 'upper') { this.setState({ upperTooltipVisible: true, lowerTooltipVisible: false, }); - } else if (this.oldDragging === 'upper' && this._moving.dragging === 'lower') { + } else if (this.oldDragging === 'upper' && this._moving!.dragging === 'lower') { this.setState({ upperTooltipVisible: false, lowerTooltipVisible: true, }); } - this.oldDragging = this._moving.dragging; + this.oldDragging = this._moving!.dragging; const nextValue = this._currentToValue( current, - tempValue, + tempValue!, this.lastPosition, this.isFixedWidth ); //计算range的新value,可能是数组,可能是单个值 this.lastPosition = current; - if (!isEqual(nextValue, tempValue)) { + if (!isEqual(nextValue!, tempValue!)) { this.setState({ tempValue: nextValue, }); - this.props.onProcess(nextValue); + this.props.onProcess(nextValue!); } } @@ -578,8 +536,8 @@ class Range extends React.Component { } // position => current (value type) - _positionToCurrent(position) { - const { start, end } = this._moving; + _positionToCurrent(position: number) { + const { start, end } = this._moving!; const { step, min, max, rtl } = this.props; if (position < start) { @@ -598,22 +556,28 @@ class Range extends React.Component { return Number(currentValue); } - _currentToValue(current, preValue, lastPosition, isFixedWidth) { - const { dragging } = this._moving; + _currentToValue( + current: number, + preValue: number | [number, number], + lastPosition: number, + isFixedWidth: boolean + ) { + const { dragging } = this._moving!; const { min, max } = this.props; if (!_isMultiple(this.props.slider, isFixedWidth)) { return current; } else { - let result; - + let result: RangeValueType | undefined; + // FIXME 对 preValue 增加类型守卫 + const arrayPreValue = preValue as [number, number]; const precision = getPrecision(this.props.step); const diff = current - lastPosition; - const newLeft = +(+preValue[0] + diff).toFixed(precision); - const newRight = +(+preValue[1] + diff).toFixed(precision); + const newLeft = +(+arrayPreValue[0] + diff).toFixed(precision); + const newRight = +(+arrayPreValue[1] + diff).toFixed(precision); - const newMaxLeft = +(max - preValue[1] + preValue[0]).toFixed(precision); - const newMinRight = +(min + preValue[1] - preValue[0]).toFixed(precision); + const newMaxLeft = +(max - arrayPreValue[1] + arrayPreValue[0]).toFixed(precision); + const newMinRight = +(min + arrayPreValue[1] - arrayPreValue[0]).toFixed(precision); if (isFixedWidth) { if (newLeft < min) { @@ -624,18 +588,18 @@ class Range extends React.Component { result = [newLeft, newRight]; } } else if (dragging === 'lower') { - if (current > preValue[1]) { - result = [preValue[1], current]; - this._moving.dragging = 'upper'; + if (current > arrayPreValue[1]) { + result = [arrayPreValue[1], current]; + this._moving!.dragging = 'upper'; } else { - result = [current, preValue[1]]; + result = [current, arrayPreValue[1]]; } } else if (dragging === 'upper') { - if (current < preValue[0]) { - result = [current, preValue[0]]; - this._moving.dragging = 'lower'; + if (current < arrayPreValue[0]) { + result = [current, arrayPreValue[0]]; + this._moving!.dragging = 'lower'; } else { - result = [preValue[0], current]; + result = [arrayPreValue[0], current]; } } @@ -643,7 +607,7 @@ class Range extends React.Component { } } - handleLowerTooltipVisibleChange(visible) { + handleLowerTooltipVisibleChange(visible: boolean) { if (this.state.hasMovingClass) { return; } @@ -652,7 +616,7 @@ class Range extends React.Component { }); } - handleUpperTooltipVisibleChange(visible) { + handleUpperTooltipVisibleChange(visible: boolean) { if (this.state.hasMovingClass) { return; } @@ -691,15 +655,16 @@ class Range extends React.Component { const classes = classNames({ [`${prefix}range`]: true, disabled: disabled, - [className]: className, + [className!]: className, }); if (Array.isArray(value)) { value.forEach((item, index) => { if (item > max) { - value[index] = max; + (value as [number, number])[index] = max; } }); + // @ts-expect-error value 存在 undefined 情况 } else if (value > max) { value = max; } @@ -730,7 +695,7 @@ class Range extends React.Component { if ('renderPreview' in this.props) { return (
- {renderPreview(value, this.props)} + {renderPreview!(value, this.props)}
); } @@ -745,7 +710,7 @@ class Range extends React.Component { return (
{ - this.dom = dom; + this.dom = dom!; }} {...others} style={style} @@ -762,6 +727,7 @@ class Range extends React.Component { {this.isFixedWidth ? ( + // @ts-expect-error value 存在 undefined 情况 ) : (
diff --git a/components/range/view/scale.tsx b/components/range/view/scale.tsx index c8202f7f5c..2f90ec7117 100644 --- a/components/range/view/scale.tsx +++ b/components/range/view/scale.tsx @@ -2,8 +2,9 @@ import classNames from 'classnames'; import React from 'react'; import PropTypes from 'prop-types'; import { inRange, getPercent } from '../utils'; +import type { RangeScaleProps } from '../types'; -export default class Scale extends React.Component { +export default class Scale extends React.Component { static propTypes = { min: PropTypes.number, max: PropTypes.number, @@ -23,7 +24,7 @@ export default class Scale extends React.Component { _renderItems() { const { min, max, value, prefix, scales, rtl } = this.props; - const items = []; + const items: React.ReactNode[] = []; scales.forEach((scale, i) => { const classes = classNames({ diff --git a/components/range/view/selected.tsx b/components/range/view/selected.tsx index 2531de64ed..3e1f5d16a9 100644 --- a/components/range/view/selected.tsx +++ b/components/range/view/selected.tsx @@ -2,8 +2,9 @@ import classNames from 'classnames'; import React from 'react'; import PropTypes from 'prop-types'; import { getPercent } from '../utils'; +import type { RangeSelectedProps } from '../types'; -export default class Selected extends React.Component { +export default class Selected extends React.Component { static propTypes = { min: PropTypes.number, max: PropTypes.number, @@ -106,7 +107,7 @@ export default class Selected extends React.Component { } render() { - const { prefix, slider, reverse, rtl } = this.props; + const { prefix, slider, reverse } = this.props; const classes = classNames({ [`${prefix}range-selected`]: true, }); diff --git a/components/range/view/slider.tsx b/components/range/view/slider.tsx index 8fa55649e6..0840ac5741 100644 --- a/components/range/view/slider.tsx +++ b/components/range/view/slider.tsx @@ -2,21 +2,23 @@ import React from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import { getPercent } from '../utils'; +import type { RangeSliderProps } from '../types'; -function _getProps(min, max, value, rtl) { +function _getProps(min: number, max: number, value: number, rtl: RangeSliderProps['rtl']) { return { style: { left: rtl ? `${100 - getPercent(min, max, value)}%` : `${getPercent(min, max, value)}%`, zIndex: 100, }, 'aria-valuenow': value, - 'aria-valuetext': value, + // @ts-expect-error aria-valuetext 应该是一个 string 类型 + 'aria-valuetext': value as string, 'aria-valuemin': min, 'aria-valuemax': max, }; } -function Slider({ prefix, hasMovingClass, min, max, value, onKeyDown, rtl }) { +function Slider({ prefix, hasMovingClass, min, max, value, onKeyDown, rtl }: RangeSliderProps) { const classes = classNames({ [`${prefix}range-slider`]: true, [`${prefix}range-slider-moving`]: hasMovingClass, diff --git a/components/range/view/track.tsx b/components/range/view/track.tsx index 61e7640341..b1b4820dc3 100644 --- a/components/range/view/track.tsx +++ b/components/range/view/track.tsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import React from 'react'; import PropTypes from 'prop-types'; -const Track = ({ prefix }) => { +const Track = ({ prefix }: { prefix: string }) => { const classes = classNames({ [`${prefix}range-track`]: true, });