baseui 10.8.0 → 10.9.0
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.
- package/a11y/a11y.js +2 -2
- package/a11y/a11y.js.flow +3 -3
- package/button/styled-components.js +47 -18
- package/button/styled-components.js.flow +25 -5
- package/combobox/combobox.js +6 -3
- package/combobox/combobox.js.flow +4 -2
- package/combobox/types.js.flow +2 -0
- package/data-table/column-categorical.js +1 -1
- package/data-table/column-categorical.js.flow +2 -2
- package/data-table/column-numerical.js +307 -355
- package/data-table/column-numerical.js.flow +273 -287
- package/data-table/constants.js +17 -11
- package/data-table/constants.js.flow +11 -8
- package/data-table/data-table.js +53 -50
- package/data-table/data-table.js.flow +18 -13
- package/data-table/filter-shell.js +27 -4
- package/data-table/filter-shell.js.flow +33 -9
- package/data-table/locale.js +4 -2
- package/data-table/locale.js.flow +6 -2
- package/data-table/measure-column-widths.js +83 -121
- package/data-table/measure-column-widths.js.flow +87 -109
- package/datepicker/styled-components.js +1 -1
- package/datepicker/styled-components.js.flow +4 -1
- package/drawer/drawer.js +3 -1
- package/drawer/drawer.js.flow +7 -1
- package/es/a11y/a11y.js +2 -2
- package/es/button/styled-components.js +32 -2
- package/es/combobox/combobox.js +6 -3
- package/es/data-table/column-categorical.js +2 -2
- package/es/data-table/column-numerical.js +245 -317
- package/es/data-table/constants.js +12 -8
- package/es/data-table/data-table.js +18 -16
- package/es/data-table/filter-shell.js +26 -4
- package/es/data-table/locale.js +4 -2
- package/es/data-table/measure-column-widths.js +75 -86
- package/es/datepicker/styled-components.js +1 -1
- package/es/drawer/drawer.js +3 -1
- package/es/index.js +1 -1
- package/es/map-marker/badge-enhancer.js +61 -0
- package/es/map-marker/constants.js +146 -2
- package/es/map-marker/drag-shadow.js +32 -0
- package/es/map-marker/fixed-marker.js +54 -48
- package/es/map-marker/floating-marker.js +21 -12
- package/es/map-marker/index.js +1 -1
- package/es/map-marker/label-enhancer.js +39 -0
- package/es/map-marker/needle.js +26 -0
- package/es/map-marker/pin-head.js +42 -40
- package/es/map-marker/styled-components.js +177 -32
- package/es/map-marker/types.js +1 -1
- package/es/menu/maybe-child-menu.js +0 -2
- package/es/menu/nested-menus.js +49 -3
- package/es/menu/stateful-container.js +13 -12
- package/es/modal/modal.js +3 -1
- package/es/popover/popover.js +3 -1
- package/es/progress-bar/index.js +1 -1
- package/es/progress-bar/progressbar.js +25 -10
- package/es/progress-bar/styled-components.js +9 -5
- package/es/select/select-component.js +2 -10
- package/es/spinner/styled-components.js +34 -16
- package/es/table/filter.js +3 -1
- package/es/themes/dark-theme/color-component-tokens.js +4 -0
- package/es/themes/light-theme/color-component-tokens.js +4 -0
- package/es/timezonepicker/timezone-picker.js +53 -36
- package/es/timezonepicker/tzdata.js +2 -0
- package/es/timezonepicker/update-tzdata.js +69 -0
- package/esm/a11y/a11y.js +3 -3
- package/esm/button/styled-components.js +47 -18
- package/esm/combobox/combobox.js +6 -3
- package/esm/data-table/column-categorical.js +2 -2
- package/esm/data-table/column-numerical.js +304 -353
- package/esm/data-table/constants.js +12 -8
- package/esm/data-table/data-table.js +53 -50
- package/esm/data-table/filter-shell.js +26 -4
- package/esm/data-table/locale.js +4 -2
- package/esm/data-table/measure-column-widths.js +83 -121
- package/esm/datepicker/styled-components.js +1 -1
- package/esm/drawer/drawer.js +3 -1
- package/esm/index.js +1 -1
- package/esm/map-marker/badge-enhancer.js +79 -0
- package/esm/map-marker/constants.js +94 -4
- package/esm/map-marker/drag-shadow.js +53 -0
- package/esm/map-marker/fixed-marker.js +84 -80
- package/esm/map-marker/floating-marker.js +22 -13
- package/esm/map-marker/index.js +1 -1
- package/esm/map-marker/label-enhancer.js +60 -0
- package/esm/map-marker/needle.js +43 -0
- package/esm/map-marker/pin-head.js +77 -66
- package/esm/map-marker/styled-components.js +182 -51
- package/esm/map-marker/types.js +1 -1
- package/esm/menu/maybe-child-menu.js +0 -2
- package/esm/menu/nested-menus.js +66 -5
- package/esm/menu/stateful-container.js +15 -13
- package/esm/modal/modal.js +3 -1
- package/esm/popover/popover.js +3 -1
- package/esm/progress-bar/index.js +1 -1
- package/esm/progress-bar/progressbar.js +32 -10
- package/esm/progress-bar/styled-components.js +9 -4
- package/esm/select/select-component.js +2 -11
- package/esm/spinner/styled-components.js +35 -16
- package/esm/table/filter.js +3 -1
- package/esm/themes/dark-theme/color-component-tokens.js +4 -0
- package/esm/themes/light-theme/color-component-tokens.js +4 -0
- package/esm/timezonepicker/timezone-picker.js +64 -36
- package/esm/timezonepicker/tzdata.js +2 -0
- package/esm/timezonepicker/update-tzdata.js +160 -0
- package/index.js +6 -0
- package/index.js.flow +1 -1
- package/map-marker/badge-enhancer.js +90 -0
- package/map-marker/badge-enhancer.js.flow +86 -0
- package/map-marker/constants.js +103 -5
- package/map-marker/constants.js.flow +152 -0
- package/map-marker/drag-shadow.js +64 -0
- package/map-marker/drag-shadow.js.flow +52 -0
- package/map-marker/fixed-marker.js +84 -78
- package/map-marker/fixed-marker.js.flow +78 -66
- package/map-marker/floating-marker.js +22 -13
- package/map-marker/floating-marker.js.flow +30 -17
- package/map-marker/index.d.ts +125 -24
- package/map-marker/index.js +18 -0
- package/map-marker/index.js.flow +3 -0
- package/map-marker/label-enhancer.js +71 -0
- package/map-marker/label-enhancer.js.flow +63 -0
- package/map-marker/needle.js +54 -0
- package/map-marker/needle.js.flow +29 -0
- package/map-marker/pin-head.js +80 -69
- package/map-marker/pin-head.js.flow +122 -84
- package/map-marker/styled-components.js +200 -62
- package/map-marker/styled-components.js.flow +172 -22
- package/map-marker/types.js.flow +69 -20
- package/menu/index.d.ts +9 -4
- package/menu/maybe-child-menu.js +0 -2
- package/menu/maybe-child-menu.js.flow +0 -2
- package/menu/nested-menus.js +66 -5
- package/menu/nested-menus.js.flow +50 -5
- package/menu/stateful-container.js +15 -13
- package/menu/stateful-container.js.flow +19 -13
- package/menu/types.js.flow +7 -1
- package/modal/modal.js +3 -1
- package/modal/modal.js.flow +2 -0
- package/package.json +5 -4
- package/popover/popover.js +3 -1
- package/popover/popover.js.flow +2 -0
- package/progress-bar/index.d.ts +2 -0
- package/progress-bar/index.js +6 -0
- package/progress-bar/index.js.flow +1 -0
- package/progress-bar/progressbar.js +32 -10
- package/progress-bar/progressbar.js.flow +35 -9
- package/progress-bar/styled-components.js +9 -4
- package/progress-bar/styled-components.js.flow +15 -4
- package/progress-bar/types.js.flow +12 -2
- package/select/select-component.js +2 -11
- package/select/select-component.js.flow +5 -7
- package/spinner/styled-components.js +35 -16
- package/spinner/styled-components.js.flow +37 -19
- package/spinner/types.js.flow +10 -0
- package/styles/index.js.flow +1 -1
- package/table/filter.js +3 -1
- package/table/filter.js.flow +5 -1
- package/themes/dark-theme/color-component-tokens.js +4 -0
- package/themes/dark-theme/color-component-tokens.js.flow +4 -0
- package/themes/light-theme/color-component-tokens.js +4 -0
- package/themes/light-theme/color-component-tokens.js.flow +4 -0
- package/themes/types.js.flow +4 -0
- package/timezonepicker/timezone-picker.js +69 -41
- package/timezonepicker/timezone-picker.js.flow +52 -46
- package/timezonepicker/types.js.flow +1 -1
- package/timezonepicker/tzdata.js +10 -0
- package/timezonepicker/tzdata.js.flow +347 -0
- package/timezonepicker/update-tzdata.js +164 -0
- package/timezonepicker/update-tzdata.js.flow +70 -0
|
@@ -12,26 +12,25 @@ 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 {Paragraph4} from '../typography/index.js';
|
|
16
15
|
|
|
17
16
|
import Column from './column.js';
|
|
18
|
-
import {
|
|
19
|
-
|
|
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';
|
|
20
24
|
import type {ColumnT, SharedColumnOptionsT} from './types.js';
|
|
21
25
|
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';
|
|
22
28
|
|
|
23
29
|
type NumericalFormats =
|
|
24
30
|
| typeof NUMERICAL_FORMATS.DEFAULT
|
|
25
31
|
| typeof NUMERICAL_FORMATS.ACCOUNTING
|
|
26
32
|
| typeof NUMERICAL_FORMATS.PERCENTAGE;
|
|
27
33
|
|
|
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
|
-
|
|
35
34
|
type OptionsT = {|
|
|
36
35
|
...SharedColumnOptionsT<number>,
|
|
37
36
|
format?: NumericalFormats | ((value: number) => string),
|
|
@@ -40,12 +39,11 @@ type OptionsT = {|
|
|
|
40
39
|
|};
|
|
41
40
|
|
|
42
41
|
type FilterParametersT = {|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
operation: NumericalOperations,
|
|
46
|
-
|}>,
|
|
42
|
+
lowerValue: number,
|
|
43
|
+
upperValue: number,
|
|
47
44
|
description: string,
|
|
48
45
|
exclude: boolean,
|
|
46
|
+
excludeKind: ExcludeKind,
|
|
49
47
|
|};
|
|
50
48
|
|
|
51
49
|
type NumericalColumnT = ColumnT<number, FilterParametersT>;
|
|
@@ -86,224 +84,197 @@ function validateInput(input) {
|
|
|
86
84
|
return Boolean(parseFloat(input)) || input === '' || input === '-';
|
|
87
85
|
}
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
}
|
|
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;
|
|
147
118
|
}
|
|
148
|
-
|
|
119
|
+
return bisect.center(bins, lower);
|
|
120
|
+
}, [isRange, data, lower, upper]);
|
|
149
121
|
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
}
|
|
153
|
+
|
|
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
|
+
});
|
|
158
169
|
|
|
159
170
|
function NumericalFilter(props) {
|
|
160
171
|
const [css, theme] = useStyletron();
|
|
161
172
|
const locale = React.useContext(LocaleContext);
|
|
162
173
|
|
|
163
|
-
const
|
|
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
|
+
|
|
164
189
|
const [exclude, setExclude] = React.useState(initialState.exclude);
|
|
165
|
-
|
|
166
|
-
|
|
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),
|
|
167
209
|
);
|
|
168
|
-
const [
|
|
169
|
-
initialState.
|
|
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),
|
|
170
218
|
);
|
|
171
|
-
const [left, setLeft] = React.useState(initialState.left);
|
|
172
|
-
const [right, setRight] = React.useState(initialState.right);
|
|
173
219
|
|
|
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.
|
|
174
223
|
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]);
|
|
177
224
|
|
|
178
|
-
|
|
179
|
-
if (!left) {
|
|
180
|
-
setLeft(min.toString());
|
|
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]);
|
|
225
|
+
const excludeKind = isRange ? 'range' : 'value';
|
|
212
226
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
case 3:
|
|
220
|
-
setRight(max.toString());
|
|
221
|
-
break;
|
|
222
|
-
case 0:
|
|
223
|
-
case 2:
|
|
224
|
-
setLeft(min.toString());
|
|
225
|
-
break;
|
|
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];
|
|
226
233
|
}
|
|
227
|
-
|
|
234
|
+
|
|
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
|
+
}
|
|
228
253
|
|
|
229
254
|
return (
|
|
230
255
|
<FilterShell
|
|
231
256
|
exclude={exclude}
|
|
232
257
|
onExcludeChange={() => setExclude(!exclude)}
|
|
258
|
+
excludeKind={excludeKind}
|
|
233
259
|
onApply={() => {
|
|
234
260
|
if (isRange) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
}
|
|
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
|
+
});
|
|
300
270
|
} else {
|
|
301
|
-
const value = parseFloat(
|
|
302
|
-
const operation = NUMERICAL_OPERATIONS.EQ;
|
|
271
|
+
const value = parseFloat(inputValueLower);
|
|
303
272
|
props.setFilter({
|
|
304
|
-
comparisons: [{value, operation}],
|
|
305
273
|
description: `= ${value}`,
|
|
306
|
-
exclude,
|
|
274
|
+
exclude: exclude,
|
|
275
|
+
lowerValue: inputValueLower,
|
|
276
|
+
upperValue: inputValueLower,
|
|
277
|
+
excludeKind,
|
|
307
278
|
});
|
|
308
279
|
}
|
|
309
280
|
|
|
@@ -311,7 +282,7 @@ function NumericalFilter(props) {
|
|
|
311
282
|
}}
|
|
312
283
|
>
|
|
313
284
|
<ButtonGroup
|
|
314
|
-
size={SIZE.
|
|
285
|
+
size={SIZE.mini}
|
|
315
286
|
mode={MODE.radio}
|
|
316
287
|
selected={comparatorIndex}
|
|
317
288
|
onClick={(_, index) => setComparatorIndex(index)}
|
|
@@ -324,100 +295,130 @@ function NumericalFilter(props) {
|
|
|
324
295
|
<Button
|
|
325
296
|
type="button"
|
|
326
297
|
overrides={{BaseButton: {style: {width: '100%'}}}}
|
|
298
|
+
aria-label={locale.datatable.numericalFilterRange}
|
|
327
299
|
>
|
|
328
300
|
{locale.datatable.numericalFilterRange}
|
|
329
301
|
</Button>
|
|
330
302
|
<Button
|
|
331
303
|
type="button"
|
|
332
304
|
overrides={{BaseButton: {style: {width: '100%'}}}}
|
|
305
|
+
aria-label={locale.datatable.numericalFilterSingleValue}
|
|
333
306
|
>
|
|
334
307
|
{locale.datatable.numericalFilterSingleValue}
|
|
335
308
|
</Button>
|
|
336
309
|
</ButtonGroup>
|
|
337
310
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
}}
|
|
344
342
|
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,
|
|
345
348
|
Root: {
|
|
346
|
-
style: (
|
|
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
|
+
}),
|
|
347
373
|
},
|
|
348
374
|
}}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
type="button"
|
|
352
|
-
overrides={{BaseButton: {style: {width: '100%'}}}}
|
|
353
|
-
>
|
|
354
|
-
<
|
|
355
|
-
</Button>
|
|
356
|
-
<Button
|
|
357
|
-
type="button"
|
|
358
|
-
overrides={{BaseButton: {style: {width: '100%'}}}}
|
|
359
|
-
>
|
|
360
|
-
>
|
|
361
|
-
</Button>
|
|
362
|
-
<Button
|
|
363
|
-
type="button"
|
|
364
|
-
overrides={{BaseButton: {style: {width: '100%'}}}}
|
|
365
|
-
>
|
|
366
|
-
≤
|
|
367
|
-
</Button>
|
|
368
|
-
<Button
|
|
369
|
-
type="button"
|
|
370
|
-
overrides={{BaseButton: {style: {width: '100%'}}}}
|
|
371
|
-
>
|
|
372
|
-
≥
|
|
373
|
-
</Button>
|
|
374
|
-
<Button
|
|
375
|
-
type="button"
|
|
376
|
-
overrides={{BaseButton: {style: {width: '100%'}}}}
|
|
377
|
-
>
|
|
378
|
-
=
|
|
379
|
-
</Button>
|
|
380
|
-
</ButtonGroup>
|
|
381
|
-
)}
|
|
382
|
-
|
|
375
|
+
/>
|
|
376
|
+
</div>
|
|
383
377
|
<div
|
|
384
378
|
className={css({
|
|
385
379
|
display: 'flex',
|
|
380
|
+
marginTop: theme.sizing.scale400,
|
|
381
|
+
// This % gap is visually appealing given the filter box width
|
|
382
|
+
gap: '30%',
|
|
386
383
|
justifyContent: 'space-between',
|
|
387
|
-
marginLeft: theme.sizing.scale300,
|
|
388
|
-
marginRight: theme.sizing.scale300,
|
|
389
384
|
})}
|
|
390
385
|
>
|
|
391
|
-
<Paragraph4>{format(min, props.options)}</Paragraph4>{' '}
|
|
392
|
-
<Paragraph4>{format(max, props.options)}</Paragraph4>
|
|
393
|
-
</div>
|
|
394
|
-
|
|
395
|
-
<div className={css({display: 'flex', justifyContent: 'space-between'})}>
|
|
396
386
|
<Input
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
value={
|
|
387
|
+
min={min}
|
|
388
|
+
max={max}
|
|
389
|
+
size={INPUT_SIZE.mini}
|
|
390
|
+
overrides={{Root: {style: {width: '100%'}}}}
|
|
391
|
+
value={inputValueLower}
|
|
402
392
|
onChange={event => {
|
|
403
393
|
if (validateInput(event.target.value)) {
|
|
404
|
-
|
|
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);
|
|
405
399
|
}
|
|
406
400
|
}}
|
|
401
|
+
onFocus={() => setFocus(true)}
|
|
402
|
+
onBlur={() => setFocus(false)}
|
|
407
403
|
/>
|
|
408
|
-
|
|
409
404
|
{isRange && (
|
|
410
405
|
<Input
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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}
|
|
416
414
|
onChange={event => {
|
|
417
415
|
if (validateInput(event.target.value)) {
|
|
418
|
-
|
|
416
|
+
// $FlowFixMe - we know it is a number by now
|
|
417
|
+
setUpper(event.target.value);
|
|
419
418
|
}
|
|
420
419
|
}}
|
|
420
|
+
onFocus={() => setFocus(true)}
|
|
421
|
+
onBlur={() => setFocus(false)}
|
|
421
422
|
/>
|
|
422
423
|
)}
|
|
423
424
|
</div>
|
|
@@ -480,24 +481,9 @@ function NumericalColumn(options: OptionsT): NumericalColumnT {
|
|
|
480
481
|
kind: COLUMNS.NUMERICAL,
|
|
481
482
|
buildFilter: function(params) {
|
|
482
483
|
return function(data) {
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
});
|
|
484
|
+
const value = roundToFixed(data, normalizedOptions.precision);
|
|
485
|
+
const included =
|
|
486
|
+
value >= params.lowerValue && value <= params.upperValue;
|
|
501
487
|
return params.exclude ? !included : included;
|
|
502
488
|
};
|
|
503
489
|
},
|