mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
Feat/input range (#3405)
* feat: inputRange滑块组件 * feat: inputRange滑块组件 * feat: inputRange 单侧 * feat: inputRange 文案 * feat: inputRange 格式 * feat: inputRange CR问题修复 * feat: inputRange CR问题修复 Co-authored-by: huangying11 <huangying11@baidu.com>
This commit is contained in:
parent
e5b44cef2d
commit
d7738f3f88
File diff suppressed because it is too large
Load Diff
@ -4,20 +4,16 @@ import '../../../src/themes/default';
|
|||||||
import {render as amisRender} from '../../../src/index';
|
import {render as amisRender} from '../../../src/index';
|
||||||
import {makeEnv} from '../../helper';
|
import {makeEnv} from '../../helper';
|
||||||
|
|
||||||
test('Renderer:number', async () => {
|
test('Renderer:range', async () => {
|
||||||
const {container, getByRole} = render(
|
const {container} = render(
|
||||||
amisRender(
|
amisRender(
|
||||||
{
|
{
|
||||||
type: 'form',
|
type: 'form',
|
||||||
api: '/api/xxx',
|
api: '/api/xxx',
|
||||||
controls: [
|
controls: [
|
||||||
{
|
{
|
||||||
type: 'range',
|
type: 'input-range',
|
||||||
name: 'a',
|
name: 'range',
|
||||||
label: 'range',
|
|
||||||
min: 0,
|
|
||||||
max: 20,
|
|
||||||
step: 2,
|
|
||||||
value: 10,
|
value: 10,
|
||||||
showInput: true
|
showInput: true
|
||||||
}
|
}
|
||||||
@ -30,19 +26,21 @@ test('Renderer:number', async () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.mouseDown(getByRole('slider'));
|
const slider = container.querySelector('.cxd-InputRange-handle');
|
||||||
fireEvent.mouseMove(getByRole('slider'), {
|
fireEvent.mouseDown(slider);
|
||||||
|
fireEvent.mouseMove(slider, {
|
||||||
clientX: 400,
|
clientX: 400,
|
||||||
clientY: 400
|
clientY: 400
|
||||||
});
|
});
|
||||||
fireEvent.mouseUp(getByRole('slider'));
|
fireEvent.mouseUp(slider);
|
||||||
|
|
||||||
const input = container.querySelector('input[name=a]');
|
const input = container.querySelector('.cxd-InputRange-input input');
|
||||||
fireEvent.change(input!, {
|
fireEvent.change(input!, {
|
||||||
target: {
|
target: {
|
||||||
value: '7'
|
value: '7'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,17 +52,10 @@ test('Renderer:range:multiple', async () => {
|
|||||||
api: '/api/xxx',
|
api: '/api/xxx',
|
||||||
controls: [
|
controls: [
|
||||||
{
|
{
|
||||||
type: 'range',
|
type: 'input-range',
|
||||||
name: 'a',
|
name: 'range',
|
||||||
label: 'range',
|
|
||||||
min: 0,
|
|
||||||
max: 20,
|
|
||||||
step: 2,
|
|
||||||
value: '10,15',
|
|
||||||
multiple: true,
|
multiple: true,
|
||||||
joinValues: true,
|
value: [10, 20],
|
||||||
delimiter: ',',
|
|
||||||
clearable: true,
|
|
||||||
showInput: true
|
showInput: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -76,7 +67,7 @@ test('Renderer:range:multiple', async () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputs = container.querySelectorAll('input[name=a]');
|
const inputs = container.querySelectorAll('.cxd-InputRange-input input');
|
||||||
fireEvent.change(inputs[0], {
|
fireEvent.change(inputs[0], {
|
||||||
target: {
|
target: {
|
||||||
value: '7'
|
value: '7'
|
||||||
@ -94,3 +85,86 @@ test('Renderer:range:multiple', async () => {
|
|||||||
|
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Renderer:range:showSteps', async () => {
|
||||||
|
const {container} = render(
|
||||||
|
amisRender(
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
api: '/api/xxx',
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
type: 'input-range',
|
||||||
|
name: 'range',
|
||||||
|
max: 10,
|
||||||
|
showSteps: true,
|
||||||
|
showInput: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: 'The form',
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
makeEnv({})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Renderer:range:marks', async () => {
|
||||||
|
const {container} = render(
|
||||||
|
amisRender(
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
api: '/api/xxx',
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
type: 'input-range',
|
||||||
|
name: 'range',
|
||||||
|
parts: 5,
|
||||||
|
marks: {
|
||||||
|
'0': '0',
|
||||||
|
'20%': '20Mbps',
|
||||||
|
'40%': '40Mbps',
|
||||||
|
'60%': '60Mbps',
|
||||||
|
'80%': '80Mbps',
|
||||||
|
'100': '100'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: 'The form',
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
makeEnv({})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Renderer:range:tooltipVisible', async () => {
|
||||||
|
const {container} = render(
|
||||||
|
amisRender(
|
||||||
|
{
|
||||||
|
type: 'form',
|
||||||
|
api: '/api/xxx',
|
||||||
|
controls: [
|
||||||
|
{
|
||||||
|
type: 'input-range',
|
||||||
|
name: 'range',
|
||||||
|
tooltipVisible: true,
|
||||||
|
tooltipPlacement: 'right'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
title: 'The form',
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
makeEnv({})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
@ -17,12 +17,14 @@ order: 38
|
|||||||
```schema: scope="body"
|
```schema: scope="body"
|
||||||
{
|
{
|
||||||
"type": "form",
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
"api": "/api/mock2/form/saveForm",
|
"api": "/api/mock2/form/saveForm",
|
||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"type": "input-range",
|
"type": "input-range",
|
||||||
"name": "range",
|
"label": '滑块',
|
||||||
"label": "range"
|
"name": 'range',
|
||||||
|
"value": 20
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -42,7 +44,11 @@ order: 38
|
|||||||
"type": "input-range",
|
"type": "input-range",
|
||||||
"name": "range",
|
"name": "range",
|
||||||
"label": "range",
|
"label": "range",
|
||||||
"multiple": true
|
"multiple": true,
|
||||||
|
"value": {
|
||||||
|
"min": 10,
|
||||||
|
"max": 50
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -55,6 +61,7 @@ order: 38
|
|||||||
```schema: scope="body"
|
```schema: scope="body"
|
||||||
{
|
{
|
||||||
"type": "form",
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
"api": "/api/mock2/form/saveForm",
|
"api": "/api/mock2/form/saveForm",
|
||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
@ -69,19 +76,205 @@ order: 38
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 禁用
|
||||||
|
|
||||||
|
使用`disabled`禁用滑块。
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"label": '滑块',
|
||||||
|
"name": 'range',
|
||||||
|
"value": 10,
|
||||||
|
"disabled": true,
|
||||||
|
"showInput": true,
|
||||||
|
"clearable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 显示步长
|
||||||
|
|
||||||
|
开启`showSteps`可显示每个`step`长度
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"label": '滑块',
|
||||||
|
"name": 'range',
|
||||||
|
"max": 10,
|
||||||
|
"showSteps": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分割块数
|
||||||
|
|
||||||
|
通过`parts`可对整个滑动条平均分为`parts`块。
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"label": '滑块',
|
||||||
|
"name": 'range',
|
||||||
|
"showSteps": true,
|
||||||
|
"parts": 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 刻度标记
|
||||||
|
|
||||||
|
通过`marks`可对刻度进行自定义。
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"label": '滑块',
|
||||||
|
"name": 'range',
|
||||||
|
"parts": 5,
|
||||||
|
"marks": {
|
||||||
|
'0': '0',
|
||||||
|
'20%': '20Mbps',
|
||||||
|
'40%': '40Mbps',
|
||||||
|
'60%': '60Mbps',
|
||||||
|
'80%': '80Mbps',
|
||||||
|
'100': '100'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输入框
|
||||||
|
|
||||||
|
通过开启`showInput`会展示输入框,输入框数据于滑块数据同步。
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"name": "range",
|
||||||
|
"label": "range",
|
||||||
|
"value": 20,
|
||||||
|
"showInput": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"name": "range",
|
||||||
|
"label": "range",
|
||||||
|
"multiple": true,
|
||||||
|
"value": [10, 20],
|
||||||
|
"showInput": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 清除输入
|
||||||
|
|
||||||
|
在打开`showInput`输入框的前提下,开启`clearable`可对数据进行清除。
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"name": "range",
|
||||||
|
"label": "range",
|
||||||
|
"value": 20,
|
||||||
|
"showInput": true,
|
||||||
|
"clearable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 显示标签
|
||||||
|
|
||||||
|
标签默认在 hover 和拖拽过程中展示,通过`tooltipVisible`或者`tipFormatter`可指定标签是否展示。标签默认展示在滑块上方,通过`tooltipPlacement`可指定标签展示的位置。
|
||||||
|
|
||||||
|
```schema: scope="body"
|
||||||
|
{
|
||||||
|
"type": "form",
|
||||||
|
"debug": true,
|
||||||
|
"api": "/api/mock2/form/saveForm",
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "input-range",
|
||||||
|
"name": "range",
|
||||||
|
"label": "range",
|
||||||
|
"value": 20,
|
||||||
|
"tooltipVisible": true,
|
||||||
|
"tooltipPlacement": "right"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 属性表
|
## 属性表
|
||||||
|
|
||||||
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
|
当做选择器表单项使用时,除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
|
||||||
|
|
||||||
| 属性名 | 类型 | 默认值 | 说明 |
|
| 属性名 | 类型 | 默认值 | 说明 |
|
||||||
| ---------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------- | ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
|
||||||
| className | `string` | | css 类名 |
|
| className | `string` | | css 类名 |
|
||||||
| min | `number` | | 最小值 |
|
| value | `number` or `string` or `{min: number, max: number}` or `[number, number]` | | |
|
||||||
| max | `number` | | 最大值 |
|
| min | `number` | `0` | 最小值 |
|
||||||
| step | `number` | | 步长 |
|
| max | `number` | `100` | 最大值 |
|
||||||
| multiple | `boolean` | `false` | 支持选择范围 |
|
| disabled | `boolean` | `false` | 是否禁用 |
|
||||||
| joinValuse | `boolean` | `true` | 默认为 `true`,选择的 `value` 会通过 `delimiter` 连接起来,否则直接将以`{min: 1, max: 100}`的形式提交,开启`multiple`时有效 |
|
| step | `number` | `1` | 步长 |
|
||||||
| delimiter | `string` | `,` | 分隔符 |
|
| showSteps | `boolean` | `false` | 是否显示步长 |
|
||||||
| unit | `string` | | 单位 |
|
| parts | `number` | `1` | 分割的块数 |
|
||||||
| clearable | `boolean` | | 是否可清除 |
|
| marks | <code>{ [number | string]: ReactNode }</code> or <code>{ [number | string]: { style: CSSProperties, label: ReactNode } }</code> | | 刻度标记<br/>- 支持自定义样式<br/>- 设置百分比 |
|
||||||
| showInput | `boolean` | | 是否显示输入框 |
|
| tooltipVisible | `boolean` | `false` | 是否显示滑块标签 |
|
||||||
|
| tooltipPlacement | `auto` or `bottom` or `left` or `right` | `top` | 滑块标签的位置,默认`auto`,方向自适应<br/>前置条件:tooltipVisible 不为 false 时有效 |
|
||||||
|
| tipFormatter | `function` | | 控制滑块标签显隐函数<br/>前置条件:tooltipVisible 不为 false 时有效 |
|
||||||
|
| multiple | `boolean` | `false` | 支持选择范围 |
|
||||||
|
| joinValues | `boolean` | `true` | 默认为 `true`,选择的 `value` 会通过 `delimiter` 连接起来,否则直接将以`{min: 1, max: 100}`的形式提交<br/>前置条件:开启`multiple`时有效 |
|
||||||
|
| delimiter | `string` | `,` | 分隔符 |
|
||||||
|
| unit | `string` | | 单位 |
|
||||||
|
| clearable | `boolean` | `false` | 是否可清除<br/>前置条件:开启`showInput`时有效 |
|
||||||
|
| showInput | `boolean` | `false` | 是否显示输入框 |
|
||||||
|
| onChange | `function` | | 当 组件 的值发生改变时,会触发 onChange 事件,并把改变后的值作为参数传入 |
|
||||||
|
| onAfterChange | `function` | | 与 `onmouseup` 触发时机一致,把当前值作为参数传入 |
|
||||||
|
|
||||||
|
@ -830,36 +830,44 @@
|
|||||||
--InputGroup-select-onFocused-bg: var(--white);
|
--InputGroup-select-onFocused-bg: var(--white);
|
||||||
--InputGroup-select-onFocused-color: var(--Form-select-onFocused-color);
|
--InputGroup-select-onFocused-color: var(--Form-select-onFocused-color);
|
||||||
|
|
||||||
--InputRange-label--value-display: block;
|
--InputRange-padding: #{px2rem(20px)};
|
||||||
--InputRange-label--value-positionTop: #{px2rem(-36px)};
|
--InputRange-onDisabled-color: var(--light);
|
||||||
--InputRange-label--value-positionLeft: #{px2rem(-6px)};
|
--InputRange-primaryColor: var(--primary);
|
||||||
--InputRange-label-color: var(--InputRange-neutralColor);
|
--InputRange-track-height: #{px2rem(4px)};
|
||||||
--InputRange-label-fontSize: 0.8rem;
|
--InputRange-track-bg: #{$gray100};
|
||||||
--InputRange-label-positionBottom: -1.4rem;
|
--InputRange-track-onDisabled-bg: var(--InputRange-onDisabled-color);
|
||||||
--InputRange-neutralColor: #aaaaaa;
|
|
||||||
--InputRange-neutralLightColor: #eeeeee;
|
|
||||||
--InputRange-onDisabled-color: #cccccc;
|
|
||||||
--InputRange-primaryColor: var(--info);
|
|
||||||
--InputRange-slider-bg: var(--InputRange-primaryColor);
|
|
||||||
--InputRange-slider-border: #{px2rem(1px)} solid var(--InputRange-primaryColor);
|
|
||||||
--InputRange-slider-height: #{px2rem(24px)};
|
|
||||||
--InputRange-slider-onActive-transform: scale(1.3);
|
|
||||||
--InputRange-slider-onDisabled-bg: var(--InputRange-onDisabled-color);
|
|
||||||
--InputRange-slider-onDisabled-border: #{px2rem(1px)} solid var(--InputRange-onDisabled-color);
|
|
||||||
--InputRange-slider-onFocus-borderRadius: var(--borderRadiusMd);
|
|
||||||
--InputRange-slider-onFocus-boxShadow: 0 0 0
|
|
||||||
var(--InputRange-slider-onFocus-borderRadius) #{transparentize($info, 0.8)};
|
|
||||||
--InputRange-slider-transition: transform var(--animation-duration) ease-out,
|
|
||||||
box-shadow var(--animation-duration) ease-out;
|
|
||||||
--InputRange-slider-width: #{px2rem(18px)};
|
|
||||||
--InputRange-sliderContainer-transition: left var(--animation-duration)
|
|
||||||
ease-out;
|
|
||||||
--InputRange-track-bg: var(--InputRange-neutralLightColor);
|
|
||||||
--InputRange-track-height: #{px2rem(12px)};
|
|
||||||
--InputRange-track-onActive-bg: var(--InputRange-primaryColor);
|
--InputRange-track-onActive-bg: var(--InputRange-primaryColor);
|
||||||
--InputRange-track-onDisabled-bg: var(--InputRange-neutralLightColor);
|
--InputRange-track-onActive-onDisabled-bg: #{$gray200};
|
||||||
|
--InputRange-track-onActive-transition: transform var(--animation-duration)
|
||||||
|
ease-out left;
|
||||||
|
--InputRange-track-border-radius: #{px2rem(4px)};
|
||||||
|
--InputRange-track-dot-width: #{px2rem(4px)};
|
||||||
|
--InputRange-track-dot-height: #{px2rem(4px)};
|
||||||
|
--InputRange-track-dot-bg: var(--white);
|
||||||
--InputRange-track-transition: left var(--animation-duration) ease-out,
|
--InputRange-track-transition: left var(--animation-duration) ease-out,
|
||||||
width var(--animation-duration) ease-out;
|
width var(--animation-duration) ease-out;
|
||||||
|
--InputRange-handle-height: #{px2rem(16px)};
|
||||||
|
--InputRange-handle-width: #{px2rem(16px)};
|
||||||
|
--InputRange-handle-bg: var(--white);
|
||||||
|
--InputRange-handle-border: #{px2rem(1px)} solid var(--InputRange-primaryColor);
|
||||||
|
--InputRange-handle-onDisabled-border-color: #ceced1;
|
||||||
|
--InputRange-handle-onActive-transform: scale(1.3);
|
||||||
|
--InputRange-handle-onDrage-border-width: #{px2rem(2px)};
|
||||||
|
--InputRange-handle-onDisabled-bg: var(--InputRange-onDisabled-color);
|
||||||
|
--InputRange-handle-onDisabled-border: #{px2rem(1px)} solid var(--InputRange-onDisabled-color);
|
||||||
|
--InputRange-handle-onFocus-borderRadius: var(--borderRadiusMd);
|
||||||
|
--InputRange-handele-onFocus-boxShadow: 0 0 0
|
||||||
|
var(--InputRange-slider-onFocus-borderRadius) #{transparentize($info, 0.8)};
|
||||||
|
--InputRange-handle-transition: transform var(--animation-duration) ease-out,
|
||||||
|
box-shadow var(--animation-duration) ease-out;
|
||||||
|
--InputRange-handle-icon-width: #{px2rem(8px)};
|
||||||
|
--InputRange-handle-icon-height: #{px2rem(8px)};
|
||||||
|
--InputRange-label-padding: #{px2rem(8px)};
|
||||||
|
--InputRange-label-bg: #000;
|
||||||
|
--InputRange-label-color: var(--white);
|
||||||
|
--InputRange-label-font-size: #{px2rem(14px)};
|
||||||
|
--InputRange-label-border-radius: #{px2rem(4px)};
|
||||||
|
--InputRange-label-position-bottom: calc(100% + 8px);
|
||||||
|
|
||||||
--Layout--offscreen-width: 75%;
|
--Layout--offscreen-width: 75%;
|
||||||
--Layout-aside--folded-width: #{px2rem(60px)};
|
--Layout-aside--folded-width: #{px2rem(60px)};
|
||||||
|
@ -1,103 +1,124 @@
|
|||||||
.#{$ns}RangeControl {
|
|
||||||
position: relative;
|
|
||||||
@include clearfix();
|
|
||||||
|
|
||||||
padding-top: 1rem;
|
|
||||||
padding-bottom: 1.1rem;
|
|
||||||
|
|
||||||
&--withInput {
|
|
||||||
.#{$ns}InputRange {
|
|
||||||
width: calc(100% - 120px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$ns}InputRange-label--mid {
|
|
||||||
left: calc(50% - 60px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-multiple {
|
|
||||||
.#{$ns}InputRange {
|
|
||||||
width: calc(100% - 210px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$ns}InputRange {
|
|
||||||
&-input {
|
|
||||||
font-size: var(--fontSizeSm);
|
|
||||||
position: absolute;
|
|
||||||
right: px2rem(26px);
|
|
||||||
top: px2rem(12px);
|
|
||||||
height: px2rem(30px);
|
|
||||||
|
|
||||||
input {
|
|
||||||
padding: px2rem(10px);
|
|
||||||
width: px2rem(74px);
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border: var(--borderWidth) solid var(--info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-separator {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-unit {
|
|
||||||
position: absolute;
|
|
||||||
right: px2rem(10px);
|
|
||||||
top: px2rem(7px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-clear {
|
|
||||||
position: absolute;
|
|
||||||
top: px2rem(18px);
|
|
||||||
right: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
height: px2rem(16px);
|
|
||||||
width: px2rem(16px);
|
|
||||||
fill: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.#{$ns}InputRange {
|
.#{$ns}InputRange {
|
||||||
height: var(--InputRange-slider-height);
|
display: flex;
|
||||||
position: relative;
|
justify-content: space-between;
|
||||||
|
padding: var(--InputRange-padding) 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
// slider
|
&-wrap {
|
||||||
&-slider {
|
position: relative;
|
||||||
appearance: none;
|
flex: auto;
|
||||||
background: var(--InputRange-slider-bg);
|
}
|
||||||
border: var(--InputRange-slider-border);
|
|
||||||
// border-radius: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
width: var(--InputRange-slider-width);
|
|
||||||
height: var(--InputRange-slider-height);
|
|
||||||
margin-left: calc(var(--InputRange-slider-width) / -2);
|
|
||||||
margin-top: calc(
|
|
||||||
var(--InputRange-slider-height) / -2 + var(--InputRange-track-height) / -2
|
|
||||||
);
|
|
||||||
outline: none;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
top: 50%;
|
|
||||||
transition: var(--InputRange-slider-transition);
|
|
||||||
|
|
||||||
&:active {
|
&-input {
|
||||||
transform: var(--InputRange-slider-onActive-transform);
|
width: 20 * 4px;
|
||||||
|
height: 8 * 4px;
|
||||||
|
margin: 0 2 * 4px;
|
||||||
|
|
||||||
|
.#{$ns}Number {
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
.#{$ns}Number-handler {
|
||||||
box-shadow: var(--InputRange-slider-onFocus-boxShadow);
|
transition: 0.3s opacity;
|
||||||
|
color: var(--Number-handler-color);
|
||||||
|
|
||||||
|
&-up-inner,
|
||||||
|
&-down-inner {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: var(--Number-handler-onHover-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-clear {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: px2rem(12px);
|
||||||
|
width: px2rem(12px);
|
||||||
|
fill: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// disabled
|
||||||
|
&.is-disabled {
|
||||||
|
.#{$ns}InputRange-track {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$ns}InputRange-track-active {
|
||||||
|
background-color: var(--InputRange-track-onActive-onDisabled-bg);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$ns}InputRange-handle-icon {
|
||||||
|
border-color: var(--InputRange-handle-onDisabled-border-color);
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$ns}Number-handler {
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
&-up-inner,
|
||||||
|
&-down-inner {
|
||||||
|
cursor: not-allowed;
|
||||||
|
&:hover {
|
||||||
|
color: var(--text--muted-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hander
|
||||||
|
&-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0 auto calc(var(--InputRange-handle-width) / -2);
|
||||||
|
width: var(--InputRange-handle-width);
|
||||||
|
height: var(--InputRange-handle-height);
|
||||||
|
|
||||||
|
&-icon,
|
||||||
|
&-drage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
appearance: none;
|
||||||
|
background-color: var(--InputRange-handle-bg);
|
||||||
|
border: var(--InputRange-handle-border);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: var(--InputRange-handle-transition);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: var(--InputRange-handle-onActive-transform);
|
||||||
|
box-shadow: var(--InputRange-handle-onFocus-boxShadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: var(--InputRange-handle-onActive-transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: var(--InputRange-handle-onFocus-boxShadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-drage {
|
||||||
|
transform: var(--InputRange-handle-onActive-transform);
|
||||||
|
box-shadow: var(--InputRange-handle-onFocus-boxShadow);
|
||||||
|
border-width: var(--InputRange-handle-onDrage-border-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-range--disabled & {
|
.input-range--disabled & {
|
||||||
@ -107,77 +128,34 @@
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:before {
|
.icon-slider-handle {
|
||||||
content: '||';
|
width: var(--InputRange-handle-icon-width);
|
||||||
color: #fff;
|
height: var(--InputRange-handle-icon-height);
|
||||||
display: block;
|
top: 0;
|
||||||
line-height: px2rem(22px);
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-sliderContainer {
|
|
||||||
transition: var(--InputRange-sliderContainer-transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// label
|
|
||||||
&-label {
|
|
||||||
color: var(--InputRange-label-color);
|
|
||||||
font-size: var(--InputRange-label-fontSize);
|
|
||||||
transform: translateZ(0);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-label--min,
|
|
||||||
&-label--max,
|
|
||||||
&-label--mid {
|
|
||||||
bottom: var(--InputRange-label-positionBottom);
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-label--mid {
|
|
||||||
left: 50%;
|
|
||||||
bottom: calc(var(--gap-xs) * -1);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-label--max {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-label--value {
|
|
||||||
position: absolute;
|
|
||||||
display: var(--InputRange-label--value-display);
|
|
||||||
top: var(--InputRange-label--value-positionTop);
|
|
||||||
left: var(--InputRange-label--value-positionLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
// label Container
|
|
||||||
|
|
||||||
// &-labelContainer {
|
|
||||||
// left: -50%;
|
|
||||||
// position: relative;
|
|
||||||
// .#{$ns}InputRange-label--max & {
|
|
||||||
// left: 50%;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// track
|
// track
|
||||||
&-track {
|
&-track {
|
||||||
background: var(--InputRange-track-bg);
|
background: var(--InputRange-track-bg);
|
||||||
// border-radius: 0;
|
border-radius: var(--InputRange-track-border-radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
display: block;
|
||||||
height: var(--InputRange-track-height);
|
height: var(--InputRange-track-height);
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: var(--InputRange-track-transition);
|
transition: var(--InputRange-track-transition);
|
||||||
|
|
||||||
.#{$ns}InputRange.is-disabled & {
|
|
||||||
background: var(--InputRange-track-onDisabled-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
background: var(--InputRange-track-onActive-bg);
|
background: var(--InputRange-track-onActive-bg);
|
||||||
|
transition: 0.3s all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-dot {
|
||||||
|
width: var(--InputRange-track-dot-height);
|
||||||
|
height: var(--InputRange-track-dot-height);
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--InputRange-track-dot-bg);
|
||||||
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,28 +165,110 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
&::before,
|
&-track-active {
|
||||||
&::after {
|
background: var(--InputRange-track-onActive-bg);
|
||||||
content: '';
|
border-radius: var(--InputRange-track-border-radius);
|
||||||
width: 0.5rem;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// label
|
||||||
|
&-label {
|
||||||
|
position: absolute;
|
||||||
|
padding: var(--InputRange-label-padding);
|
||||||
|
background-color: var(--InputRange-label-bg);
|
||||||
|
color: var(--InputRange-label-color);
|
||||||
|
font-size: var(--InputRange-label-font-size);
|
||||||
|
border-radius: var(--InputRange-label-border-radius);
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
&-visible {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pos-top {
|
||||||
|
left: 50%;
|
||||||
|
bottom: var(--InputRange-label-position-bottom);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
bottom: -4px;
|
||||||
|
border-width: 4px 4px 0 4px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #000 transparent transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pos-bottom {
|
||||||
|
top: var(--InputRange-label-position-bottom);
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
top: -4px;
|
||||||
|
border-width: 0 4px 4px 4px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent transparent #000 transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pos-left {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%) translateX(-100%) translateX(-12px);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
right: -4px;
|
||||||
|
border-width: 4px 0 4px 4px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent transparent transparent #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pos-right {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%) translateX(26px);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: -4px;
|
||||||
|
border-width: 4px 4px 4px 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: transparent #000 transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// marks
|
||||||
|
&-marks {
|
||||||
|
position: relative;
|
||||||
|
top: 8px;
|
||||||
|
div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
background: inherit;
|
span {
|
||||||
z-index: 1;
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
|
||||||
left: -0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
right: -0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-track--active {
|
|
||||||
background: var(--InputRange-track-onActive-bg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,60 +4,523 @@
|
|||||||
* @author fex
|
* @author fex
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import range from 'lodash/range';
|
||||||
|
import keys from 'lodash/keys';
|
||||||
|
import isString from 'lodash/isString';
|
||||||
|
import difference from 'lodash/difference';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InputRange from 'react-input-range';
|
|
||||||
import {uncontrollable} from 'uncontrollable';
|
import {uncontrollable} from 'uncontrollable';
|
||||||
import {RendererProps} from '../factory';
|
|
||||||
import {ClassNamesFn, themeable} from '../theme';
|
|
||||||
|
|
||||||
interface RangeProps extends RendererProps {
|
import {RendererProps} from '../factory';
|
||||||
id?: string;
|
import Overlay from './Overlay';
|
||||||
className?: string;
|
import {ThemeProps, themeable} from '../theme';
|
||||||
min: number;
|
import {autobind, camel} from '../utils/helper';
|
||||||
max: number;
|
import {Icon} from '../../src';
|
||||||
value:
|
import {
|
||||||
| {
|
MultipleValue,
|
||||||
min: number;
|
Value,
|
||||||
max: number;
|
FormatValue,
|
||||||
}
|
MarksType,
|
||||||
| number;
|
RangeItemProps
|
||||||
classPrefix: string;
|
} from '../renderers/Form/InputRange';
|
||||||
classnames: ClassNamesFn;
|
import {stripNumber} from '../utils/tpl-builtin';
|
||||||
onChange: (value: any) => void;
|
import {findDOMNode} from 'react-dom';
|
||||||
|
|
||||||
|
interface HandleItemState {
|
||||||
|
isDrag: boolean;
|
||||||
|
labelActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Range extends React.Component<RangeProps, any> {
|
interface HandleItemProps extends ThemeProps {
|
||||||
static defaultProps: Partial<RangeProps> = {
|
disabled: boolean;
|
||||||
min: 1,
|
value: number;
|
||||||
max: 100
|
min: number;
|
||||||
};
|
max: number;
|
||||||
|
type?: 'min' | 'max';
|
||||||
|
onChange: (value: number, type: 'min' | 'max') => void;
|
||||||
|
onAfterChange: () => void;
|
||||||
|
tooltipVisible?: boolean;
|
||||||
|
tipFormatter?: (value: Value) => boolean;
|
||||||
|
unit?: string;
|
||||||
|
tooltipPlacement?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LabelProps extends ThemeProps {
|
||||||
|
show: boolean;
|
||||||
|
value: number;
|
||||||
|
tooltipVisible?: boolean;
|
||||||
|
tipFormatter?: (value: Value) => boolean;
|
||||||
|
unit?: string;
|
||||||
|
placement?: string;
|
||||||
|
activePlacement?: string;
|
||||||
|
positionLeft?: number;
|
||||||
|
positionTop?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑块值 -> position.left
|
||||||
|
* @param value 滑块值
|
||||||
|
* @param min 最小值
|
||||||
|
* @param max 最大值
|
||||||
|
* @returns position.left
|
||||||
|
*/
|
||||||
|
const valueToOffsetLeft = (value: any, min: number, max: number) =>
|
||||||
|
(value * 100) / (max - min) + '%';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑块handle
|
||||||
|
* 双滑块涉及两个handle,单独抽一个组件
|
||||||
|
*/
|
||||||
|
class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
|
||||||
|
handleRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||||
|
constructor(props: HandleItemProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isDrag: false,
|
||||||
|
labelActive: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mouseDown事件
|
||||||
|
* 防止拖动过快,全局监听 mousemove、mouseup
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onMouseDown() {
|
||||||
|
this.setState({
|
||||||
|
isDrag: true,
|
||||||
|
labelActive: true
|
||||||
|
});
|
||||||
|
window.addEventListener('mousemove', this.onMouseMove);
|
||||||
|
window.addEventListener('mouseup', this.onMouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mouseMove事件
|
||||||
|
* 触发公共onchange事件
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onMouseMove(e: MouseEvent) {
|
||||||
|
const {isDrag} = this.state;
|
||||||
|
const {type = 'min'} = this.props;
|
||||||
|
if (!isDrag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onChange(e.pageX, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mouseUp事件
|
||||||
|
* 移除全局 mousemove、mouseup
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onMouseUp() {
|
||||||
|
this.setState({
|
||||||
|
isDrag: false
|
||||||
|
});
|
||||||
|
this.props.onAfterChange();
|
||||||
|
window.removeEventListener('mousemove', this.onMouseMove);
|
||||||
|
window.removeEventListener('mouseup', this.onMouseUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mouseEnter事件
|
||||||
|
* 鼠标移入 -> 展示label
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onMouseEnter() {
|
||||||
|
this.setState({
|
||||||
|
labelActive: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mouseLeave事件
|
||||||
|
* 鼠标移出 & !isDrag -> 隐藏label
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onMouseLeave() {
|
||||||
|
const {isDrag} = this.state;
|
||||||
|
if (isDrag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
labelActive: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {min, max, value, className, classPrefix: ns, multiple} = this.props;
|
const {
|
||||||
|
classnames: cx,
|
||||||
const classNames = {
|
disabled,
|
||||||
activeTrack: multiple
|
value,
|
||||||
? `${ns}InputRange-track is-active`
|
min,
|
||||||
: `${ns}InputRange-track`,
|
max,
|
||||||
disabledInputRange: `${ns}InputRange is-disabled`,
|
tooltipVisible,
|
||||||
inputRange: `${ns}InputRange`,
|
tipFormatter,
|
||||||
labelContainer: `${ns}InputRange-labelContainer`,
|
unit,
|
||||||
maxLabel: `${ns}InputRange-label ${ns}InputRange-label--max`,
|
tooltipPlacement = 'auto'
|
||||||
minLabel: `${ns}InputRange-label ${ns}InputRange-label--min`,
|
} = this.props;
|
||||||
slider: `${ns}InputRange-slider`,
|
const {isDrag, labelActive} = this.state;
|
||||||
sliderContainer: `${ns}InputRange-sliderContainer`,
|
const style = {
|
||||||
track: `${ns}InputRange-track ${ns}InputRange-track--background`,
|
left: valueToOffsetLeft(value, min, max),
|
||||||
valueLabel: `${ns}InputRange-label ${ns}InputRange-label--value`
|
zIndex: isDrag ? 2 : 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return disabled ? (
|
||||||
|
<div className={cx('InputRange-handle')} style={style}>
|
||||||
|
<div className={cx('InputRange-handle-icon')}>
|
||||||
|
<Icon icon="slider-handle" className="icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={cx('InputRange-handle')}
|
||||||
|
style={style}
|
||||||
|
ref={this.handleRef}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
isDrag ? 'InputRange-handle-drage' : 'InputRange-handle-icon'
|
||||||
|
)}
|
||||||
|
onMouseDown={this.onMouseDown}
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
>
|
||||||
|
<Icon icon="slider-handle" className="icon" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Overlay
|
||||||
|
placement={tooltipPlacement}
|
||||||
|
target={() => findDOMNode(this)}
|
||||||
|
container={() => findDOMNode(this)}
|
||||||
|
rootClose={false}
|
||||||
|
show={true}
|
||||||
|
>
|
||||||
|
<Label
|
||||||
|
show={labelActive}
|
||||||
|
classPrefix={this.props.classPrefix}
|
||||||
|
classnames={cx}
|
||||||
|
value={value}
|
||||||
|
tooltipVisible={tooltipVisible}
|
||||||
|
tipFormatter={tipFormatter}
|
||||||
|
unit={unit}
|
||||||
|
placement={tooltipPlacement}
|
||||||
|
/>
|
||||||
|
</Overlay>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑块标签
|
||||||
|
*/
|
||||||
|
class Label extends React.Component<LabelProps, any> {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
value,
|
||||||
|
show,
|
||||||
|
tooltipVisible,
|
||||||
|
tipFormatter,
|
||||||
|
unit = '',
|
||||||
|
positionLeft = 0,
|
||||||
|
positionTop = 0
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
let {placement} = this.props;
|
||||||
|
if (placement === 'auto') {
|
||||||
|
positionLeft >= 0 && positionTop >= 0 && (placement = 'top');
|
||||||
|
positionLeft >= 0 && positionTop < 0 && (placement = 'bottom');
|
||||||
|
positionLeft < 0 && positionTop >= 0 && (placement = 'left');
|
||||||
|
positionLeft < 0 && positionTop < 0 && (placement = 'right');
|
||||||
|
}
|
||||||
|
|
||||||
|
// tooltipVisible 优先级 比show高
|
||||||
|
// tooltipVisible 为 true时,tipFormatter才生效
|
||||||
|
const isShow =
|
||||||
|
tooltipVisible !== undefined
|
||||||
|
? tooltipVisible && tipFormatter
|
||||||
|
? tipFormatter(value)
|
||||||
|
: tooltipVisible
|
||||||
|
: show;
|
||||||
return (
|
return (
|
||||||
<InputRange
|
<div
|
||||||
{...this.props}
|
className={cx('InputRange-label', `pos-${camel(placement)}`, {
|
||||||
classNames={classNames}
|
'InputRange-label-visible': isShow
|
||||||
minValue={min}
|
})}
|
||||||
maxValue={max}
|
>
|
||||||
value={value}
|
<span>{value + unit}</span>
|
||||||
/>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Range extends React.Component<RangeItemProps, any> {
|
||||||
|
multipleValue: MultipleValue = {
|
||||||
|
min: (this.props.value as MultipleValue).min,
|
||||||
|
max: (this.props.value as MultipleValue).max
|
||||||
|
};
|
||||||
|
trackRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收组件value变换
|
||||||
|
* value变换 -> Range.updateValue
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
updateValue(value: FormatValue) {
|
||||||
|
this.props.updateValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 坐标、宽高
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
getBoundingClient(dom: Element) {
|
||||||
|
const {x, y, width, height} = dom?.getBoundingClientRect();
|
||||||
|
return {x, y, width, height};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 坐标 -> 滑块值
|
||||||
|
* @param pageX target.target 坐标
|
||||||
|
* @returns 滑块值
|
||||||
|
*/
|
||||||
|
pageXToValue(pageX: number) {
|
||||||
|
const {x, width} = this.getBoundingClient(this.trackRef.current as Element);
|
||||||
|
const {max, min} = this.props;
|
||||||
|
return ((pageX - x) * (max - min)) / width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑块改变事件
|
||||||
|
* @param pageX target.pageX 坐标
|
||||||
|
* @param type min max
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onChange(pageX: number, type: string = 'min') {
|
||||||
|
const {max, min, step, multiple, value: originValue} = this.props;
|
||||||
|
const value = this.pageXToValue(pageX);
|
||||||
|
if (value > max || value < min) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const result = stripNumber(this.getStepValue(value, step));
|
||||||
|
if (multiple) {
|
||||||
|
this.updateValue({...(originValue as MultipleValue), [type]: result});
|
||||||
|
} else {
|
||||||
|
this.updateValue(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取step为单位的value
|
||||||
|
* @param value 拖拽后计算的value
|
||||||
|
* @param step 步长
|
||||||
|
* @returns step为单位的value
|
||||||
|
*/
|
||||||
|
getStepValue(value: number, step: number) {
|
||||||
|
const surplus = value % step;
|
||||||
|
let result = 0;
|
||||||
|
// 余数 >= 步长一半 -> 向上取
|
||||||
|
// 余数 < 步长一半 -> 向下取
|
||||||
|
const _value = surplus >= step / 2 ? value : value - step;
|
||||||
|
while (result <= _value) {
|
||||||
|
result += step;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击滑轨 -> 触发onchange 改变value
|
||||||
|
* @param e event
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onClickTrack(e: any) {
|
||||||
|
if (!!this.props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {value} = this.props;
|
||||||
|
const _value = this.pageXToValue(e.pageX);
|
||||||
|
const type =
|
||||||
|
Math.abs(_value - (value as MultipleValue).min) >
|
||||||
|
Math.abs(_value - (value as MultipleValue).max)
|
||||||
|
? 'max'
|
||||||
|
: 'min';
|
||||||
|
this.onChange(e.pageX, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置步长
|
||||||
|
* @returns ReactNode
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
renderSteps() {
|
||||||
|
const {max, min, step, showSteps, classnames: cx} = this.props;
|
||||||
|
const steps = Math.floor((max - min) / step);
|
||||||
|
return (
|
||||||
|
showSteps && (
|
||||||
|
<div>
|
||||||
|
{range(steps - 1).map(item => (
|
||||||
|
<span
|
||||||
|
key={item}
|
||||||
|
className={cx('InputRange-track-dot')}
|
||||||
|
style={{left: ((item + 1) * 100) / steps + '%'}}
|
||||||
|
></span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双滑块改变最大值、最小值
|
||||||
|
* @param pageX 拖拽后的pageX
|
||||||
|
* @param type 'min' | 'max'
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onGetChangeValue(pageX: number, type: keyof MultipleValue) {
|
||||||
|
const {max, min} = this.props;
|
||||||
|
const value = this.pageXToValue(pageX);
|
||||||
|
if (value > max || value < min) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.multipleValue[type] = stripNumber(
|
||||||
|
this.getStepValue(value, this.props.step)
|
||||||
|
);
|
||||||
|
const _min = Math.min(this.multipleValue.min, this.multipleValue.max);
|
||||||
|
const _max = Math.max(this.multipleValue.min, this.multipleValue.max);
|
||||||
|
this.updateValue({max: _max, min: _min});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算每个标记 position.left
|
||||||
|
* @param value 滑块值
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
getOffsetLeft(value: number | string) {
|
||||||
|
const {max, min} = this.props;
|
||||||
|
if (isString(value) && /^\d+%$/.test(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return (+value * 100) / (max - min) + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
marks,
|
||||||
|
multiple,
|
||||||
|
value,
|
||||||
|
max,
|
||||||
|
min,
|
||||||
|
disabled,
|
||||||
|
tooltipVisible,
|
||||||
|
unit,
|
||||||
|
tooltipPlacement,
|
||||||
|
tipFormatter,
|
||||||
|
onAfterChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
// trace
|
||||||
|
const traceActiveStyle = {
|
||||||
|
width: valueToOffsetLeft(
|
||||||
|
multiple
|
||||||
|
? (value as MultipleValue).max - (value as MultipleValue).min
|
||||||
|
: value,
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
),
|
||||||
|
left: valueToOffsetLeft(
|
||||||
|
multiple ? (value as MultipleValue).min : 0,
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle 双滑块
|
||||||
|
const diff = difference(
|
||||||
|
Object.values(value as MultipleValue),
|
||||||
|
Object.values(this.multipleValue)
|
||||||
|
);
|
||||||
|
if (diff && !!diff.length) {
|
||||||
|
this.multipleValue = {
|
||||||
|
min: (value as MultipleValue).min,
|
||||||
|
max: (value as MultipleValue).max
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('InputRange-wrap')}>
|
||||||
|
<div
|
||||||
|
ref={this.trackRef}
|
||||||
|
className={cx('InputRange-track', 'InputRange-track--background')}
|
||||||
|
onClick={this.onClickTrack}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx('InputRange-track-active')}
|
||||||
|
style={traceActiveStyle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 显示步长 */}
|
||||||
|
{this.renderSteps()}
|
||||||
|
|
||||||
|
{/* 滑块handle */}
|
||||||
|
{multiple ? (
|
||||||
|
['min', 'max'].map((type: 'min' | 'max') => (
|
||||||
|
<HandleItem
|
||||||
|
key={type}
|
||||||
|
value={this.multipleValue[type]}
|
||||||
|
type={type}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
classPrefix={this.props.classPrefix}
|
||||||
|
classnames={cx}
|
||||||
|
disabled={disabled}
|
||||||
|
tooltipVisible={tooltipVisible}
|
||||||
|
tipFormatter={tipFormatter}
|
||||||
|
unit={unit}
|
||||||
|
tooltipPlacement={tooltipPlacement}
|
||||||
|
onAfterChange={onAfterChange}
|
||||||
|
onChange={this.onGetChangeValue.bind(this)}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<HandleItem
|
||||||
|
value={+value}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
classPrefix={this.props.classPrefix}
|
||||||
|
classnames={cx}
|
||||||
|
disabled={disabled}
|
||||||
|
tooltipVisible={tooltipVisible}
|
||||||
|
tipFormatter={tipFormatter}
|
||||||
|
unit={unit}
|
||||||
|
tooltipPlacement={tooltipPlacement}
|
||||||
|
onAfterChange={onAfterChange}
|
||||||
|
onChange={this.onChange.bind(this)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 刻度标记 */}
|
||||||
|
{marks && (
|
||||||
|
<div className={cx('InputRange-marks')}>
|
||||||
|
{keys(marks).map((key: keyof MarksType) => (
|
||||||
|
<div key={key} style={{left: this.getOffsetLeft(key)}}>
|
||||||
|
<span style={(marks[key] as any)?.style}>
|
||||||
|
{(marks[key] as any)?.label || marks[key]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ import AlertWarning from '../icons/alert-warning.svg';
|
|||||||
import AlertDanger from '../icons/alert-danger.svg';
|
import AlertDanger from '../icons/alert-danger.svg';
|
||||||
import FunctionIcon from '../icons/function.svg';
|
import FunctionIcon from '../icons/function.svg';
|
||||||
import InputClearIcon from '../icons/input-clear.svg';
|
import InputClearIcon from '../icons/input-clear.svg';
|
||||||
|
import SliderHandleIcon from '../icons/slider-handle-icon.svg';
|
||||||
|
|
||||||
// 兼容原来的用法,后续不直接试用。
|
// 兼容原来的用法,后续不直接试用。
|
||||||
|
|
||||||
@ -184,6 +185,7 @@ registerIcon('alert-danger', AlertDanger);
|
|||||||
registerIcon('tree-down', TreeDownIcon);
|
registerIcon('tree-down', TreeDownIcon);
|
||||||
registerIcon('function', FunctionIcon);
|
registerIcon('function', FunctionIcon);
|
||||||
registerIcon('input-clear', InputClearIcon);
|
registerIcon('input-clear', InputClearIcon);
|
||||||
|
registerIcon('slider-handle', SliderHandleIcon);
|
||||||
|
|
||||||
export function Icon({
|
export function Icon({
|
||||||
icon,
|
icon,
|
||||||
|
6
src/icons/slider-handle-icon.svg
Normal file
6
src/icons/slider-handle-icon.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="6px" height="4px" viewBox="0 0 6 4" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="控件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M1.5,2.28847549e-17 L1.5,4 L0.5,4 L0.5,-2.22044605e-16 L1.5,2.28847549e-17 Z M3.5,2.28847549e-17 L3.5,4 L2.5,4 L2.5,-2.22044605e-16 L3.5,2.28847549e-17 Z M5.5,2.28847549e-17 L5.5,4 L4.5,4 L4.5,-2.22044605e-16 L5.5,2.28847549e-17 Z" id="形状结合" fill="#D4E5FF"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 569 B |
@ -1,21 +1,32 @@
|
|||||||
import React from 'react';
|
import React, {CSSProperties, ReactNode} from 'react';
|
||||||
import isNumber from 'lodash/isNumber';
|
import isNumber from 'lodash/isNumber';
|
||||||
import isObject from 'lodash/isObject';
|
import isObject from 'lodash/isObject';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import {FormItem, FormControlProps, FormBaseControl} from './Item';
|
|
||||||
|
|
||||||
|
import {FormItem, FormControlProps, FormBaseControl} from './Item';
|
||||||
import InputRange from '../../components/Range';
|
import InputRange from '../../components/Range';
|
||||||
|
import NumberInput from '../../components/NumberInput';
|
||||||
import {Icon} from '../../components/icons';
|
import {Icon} from '../../components/icons';
|
||||||
import {FormOptionsControl} from './Options';
|
|
||||||
import {stripNumber} from '../../utils/tpl-builtin';
|
import {stripNumber} from '../../utils/tpl-builtin';
|
||||||
|
import {autobind} from '../../utils/helper';
|
||||||
|
import {filter} from '../../utils/tpl';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Range
|
* Range
|
||||||
* 文档:https://baidu.gitee.io/amis/docs/components/form/range
|
* 文档:https://baidu.gitee.io/amis/docs/components/form/range
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type Value = string | MultipleValue | number | [number, number];
|
||||||
|
export type FormatValue = MultipleValue | number;
|
||||||
|
export type TooltipPosType = 'auto' | 'top' | 'right' | 'bottom' | 'left';
|
||||||
export interface RangeControlSchema extends FormBaseControl {
|
export interface RangeControlSchema extends FormBaseControl {
|
||||||
type: 'input-range';
|
type: 'input-range';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滑块值
|
||||||
|
*/
|
||||||
|
value?: Value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最大值
|
* 最大值
|
||||||
*/
|
*/
|
||||||
@ -35,25 +46,164 @@ export interface RangeControlSchema extends FormBaseControl {
|
|||||||
* 单位
|
* 单位
|
||||||
*/
|
*/
|
||||||
unit?: string;
|
unit?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示步长
|
||||||
|
*/
|
||||||
|
showSteps?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分割块数
|
||||||
|
*/
|
||||||
|
parts?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刻度
|
||||||
|
*/
|
||||||
|
marks?: MarksType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示标签
|
||||||
|
*/
|
||||||
|
tooltipVisible?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签方向
|
||||||
|
*/
|
||||||
|
tooltipPlacement?: TooltipPosType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为双滑块
|
||||||
|
*/
|
||||||
|
multiple?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否通过分隔符连接
|
||||||
|
*/
|
||||||
|
joinValues?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分隔符
|
||||||
|
*/
|
||||||
|
delimiter?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示输入框
|
||||||
|
*/
|
||||||
|
showInput?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用
|
||||||
|
*/
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MarksType = {
|
||||||
|
[index: number | string]: Record<
|
||||||
|
number,
|
||||||
|
React.ReactNode | {style?: React.CSSProperties; label?: string}
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface RangeProps extends FormControlProps {
|
export interface RangeProps extends FormControlProps {
|
||||||
max?: number;
|
/**
|
||||||
min?: number;
|
* 滑块值
|
||||||
step?: number;
|
*/
|
||||||
|
value: Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最小值
|
||||||
|
*/
|
||||||
|
min: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大值
|
||||||
|
*/
|
||||||
|
max: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 步长
|
||||||
|
*/
|
||||||
|
step: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示步长
|
||||||
|
*/
|
||||||
|
showSteps: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分割块数
|
||||||
|
*/
|
||||||
|
parts: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刻度
|
||||||
|
*/
|
||||||
|
marks?: MarksType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示标签
|
||||||
|
*/
|
||||||
|
tooltipVisible: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签方向
|
||||||
|
*/
|
||||||
|
tooltipPlacement: TooltipPosType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制滑块标签显隐函数
|
||||||
|
*/
|
||||||
|
tipFormatter?: (value: Value) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为双滑块
|
||||||
|
*/
|
||||||
|
multiple: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否通过分隔符连接
|
||||||
|
*/
|
||||||
|
joinValues: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分隔符
|
||||||
|
*/
|
||||||
|
delimiter: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单位
|
||||||
|
*/
|
||||||
unit?: string;
|
unit?: string;
|
||||||
clearable?: boolean;
|
|
||||||
name?: string;
|
/**
|
||||||
showInput?: boolean;
|
* 是否展示输入框
|
||||||
className?: string;
|
*/
|
||||||
value: any;
|
showInput: boolean;
|
||||||
onChange: (value: any) => void;
|
|
||||||
multiple?: boolean;
|
/**
|
||||||
joinValues?: boolean;
|
* 是否禁用
|
||||||
delimiter?: string;
|
*/
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* value改变事件
|
||||||
|
*/
|
||||||
|
onChange: (value: Value) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鼠标松开事件
|
||||||
|
*/
|
||||||
|
onAfterChange?: (value: Value) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MultipleValue {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DefaultProps {
|
export interface DefaultProps {
|
||||||
|
value: Value;
|
||||||
max: number;
|
max: number;
|
||||||
min: number;
|
min: number;
|
||||||
step: number;
|
step: number;
|
||||||
@ -64,56 +214,181 @@ export interface DefaultProps {
|
|||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
joinValues: boolean;
|
joinValues: boolean;
|
||||||
delimiter: string;
|
delimiter: string;
|
||||||
|
showSteps: boolean;
|
||||||
|
parts: number;
|
||||||
|
tooltipPlacement: TooltipPosType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatValue(
|
export interface RangeItemProps extends RangeProps {
|
||||||
value: string | number | {min: number; max: number},
|
value: FormatValue;
|
||||||
props: Partial<RangeProps>
|
updateValue: (value: Value) => void;
|
||||||
) {
|
onAfterChange: () => void;
|
||||||
if (props.multiple) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const [minValue, maxValue] = value
|
|
||||||
.split(props.delimiter || ',')
|
|
||||||
.map(v => Number(v));
|
|
||||||
return {
|
|
||||||
min:
|
|
||||||
(props.min && minValue < props.min && props.min) ||
|
|
||||||
minValue ||
|
|
||||||
props.min,
|
|
||||||
max:
|
|
||||||
(props.max && maxValue > props.max && props.max) ||
|
|
||||||
maxValue ||
|
|
||||||
props.max
|
|
||||||
};
|
|
||||||
} else if (typeof value === 'object') {
|
|
||||||
return {
|
|
||||||
min:
|
|
||||||
(props.min && value.min < props.min && props.min) ||
|
|
||||||
value.min ||
|
|
||||||
props.min,
|
|
||||||
max:
|
|
||||||
(props.max && value.max > props.max && props.max) ||
|
|
||||||
value.max ||
|
|
||||||
props.max
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value ?? props.min;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PropsWithDefaults = RangeProps & DefaultProps;
|
|
||||||
|
|
||||||
export interface RangeState {
|
export interface RangeState {
|
||||||
value:
|
value: FormatValue;
|
||||||
| {
|
}
|
||||||
min?: number;
|
|
||||||
max?: number;
|
/**
|
||||||
|
* 格式化初始value值
|
||||||
|
* @param value 初始value值 Value
|
||||||
|
* @param props RangeProps
|
||||||
|
* @returns number | {min: number, max: number}
|
||||||
|
*/
|
||||||
|
export function formatValue(
|
||||||
|
value: Value,
|
||||||
|
props: {
|
||||||
|
multiple: boolean;
|
||||||
|
delimiter: string;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
): FormatValue {
|
||||||
|
if (props.multiple) {
|
||||||
|
let {min, max} = props;
|
||||||
|
// value是字符串
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
[min, max] = value.split(props.delimiter || ',').map(v => Number(v));
|
||||||
|
}
|
||||||
|
// value是数组
|
||||||
|
else if (Array.isArray(value)) {
|
||||||
|
[min, max] = value;
|
||||||
|
}
|
||||||
|
// value是对象
|
||||||
|
else if (typeof value === 'object') {
|
||||||
|
min = value.min;
|
||||||
|
max = value.max;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
min: min === undefined || min < props.min ? props.min : min,
|
||||||
|
max: max === undefined || max > props.max ? props.max : max
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return +value ?? props.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输入框
|
||||||
|
*/
|
||||||
|
export class Input extends React.Component<RangeItemProps, any> {
|
||||||
|
/**
|
||||||
|
* onChange事件,只能输入数字
|
||||||
|
* @param e React.ChangeEvent
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onChange(value: number) {
|
||||||
|
const {multiple, value: originValue, type} = this.props;
|
||||||
|
const _value = this.getValue(value, type);
|
||||||
|
|
||||||
|
this.props.updateValue(
|
||||||
|
multiple ? {...(originValue as MultipleValue), [type]: _value} : value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双滑块 更新value
|
||||||
|
* @param value 输入的value值
|
||||||
|
*/
|
||||||
|
@autobind
|
||||||
|
onUpdateValue(value: number) {
|
||||||
|
const {multiple, value: originValue, type} = this.props;
|
||||||
|
const _value = this.getValue(value, type);
|
||||||
|
|
||||||
|
this.props.updateValue(
|
||||||
|
multiple ? {...(originValue as MultipleValue), [type]: _value} : value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkNum(value: number | string | undefined) {
|
||||||
|
if (typeof value !== 'number') {
|
||||||
|
value = filter(value, this.props.data);
|
||||||
|
value = /^[-]?\d+/.test(value) ? +value : undefined;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取步长小数精度
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
getStepPrecision() {
|
||||||
|
const {step} = this.props;
|
||||||
|
const stepIsDecimal = /^\d+\.\d+$/.test(step.toString());
|
||||||
|
return !stepIsDecimal || step < 0
|
||||||
|
? 0
|
||||||
|
: step.toString().split('.')[1]?.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理数据
|
||||||
|
* @param value input数据
|
||||||
|
* @param type min | max 双滑块
|
||||||
|
* @returns 处理之后数据
|
||||||
|
*/
|
||||||
|
getValue(value: string | number, type?: string) {
|
||||||
|
const {max, min, step, value: stateValue} = this.props as RangeItemProps;
|
||||||
|
|
||||||
|
// 校正value为step的倍数
|
||||||
|
let _value = Math.round(parseFloat(value + '') / step) * step;
|
||||||
|
// 同步value与步长小数位数
|
||||||
|
_value = parseFloat(_value.toFixed(this.getStepPrecision()));
|
||||||
|
// 单滑块只用考虑 轨道边界 ,双滑块需要考虑 两端滑块边界
|
||||||
|
switch (type) {
|
||||||
|
case 'min': {
|
||||||
|
if (isObject(stateValue) && isNumber(stateValue.max)) {
|
||||||
|
// 如果 大于当前双滑块最大值 取 当前双滑块max值 - 步长
|
||||||
|
if (_value >= stateValue.max) {
|
||||||
|
return stateValue.max - step;
|
||||||
|
}
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
return min;
|
||||||
}
|
}
|
||||||
| number
|
case 'max':
|
||||||
| string
|
if (isObject(stateValue) && isNumber(stateValue.min)) {
|
||||||
| undefined;
|
// 如果 小于当前双滑块最大值 取 当前双滑块min值 + 步长
|
||||||
minValue?: any;
|
if (_value <= stateValue.min) {
|
||||||
maxValue?: any;
|
return stateValue.min + step;
|
||||||
|
}
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
default:
|
||||||
|
// 轨道边界
|
||||||
|
return (_value < min && min) || (_value > max && max) || _value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
value,
|
||||||
|
multiple,
|
||||||
|
type,
|
||||||
|
step,
|
||||||
|
classPrefix: ns,
|
||||||
|
disabled,
|
||||||
|
max,
|
||||||
|
min
|
||||||
|
} = this.props;
|
||||||
|
const _value = multiple
|
||||||
|
? type === 'min'
|
||||||
|
? Math.min((value as MultipleValue).min, (value as MultipleValue).max)
|
||||||
|
: Math.max((value as MultipleValue).min, (value as MultipleValue).max)
|
||||||
|
: value;
|
||||||
|
return (
|
||||||
|
<div className={cx(`${ns}InputRange-input`)}>
|
||||||
|
<NumberInput
|
||||||
|
value={+_value}
|
||||||
|
step={step}
|
||||||
|
max={this.checkNum(max)}
|
||||||
|
min={this.checkNum(min)}
|
||||||
|
onChange={this.onChange}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RangeControl extends React.PureComponent<
|
export default class RangeControl extends React.PureComponent<
|
||||||
@ -123,6 +398,7 @@ export default class RangeControl extends React.PureComponent<
|
|||||||
midLabel?: HTMLSpanElement;
|
midLabel?: HTMLSpanElement;
|
||||||
|
|
||||||
static defaultProps: DefaultProps = {
|
static defaultProps: DefaultProps = {
|
||||||
|
value: 0,
|
||||||
max: 100,
|
max: 100,
|
||||||
min: 0,
|
min: 0,
|
||||||
step: 1,
|
step: 1,
|
||||||
@ -132,7 +408,10 @@ export default class RangeControl extends React.PureComponent<
|
|||||||
showInput: false,
|
showInput: false,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
joinValues: true,
|
joinValues: true,
|
||||||
delimiter: ','
|
delimiter: ',',
|
||||||
|
showSteps: false,
|
||||||
|
parts: 1,
|
||||||
|
tooltipPlacement: 'auto'
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: RangeProps) {
|
constructor(props: RangeProps) {
|
||||||
@ -146,28 +425,20 @@ export default class RangeControl extends React.PureComponent<
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
value: value,
|
value: this.getValue(value)
|
||||||
minValue: isObject(value) ? value.min : min,
|
|
||||||
maxValue: isObject(value) ? value.max : max
|
|
||||||
};
|
};
|
||||||
this.handleChange = this.handleChange.bind(this);
|
|
||||||
this.handleEnd = this.handleEnd.bind(this);
|
|
||||||
this.handleInputChange = this.handleInputChange.bind(this);
|
|
||||||
this.midLabelRef = this.midLabelRef.bind(this);
|
|
||||||
this.clearValue = this.clearValue.bind(this);
|
|
||||||
this.handleMinInputBlur = this.handleMinInputBlur.bind(this);
|
|
||||||
this.handleMaxInputBlur = this.handleMaxInputBlur.bind(this);
|
|
||||||
this.handleMinInputChange = this.handleMinInputChange.bind(this);
|
|
||||||
this.handleMaxInputChange = this.handleMaxInputChange.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.updateStyle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: RangeProps) {
|
componentDidUpdate(prevProps: RangeProps) {
|
||||||
const {value} = prevProps;
|
const {value} = prevProps;
|
||||||
const {value: nextPropsValue, multiple, delimiter, min, max} = this.props;
|
const {
|
||||||
|
value: nextPropsValue,
|
||||||
|
multiple,
|
||||||
|
delimiter,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
if (value !== nextPropsValue) {
|
if (value !== nextPropsValue) {
|
||||||
const value = formatValue(nextPropsValue, {
|
const value = formatValue(nextPropsValue, {
|
||||||
multiple,
|
multiple,
|
||||||
@ -175,312 +446,117 @@ export default class RangeControl extends React.PureComponent<
|
|||||||
min,
|
min,
|
||||||
max
|
max
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
value: value,
|
value: this.getValue(value)
|
||||||
minValue: isObject(value) ? value.min : min,
|
|
||||||
maxValue: isObject(value) ? value.max : max
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevProps.showInput !== this.props.showInput) {
|
|
||||||
this.updateStyle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStyle() {
|
|
||||||
const {showInput, classPrefix: ns, max, min} = this.props;
|
|
||||||
|
|
||||||
let offsetWidth = (this.midLabel as HTMLSpanElement).offsetWidth;
|
|
||||||
const midValue = parseFloat(
|
|
||||||
((max! + min! - 0.000001) / 2).toFixed(this.getStepPrecision())
|
|
||||||
);
|
|
||||||
|
|
||||||
let left = `${100 * ((midValue - min!) / (max! - min!))}%`;
|
|
||||||
(this.midLabel as HTMLSpanElement).style.left = left;
|
|
||||||
}
|
|
||||||
|
|
||||||
midLabelRef(ref: any) {
|
|
||||||
this.midLabel = ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange(value: any) {
|
|
||||||
this.setState({
|
|
||||||
value: stripNumber(value),
|
|
||||||
minValue: value.min,
|
|
||||||
maxValue: value.max
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
clearValue() {
|
clearValue() {
|
||||||
const {multiple, joinValues, delimiter, min, max, onChange} = this.props;
|
const {multiple, min, max} = this.props;
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
this.setState(
|
this.updateValue({min, max});
|
||||||
{
|
|
||||||
value: {
|
|
||||||
min: min,
|
|
||||||
max: max
|
|
||||||
},
|
|
||||||
minValue: min,
|
|
||||||
maxValue: max
|
|
||||||
},
|
|
||||||
() =>
|
|
||||||
onChange(
|
|
||||||
joinValues
|
|
||||||
? [min, max].join(delimiter || ',')
|
|
||||||
: {
|
|
||||||
min: min,
|
|
||||||
max: max
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.setState(
|
this.updateValue(min);
|
||||||
{
|
|
||||||
value: min
|
|
||||||
},
|
|
||||||
() => onChange(min)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEnd(value: any) {
|
@autobind
|
||||||
const {multiple, joinValues, delimiter} = this.props;
|
getValue(value: FormatValue) {
|
||||||
let endValue = value;
|
const {multiple} = this.props;
|
||||||
if (multiple) {
|
return multiple
|
||||||
endValue = joinValues
|
? {
|
||||||
? [value.min, value.max].join(delimiter || ',')
|
max: stripNumber((value as MultipleValue).max),
|
||||||
: {
|
min: stripNumber((value as MultipleValue).min)
|
||||||
min: value.min,
|
|
||||||
max: value.max
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
endValue = stripNumber(value);
|
|
||||||
}
|
|
||||||
const {onChange} = this.props;
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
value
|
|
||||||
},
|
|
||||||
() => onChange(endValue)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getStepPrecision() {
|
|
||||||
const {step} = this.props;
|
|
||||||
|
|
||||||
return typeof step !== 'number' || step >= 1 || step < 0
|
|
||||||
? 0
|
|
||||||
: step.toString().split('.')[1]?.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue(value: any, type?: string) {
|
|
||||||
const {max, min, step} = this.props as PropsWithDefaults;
|
|
||||||
const {value: stateValue} = this.state;
|
|
||||||
|
|
||||||
if (
|
|
||||||
value === '' ||
|
|
||||||
value === '-' ||
|
|
||||||
new RegExp('^[-]?\\d+[.]{1}[0]{0,' + this.getStepPrecision() + '}$').test(
|
|
||||||
value
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = Math.round(parseFloat(value) / step) * step;
|
|
||||||
value =
|
|
||||||
step < 1 ? parseFloat(value.toFixed(this.getStepPrecision())) : ~~value;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'min': {
|
|
||||||
if (isObject(stateValue) && isNumber(stateValue.max)) {
|
|
||||||
if (value >= stateValue.max && min <= stateValue.max - step) {
|
|
||||||
return stateValue.max - step;
|
|
||||||
}
|
|
||||||
if (value < stateValue.max - step) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return min;
|
: stripNumber(value as number);
|
||||||
}
|
|
||||||
case 'max':
|
|
||||||
return isObject(stateValue) && isNumber(stateValue.min)
|
|
||||||
? (value > max && max) ||
|
|
||||||
(value <= stateValue.min && stateValue.min + step) ||
|
|
||||||
value
|
|
||||||
: max;
|
|
||||||
default:
|
|
||||||
return (value < min && min) || (value > max && max) || value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
|
/**
|
||||||
const value = this.getValue(evt.target.value);
|
* 所有触发value变换 -> updateValue
|
||||||
this.setState(
|
* @param value
|
||||||
{
|
*/
|
||||||
value
|
@autobind
|
||||||
},
|
updateValue(value: FormatValue) {
|
||||||
() => this.props.onChange(value)
|
this.setState({value: this.getValue(value)});
|
||||||
|
const {multiple, joinValues, delimiter, onChange} = this.props;
|
||||||
|
onChange(
|
||||||
|
multiple
|
||||||
|
? joinValues
|
||||||
|
? [(value as MultipleValue).min, (value as MultipleValue).max].join(
|
||||||
|
delimiter || ','
|
||||||
|
)
|
||||||
|
: {
|
||||||
|
min: (value as MultipleValue).min,
|
||||||
|
max: (value as MultipleValue).max
|
||||||
|
}
|
||||||
|
: value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMinInputBlur(evt: React.ChangeEvent<HTMLInputElement>) {
|
/**
|
||||||
const {joinValues, delimiter} = this.props;
|
* 鼠标松开事件
|
||||||
const minValue = this.getValue(evt.target.value, 'min');
|
*/
|
||||||
|
@autobind
|
||||||
|
onAfterChange() {
|
||||||
const {value} = this.state;
|
const {value} = this.state;
|
||||||
isObject(value)
|
const {multiple, joinValues, delimiter, onAfterChange} = this.props;
|
||||||
? this.setState(
|
onAfterChange &&
|
||||||
{
|
onAfterChange(
|
||||||
value: {
|
multiple
|
||||||
min: minValue,
|
? joinValues
|
||||||
max: value.max
|
? [(value as MultipleValue).min, (value as MultipleValue).max].join(
|
||||||
},
|
delimiter || ','
|
||||||
minValue: minValue
|
)
|
||||||
},
|
: {
|
||||||
() =>
|
min: (value as MultipleValue).min,
|
||||||
this.props.onChange(
|
max: (value as MultipleValue).max
|
||||||
joinValues
|
}
|
||||||
? [minValue, value.max].join(delimiter || ',')
|
: value
|
||||||
: {
|
|
||||||
min: minValue,
|
|
||||||
max: value.max
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMaxInputBlur(evt: React.ChangeEvent<HTMLInputElement>) {
|
|
||||||
const {joinValues, delimiter} = this.props;
|
|
||||||
const maxValue = this.getValue(evt.target.value, 'max');
|
|
||||||
const {value} = this.state;
|
|
||||||
|
|
||||||
if (isObject(value)) {
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
value: {
|
|
||||||
min: value.min,
|
|
||||||
max: maxValue
|
|
||||||
},
|
|
||||||
maxValue: maxValue
|
|
||||||
},
|
|
||||||
() =>
|
|
||||||
this.props.onChange(
|
|
||||||
joinValues
|
|
||||||
? [value.min, maxValue].join(delimiter || ',')
|
|
||||||
: {
|
|
||||||
min: value.min,
|
|
||||||
max: maxValue
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMinInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
|
|
||||||
this.setState({
|
|
||||||
minValue: evt.target.value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMaxInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
|
|
||||||
this.setState({
|
|
||||||
maxValue: evt.target.value
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {value} = this.state;
|
||||||
|
const props: RangeItemProps = {
|
||||||
|
...this.props,
|
||||||
|
value,
|
||||||
|
updateValue: this.updateValue,
|
||||||
|
onAfterChange: this.onAfterChange
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
max,
|
classPrefix: ns,
|
||||||
min,
|
|
||||||
step,
|
|
||||||
unit,
|
|
||||||
clearable,
|
|
||||||
name,
|
|
||||||
disabled,
|
|
||||||
className,
|
|
||||||
showInput,
|
|
||||||
multiple,
|
multiple,
|
||||||
|
parts,
|
||||||
|
showInput,
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
classPrefix: ns
|
className,
|
||||||
} = this.props as PropsWithDefaults;
|
disabled,
|
||||||
const midValue = ((max + min - 0.000001) / 2).toFixed(
|
clearable,
|
||||||
this.getStepPrecision()
|
min,
|
||||||
);
|
max
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
// 指定parts -> 重新计算步长
|
||||||
|
if (parts > 1) {
|
||||||
|
props.step = (props.max - props.min) / props.parts;
|
||||||
|
props.showSteps = true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'RangeControl',
|
'RangeControl',
|
||||||
{
|
`${ns}InputRange`,
|
||||||
'RangeControl--withInput': showInput,
|
{'is-disabled': disabled},
|
||||||
'RangeControl--clearable': clearable,
|
|
||||||
'is-multiple': multiple
|
|
||||||
},
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<InputRange
|
{showInput && multiple && <Input {...props} type="min" />}
|
||||||
classPrefix={ns}
|
<InputRange {...props} />
|
||||||
value={this.state.value}
|
{showInput && <Input {...props} type="max" />}
|
||||||
disabled={disabled}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
onChangeComplete={this.handleEnd}
|
|
||||||
max={max}
|
|
||||||
min={min}
|
|
||||||
step={step}
|
|
||||||
formatLabel={(value: any) => value + unit}
|
|
||||||
multiple={multiple}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span
|
|
||||||
className={cx('InputRange-label InputRange-label--mid')}
|
|
||||||
ref={this.midLabelRef}
|
|
||||||
>
|
|
||||||
<span className={cx('InputRange-labelContainer')}>
|
|
||||||
{midValue}
|
|
||||||
{unit}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{showInput ? (
|
|
||||||
multiple && isObject(this.state.value) ? (
|
|
||||||
<div className={cx('InputRange-input is-multiple')}>
|
|
||||||
<input
|
|
||||||
className={this.state.value.min !== min ? 'is-active' : ''}
|
|
||||||
type="text"
|
|
||||||
name={name}
|
|
||||||
value={this.state.minValue}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={this.handleMinInputChange}
|
|
||||||
onBlur={this.handleMinInputBlur}
|
|
||||||
/>
|
|
||||||
<span className={cx('InputRange-input-separator')}> - </span>
|
|
||||||
<input
|
|
||||||
className={this.state.value.max !== max ? 'is-active' : ''}
|
|
||||||
type="text"
|
|
||||||
name={name}
|
|
||||||
value={this.state.maxValue}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={this.handleMaxInputChange}
|
|
||||||
onBlur={this.handleMaxInputBlur}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={cx('InputRange-input')}>
|
|
||||||
<input
|
|
||||||
className={this.state.value !== min ? 'is-active' : ''}
|
|
||||||
type="text"
|
|
||||||
name={name}
|
|
||||||
value={!isObject(this.state.value) ? this.state.value : 0}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={this.handleInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{clearable && !disabled && showInput ? (
|
{clearable && !disabled && showInput ? (
|
||||||
<a
|
<a
|
||||||
onClick={() => this.clearValue()}
|
onClick={() => this.clearValue()}
|
||||||
|
Loading…
Reference in New Issue
Block a user