baseui 10.9.0 → 10.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/data-table/column-numerical.js +355 -307
  2. package/data-table/column-numerical.js.flow +287 -273
  3. package/data-table/constants.js +11 -17
  4. package/data-table/constants.js.flow +8 -11
  5. package/data-table/data-table.js +50 -53
  6. package/data-table/data-table.js.flow +13 -18
  7. package/data-table/filter-shell.js +4 -27
  8. package/data-table/filter-shell.js.flow +9 -33
  9. package/data-table/locale.js +2 -4
  10. package/data-table/locale.js.flow +2 -6
  11. package/data-table/measure-column-widths.js +121 -83
  12. package/data-table/measure-column-widths.js.flow +109 -87
  13. package/es/data-table/column-numerical.js +317 -245
  14. package/es/data-table/constants.js +8 -12
  15. package/es/data-table/data-table.js +16 -18
  16. package/es/data-table/filter-shell.js +4 -26
  17. package/es/data-table/locale.js +2 -4
  18. package/es/data-table/measure-column-widths.js +86 -75
  19. package/es/form-control/form-control.js +58 -7
  20. package/es/form-control/styled-components.js +27 -6
  21. package/es/popover/popover.js +1 -1
  22. package/esm/data-table/column-numerical.js +353 -304
  23. package/esm/data-table/constants.js +8 -12
  24. package/esm/data-table/data-table.js +50 -53
  25. package/esm/data-table/filter-shell.js +4 -26
  26. package/esm/data-table/locale.js +2 -4
  27. package/esm/data-table/measure-column-widths.js +121 -83
  28. package/esm/form-control/form-control.js +60 -9
  29. package/esm/form-control/styled-components.js +23 -3
  30. package/esm/popover/popover.js +1 -1
  31. package/form-control/form-control.js +61 -8
  32. package/form-control/form-control.js.flow +82 -10
  33. package/form-control/index.d.ts +1 -0
  34. package/form-control/styled-components.js +27 -5
  35. package/form-control/styled-components.js.flow +25 -3
  36. package/form-control/types.js.flow +20 -8
  37. package/package.json +1 -2
  38. package/popover/popover.js +1 -1
  39. package/popover/popover.js.flow +1 -1
@@ -12,25 +12,26 @@ import {Button, SIZE} from '../button/index.js';
12
12
  import {ButtonGroup, MODE} from '../button-group/index.js';
13
13
  import {Input, SIZE as INPUT_SIZE} from '../input/index.js';
14
14
  import {useStyletron} from '../styles/index.js';
15
+ import {ParagraphXSmall} from '../typography/index.js';
15
16
 
16
17
  import Column from './column.js';
17
- import {
18
- COLUMNS,
19
- NUMERICAL_FORMATS,
20
- MAX_BIN_COUNT,
21
- HISTOGRAM_SIZE,
22
- } from './constants.js';
23
- import FilterShell, {type ExcludeKind} from './filter-shell.js';
18
+ import {COLUMNS, NUMERICAL_FORMATS, NUMERICAL_OPERATIONS} from './constants.js';
19
+ import FilterShell from './filter-shell.js';
24
20
  import type {ColumnT, SharedColumnOptionsT} from './types.js';
25
21
  import {LocaleContext} from '../locale/index.js';
26
- import {bin, max as maxFunc, extent, scaleLinear, median, bisector} from 'd3';
27
- import {Slider} from '../slider/index.js';
28
22
 
29
23
  type NumericalFormats =
30
24
  | typeof NUMERICAL_FORMATS.DEFAULT
31
25
  | typeof NUMERICAL_FORMATS.ACCOUNTING
32
26
  | typeof NUMERICAL_FORMATS.PERCENTAGE;
33
27
 
28
+ type NumericalOperations =
29
+ | typeof NUMERICAL_OPERATIONS.EQ
30
+ | typeof NUMERICAL_OPERATIONS.GT
31
+ | typeof NUMERICAL_OPERATIONS.GTE
32
+ | typeof NUMERICAL_OPERATIONS.LT
33
+ | typeof NUMERICAL_OPERATIONS.LTE;
34
+
34
35
  type OptionsT = {|
35
36
  ...SharedColumnOptionsT<number>,
36
37
  format?: NumericalFormats | ((value: number) => string),
@@ -39,11 +40,12 @@ type OptionsT = {|
39
40
  |};
40
41
 
41
42
  type FilterParametersT = {|
42
- lowerValue: number,
43
- upperValue: number,
43
+ comparisons: Array<{|
44
+ value: number,
45
+ operation: NumericalOperations,
46
+ |}>,
44
47
  description: string,
45
48
  exclude: boolean,
46
- excludeKind: ExcludeKind,
47
49
  |};
48
50
 
49
51
  type NumericalColumnT = ColumnT<number, FilterParametersT>;
@@ -84,197 +86,224 @@ function validateInput(input) {
84
86
  return Boolean(parseFloat(input)) || input === '' || input === '-';
85
87
  }
86
88
 
87
- const bisect = bisector(d => d.x0);
88
-
89
- const Histogram = React.memo(function Histogram({
90
- data,
91
- lower,
92
- upper,
93
- isRange,
94
- exclude,
95
- precision,
96
- }) {
97
- const [css, theme] = useStyletron();
98
-
99
- const {bins, xScale, yScale} = React.useMemo(() => {
100
- const bins = bin().thresholds(Math.min(data.length, MAX_BIN_COUNT))(data);
101
-
102
- const xScale = scaleLinear()
103
- .domain([bins[0].x0, bins[bins.length - 1].x1])
104
- .range([0, HISTOGRAM_SIZE.width])
105
- .clamp(true);
106
-
107
- const yScale = scaleLinear()
108
- .domain([0, maxFunc(bins, d => d.length)])
109
- .nice()
110
- .range([HISTOGRAM_SIZE.height, 0]);
111
- return {bins, xScale, yScale};
112
- }, [data]);
113
-
114
- // We need to find the index of bar which is nearest to the given single value
115
- const singleIndexNearest = React.useMemo(() => {
116
- if (isRange) {
117
- return null;
89
+ function filterParamsToInitialState(filterParams) {
90
+ if (filterParams) {
91
+ if (filterParams.comparisons.length > 1) {
92
+ if (
93
+ filterParams.comparisons[0].operation === NUMERICAL_OPERATIONS.LT &&
94
+ filterParams.comparisons[1].operation === NUMERICAL_OPERATIONS.GT
95
+ ) {
96
+ return {
97
+ exclude: !filterParams.exclude,
98
+ comparatorIndex: 0,
99
+ operatorIndex: 4,
100
+ right: filterParams.comparisons[1].value.toString(),
101
+ left: filterParams.comparisons[0].value.toString(),
102
+ };
103
+ }
104
+ } else {
105
+ const comparison = filterParams.comparisons[0];
106
+ if (comparison.operation === NUMERICAL_OPERATIONS.LT) {
107
+ return {
108
+ exclude: filterParams.exclude,
109
+ comparatorIndex: 0,
110
+ operatorIndex: 0,
111
+ left: '',
112
+ right: comparison.value.toString(),
113
+ };
114
+ } else if (comparison.operation === NUMERICAL_OPERATIONS.GT) {
115
+ return {
116
+ exclude: filterParams.exclude,
117
+ comparatorIndex: 0,
118
+ operatorIndex: 1,
119
+ left: comparison.value.toString(),
120
+ right: '',
121
+ };
122
+ } else if (comparison.operation === NUMERICAL_OPERATIONS.LTE) {
123
+ return {
124
+ exclude: filterParams.exclude,
125
+ comparatorIndex: 0,
126
+ operatorIndex: 2,
127
+ left: '',
128
+ right: comparison.value.toString(),
129
+ };
130
+ } else if (comparison.operation === NUMERICAL_OPERATIONS.GTE) {
131
+ return {
132
+ exclude: filterParams.exclude,
133
+ comparatorIndex: 0,
134
+ operatorIndex: 3,
135
+ left: comparison.value.toString(),
136
+ right: '',
137
+ };
138
+ } else if (comparison.operation === NUMERICAL_OPERATIONS.EQ) {
139
+ return {
140
+ exclude: filterParams.exclude,
141
+ comparatorIndex: 1,
142
+ operatorIndex: 0,
143
+ left: comparison.value.toString(),
144
+ right: '',
145
+ };
146
+ }
118
147
  }
119
- return bisect.center(bins, lower);
120
- }, [isRange, data, lower, upper]);
121
-
122
- return (
123
- <div
124
- className={css({
125
- display: 'flex',
126
- marginTop: theme.sizing.scale600,
127
- marginLeft: theme.sizing.scale200,
128
- marginRight: 0,
129
- marginBottom: theme.sizing.scale400,
130
- justifyContent: 'space-between',
131
- overflow: 'visible',
132
- })}
133
- >
134
- <svg {...HISTOGRAM_SIZE}>
135
- {bins.map((d, index) => {
136
- const x = xScale(d.x0) + 1;
137
- const y = yScale(d.length);
138
- const width = Math.max(0, xScale(d.x1) - xScale(d.x0) - 1);
139
- const height = yScale(0) - yScale(d.length);
140
-
141
- let included;
142
- if (singleIndexNearest != null) {
143
- included = index === singleIndexNearest;
144
- } else {
145
- const withinLower = d.x1 > lower;
146
- const withinUpper = d.x0 <= upper;
147
- included = withinLower && withinUpper;
148
- }
149
-
150
- if (exclude) {
151
- included = !included;
152
- }
148
+ }
153
149
 
154
- return (
155
- <rect
156
- key={`bar-${index}`}
157
- fill={included ? theme.colors.primary : theme.colors.mono400}
158
- x={x}
159
- y={y}
160
- width={width}
161
- height={height}
162
- />
163
- );
164
- })}
165
- </svg>
166
- </div>
167
- );
168
- });
150
+ return {
151
+ exclude: false,
152
+ comparatorIndex: 0,
153
+ operatorIndex: 0,
154
+ left: '',
155
+ right: '',
156
+ };
157
+ }
169
158
 
170
159
  function NumericalFilter(props) {
171
160
  const [css, theme] = useStyletron();
172
161
  const locale = React.useContext(LocaleContext);
173
162
 
174
- const precision = props.options.precision;
175
-
176
- // The state handling of this component could be refactored and cleaned up if we used useReducer.
177
- const initialState = React.useMemo(() => {
178
- return (
179
- props.filterParams || {
180
- exclude: false,
181
- excludeKind: 'range',
182
- comparatorIndex: 0,
183
- lowerValue: null,
184
- upperValue: null,
185
- }
186
- );
187
- }, [props.filterParams]);
188
-
163
+ const initialState = filterParamsToInitialState(props.filterParams);
189
164
  const [exclude, setExclude] = React.useState(initialState.exclude);
190
-
191
- // the api of our ButtonGroup forces these numerical indexes...
192
- // TODO look into allowing semantic names, similar to the radio component. Tricky part would be backwards compat
193
- const [comparatorIndex, setComparatorIndex] = React.useState(() => {
194
- switch (initialState.excludeKind) {
195
- case 'value':
196
- return 1;
197
- case 'range':
198
- default:
199
- // fallthrough
200
- return 0;
201
- }
202
- });
203
-
204
- // We use the d3 function to get the extent as it's a little more robust to null, -Infinity, etc.
205
- const [min, max] = React.useMemo(() => extent(props.data), [props.data]);
206
-
207
- const [lv, setLower] = React.useState<number>(() =>
208
- roundToFixed(initialState.lowerValue || min, precision),
165
+ const [comparatorIndex, setComparatorIndex] = React.useState(
166
+ initialState.comparatorIndex,
209
167
  );
210
- const [uv, setUpper] = React.useState<number>(() =>
211
- roundToFixed(initialState.upperValue || max, precision),
212
- );
213
-
214
- // We keep a separate value for the single select, to give a user the ability to toggle between
215
- // the range and single values without losing their previous input.
216
- const [sv, setSingle] = React.useState<number>(() =>
217
- roundToFixed(initialState.lowerValue || median(props.data), precision),
168
+ const [operatorIndex, setOperatorIndex] = React.useState(
169
+ initialState.operatorIndex,
218
170
  );
171
+ const [left, setLeft] = React.useState(initialState.left);
172
+ const [right, setRight] = React.useState(initialState.right);
219
173
 
220
- // This is the only conditional which we want to use to determine
221
- // if we are in range or single value mode.
222
- // Don't derive it via something else, e.g. lowerValue === upperValue, etc.
223
174
  const isRange = comparatorIndex === 0;
175
+ const min = React.useMemo(() => Math.min(...props.data), [props.data]);
176
+ const max = React.useMemo(() => Math.max(...props.data), [props.data]);
224
177
 
225
- const excludeKind = isRange ? 'range' : 'value';
226
-
227
- // while the user is inputting values, we take their input at face value,
228
- // if we don't do this, a user can't input partial numbers, e.g. "-", or "3."
229
- const [focused, setFocus] = React.useState(false);
230
- const [inputValueLower, inputValueUpper] = React.useMemo(() => {
231
- if (focused) {
232
- return [isRange ? lv : sv, uv];
178
+ React.useEffect(() => {
179
+ if (!left) {
180
+ setLeft(min.toString());
233
181
  }
182
+ if (!right) {
183
+ setRight(max.toString());
184
+ }
185
+ }, []);
186
+
187
+ const [leftDisabled, rightDisabled] = React.useMemo(() => {
188
+ if (!isRange) return [false, false];
189
+ switch (operatorIndex) {
190
+ case 4:
191
+ return [false, false];
192
+ case 0:
193
+ case 2:
194
+ return [true, false];
195
+ case 1:
196
+ case 3:
197
+ return [false, true];
198
+ default:
199
+ return [true, true];
200
+ }
201
+ }, [operatorIndex, isRange]);
202
+
203
+ const leftInputRef = React.useRef(null);
204
+ const rightInputRef = React.useRef(null);
205
+ React.useEffect(() => {
206
+ if (!leftDisabled && leftInputRef.current) {
207
+ leftInputRef.current.focus({preventScroll: true});
208
+ } else if (!rightDisabled && rightInputRef.current) {
209
+ rightInputRef.current.focus({preventScroll: true});
210
+ }
211
+ }, [leftDisabled, rightDisabled, comparatorIndex]);
234
212
 
235
- // once the user is done inputting.
236
- // we validate then format to the given precision
237
- let l = isRange ? lv : sv;
238
- l = validateInput(l) ? l : min;
239
- let h = validateInput(uv) ? uv : max;
240
-
241
- return [roundToFixed(l, precision), roundToFixed(h, precision)];
242
- }, [isRange, focused, sv, lv, uv, precision]);
243
-
244
- // We bound the values within our min and max even if a user enters a huge number
245
- let sliderValue = isRange
246
- ? [Math.max(inputValueLower, min), Math.min(inputValueUpper, max)]
247
- : [Math.min(Math.max(inputValueLower, min), max)];
248
-
249
- // keep the slider happy by sorting the two values
250
- if (isRange && sliderValue[0] > sliderValue[1]) {
251
- sliderValue = [sliderValue[1], sliderValue[0]];
252
- }
213
+ React.useEffect(() => {
214
+ switch (operatorIndex) {
215
+ case 4:
216
+ default:
217
+ break;
218
+ case 1:
219
+ case 3:
220
+ setRight(max.toString());
221
+ break;
222
+ case 0:
223
+ case 2:
224
+ setLeft(min.toString());
225
+ break;
226
+ }
227
+ }, [operatorIndex]);
253
228
 
254
229
  return (
255
230
  <FilterShell
256
231
  exclude={exclude}
257
232
  onExcludeChange={() => setExclude(!exclude)}
258
- excludeKind={excludeKind}
259
233
  onApply={() => {
260
234
  if (isRange) {
261
- const lowerValue = parseFloat(inputValueLower);
262
- const upperValue = parseFloat(inputValueUpper);
263
- props.setFilter({
264
- description: `≥ ${lowerValue} and ≤ ${upperValue}`,
265
- exclude: exclude,
266
- lowerValue,
267
- upperValue,
268
- excludeKind,
269
- });
235
+ switch (operatorIndex) {
236
+ case 0: {
237
+ const value = parseFloat(right);
238
+ const operation = NUMERICAL_OPERATIONS.LT;
239
+ props.setFilter({
240
+ comparisons: [{value, operation}],
241
+ description: `< ${value}`,
242
+ exclude,
243
+ });
244
+ break;
245
+ }
246
+ case 1: {
247
+ const value = parseFloat(left);
248
+ const operation = NUMERICAL_OPERATIONS.GT;
249
+ props.setFilter({
250
+ comparisons: [{value, operation}],
251
+ description: `> ${value}`,
252
+ exclude,
253
+ });
254
+ break;
255
+ }
256
+ case 2: {
257
+ const value = parseFloat(right);
258
+ const operation = NUMERICAL_OPERATIONS.LTE;
259
+ props.setFilter({
260
+ comparisons: [{value, operation}],
261
+ description: `≤ ${value}`,
262
+ exclude,
263
+ });
264
+ break;
265
+ }
266
+ case 3: {
267
+ const value = parseFloat(left);
268
+ const operation = NUMERICAL_OPERATIONS.GTE;
269
+ props.setFilter({
270
+ comparisons: [{value, operation}],
271
+ description: `≥ ${value}`,
272
+ exclude,
273
+ });
274
+ break;
275
+ }
276
+ case 4: {
277
+ // 'between' case is interesting since if we want less than 10 plus greater than 5
278
+ // comparators, the filter will include _all_ numbers.
279
+ const leftValue = parseFloat(left);
280
+ const rightValue = parseFloat(right);
281
+ props.setFilter({
282
+ comparisons: [
283
+ {
284
+ value: leftValue,
285
+ operation: NUMERICAL_OPERATIONS.LT,
286
+ },
287
+ {
288
+ value: rightValue,
289
+ operation: NUMERICAL_OPERATIONS.GT,
290
+ },
291
+ ],
292
+ description: `≥ ${leftValue} & ≤ ${rightValue}`,
293
+ exclude: !exclude,
294
+ });
295
+ break;
296
+ }
297
+ default:
298
+ break;
299
+ }
270
300
  } else {
271
- const value = parseFloat(inputValueLower);
301
+ const value = parseFloat(left);
302
+ const operation = NUMERICAL_OPERATIONS.EQ;
272
303
  props.setFilter({
304
+ comparisons: [{value, operation}],
273
305
  description: `= ${value}`,
274
- exclude: exclude,
275
- lowerValue: inputValueLower,
276
- upperValue: inputValueLower,
277
- excludeKind,
306
+ exclude,
278
307
  });
279
308
  }
280
309
 
@@ -282,7 +311,7 @@ function NumericalFilter(props) {
282
311
  }}
283
312
  >
284
313
  <ButtonGroup
285
- size={SIZE.mini}
314
+ size={SIZE.compact}
286
315
  mode={MODE.radio}
287
316
  selected={comparatorIndex}
288
317
  onClick={(_, index) => setComparatorIndex(index)}
@@ -295,130 +324,100 @@ function NumericalFilter(props) {
295
324
  <Button
296
325
  type="button"
297
326
  overrides={{BaseButton: {style: {width: '100%'}}}}
298
- aria-label={locale.datatable.numericalFilterRange}
299
327
  >
300
328
  {locale.datatable.numericalFilterRange}
301
329
  </Button>
302
330
  <Button
303
331
  type="button"
304
332
  overrides={{BaseButton: {style: {width: '100%'}}}}
305
- aria-label={locale.datatable.numericalFilterSingleValue}
306
333
  >
307
334
  {locale.datatable.numericalFilterSingleValue}
308
335
  </Button>
309
336
  </ButtonGroup>
310
337
 
311
- <Histogram
312
- data={props.data}
313
- lower={inputValueLower}
314
- upper={inputValueUpper}
315
- isRange={isRange}
316
- exclude={exclude}
317
- precision={props.options.precision}
318
- />
319
-
320
- <div className={css({display: 'flex', justifyContent: 'space-between'})}>
321
- <Slider
322
- // The slider throws errors when switching between single and two values
323
- // when it tries to read getThumbDistance on a thumb which is not there anymore
324
- // if we create a new instance these errors are prevented.
325
- key={isRange.toString()}
326
- min={min}
327
- max={max}
328
- value={sliderValue}
329
- onChange={({value}) => {
330
- if (!value) {
331
- return;
332
- }
333
- if (isRange) {
334
- const [lowerValue, upperValue] = value;
335
- setLower(lowerValue);
336
- setUpper(upperValue);
337
- } else {
338
- const [singleValue] = value;
339
- setSingle(singleValue);
340
- }
341
- }}
338
+ {isRange && (
339
+ <ButtonGroup
340
+ size={SIZE.compact}
341
+ mode={MODE.radio}
342
+ selected={operatorIndex}
343
+ onClick={(_, index) => setOperatorIndex(index)}
342
344
  overrides={{
343
- InnerThumb: function InnerThumb({$value, $thumbIndex}) {
344
- return <React.Fragment>{$value[$thumbIndex]}</React.Fragment>;
345
- },
346
- TickBar: ({$min, $max}) => null, // we don't want the ticks
347
- ThumbValue: () => null,
348
345
  Root: {
349
- style: () => ({
350
- // Aligns the center of the slider handles with the histogram bars
351
- width: 'calc(100% + 14px)',
352
- margin: '0 -7px',
353
- }),
354
- },
355
- InnerTrack: {
356
- style: ({$theme}) => {
357
- if (!isRange) {
358
- return {
359
- // For range selection we use the color as is, but when selecting the single value,
360
- // we don't want the track standing out, so mute its color
361
- background: theme.colors.mono400,
362
- };
363
- }
364
- },
365
- },
366
- Thumb: {
367
- style: () => ({
368
- // Slider handles are small enough to visually be centered within each histogram bar
369
- height: '18px',
370
- width: '18px',
371
- fontSize: '0px',
372
- }),
346
+ style: ({$theme}) => ({marginBottom: $theme.sizing.scale500}),
373
347
  },
374
348
  }}
375
- />
376
- </div>
349
+ >
350
+ <Button
351
+ type="button"
352
+ overrides={{BaseButton: {style: {width: '100%'}}}}
353
+ >
354
+ &#60;
355
+ </Button>
356
+ <Button
357
+ type="button"
358
+ overrides={{BaseButton: {style: {width: '100%'}}}}
359
+ >
360
+ &#62;
361
+ </Button>
362
+ <Button
363
+ type="button"
364
+ overrides={{BaseButton: {style: {width: '100%'}}}}
365
+ >
366
+ &#8804;
367
+ </Button>
368
+ <Button
369
+ type="button"
370
+ overrides={{BaseButton: {style: {width: '100%'}}}}
371
+ >
372
+ &#8805;
373
+ </Button>
374
+ <Button
375
+ type="button"
376
+ overrides={{BaseButton: {style: {width: '100%'}}}}
377
+ >
378
+ &#61;
379
+ </Button>
380
+ </ButtonGroup>
381
+ )}
382
+
377
383
  <div
378
384
  className={css({
379
385
  display: 'flex',
380
- marginTop: theme.sizing.scale400,
381
- // This % gap is visually appealing given the filter box width
382
- gap: '30%',
383
386
  justifyContent: 'space-between',
387
+ marginLeft: theme.sizing.scale300,
388
+ marginRight: theme.sizing.scale300,
384
389
  })}
385
390
  >
391
+ <ParagraphXSmall>{format(min, props.options)}</ParagraphXSmall>{' '}
392
+ <ParagraphXSmall>{format(max, props.options)}</ParagraphXSmall>
393
+ </div>
394
+
395
+ <div className={css({display: 'flex', justifyContent: 'space-between'})}>
386
396
  <Input
387
- min={min}
388
- max={max}
389
- size={INPUT_SIZE.mini}
390
- overrides={{Root: {style: {width: '100%'}}}}
391
- value={inputValueLower}
397
+ size={INPUT_SIZE.compact}
398
+ overrides={{Root: {style: {width: isRange ? '152px' : '100%'}}}}
399
+ disabled={leftDisabled}
400
+ inputRef={leftInputRef}
401
+ value={left}
392
402
  onChange={event => {
393
403
  if (validateInput(event.target.value)) {
394
- isRange
395
- ? // $FlowFixMe - we know it is a number by now
396
- setLower(event.target.value)
397
- : // $FlowFixMe - we know it is a number by now
398
- setSingle(event.target.value);
404
+ setLeft(event.target.value);
399
405
  }
400
406
  }}
401
- onFocus={() => setFocus(true)}
402
- onBlur={() => setFocus(false)}
403
407
  />
408
+
404
409
  {isRange && (
405
410
  <Input
406
- min={min}
407
- max={max}
408
- size={INPUT_SIZE.mini}
409
- overrides={{
410
- Input: {style: {textAlign: 'right'}},
411
- Root: {style: {width: '100%'}},
412
- }}
413
- value={inputValueUpper}
411
+ size={INPUT_SIZE.compact}
412
+ overrides={{Root: {style: {width: '152px'}}}}
413
+ disabled={rightDisabled}
414
+ inputRef={rightInputRef}
415
+ value={right}
414
416
  onChange={event => {
415
417
  if (validateInput(event.target.value)) {
416
- // $FlowFixMe - we know it is a number by now
417
- setUpper(event.target.value);
418
+ setRight(event.target.value);
418
419
  }
419
420
  }}
420
- onFocus={() => setFocus(true)}
421
- onBlur={() => setFocus(false)}
422
421
  />
423
422
  )}
424
423
  </div>
@@ -481,9 +480,24 @@ function NumericalColumn(options: OptionsT): NumericalColumnT {
481
480
  kind: COLUMNS.NUMERICAL,
482
481
  buildFilter: function(params) {
483
482
  return function(data) {
484
- const value = roundToFixed(data, normalizedOptions.precision);
485
- const included =
486
- value >= params.lowerValue && value <= params.upperValue;
483
+ const included = params.comparisons.some(c => {
484
+ const left = roundToFixed(data, normalizedOptions.precision);
485
+ const right = roundToFixed(c.value, normalizedOptions.precision);
486
+ switch (c.operation) {
487
+ case NUMERICAL_OPERATIONS.EQ:
488
+ return left === right;
489
+ case NUMERICAL_OPERATIONS.GT:
490
+ return left > right;
491
+ case NUMERICAL_OPERATIONS.GTE:
492
+ return left >= right;
493
+ case NUMERICAL_OPERATIONS.LT:
494
+ return left < right;
495
+ case NUMERICAL_OPERATIONS.LTE:
496
+ return left <= right;
497
+ default:
498
+ return true;
499
+ }
500
+ });
487
501
  return params.exclude ? !included : included;
488
502
  };
489
503
  },