select multiple 增加全选模式

select支持全选,range支持范围选择
This commit is contained in:
catchonme 2019-05-19 11:45:40 +08:00
parent f8f299f1e2
commit 3c613d9646
9 changed files with 431 additions and 112 deletions

View File

@ -6,6 +6,9 @@
- `min` 最小值 - `min` 最小值
- `max` 最大值 - `max` 最大值
- `step` 步长 - `step` 步长
- `multiple` 支持选择范围,默认为`false`
- `joinValuse` 默认为 `true`,选择的 `value` 会通过 `delimiter` 连接起来,否则直接将以`{min: 1, max: 100}`的形式提交,开启`multiple`时有效
- `delimiter` 默认为 `,`
```schema:height="400" scope="form-item" ```schema:height="400" scope="form-item"
{ {

View File

@ -16,6 +16,9 @@
- `delimiter` 默认为 `,` - `delimiter` 默认为 `,`
- `clearable` 默认为 `false`, 当设置为 `true` 时,已选中的选项右侧会有个小 `X` 用来取消设置。 - `clearable` 默认为 `false`, 当设置为 `true` 时,已选中的选项右侧会有个小 `X` 用来取消设置。
- `searchable` 默认为 `false`,表示可以通过输入部分内容检索出选项。 - `searchable` 默认为 `false`,表示可以通过输入部分内容检索出选项。
- `checkall` 默认为 `false` 开启后支持全选
- `checkAllLabel` 默认为 `全选`, 全选的文字
- `defaultCheckAll` 是否默认全选,默认为`false`
- 更多配置请参考 [FormItem](./FormItem.md) - 更多配置请参考 [FormItem](./FormItem.md)
单选 单选

View File

@ -675,6 +675,7 @@ $Form-select-menu-onActive-color: $info !default;
$Form-select-menu-onActive-bg: transparent !default; $Form-select-menu-onActive-bg: transparent !default;
$Form-select-menu-onDisabled-color: $text--muted-color !default; $Form-select-menu-onDisabled-color: $text--muted-color !default;
$Form-select-menu-onDisabled-bg: transparent !default; $Form-select-menu-onDisabled-bg: transparent !default;
$Form-select-checkall-bottomBorder: #eceff8 !default;
// InputGroup // InputGroup
$InputGroup-height: $Form-input-height !default; $InputGroup-height: $Form-input-height !default;

View File

@ -13,12 +13,17 @@
.#{$ns}InputRange-label--mid { .#{$ns}InputRange-label--mid {
left: calc(50% - 60px); left: calc(50% - 60px);
} }
&.is-multiple {
.#{$ns}InputRange {
width: calc(100% - 210px);
}
}
} }
.#{$ns}InputRange { .#{$ns}InputRange {
&-input { &-input {
font-size: $fontSizeSm; font-size: $fontSizeSm;
width: px2rem(74px);
position: absolute; position: absolute;
right: px2rem(26px); right: px2rem(26px);
top: px2rem(12px); top: px2rem(12px);
@ -26,7 +31,7 @@
input { input {
padding: px2rem(10px); padding: px2rem(10px);
width: 100%; width: px2rem(74px);
height: 100%; height: 100%;
&:focus { &:focus {
@ -34,6 +39,11 @@
border: $borderWidth solid $info; border: $borderWidth solid $info;
} }
} }
&-separator {
display: inline-block;
padding: 0 5px;
}
} }
&-unit { &-unit {
@ -167,6 +177,10 @@
.#{$ns}InputRange.is-disabled & { .#{$ns}InputRange.is-disabled & {
background: $InputRange-track-onDisabled-bg; background: $InputRange-track-onDisabled-bg;
} }
&.is-active {
background: $InputRange-track-onActive-bg;
}
} }
&-track--background { &-track--background {

View File

@ -156,7 +156,17 @@
user-select: none; user-select: none;
} }
&-checkall {
padding: (
$Form-select-menu-height - $Form-input-lineHeight *
$Form-input-fontSize - px2rem(2px)
)/2 $Form-select-paddingX;
border-bottom: px2rem(1px) solid $Form-select-checkall-bottomBorder;
min-width: px2rem(100px);
}
&-option { &-option {
min-width: px2rem(100px);
padding: ( padding: (
$Form-select-menu-height - $Form-input-lineHeight * $Form-select-menu-height - $Form-input-lineHeight *
$Form-input-fontSize - px2rem(2px) $Form-input-fontSize - px2rem(2px)
@ -177,6 +187,10 @@
background-color: $Form-select-menu-onDisabled-bg; background-color: $Form-select-menu-onDisabled-bg;
} }
&.is-checkAll {
border-bottom: px2rem(1px) solid $Form-select-checkall-bottomBorder;
}
&--placeholder { &--placeholder {
color: $Form-input-placeholderColor; color: $Form-input-placeholderColor;
} }

View File

@ -1,3 +1,4 @@
/** /**
* @file Checkbox * @file Checkbox
* @author fex * @author fex
@ -6,6 +7,7 @@
import * as React from 'react'; import * as React from 'react';
import * as cx from 'classnames'; import * as cx from 'classnames';
import {ClassNamesFn, themeable} from '../theme'; import {ClassNamesFn, themeable} from '../theme';
import { autobind } from '../utils/helper';
const sizeMap = { const sizeMap = {
sm: 'i-checks-sm', sm: 'i-checks-sm',
@ -23,6 +25,7 @@ interface CheckboxProps {
label?: string; label?: string;
className?: string; className?: string;
onChange?: (value: any) => void; onChange?: (value: any) => void;
onClick?: (e:any, checked:boolean) => void;
value?: any; value?: any;
containerClass?: string; containerClass?: string;
inline?: boolean; inline?: boolean;
@ -44,13 +47,8 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
type: 'checkbox', type: 'checkbox',
}; };
constructor(props: CheckboxProps) { @autobind
super(props); handleCheck(e: React.ChangeEvent<any>) {
this.hanldeCheck = this.hanldeCheck.bind(this);
}
hanldeCheck(e: React.ChangeEvent<any>) {
const {trueValue, falseValue, onChange} = this.props; const {trueValue, falseValue, onChange} = this.props;
if (!onChange) { if (!onChange) {
@ -60,6 +58,25 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
onChange(e.currentTarget.checked ? trueValue : falseValue); onChange(e.currentTarget.checked ? trueValue : falseValue);
} }
@autobind
handleClick(e:any) {
const {
checked,
value,
trueValue,
onClick,
disabled
} = this.props;
const isChecked:boolean = !!(typeof checked !== 'undefined'
? checked
: typeof value === 'undefined'
? value
: value == trueValue);
disabled ? null : onClick && onClick(e, isChecked);
}
render() { render() {
let { let {
size, size,
@ -88,6 +105,7 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
}, },
className className
)} )}
onClick={this.handleClick}
> >
<input <input
type={type} type={type}
@ -98,7 +116,7 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
? value ? value
: value == trueValue : value == trueValue
} }
onChange={this.hanldeCheck} onChange={this.handleCheck}
disabled={disabled} disabled={disabled}
readOnly={readOnly} readOnly={readOnly}
name={name} name={name}
@ -110,4 +128,4 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
} }
} }
export default themeable(Checkbox); export default themeable(Checkbox);

View File

@ -16,7 +16,10 @@ interface RangeProps extends RendererProps {
className?: string; className?: string;
min: number; min: number;
max: number; max: number;
value?: number; value: {
min: number,
max: number
} | number;
classPrefix: string; classPrefix: string;
classnames: ClassNamesFn; classnames: ClassNamesFn;
} }
@ -28,10 +31,10 @@ export class Range extends React.Component<RangeProps, any> {
}; };
render() { render() {
const {min, max, value, className, classPrefix: ns} = this.props; const {min, max, value, className, classPrefix: ns, multiple} = this.props;
const classNames = { const classNames = {
activeTrack: `${ns}InputRange-track is-active`, activeTrack: multiple ? `${ns}InputRange-track is-active` : `${ns}InputRange-track`,
disabledInputRange: `${ns}InputRange is-disabled`, disabledInputRange: `${ns}InputRange is-disabled`,
inputRange: `${ns}InputRange`, inputRange: `${ns}InputRange`,
labelContainer: `${ns}InputRange-labelContainer`, labelContainer: `${ns}InputRange-labelContainer`,
@ -50,7 +53,8 @@ export class Range extends React.Component<RangeProps, any> {
classNames={classNames} classNames={classNames}
minValue={min} minValue={min}
maxValue={max} maxValue={max}
value={typeof value === 'number' ? value : min} value={value}
multiple={multiple}
/> />
); );
} }

View File

@ -14,12 +14,14 @@ import Downshift, {ControllerStateAndHelpers} from 'downshift';
import * as cx from 'classnames'; import * as cx from 'classnames';
import {closeIcon} from './icons'; import {closeIcon} from './icons';
import * as matchSorter from 'match-sorter'; import * as matchSorter from 'match-sorter';
import {noop, anyChanged} from '../utils/helper'; import {noop} from '../utils/helper';
import find = require('lodash/find'); import find = require('lodash/find');
import isPlainObject = require('lodash/isPlainObject'); import isPlainObject = require('lodash/isPlainObject');
import union = require('lodash/union');
import {highlight} from '../renderers/Form/Options'; import {highlight} from '../renderers/Form/Options';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import {ClassNamesFn, themeable} from '../theme'; import {ClassNamesFn, themeable} from '../theme';
import Checkbox from './Checkbox';
export interface Option { export interface Option {
label?: string; label?: string;
@ -64,7 +66,7 @@ export function value2array(value: OptionValue | Array<OptionValue>, props: Part
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
value = value[0]; value = value[0];
} }
let expandedValue = expandValue(value as OptionValue, props); let expandedValue = expandValue(value as OptionValue, props);
return expandedValue ? [expandedValue] : []; return expandedValue ? [expandedValue] : [];
} }
@ -148,6 +150,9 @@ interface SelectProps {
onNewOptionClick: (value: Option) => void; onNewOptionClick: (value: Option) => void;
onFocus?: Function; onFocus?: Function;
onBlur?: Function; onBlur?: Function;
checkAll?: boolean;
checkAllLabel?: string;
defaultCheckAll?: boolean;
} }
interface SelectState { interface SelectState {
@ -156,6 +161,8 @@ interface SelectState {
inputValue: string; inputValue: string;
highlightedIndex: number; highlightedIndex: number;
selection: Array<Option>; selection: Array<Option>;
checkedAll: boolean;
checkedPartial: boolean;
} }
export class Select extends React.Component<SelectProps, SelectState> { export class Select extends React.Component<SelectProps, SelectState> {
@ -176,6 +183,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
onNewOptionClick: noop, onNewOptionClick: noop,
inline: false, inline: false,
disabled: false, disabled: false,
checkAll: false,
checkAllLabel: '全选',
defaultCheckAll: false
}; };
input: HTMLInputElement; input: HTMLInputElement;
@ -196,6 +206,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
this.handleStateChange = this.handleStateChange.bind(this); this.handleStateChange = this.handleStateChange.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this);
this.getTarget = this.getTarget.bind(this); this.getTarget = this.getTarget.bind(this);
this.toggleCheckAll = this.toggleCheckAll.bind(this);
this.state = { this.state = {
isOpen: false, isOpen: false,
@ -203,11 +214,34 @@ export class Select extends React.Component<SelectProps, SelectState> {
inputValue: '', inputValue: '',
highlightedIndex: -1, highlightedIndex: -1,
selection: value2array(props.value, props), selection: value2array(props.value, props),
checkedAll: false,
checkedPartial: false
}; };
} }
componentDidMount() { componentDidMount() {
const {loadOptions} = this.props; const {loadOptions, options, checkAll, defaultCheckAll} = this.props;
let {selection} = this.state;
if (checkAll && options.length) {
let checkedAll = false;
if (selection.length) {
const optionsValues = options.map(option => option.value);
const selectionValues = selection.map(select => select.value);
checkedAll = optionsValues.every(option => selectionValues.indexOf(option) > -1);
}
if (defaultCheckAll) {
checkedAll = true;
selection = union(options, selection);
}
if (checkedAll) {
this.setState({
checkedAll: true,
checkedPartial: true,
selection: selection
});
}
}
loadOptions && loadOptions(''); loadOptions && loadOptions('');
} }
@ -282,13 +316,44 @@ export class Select extends React.Component<SelectProps, SelectState> {
this.input = ref; this.input = ref;
} }
toggleCheckAll() {
let {options, onChange} = this.props;
const {checkedAll} = this.state;
if (!checkedAll) {
this.setState({
selection: options,
checkedAll: true,
checkedPartial: true,
isOpen: true
});
onChange(options);
} else {
this.setState({
selection: [],
checkedAll: false,
checkedPartial: false,
isOpen: true
});
onChange([]);
}
}
removeItem(index: number, e?: React.MouseEvent<HTMLElement>) { removeItem(index: number, e?: React.MouseEvent<HTMLElement>) {
let value = this.props.value; const {onChange, options, checkAll} = this.props;
const onChange = this.props.onChange; let {selection: value} = this.state;
e && e.stopPropagation(); e && e.stopPropagation();
value = Array.isArray(value) ? value.concat() : [value]; value = Array.isArray(value) ? value.concat() : [value];
value.splice(index, 1); value.splice(index, 1);
if (checkAll && (!value.length || value.length < options.length)) {
const checkedPartial = value.some((item:Option) => options.indexOf(item) > -1);
this.setState({
checkedAll: false,
checkedPartial: checkedPartial
});
}
onChange(value); onChange(value);
} }
@ -304,9 +369,8 @@ export class Select extends React.Component<SelectProps, SelectState> {
} }
handleChange(selectItem: any) { handleChange(selectItem: any) {
const {onChange, multiple, onNewOptionClick} = this.props; const {onChange, multiple, onNewOptionClick, options, checkAll} = this.props;
let {checkedAll, selection, checkedPartial} = this.state;
let selection = this.state.selection;
if (selectItem.isNew) { if (selectItem.isNew) {
delete selectItem.isNew; delete selectItem.isNew;
@ -314,20 +378,46 @@ export class Select extends React.Component<SelectProps, SelectState> {
} }
if (multiple) { if (multiple) {
selection = selection.concat(); if (checkAll) {
const idx = selection.indexOf(selectItem); if (selectItem.__all) {
if (~idx) { this.toggleCheckAll();
selection.splice(idx, 1); } else {
selection = selection.concat();
const idx = selection.indexOf(selectItem);
if (~idx) {
selection.splice(idx, 1);
} else {
selection.push(selectItem);
}
onChange(selection);
const optionsValues = options.map(option => option.value);
const selectionValues = selection.map(select => select.value);
checkedAll = optionsValues.every(option => selectionValues.indexOf(option) > -1);
checkedPartial = optionsValues.some(option => selectionValues.indexOf(option) > -1);
this.setState({
checkedAll,
checkedPartial
});
}
} else { } else {
selection.push(selectItem); selection = selection.concat();
const idx = selection.indexOf(selectItem);
if (~idx) {
selection.splice(idx, 1);
} else {
selection.push(selectItem);
}
onChange(selection);
} }
onChange(selection);
} else { } else {
onChange(selectItem); onChange(selectItem);
} }
} }
handleStateChange(changes: any) { handleStateChange(changes: any) {
const {multiple, checkAll} = this.props;
let update: any = {}; let update: any = {};
const loadOptions = this.props.loadOptions; const loadOptions = this.props.loadOptions;
let doLoad = false; let doLoad = false;
@ -346,8 +436,8 @@ export class Select extends React.Component<SelectProps, SelectState> {
update = { update = {
...update, ...update,
inputValue: '', inputValue: '',
isOpen: false, isOpen: multiple && checkAll ? true : false,
isFocused: false, isFocused: multiple && checkAll ? true : false
}; };
doLoad = true; doLoad = true;
break; break;
@ -419,7 +509,10 @@ export class Select extends React.Component<SelectProps, SelectState> {
promptTextCreator, promptTextCreator,
multiple, multiple,
classnames: cx, classnames: cx,
checkAll,
checkAllLabel,
} = this.props; } = this.props;
const {selection, checkedAll, checkedPartial} = this.state;
let filtedOptions: Array<Option> = let filtedOptions: Array<Option> =
inputValue && isOpen && !loadOptions inputValue && isOpen && !loadOptions
@ -429,7 +522,11 @@ export class Select extends React.Component<SelectProps, SelectState> {
: options.concat(); : options.concat();
if (multiple) { if (multiple) {
filtedOptions = filtedOptions.filter((option: any) => !~selectedItem.indexOf(option)); if (checkAll) {
filtedOptions.unshift({label: checkAllLabel, value: 'all', __all: true});
} else {
filtedOptions = filtedOptions.filter((option: any) => !~selectedItem.indexOf(option));
}
} }
if ( if (
@ -452,29 +549,54 @@ export class Select extends React.Component<SelectProps, SelectState> {
const menu = ( const menu = (
<div className={cx('Select-menu')}> <div className={cx('Select-menu')}>
{filtedOptions.length ? ( {filtedOptions.length ? (
filtedOptions.map((item, index) => ( filtedOptions.map((item, index) => {
<div const checked = checkAll ? selection.some((o:Option) => o.value == item.value) : false;
{...getItemProps({
key: index, return (
index, <div
item, {...getItemProps({
disabled: item.disabled, key: index,
})} index,
className={cx(`Select-option`, { item,
'is-disabled': item.disabled, disabled: item.disabled,
'is-highlight': highlightedIndex === index, })}
'is-active': className={cx(`Select-option`, {
selectedItem === item || 'is-disabled': item.disabled,
(Array.isArray(selectedItem) && ~selectedItem.indexOf(item)), 'is-highlight': highlightedIndex === index,
})} 'is-checkAll': checkAll && index === 0,
> 'is-active':
{item.isNew selectedItem === item ||
? promptTextCreator(item.label as string) (Array.isArray(selectedItem) && ~selectedItem.indexOf(item)),
: item.disabled })}
? item[labelField] >
: highlight(item[labelField], inputValue as string, cx('Select-option-hl'))} {checkAll ?
</div> index === 0 ? (
)) <Checkbox
checked={checkedPartial}
partial={checkedPartial && !checkedAll}
>
{checkAllLabel}
</Checkbox>
) : (
<Checkbox
checked={checked}
trueValue={item.value}
>
{item.isNew
? promptTextCreator(item.label as string)
: item.disabled
? item[labelField]
: highlight(item[labelField], inputValue as string, cx('Select-option-hl'))}
</Checkbox>
) : (
item.isNew
? promptTextCreator(item.label as string)
: item.disabled
? item[labelField]
: highlight(item[labelField], inputValue as string, cx('Select-option-hl'))
)}
</div>
)})
) : ( ) : (
<div className={cx('Select-option Select-option--placeholder')}>{noResultsText}</div> <div className={cx('Select-option Select-option--placeholder')}>{noResultsText}</div>
)} )}

View File

@ -1,4 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import isNumber = require('lodash/isNumber');
import isObject = require('lodash/isObject');
import isEqual = require('lodash/isEqual');
import { import {
FormItem, FormItem,
FormControlProps FormControlProps
@ -17,7 +20,11 @@ export interface RangeProps extends FormControlProps {
disabled?: boolean; disabled?: boolean;
showInput?: boolean; showInput?: boolean;
className?: string; className?: string;
onChange: (value: number) => void; value: any;
onChange: (value: any) => void;
multiple?: boolean;
joinValues?: boolean;
delimiter?: string;
}; };
export interface DefaultProps { export interface DefaultProps {
@ -28,12 +35,38 @@ export interface DefaultProps {
clearable: boolean; clearable: boolean;
disabled: boolean; disabled: boolean;
showInput: boolean; showInput: boolean;
multiple: boolean;
joinValues: boolean;
delimiter: string;
}; };
export function formatValue(value: string | number | {min: number, max: number}, props: Partial<RangeProps>) {
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; type PropsWithDefaults = RangeProps & DefaultProps;
export interface RangeState { export interface RangeState {
value: any; value: {
min?: number,
max?: number
} | number | string | undefined;
minValue?: any;
maxValue?: any;
}; };
export default class RangeControl extends React.PureComponent<RangeProps, RangeState> { export default class RangeControl extends React.PureComponent<RangeProps, RangeState> {
@ -46,27 +79,53 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
unit: '', unit: '',
clearable: true, clearable: true,
disabled: false, disabled: false,
showInput: false showInput: false,
multiple: false,
joinValues: true,
delimiter: ','
} }
constructor(props: RangeProps) { constructor(props: RangeProps) {
super(props); super(props);
this.state = { const {value: propsValue, multiple, delimiter, min, max} = this.props;
value: parseFloat(props.value) || 0 const value = formatValue(propsValue, {
}; multiple,
delimiter,
min,
max
});
this.state = {
value: value,
minValue: isObject(value) ? value.min : min,
maxValue: isObject(value) ? value.max : max
};
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.handleEnd = this.handleEnd.bind(this); this.handleEnd = this.handleEnd.bind(this);
this.handleInputChange = this.handleInputChange.bind(this); this.handleInputChange = this.handleInputChange.bind(this);
this.rangeValue = this.rangeValue.bind(this);
this.midLabelRef = this.midLabelRef.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);
} }
componentWillReceiveProps(nextProps: RangeProps) { componentWillReceiveProps(nextProps: RangeProps) {
const { value } = this.props; const {value} = this.props;
if (value !== nextProps.value) { const {value: nextPropsValue, multiple, delimiter, min, max} = nextProps;
if (value !== nextPropsValue) {
const value = formatValue(nextPropsValue, {
multiple,
delimiter,
min,
max
});
this.setState({ this.setState({
value: parseFloat(nextProps.value) || 0 value: value,
minValue: isObject(value) ? value.min : min,
maxValue: isObject(value) ? value.max : max
}); });
} }
} }
@ -102,17 +161,42 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
handleChange(value: any) { handleChange(value: any) {
this.setState({ this.setState({
value: this.getValue(value) value: value,
minValue: value.min,
maxValue: value.max
}); });
} }
clearValue() {
const {multiple, min, max} = this.props;
if (multiple) {
this.setState({
value: {
min: min,
max: max
},
minValue: min,
maxValue: max
});
} else {
this.setState({
value: min
});
}
}
handleEnd(value: any) { handleEnd(value: any) {
const {multiple, joinValues, delimiter} = this.props;
let endValue = value;
if (multiple && joinValues) {
endValue = [value.min, value.max].join(delimiter || ',');
}
const { const {
onChange onChange
} = this.props; } = this.props;
this.setState({ this.setState({
value value
}, () => onChange(value)); }, () => onChange(endValue));
} }
getStepPrecision() { getStepPrecision() {
@ -125,25 +209,39 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
: step.toString().split(".")[1].length; : step.toString().split(".")[1].length;
} }
getValue(value: any) { getValue(value: any, type?: string) {
const { const {
max, max,
min, min,
step step
} = this.props as PropsWithDefaults; } = this.props as PropsWithDefaults;
const {value: stateValue} = this.state;
if (value === '' || value === '-' || new RegExp('^[-]?\\d+[.]{1}[0]{0,' + this.getStepPrecision() + '}$').test(value)) { if (value === '' || value === '-' || new RegExp('^[-]?\\d+[.]{1}[0]{0,' + this.getStepPrecision() + '}$').test(value)) {
return value; return value;
} }
// if (!value || value === '00') {
// return min;
// }
value = Math.round(parseFloat(value) / step) * step; value = Math.round(parseFloat(value) / step) * step;
value = (step < 1) ? parseFloat(value.toFixed(this.getStepPrecision())) : ~~value; value = (step < 1) ? parseFloat(value.toFixed(this.getStepPrecision())) : ~~value;
return (value < min && min) || (value > max && max) || 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;
}
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>) { handleInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
@ -152,12 +250,44 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
}); });
} }
rangeValue() { handleMinInputBlur(evt: React.ChangeEvent<HTMLInputElement>) {
let { value } = this.state; const minValue = this.getValue(evt.target.value, 'min');
// if (/^\d+[.]{1}$/.test(value)) { const {value} = this.state;
// return this.props.min; isObject(value) ?
// } this.setState({
return parseFloat(value); value: {
min: minValue,
max: value.max
},
minValue: minValue
})
: null;
}
handleMaxInputBlur(evt: React.ChangeEvent<HTMLInputElement>) {
const maxValue = this.getValue(evt.target.value, 'max');
const {value} = this.state;
isObject(value) ?
this.setState({
value: {
min: value.min,
max: maxValue
},
maxValue: maxValue
})
: null;
}
handleMinInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
this.setState({
minValue: evt.target.value
});
}
handleMaxInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
this.setState({
maxValue: evt.target.value
});
} }
render() { render() {
@ -170,8 +300,8 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
name, name,
disabled, disabled,
className, className,
onChange,
showInput, showInput,
multiple,
classnames: cx, classnames: cx,
classPrefix: ns, classPrefix: ns,
} = this.props as PropsWithDefaults; } = this.props as PropsWithDefaults;
@ -180,10 +310,11 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
<div className={cx("RangeControl", { <div className={cx("RangeControl", {
'RangeControl--withInput': showInput, 'RangeControl--withInput': showInput,
'RangeControl--clearable': clearable, 'RangeControl--clearable': clearable,
'is-multiple': multiple
}, className)}> }, className)}>
<InputRange <InputRange
classPrefix={ns} classPrefix={ns}
value={this.rangeValue()} value={this.state.value}
disabled={disabled} disabled={disabled}
onChange={this.handleChange} onChange={this.handleChange}
onChangeComplete={this.handleEnd} onChangeComplete={this.handleEnd}
@ -191,6 +322,7 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
min={min} min={min}
step={step} step={step}
formatLabel={(value: any) => value + unit} formatLabel={(value: any) => value + unit}
multiple={multiple}
/> />
<span className={cx("InputRange-label InputRange-label--mid")} ref={this.midLabelRef}> <span className={cx("InputRange-label InputRange-label--mid")} ref={this.midLabelRef}>
@ -199,38 +331,46 @@ export default class RangeControl extends React.PureComponent<RangeProps, RangeS
</span> </span>
</span> </span>
{showInput ? ( {showInput ?
<div className={cx("InputRange-input")}> multiple && isObject(this.state.value) ? (
<input <div className={cx("InputRange-input is-multiple")}>
className={this.state.value !== min ? 'is-active' : ''} <input
type="text" className={this.state.value.min !== min ? 'is-active' : ''}
name={name} type="text"
value={this.state.value} name={name}
disabled={disabled} value={this.state.minValue}
onChange={this.handleInputChange} disabled={disabled}
/> onChange={this.handleMinInputChange}
{/* <span onBlur={this.handleMinInputBlur}
className={cx("InputRange-unit", this.state.value !== min ? 'is-active' : '')} />
> <span className={cx("InputRange-input-separator")}> - </span>
{unit} <input
</span> */} className={this.state.value.max !== max ? 'is-active' : ''}
</div> type="text"
) : null} 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 && this.sliderValue() ? ( {clearable && showInput ? (
<span <a onClick={() => this.clearValue()} className={cx("InputRange-clear", {
className={cx('icon icon-clear', { 'is-active': (multiple ? isEqual(this.state.value, {min: min, max: max}) : this.state.value !== min)
'active': (this.state.value !== min)
})}
onClick={() => this.handleChange(0)}
>
<i className="iconfont icon-x"></i>
</span>
) : null} */}
{clearable && this.rangeValue() !== min && showInput ? (
<a onClick={() => this.handleChange(min)} className={cx("InputRange-clear", {
'is-active': (this.state.value !== min)
})}>{closeIcon}</a> })}>{closeIcon}</a>
) : null} ) : null}
</div > </div >