mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Input controls vis] replace react-input-range with EuiDualRange (#31119)
* [Input controls vis] replace react-input-range with EuiDualRange * remove unneeded styles * correctly format I18n with variables * fix problems with long decimals * fix one test case * clean-up error message text, remove extra commas in jest test * fix bug where values were returned as strings * use i18n.translate instead of passing formatMessage into isRangeValid * assert error message in isRangeValid unit test * changes from node scripts/i18n_check --fix * remove localization from RangeControl component since it no longer has any text, update snapshots * another snapshot update * ensure min and max are not null * check for value before checking if min/max is null
This commit is contained in:
parent
5664bbe583
commit
b5c958ea53
14 changed files with 344 additions and 441 deletions
|
@ -201,7 +201,6 @@
|
|||
"react-color": "^2.13.8",
|
||||
"react-dom": "^16.8.0",
|
||||
"react-grid-layout": "^0.16.2",
|
||||
"react-input-range": "^1.3.0",
|
||||
"react-markdown": "^3.1.4",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-router-dom": "^4.3.1",
|
||||
|
|
|
@ -307,7 +307,7 @@ exports[`Renders range control 1`] = `
|
|||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(RangeControlUi)
|
||||
<RangeControl
|
||||
control={
|
||||
Object {
|
||||
"format": [Function],
|
||||
|
|
|
@ -7,19 +7,9 @@ exports[`disabled 1`] = `
|
|||
id="mock-range-control"
|
||||
label="range control"
|
||||
>
|
||||
<EuiRange
|
||||
compressed={false}
|
||||
<ValidatedDualRange
|
||||
disabled={true}
|
||||
fullWidth={false}
|
||||
levels={Array []}
|
||||
max={100}
|
||||
min={0}
|
||||
showInput={false}
|
||||
showLabels={false}
|
||||
showRange={false}
|
||||
showTicks={false}
|
||||
showValue={false}
|
||||
step={1}
|
||||
showInput={true}
|
||||
/>
|
||||
</FormRow>
|
||||
`;
|
||||
|
@ -31,87 +21,32 @@ exports[`renders RangeControl 1`] = `
|
|||
id="mock-range-control"
|
||||
label="range control"
|
||||
>
|
||||
<EuiFormRow
|
||||
data-test-subj="rangeControlFormRow"
|
||||
describedByIds={Array []}
|
||||
error={Array []}
|
||||
fullWidth={false}
|
||||
hasEmptyLabelSpace={false}
|
||||
isInvalid={false}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<input
|
||||
className="euiFieldNumber"
|
||||
data-test-subj="rangeControlMinInputValue"
|
||||
id="mock-range-control_min"
|
||||
max={100}
|
||||
min={0}
|
||||
name="min"
|
||||
onChange={[Function]}
|
||||
type="number"
|
||||
value=""
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
className="icvInputRange__container"
|
||||
grow={false}
|
||||
>
|
||||
<InputRange
|
||||
allowSameValues={false}
|
||||
ariaLabelledby="mock-range-control"
|
||||
classNames={
|
||||
Object {
|
||||
"activeTrack": "input-range__track input-range__track--active",
|
||||
"disabledInputRange": "input-range input-range--disabled",
|
||||
"inputRange": "input-range",
|
||||
"labelContainer": "input-range__label-container",
|
||||
"maxLabel": "input-range__label input-range__label--max",
|
||||
"minLabel": "input-range__label input-range__label--min",
|
||||
"slider": "input-range__slider",
|
||||
"sliderContainer": "input-range__slider-container",
|
||||
"track": "input-range__track input-range__track--background",
|
||||
"valueLabel": "input-range__label input-range__label--value",
|
||||
}
|
||||
}
|
||||
disabled={false}
|
||||
draggableTrack={true}
|
||||
formatLabel={[Function]}
|
||||
maxValue={100}
|
||||
minValue={0}
|
||||
onChange={[Function]}
|
||||
onChangeComplete={[Function]}
|
||||
step={1}
|
||||
value={
|
||||
Object {
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<input
|
||||
className="euiFieldNumber"
|
||||
data-test-subj="rangeControlMaxInputValue"
|
||||
id="mock-range-control_max"
|
||||
max={100}
|
||||
min={0}
|
||||
name="max"
|
||||
onChange={[Function]}
|
||||
type="number"
|
||||
value=""
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<ValidatedDualRange
|
||||
id="mock-range-control"
|
||||
max={100}
|
||||
min={0}
|
||||
onChange={[Function]}
|
||||
showInput={true}
|
||||
showRange={true}
|
||||
showTicks={true}
|
||||
ticks={
|
||||
Array [
|
||||
Object {
|
||||
"label": 0,
|
||||
"value": 0,
|
||||
},
|
||||
Object {
|
||||
"label": 100,
|
||||
"value": 100,
|
||||
},
|
||||
]
|
||||
}
|
||||
value={
|
||||
Array [
|
||||
"",
|
||||
"",
|
||||
]
|
||||
}
|
||||
/>
|
||||
</FormRow>
|
||||
`;
|
||||
|
|
|
@ -2,25 +2,4 @@
|
|||
width: 100%;
|
||||
margin: 0 $euiSizeXS;
|
||||
padding: $euiSizeS;
|
||||
|
||||
// Removes the browser's number stepper
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// hide slider labels since they are displayed in inputs
|
||||
.input-range__track .input-range__label-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// do not center min/max labels - otherwise the overflow slider sides
|
||||
.input-range__label-container {
|
||||
left: 0% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.icvInputRange__container {
|
||||
min-width: $euiSize * 10 !important; // !important needed for IE
|
||||
}
|
||||
|
|
|
@ -17,175 +17,98 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import InputRange from 'react-input-range';
|
||||
import { FormRow } from './form_row';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import { ValidatedDualRange } from 'ui/validated_range';
|
||||
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiRange,
|
||||
} from '@elastic/eui';
|
||||
|
||||
const toState = ({ control }) => {
|
||||
const sliderValue = control.hasValue() ?
|
||||
control.value :
|
||||
// InputRange component does not have an "empty state"
|
||||
// Faking an empty state by setting the slider value range to length of zero anchored at the range minimum
|
||||
{
|
||||
min: control.min,
|
||||
max: control.min
|
||||
};
|
||||
const state = {
|
||||
sliderValue,
|
||||
minValue: control.hasValue() ? control.value.min : '',
|
||||
maxValue: control.hasValue() ? control.value.max : '',
|
||||
isRangeValid: true,
|
||||
errorMessage: '',
|
||||
};
|
||||
return state;
|
||||
};
|
||||
|
||||
class RangeControlUi extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = toState(props);
|
||||
function roundWithPrecision(value, decimalPlaces, roundFunction) {
|
||||
if (decimalPlaces <= 0) {
|
||||
return roundFunction(value);
|
||||
}
|
||||
|
||||
componentWillReceiveProps = (nextProps) => {
|
||||
this.setState(toState(nextProps));
|
||||
};
|
||||
let results = value;
|
||||
results = results * Math.pow(10, decimalPlaces);
|
||||
results = roundFunction(results);
|
||||
results = results / Math.pow(10, decimalPlaces);
|
||||
return results;
|
||||
}
|
||||
|
||||
handleOnChange = (value) => {
|
||||
this.setState({
|
||||
sliderValue: value,
|
||||
minValue: value.min,
|
||||
isRangeValid: true,
|
||||
maxValue: value.max,
|
||||
errorMessage: '',
|
||||
});
|
||||
};
|
||||
export function ceilWithPrecision(value, decimalPlaces) {
|
||||
return roundWithPrecision(value, decimalPlaces, Math.ceil);
|
||||
}
|
||||
|
||||
handleOnChangeComplete = (value) => {
|
||||
this.props.stageFilter(this.props.controlIndex, value);
|
||||
};
|
||||
export function floorWithPrecision(value, decimalPlaces) {
|
||||
return roundWithPrecision(value, decimalPlaces, Math.floor);
|
||||
}
|
||||
|
||||
handleMinChange = (evt) => {
|
||||
this.handleChange(parseFloat(evt.target.value), this.state.maxValue);
|
||||
};
|
||||
export class RangeControl extends Component {
|
||||
state = {};
|
||||
|
||||
handleMaxChange = (evt) => {
|
||||
this.handleChange(this.state.minValue, parseFloat(evt.target.value));
|
||||
};
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const nextValue = nextProps.control.hasValue()
|
||||
? [nextProps.control.value.min, nextProps.control.value.max]
|
||||
: ['', ''];
|
||||
|
||||
handleChange = (min, max) => {
|
||||
min = isNaN(min) ? '' : min;
|
||||
max = isNaN(max) ? '' : max;
|
||||
|
||||
const isMinValid = min !== '';
|
||||
const isMaxValid = max !== '';
|
||||
let isRangeValid = true;
|
||||
let errorMessage = '';
|
||||
|
||||
if ((!isMinValid && isMaxValid) || (isMinValid && !isMaxValid)) {
|
||||
isRangeValid = false;
|
||||
errorMessage = this.props.intl.formatMessage({
|
||||
id: 'inputControl.vis.rangeControl.minMaxValidErrorMessage',
|
||||
defaultMessage: 'both min and max must be set'
|
||||
});
|
||||
if (nextProps.control.hasValue() && nextProps.control.value.min == null) {
|
||||
nextValue[0] = '';
|
||||
}
|
||||
if (nextProps.control.hasValue() && nextProps.control.value.max == null) {
|
||||
nextValue[1] = '';
|
||||
}
|
||||
|
||||
if (isMinValid && isMaxValid && max < min) {
|
||||
isRangeValid = false;
|
||||
errorMessage = this.props.intl.formatMessage({
|
||||
id: 'inputControl.vis.rangeControl.maxValidErrorMessage',
|
||||
defaultMessage: 'max must be greater or equal to min'
|
||||
});
|
||||
if (nextValue !== prevState.prevValue) {
|
||||
return {
|
||||
value: nextValue,
|
||||
prevValue: nextValue,
|
||||
};
|
||||
}
|
||||
|
||||
this.setState({
|
||||
minValue: min,
|
||||
maxValue: max,
|
||||
isRangeValid,
|
||||
errorMessage,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
onChangeComplete = _.debounce(value => {
|
||||
const controlValue = {
|
||||
min: value[0],
|
||||
max: value[1]
|
||||
};
|
||||
this.props.stageFilter(this.props.controlIndex, controlValue);
|
||||
}, 200);
|
||||
|
||||
if (isRangeValid && isMaxValid && isMinValid) {
|
||||
this.handleOnChangeComplete({ min, max });
|
||||
}
|
||||
};
|
||||
|
||||
formatLabel = (value) => {
|
||||
let formatedValue = value;
|
||||
const decimalPlaces = _.get(this.props, 'control.options.decimalPlaces');
|
||||
if (decimalPlaces !== null && decimalPlaces >= 0) {
|
||||
formatedValue = value.toFixed(decimalPlaces);
|
||||
}
|
||||
return formatedValue;
|
||||
};
|
||||
|
||||
renderControl() {
|
||||
if (!this.props.control.isEnabled()) {
|
||||
return (
|
||||
<EuiRange
|
||||
<ValidatedDualRange
|
||||
disabled
|
||||
showInput
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const decimalPlaces = _.get(this.props, 'control.options.decimalPlaces', 0);
|
||||
const min = floorWithPrecision(this.props.control.min, decimalPlaces);
|
||||
const max = ceilWithPrecision(this.props.control.max, decimalPlaces);
|
||||
|
||||
const ticks = [
|
||||
{ value: min, label: min },
|
||||
{ value: max, label: max }
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
isInvalid={!this.state.isRangeValid}
|
||||
error={this.state.errorMessage ? [this.state.errorMessage] : []}
|
||||
data-test-subj="rangeControlFormRow"
|
||||
>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<input
|
||||
id={`${this.props.control.id}_min`}
|
||||
name="min"
|
||||
type="number"
|
||||
data-test-subj="rangeControlMinInputValue"
|
||||
className="euiFieldNumber"
|
||||
value={this.state.minValue}
|
||||
min={this.props.control.min}
|
||||
max={this.props.control.max}
|
||||
onChange={this.handleMinChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="icvInputRange__container">
|
||||
<InputRange
|
||||
maxValue={this.props.control.max}
|
||||
minValue={this.props.control.min}
|
||||
step={this.props.control.options.step}
|
||||
value={this.state.sliderValue}
|
||||
onChange={this.handleOnChange}
|
||||
onChangeComplete={this.handleOnChangeComplete}
|
||||
draggableTrack={true}
|
||||
ariaLabelledby={this.props.control.id}
|
||||
formatLabel={this.formatLabel}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<input
|
||||
id={`${this.props.control.id}_max`}
|
||||
name="max"
|
||||
type="number"
|
||||
className="euiFieldNumber"
|
||||
data-test-subj="rangeControlMaxInputValue"
|
||||
value={this.state.maxValue}
|
||||
min={this.props.control.min}
|
||||
max={this.props.control.max}
|
||||
onChange={this.handleMaxChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
<ValidatedDualRange
|
||||
id={this.props.control.id}
|
||||
min={min}
|
||||
max={max}
|
||||
value={this.state.value}
|
||||
onChange={this.onChangeComplete}
|
||||
showInput
|
||||
showRange
|
||||
showTicks
|
||||
ticks={ticks}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -203,10 +126,8 @@ class RangeControlUi extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
RangeControlUi.propTypes = {
|
||||
RangeControl.propTypes = {
|
||||
control: PropTypes.object.isRequired,
|
||||
controlIndex: PropTypes.number.isRequired,
|
||||
stageFilter: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export const RangeControl = injectI18n(RangeControlUi);
|
||||
|
|
|
@ -18,11 +18,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
|
||||
|
||||
import {
|
||||
RangeControl,
|
||||
ceilWithPrecision,
|
||||
floorWithPrecision,
|
||||
} from './range_control';
|
||||
|
||||
const control = {
|
||||
|
@ -43,7 +44,7 @@ const control = {
|
|||
};
|
||||
|
||||
test('renders RangeControl', () => {
|
||||
const component = shallowWithIntl(<RangeControl.WrappedComponent
|
||||
const component = shallowWithIntl(<RangeControl
|
||||
control={control}
|
||||
controlIndex={0}
|
||||
stageFilter={() => {}}
|
||||
|
@ -66,7 +67,7 @@ test('disabled', () => {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
const component = shallowWithIntl(<RangeControl.WrappedComponent
|
||||
const component = shallowWithIntl(<RangeControl
|
||||
control={disabledRangeControl}
|
||||
controlIndex={0}
|
||||
stageFilter={() => {}}
|
||||
|
@ -74,59 +75,12 @@ test('disabled', () => {
|
|||
expect(component).toMatchSnapshot(); // eslint-disable-line
|
||||
});
|
||||
|
||||
describe('min and max input values', () => {
|
||||
const component = mountWithIntl(<RangeControl.WrappedComponent
|
||||
control={control}
|
||||
controlIndex={0}
|
||||
stageFilter={() => {}}
|
||||
/>);
|
||||
|
||||
const BOTH_MIN_AND_MAX_MUST_BE_SET_ERROR = 'both min and max must be set';
|
||||
|
||||
const getMinInput = () => findTestSubject(component, 'rangeControlMinInputValue');
|
||||
const getMaxInput = () => findTestSubject(component, 'rangeControlMaxInputValue');
|
||||
const getRangeRow = () => findTestSubject(component, 'rangeControlFormRow');
|
||||
|
||||
test('are initially blank', () => {
|
||||
expect(getMinInput().props().value).toBe('');
|
||||
expect(getMaxInput().props().value).toBe('');
|
||||
});
|
||||
|
||||
test('min can be set manually', () => {
|
||||
getMinInput().simulate('change', { target: { value: 3 } });
|
||||
expect(getMinInput().props().value).toBe(3);
|
||||
});
|
||||
|
||||
test('when only min is specified an error is shown', () => {
|
||||
expect(getRangeRow().text().indexOf(BOTH_MIN_AND_MAX_MUST_BE_SET_ERROR)).toBeGreaterThan(-1);
|
||||
});
|
||||
|
||||
test('max can be set manually', () => {
|
||||
getMaxInput().simulate('change', { target: { value: 6 } });
|
||||
expect(getMaxInput().props().value).toBe(6);
|
||||
});
|
||||
|
||||
test('when both min and max are set there is no error', () => {
|
||||
expect(getRangeRow().text().indexOf(BOTH_MIN_AND_MAX_MUST_BE_SET_ERROR)).toBe(-1);
|
||||
});
|
||||
|
||||
test('0 is a valid minimum value', () => {
|
||||
getMinInput().simulate('change', { target: { value: 0 } });
|
||||
expect(getMinInput().props().value).toBe(0);
|
||||
expect(getRangeRow().text().indexOf(BOTH_MIN_AND_MAX_MUST_BE_SET_ERROR)).toBe(-1);
|
||||
});
|
||||
|
||||
test('min can be deleted and there will be an error shown', () => {
|
||||
getMinInput().simulate('change', { target: { value: '' } });
|
||||
expect(getMinInput().props().value).toBe('');
|
||||
expect(getRangeRow().text().indexOf(BOTH_MIN_AND_MAX_MUST_BE_SET_ERROR)).toBeGreaterThan(-1);
|
||||
});
|
||||
|
||||
test('both max and min can be deleted and there will not be an error shown', () => {
|
||||
getMaxInput().simulate('change', { target: { value: '' } });
|
||||
expect(getMaxInput().props().value).toBe('');
|
||||
expect(getRangeRow().text().indexOf(BOTH_MIN_AND_MAX_MUST_BE_SET_ERROR)).toBe(-1);
|
||||
});
|
||||
test('ceilWithPrecision', () => {
|
||||
expect(ceilWithPrecision(999.133, 0)).toBe(1000);
|
||||
expect(ceilWithPrecision(999.133, 2)).toBe(999.14);
|
||||
});
|
||||
|
||||
|
||||
test('floorWithPrecision', () => {
|
||||
expect(floorWithPrecision(100.777, 0)).toBe(100);
|
||||
expect(floorWithPrecision(100.777, 2)).toBe(100.77);
|
||||
});
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
@import './navbar';
|
||||
@import './config';
|
||||
@import './pagination';
|
||||
@import './react_input_range';
|
||||
@import './sidebar';
|
||||
@import './spinner';
|
||||
@import './table';
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
// SASSTODO: rem values are actually based off of a 14px base
|
||||
// so changing them to EUI variables would alter computed values
|
||||
.input-range__slider {
|
||||
appearance: none;
|
||||
background: $euiColorPrimary;
|
||||
border: 1px solid $euiColorPrimary;
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 1rem;
|
||||
margin-left: -0.5rem;
|
||||
margin-top: -0.65rem;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
|
||||
width: 1rem;
|
||||
|
||||
&:active {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include euiFocusRing('large');
|
||||
}
|
||||
}
|
||||
|
||||
.input-range--disabled .input-range__slider {
|
||||
background: $euiColorLightShade;
|
||||
border: 1px solid $euiColorLightShade;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.input-range__slider-container {
|
||||
transition: left 0.3s ease-out;
|
||||
}
|
||||
|
||||
.input-range__label {
|
||||
color: $euiColorMediumShade;
|
||||
font-size: 0.8rem;
|
||||
transform: translateZ(0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.input-range__label--min,
|
||||
.input-range__label--max {
|
||||
bottom: -1.4rem;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.input-range__label--min {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.input-range__label--max {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.input-range__label--value {
|
||||
position: absolute;
|
||||
top: -1.8rem;
|
||||
}
|
||||
|
||||
.input-range__label-container {
|
||||
left: -50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-range__label--max .input-range__label-container {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.input-range__track {
|
||||
background: $euiColorLightShade;
|
||||
border-radius: 0.3rem;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
height: 0.3rem;
|
||||
position: relative;
|
||||
transition: left 0.3s ease-out, width 0.3s ease-out;
|
||||
}
|
||||
|
||||
.input-range--disabled .input-range__track {
|
||||
background: $euiColorLightShade;
|
||||
}
|
||||
|
||||
.input-range__track--background {
|
||||
left: 0;
|
||||
margin-top: -0.15rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.input-range__track--active {
|
||||
background: $euiColorPrimary;
|
||||
}
|
||||
|
||||
.input-range {
|
||||
margin: 0 $euiSizeS;
|
||||
position: relative;
|
||||
width: calc(100% - #{$euiSize});
|
||||
}
|
20
src/legacy/ui/public/validated_range/index.js
Normal file
20
src/legacy/ui/public/validated_range/index.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { ValidatedDualRange } from './validated_dual_range';
|
66
src/legacy/ui/public/validated_range/is_range_valid.js
Normal file
66
src/legacy/ui/public/validated_range/is_range_valid.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const LOWER_VALUE_INDEX = 0;
|
||||
const UPPER_VALUE_INDEX = 1;
|
||||
|
||||
export function isRangeValid(value, min, max) {
|
||||
let lowerValue = isNaN(value[LOWER_VALUE_INDEX]) ? '' : value[LOWER_VALUE_INDEX];
|
||||
let upperValue = isNaN(value[UPPER_VALUE_INDEX]) ? '' : value[UPPER_VALUE_INDEX];
|
||||
|
||||
const isLowerValueValid = lowerValue !== '';
|
||||
const isUpperValueValid = upperValue !== '';
|
||||
if (isLowerValueValid) {
|
||||
lowerValue = parseFloat(lowerValue);
|
||||
}
|
||||
if (isUpperValueValid) {
|
||||
upperValue = parseFloat(upperValue);
|
||||
}
|
||||
let isValid = true;
|
||||
let errorMessage = '';
|
||||
|
||||
if ((!isLowerValueValid && isUpperValueValid) || (isLowerValueValid && !isUpperValueValid)) {
|
||||
isValid = false;
|
||||
errorMessage = i18n.translate('common.ui.dualRangeControl.mustSetBothErrorMessage', {
|
||||
defaultMessage: 'Both lower and upper values must be set'
|
||||
});
|
||||
}
|
||||
|
||||
if ((isLowerValueValid && lowerValue < min) || (isUpperValueValid && upperValue > max)) {
|
||||
isValid = false;
|
||||
errorMessage = i18n.translate('common.ui.dualRangeControl.outsideOfRangeErrorMessage', {
|
||||
defaultMessage: 'Values must be on or between {min} and {max}',
|
||||
values: { min, max }
|
||||
});
|
||||
}
|
||||
|
||||
if (isLowerValueValid && isUpperValueValid && upperValue < lowerValue) {
|
||||
isValid = false;
|
||||
errorMessage = i18n.translate('common.ui.dualRangeControl.upperValidErrorMessage', {
|
||||
defaultMessage: 'Upper value must be greater or equal to lower value'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
isValid,
|
||||
errorMessage,
|
||||
};
|
||||
}
|
65
src/legacy/ui/public/validated_range/is_range_valid.test.js
Normal file
65
src/legacy/ui/public/validated_range/is_range_valid.test.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { isRangeValid } from './is_range_valid';
|
||||
|
||||
it('Should return true when lower and upper values are set and between min and max', () => {
|
||||
const { isValid } = isRangeValid([2, 3], 1, 10);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('Should handle string values and return true when lower and upper values are set and between min and max', () => {
|
||||
const { isValid } = isRangeValid(['192', '1000'], 100, 1000);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('Should return true when lower and upper values are not set (empty range)', () => {
|
||||
const { isValid } = isRangeValid(['', ''], 1, 10);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('Should return false when lower value is not set and upper value is set', () => {
|
||||
const { isValid, errorMessage } = isRangeValid(['', 3], 1, 10);
|
||||
expect(isValid).toBe(false);
|
||||
expect(errorMessage).toBe('Both lower and upper values must be set');
|
||||
});
|
||||
|
||||
it('Should return false when lower value is set and upper value is not set', () => {
|
||||
const { isValid, errorMessage } = isRangeValid([2, ''], 1, 10);
|
||||
expect(isValid).toBe(false);
|
||||
expect(errorMessage).toBe('Both lower and upper values must be set');
|
||||
});
|
||||
|
||||
it('Should return false when lower value is greater than upper value', () => {
|
||||
const { isValid, errorMessage } = isRangeValid([3, 2], 1, 10);
|
||||
expect(isValid).toBe(false);
|
||||
expect(errorMessage).toBe('Upper value must be greater or equal to lower value');
|
||||
});
|
||||
|
||||
it('Should return false when lower value is less than min', () => {
|
||||
const { isValid, errorMessage } = isRangeValid([0, 2], 1, 10);
|
||||
expect(isValid).toBe(false);
|
||||
expect(errorMessage).toBe('Values must be on or between 1 and 10');
|
||||
});
|
||||
|
||||
it('Should return false when upper value is greater than max', () => {
|
||||
const { isValid, errorMessage } = isRangeValid([2, 12], 1, 10);
|
||||
expect(isValid).toBe(false);
|
||||
expect(errorMessage).toBe('Values must be on or between 1 and 10');
|
||||
});
|
84
src/legacy/ui/public/validated_range/validated_dual_range.js
Normal file
84
src/legacy/ui/public/validated_range/validated_dual_range.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { isRangeValid } from './is_range_valid';
|
||||
|
||||
import {
|
||||
EuiFormRow,
|
||||
EuiDualRange,
|
||||
} from '@elastic/eui';
|
||||
|
||||
// Wrapper around EuiDualRange that ensures onChange callback is only called when range value
|
||||
// is valid and within min/max
|
||||
export class ValidatedDualRange extends Component {
|
||||
state = {};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.value !== prevState.prevValue) {
|
||||
const { isValid, errorMessage } = isRangeValid(
|
||||
nextProps.value,
|
||||
nextProps.min,
|
||||
nextProps.max);
|
||||
return {
|
||||
value: nextProps.value,
|
||||
prevValue: nextProps.value,
|
||||
isValid,
|
||||
errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
const { isValid, errorMessage } = isRangeValid(value, this.props.min, this.props.max);
|
||||
|
||||
this.setState({
|
||||
value,
|
||||
isValid,
|
||||
errorMessage,
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
value, // eslint-disable-line no-unused-vars
|
||||
onChange, // eslint-disable-line no-unused-vars
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
isInvalid={!this.state.isValid}
|
||||
error={this.state.errorMessage ? [this.state.errorMessage] : []}
|
||||
>
|
||||
<EuiDualRange
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
{...rest}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -777,8 +777,6 @@
|
|||
"inputControl.vis.inputControlVis.clearFormButtonLabel": "清除表单",
|
||||
"inputControl.vis.listControl.selectPlaceholder": "选择......",
|
||||
"inputControl.vis.listControl.selectTextPlaceholder": "选择......",
|
||||
"inputControl.vis.rangeControl.maxValidErrorMessage": "最大值必须大于或等于最小值",
|
||||
"inputControl.vis.rangeControl.minMaxValidErrorMessage": "必须设置最小值和最大值",
|
||||
"inspectorViews.data.dataDescriptionTooltip": "查看可视化后面的数据",
|
||||
"inspectorViews.data.dataTitle": "数据",
|
||||
"inspectorViews.data.downloadCSVButtonLabel": "下载 CSV",
|
||||
|
@ -8193,4 +8191,4 @@
|
|||
"xpack.watcher.watchActionsTitle": "满足后将执行 {watchActionsCount, plural, one{# 个操作} other {# 个操作}}",
|
||||
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
|
||||
}
|
||||
}
|
||||
}
|
13
yarn.lock
13
yarn.lock
|
@ -3785,11 +3785,6 @@ attr-accept@^1.1.3:
|
|||
dependencies:
|
||||
core-js "^2.5.0"
|
||||
|
||||
autobind-decorator@^1.3.4:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-1.4.3.tgz#4c96ffa77b10622ede24f110f5dbbf56691417d1"
|
||||
integrity sha1-TJb/p3sQYi7eJPEQ9du/VmkUF9E=
|
||||
|
||||
autolinker@~0.15.0:
|
||||
version "0.15.3"
|
||||
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832"
|
||||
|
@ -19149,14 +19144,6 @@ react-input-autosize@^2.1.2, react-input-autosize@^2.2.1:
|
|||
dependencies:
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-input-range@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-input-range/-/react-input-range-1.3.0.tgz#f96d001631ab817417f1e26d8f9f9684b4827f59"
|
||||
integrity sha1-+W0AFjGrgXQX8eJtj5+WhLSCf1k=
|
||||
dependencies:
|
||||
autobind-decorator "^1.3.4"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-inspector@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-2.3.0.tgz#fc9c1d38ab687fc0d190dcaf133ae40158968fc8"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue