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