@vuu-ui/vuu-filters 4.0.0-beta.7 → 4.0.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.
- package/README.md +40 -0
- package/package.json +14 -14
- package/src/FilterAggregator.js +16 -5
- package/src/column-filter/ColumnFilter.css.js +23 -0
- package/src/column-filter/ColumnFilter.js +35 -8
- package/src/column-filter/column-filter-utils.js +40 -0
- package/src/column-filter/useColumnFilter.js +68 -16
- package/src/filter-clause/operator-picker/OperatorPicker.js +30 -3
- package/src/filter-container/ExtendedSingleValueFilterClause.js +25 -17
- package/src/filter-container/FilterContainer.js +75 -5
- package/src/filter-container/useFilterContainer.js +7 -0
- package/src/filter-context-menu/useFilterContextMenu.js +6 -1
- package/types/column-filter/column-filter-utils.d.ts +8 -0
- package/types/column-filter/useColumnFilter.d.ts +15 -1
- package/types/filter-clause/operator-picker/OperatorPicker.d.ts +17 -7
- package/types/filter-container/ExtendedSingleValueFilterClause.d.ts +6 -3
- package/types/filter-container/FilterContainer.d.ts +1 -1
- package/types/filter-container/useFilterContainer.d.ts +3 -1
- package/types/index.d.ts +1 -0
- package/types/src/FilterAggregator.d.ts +28 -0
- package/types/src/FilterModel.d.ts +44 -0
- package/types/src/column-filter/ColumnFilter.d.ts +6 -0
- package/types/src/column-filter/useColumnFilter.d.ts +43 -0
- package/types/src/column-filter/utils.d.ts +4 -0
- package/types/src/custom-filters/CustomFilters.d.ts +7 -0
- package/types/src/custom-filters/filterBarFocusManagement.d.ts +1 -0
- package/types/src/custom-filters/index.d.ts +1 -0
- package/types/src/custom-filters/useCustomFilters.d.ts +33 -0
- package/types/src/custom-filters/useFilterState.d.ts +17 -0
- package/types/src/filter-bar/FilterBar.d.ts +33 -0
- package/types/src/filter-bar/FilterBarMenu.d.ts +1 -0
- package/types/src/filter-bar/index.d.ts +2 -0
- package/types/src/filter-bar/useFilterBar.d.ts +7 -0
- package/types/src/filter-clause/ExpandoCombobox.d.ts +10 -0
- package/types/src/filter-clause/FilterClause.d.ts +16 -0
- package/types/src/filter-clause/column-picker/ColumnPicker.d.ts +13 -0
- package/types/src/filter-clause/column-picker/useColumnPicker.d.ts +11 -0
- package/types/src/filter-clause/filterClauseFocusManagement.d.ts +15 -0
- package/types/src/filter-clause/filterClauseTypes.d.ts +12 -0
- package/types/src/filter-clause/index.d.ts +5 -0
- package/types/src/filter-clause/operator-picker/OperatorPicker.d.ts +14 -0
- package/types/src/filter-clause/operator-picker/operator-utils.d.ts +5 -0
- package/types/src/filter-clause/operator-picker/useOperatorPicker.d.ts +12 -0
- package/types/src/filter-clause/useFilterClause.d.ts +26 -0
- package/types/src/filter-clause/value-editors/FilterClauseValueEditor.d.ts +26 -0
- package/types/src/filter-clause/value-editors/FilterClauseValueEditorDate.d.ts +9 -0
- package/types/src/filter-clause/value-editors/FilterClauseValueEditorNumber.d.ts +9 -0
- package/types/src/filter-clause/value-editors/FilterClauseValueEditorText.d.ts +9 -0
- package/types/src/filter-clause/value-editors/FilterClauseValueEditorTime.d.ts +9 -0
- package/types/src/filter-container/ExtendedSingleValueFilterClause.d.ts +21 -0
- package/types/src/filter-container/FilterContainer.d.ts +23 -0
- package/types/src/filter-container/useFilterContainer.d.ts +22 -0
- package/types/src/filter-context-menu/useFilterContextMenu.d.ts +6 -0
- package/types/src/filter-display/FilterDisplay.d.ts +10 -0
- package/types/src/filter-editor/FilterClauseCombinator.d.ts +9 -0
- package/types/src/filter-editor/FilterEditor.d.ts +14 -0
- package/types/src/filter-editor/index.d.ts +1 -0
- package/types/src/filter-editor/useFilterEditor.d.ts +32 -0
- package/types/src/filter-panel/FilterPanel.d.ts +9 -0
- package/types/src/filter-panel/useFilterPanel.d.ts +14 -0
- package/types/src/filter-pill/FilterMenu.d.ts +24 -0
- package/types/src/filter-pill/FilterPill.d.ts +16 -0
- package/types/src/filter-pill/FilterPillNext.d.ts +15 -0
- package/types/src/filter-pill/filterAsReactNode.d.ts +3 -0
- package/types/src/filter-pill/getFilterLabel.d.ts +2 -0
- package/types/src/filter-pill/getFilterTooltipText.d.ts +2 -0
- package/types/src/filter-pill/index.d.ts +1 -0
- package/types/src/filter-pill/useFilterPillNext.d.ts +25 -0
- package/types/src/filter-pill-menu/FilterPillMenu.d.ts +17 -0
- package/types/src/filter-pill-menu/FilterPillMenuOptions.d.ts +12 -0
- package/types/src/filter-pill-menu/index.d.ts +1 -0
- package/types/src/filter-provider/FilterContext.d.ts +39 -0
- package/types/src/filter-provider/FilterProvider.d.ts +10 -0
- package/types/src/filter-provider/filter-descriptor-utils.d.ts +7 -0
- package/types/src/filter-toggle-button/FilterToggleButton.d.ts +9 -0
- package/types/src/filter-utils.d.ts +60 -0
- package/types/src/index.d.ts +24 -0
- package/types/src/inline-filter/InlineFilter.d.ts +12 -0
- package/types/src/inline-filter/index.d.ts +1 -0
- package/types/src/inline-filter/useInlineFilter.d.ts +5 -0
- package/types/src/quick-filters/QuickFilters.d.ts +17 -0
- package/types/src/quick-filters/index.d.ts +1 -0
- package/types/src/quick-filters/useQuickFilters.d.ts +12 -0
- package/types/src/saved-filters/DeleteFilterPrompt.d.ts +9 -0
- package/types/src/saved-filters/FilterNamePrompt.d.ts +6 -0
- package/types/src/saved-filters/SavedFilterPanel.d.ts +14 -0
- package/types/src/saved-filters/useFilterNamePrompt.d.ts +28 -0
- package/types/src/saved-filters/useSavedFilterPanel.d.ts +13 -0
- package/types/src/tabbed-filter-container/TabbedFilterContainer.d.ts +10 -0
- package/types/src/toggle-filter/ToggleFilter.d.ts +17 -0
- package/types/src/toggle-filter/useToggleFilter.d.ts +11 -0
package/README.md
CHANGED
|
@@ -15,6 +15,46 @@ Vuu also supports `MultiValueFilterClause`
|
|
|
15
15
|
|
|
16
16
|
## ColumnFilter
|
|
17
17
|
|
|
18
|
+
Set `showOperatorPicker` to render a compact operator dropdown before the value.
|
|
19
|
+
It is opt-in; existing fixed-operator filters are unchanged.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<ColumnFilter
|
|
23
|
+
column={{ name: "price", serverDataType: "double" }}
|
|
24
|
+
showOperatorPicker
|
|
25
|
+
operators={["=", ">", ">=", "between-inclusive"]}
|
|
26
|
+
defaultValue=""
|
|
27
|
+
onCommit={handleCommit}
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`operators` replaces the default options and preserves their order. Numeric
|
|
32
|
+
(including scaled-decimal) and temporal columns default to `=`, `!=`, `>`, `>=`,
|
|
33
|
+
`<`, `<=`; text columns to `=`, `!=`, `starts`, `ends`, `contains`; boolean
|
|
34
|
+
columns to `=`, `!=`. Numeric and temporal filters can also opt into `between`
|
|
35
|
+
and `between-inclusive`. Multi-value `in` filters are not supported by this
|
|
36
|
+
control. Empty lists, incompatible operators, and selections outside the list
|
|
37
|
+
raise an error.
|
|
38
|
+
|
|
39
|
+
Use `defaultOperator` for an uncontrolled picker, or `operator` together with
|
|
40
|
+
`onOperatorChange(event, operator)` for a controlled picker. The initial operator
|
|
41
|
+
is `=` if allowed, otherwise the first option. Operator control is independent
|
|
42
|
+
of value control (`value` / `defaultValue`).
|
|
43
|
+
|
|
44
|
+
Selecting a different operator preserves the value and immediately commits a
|
|
45
|
+
populated, valid filter using the new operator. An already empty value only changes the
|
|
46
|
+
selection. Switching to a range retains the lower value and starts with an empty
|
|
47
|
+
upper value; switching back retains the lower value (and clears a filter if only
|
|
48
|
+
the upper value was populated). Incomplete ranges use the
|
|
49
|
+
existing partial-range commit behaviour. The operator picker respects
|
|
50
|
+
`InputProps.disabled` and `InputProps.readOnly`.
|
|
51
|
+
|
|
52
|
+
`FilterContainerColumnFilter` accepts the same picker options, manages selection
|
|
53
|
+
internally, and restores operators and values from externally loaded or saved
|
|
54
|
+
filters. A loaded operator must be included in the configured options. Clearing
|
|
55
|
+
an external filter resets the operator to its default; clearing a value locally
|
|
56
|
+
keeps the selected operator.
|
|
57
|
+
|
|
18
58
|
onCommit is a `ColumnFilterCommitHandler`
|
|
19
59
|
|
|
20
60
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.0.
|
|
2
|
+
"version": "4.0.1",
|
|
3
3
|
"main": "./src/index.js",
|
|
4
4
|
"author": "heswell",
|
|
5
5
|
"license": "Apache-2.0",
|
|
@@ -10,21 +10,21 @@
|
|
|
10
10
|
"type-defs": "node ../../scripts/build-type-defs.mjs"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@vuu-ui/vuu-data-types": "4.0.
|
|
14
|
-
"@vuu-ui/vuu-protocol-types": "4.0.
|
|
15
|
-
"@vuu-ui/vuu-table-types": "4.0.
|
|
16
|
-
"@vuu-ui/vuu-filter-types": "4.0.
|
|
13
|
+
"@vuu-ui/vuu-data-types": "4.0.1",
|
|
14
|
+
"@vuu-ui/vuu-protocol-types": "4.0.1",
|
|
15
|
+
"@vuu-ui/vuu-table-types": "4.0.1",
|
|
16
|
+
"@vuu-ui/vuu-filter-types": "4.0.1"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@vuu-ui/vuu-context-menu": "4.0.
|
|
20
|
-
"@vuu-ui/vuu-data-react": "4.0.
|
|
21
|
-
"@vuu-ui/vuu-filter-parser": "4.0.
|
|
22
|
-
"@vuu-ui/vuu-popups": "4.0.
|
|
23
|
-
"@vuu-ui/vuu-ui-controls": "4.0.
|
|
24
|
-
"@vuu-ui/vuu-table": "4.0.
|
|
25
|
-
"@vuu-ui/vuu-utils": "4.0.
|
|
26
|
-
"@salt-ds/core": "1.
|
|
27
|
-
"@salt-ds/styles": "0.4.
|
|
19
|
+
"@vuu-ui/vuu-context-menu": "4.0.1",
|
|
20
|
+
"@vuu-ui/vuu-data-react": "4.0.1",
|
|
21
|
+
"@vuu-ui/vuu-filter-parser": "4.0.1",
|
|
22
|
+
"@vuu-ui/vuu-popups": "4.0.1",
|
|
23
|
+
"@vuu-ui/vuu-ui-controls": "4.0.1",
|
|
24
|
+
"@vuu-ui/vuu-table": "4.0.1",
|
|
25
|
+
"@vuu-ui/vuu-utils": "4.0.1",
|
|
26
|
+
"@salt-ds/core": "1.74.0",
|
|
27
|
+
"@salt-ds/styles": "0.4.2",
|
|
28
28
|
"@salt-ds/window": "0.1.2"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
package/src/FilterAggregator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EpochTimestamp, getTemporalInfo, getTypedValueForDescriptor, isAndFilter, isBetweenFilter, isExtendedFilter, isMultiClauseFilter, isSingleValueFilter } from "@vuu-ui/vuu-utils";
|
|
1
|
+
import { DataValidationError, EpochTimestamp, getScaledDecimalFilterOptions, getTemporalInfo, getTypedValueForDescriptor, isAndFilter, isBetweenFilter, isBetweenOperator, isExtendedFilter, isMultiClauseFilter, isScaledDecimalFilterOptions, isSingleValueFilter, isValidDecimalString } from "@vuu-ui/vuu-utils";
|
|
2
2
|
import { ExtendedSingleValueFilterClause } from "./filter-container/ExtendedSingleValueFilterClause.js";
|
|
3
3
|
function installExtendedFilters(filter, throwIfUndefined = false) {
|
|
4
4
|
if (void 0 !== filter) if (isExtendedFilter(filter)) {
|
|
@@ -12,7 +12,9 @@ function installExtendedFilters(filter, throwIfUndefined = false) {
|
|
|
12
12
|
else return filter;
|
|
13
13
|
if (throwIfUndefined) throw Error("filter is undefined");
|
|
14
14
|
}
|
|
15
|
-
const getExtendedFilterOptions = (temporalInfo, extendedFilterOptions)=>{
|
|
15
|
+
const getExtendedFilterOptions = (column, temporalInfo, extendedFilterOptions)=>{
|
|
16
|
+
const scaledDecimalOptions = getScaledDecimalFilterOptions(column);
|
|
17
|
+
if (scaledDecimalOptions) return scaledDecimalOptions;
|
|
16
18
|
if (temporalInfo?.kind === "time") return {
|
|
17
19
|
type: "TimeString",
|
|
18
20
|
date: "today",
|
|
@@ -28,8 +30,13 @@ const getFilterValue = (column, value, temporalInfo, options, throwIfInvalid)=>{
|
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
32
|
if (options?.type === "TimeString") return asFilterTimeString(value, temporalInfo, throwIfInvalid);
|
|
33
|
+
if (isScaledDecimalFilterOptions(options)) return asFilterDecimalString(value, throwIfInvalid);
|
|
31
34
|
return getTypedValueForDescriptor(value, column, throwIfInvalid);
|
|
32
35
|
};
|
|
36
|
+
const asFilterDecimalString = (value, throwIfInvalid)=>{
|
|
37
|
+
if (isValidDecimalString(value)) return value;
|
|
38
|
+
if (throwIfInvalid) throw new DataValidationError(`value ${value} is not a valid decimal`, "decimal", "string");
|
|
39
|
+
};
|
|
33
40
|
const timeOfDayPattern = /^\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
34
41
|
const asFilterTimeString = (value, temporalInfo, throwIfInvalid)=>{
|
|
35
42
|
if (timeOfDayPattern.test(value)) return 5 === value.length ? `${value}:00` : value;
|
|
@@ -52,7 +59,11 @@ const singleValueOps = [
|
|
|
52
59
|
"starts",
|
|
53
60
|
"ends"
|
|
54
61
|
];
|
|
55
|
-
const asSingleValueOp = (op)=>
|
|
62
|
+
const asSingleValueOp = (op)=>{
|
|
63
|
+
const operator = singleValueOps.find((candidate)=>candidate === op);
|
|
64
|
+
if (void 0 === operator) throw Error(`[FilterAggregator] unsupported single-value operator ${op}`);
|
|
65
|
+
return operator;
|
|
66
|
+
};
|
|
56
67
|
class FilterAggregator {
|
|
57
68
|
#filters = new Map();
|
|
58
69
|
constructor(filter){
|
|
@@ -66,7 +77,7 @@ class FilterAggregator {
|
|
|
66
77
|
}
|
|
67
78
|
add(column, value, op = "=", extendedFilterOptions) {
|
|
68
79
|
const temporalInfo = getTemporalInfo(column);
|
|
69
|
-
const options = getExtendedFilterOptions(temporalInfo, extendedFilterOptions);
|
|
80
|
+
const options = getExtendedFilterOptions(column, temporalInfo, extendedFilterOptions);
|
|
70
81
|
const isInclusive = "between-inclusive" === op;
|
|
71
82
|
const createClause = (op, value)=>options ? new ExtendedSingleValueFilterClause(column.name, op, value, options) : {
|
|
72
83
|
column: column.name,
|
|
@@ -87,7 +98,7 @@ class FilterAggregator {
|
|
|
87
98
|
} else {
|
|
88
99
|
const typedValue = getFilterValue(column, value.toString(), temporalInfo, options, true);
|
|
89
100
|
if (void 0 === typedValue) this.#filters.delete(column.name);
|
|
90
|
-
else this.#filters.set(column.name, createClause(
|
|
101
|
+
else this.#filters.set(column.name, createClause(isBetweenOperator(op) ? "=" : asSingleValueOp(op), typedValue));
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
has({ name }) {
|
|
@@ -51,6 +51,29 @@ const css = `
|
|
|
51
51
|
border-color: red;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
.vuuColumnFilter.vuuColumnFilter-withOperator {
|
|
55
|
+
max-width: none;
|
|
56
|
+
|
|
57
|
+
& [data-field="operator"] {
|
|
58
|
+
border-right: 1px solid var(--salt-separable-primary-borderColor);
|
|
59
|
+
margin-right: var(--salt-spacing-100);
|
|
60
|
+
align-self: stretch;
|
|
61
|
+
display: flex;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
& .vuuColumnFilter-operator {
|
|
65
|
+
background: none;
|
|
66
|
+
border: none;
|
|
67
|
+
width: auto;
|
|
68
|
+
min-width: 60px;
|
|
69
|
+
|
|
70
|
+
&.saltDropdown .saltDropdown-toggle {
|
|
71
|
+
flex-shrink: 0;
|
|
72
|
+
display: block;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
|
|
55
78
|
`;
|
|
56
79
|
export default css;
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { SegmentedButtonGroup } from "@salt-ds/core";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { SegmentedButtonGroup, useForkRef } from "@salt-ds/core";
|
|
3
3
|
import { getDataItemEditControl } from "@vuu-ui/vuu-data-react";
|
|
4
4
|
import clsx from "clsx";
|
|
5
|
-
import { forwardRef as external_react_forwardRef, useMemo } from "react";
|
|
5
|
+
import { forwardRef as external_react_forwardRef, useMemo, useRef } from "react";
|
|
6
6
|
import { useColumnFilter } from "./useColumnFilter.js";
|
|
7
7
|
import { useComponentCssInjection } from "@salt-ds/styles";
|
|
8
8
|
import { useWindow } from "@salt-ds/window";
|
|
9
9
|
import ColumnFilter_0 from "./ColumnFilter.css";
|
|
10
10
|
import { isBetweenOperator, withDateTimePattern } from "@vuu-ui/vuu-utils";
|
|
11
|
+
import { OperatorPicker } from "../filter-clause/operator-picker/OperatorPicker.js";
|
|
11
12
|
const classBase = "vuuColumnFilter";
|
|
12
|
-
const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(function({ InputProps: InputPropsProp, TypeaheadProps, className, column, defaultValue, extendedFilterOptions, labels, onColumnFilterChange, onColumnRangeFilterChange, onCommit: onCommitProp, operator =
|
|
13
|
+
const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(function({ InputProps: InputPropsProp, TypeaheadProps, className, column, defaultValue, defaultOperator, extendedFilterOptions, labels, onColumnFilterChange, onColumnRangeFilterChange, onCommit: onCommitProp, onOperatorChange, operator: operatorProp, operators: operatorsProp, showOperatorPicker = false, pattern, table, value: valueProp, values, variant, ...buttonGroupProps }, forwardRef) {
|
|
14
|
+
const rootRef = useRef(null);
|
|
15
|
+
const ref = useForkRef(rootRef, forwardRef);
|
|
13
16
|
const targetWindow = useWindow();
|
|
14
17
|
useComponentCssInjection({
|
|
15
18
|
testId: "vuu-filter-editor",
|
|
@@ -25,25 +28,49 @@ const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(functi
|
|
|
25
28
|
date,
|
|
26
29
|
time
|
|
27
30
|
]);
|
|
28
|
-
const { InputProps, InputPropsRange, isInvalid, onCommit, onCommitRange } = useColumnFilter({
|
|
31
|
+
const { InputProps, InputPropsRange, isInvalid, onCommit, onCommitRange, operator, operators, onOperatorSelect } = useColumnFilter({
|
|
29
32
|
InputProps: InputPropsProp,
|
|
30
33
|
column: controlDescriptor,
|
|
31
34
|
defaultValue,
|
|
35
|
+
defaultOperator,
|
|
32
36
|
extendedFilterOptions,
|
|
33
37
|
onColumnFilterChange,
|
|
34
38
|
onColumnRangeFilterChange,
|
|
35
39
|
onCommit: onCommitProp,
|
|
36
|
-
|
|
40
|
+
onOperatorChange,
|
|
41
|
+
operator: operatorProp,
|
|
42
|
+
operators: operatorsProp,
|
|
43
|
+
showOperatorPicker,
|
|
37
44
|
value: valueProp
|
|
38
45
|
});
|
|
39
46
|
return /*#__PURE__*/ jsxs(SegmentedButtonGroup, {
|
|
40
47
|
...buttonGroupProps,
|
|
41
48
|
className: clsx(classBase, className, {
|
|
42
49
|
[`${classBase}-invalid`]: isInvalid,
|
|
43
|
-
[`${classBase}-toggle`]: "toggle" === variant
|
|
50
|
+
[`${classBase}-toggle`]: "toggle" === variant,
|
|
51
|
+
[`${classBase}-withOperator`]: showOperatorPicker
|
|
44
52
|
}),
|
|
45
|
-
ref:
|
|
53
|
+
ref: ref,
|
|
46
54
|
children: [
|
|
55
|
+
operators ? /*#__PURE__*/ jsx(OperatorPicker, {
|
|
56
|
+
"aria-label": `${column.label ?? column.name} operator`,
|
|
57
|
+
className: `${classBase}-operator`,
|
|
58
|
+
disabled: InputProps?.disabled,
|
|
59
|
+
readOnly: InputProps?.readOnly,
|
|
60
|
+
operators: operators,
|
|
61
|
+
onSelect: (event, operator)=>{
|
|
62
|
+
onOperatorSelect(event, operator);
|
|
63
|
+
requestAnimationFrame(()=>{
|
|
64
|
+
const root = rootRef.current;
|
|
65
|
+
if (!root) return;
|
|
66
|
+
const { activeElement, body } = root.ownerDocument;
|
|
67
|
+
if (activeElement && activeElement !== body && !root.contains(activeElement)) return;
|
|
68
|
+
root.querySelector("input:not(:disabled)")?.focus();
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
value: operator,
|
|
72
|
+
variant: "dropdown"
|
|
73
|
+
}) : null,
|
|
47
74
|
getDataItemEditControl({
|
|
48
75
|
InputProps,
|
|
49
76
|
TypeaheadProps,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { extractFilterForColumn, isBetweenFilter, isBetweenOperator, isNumericColumn, isSingleValueFilter, isTemporalColumn, isTextColumn } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { numericOperators, textOperators } from "../filter-clause/operator-picker/operator-utils.js";
|
|
3
|
+
const getColumnFilterOperators = (column, operators)=>{
|
|
4
|
+
let supported;
|
|
5
|
+
if (isTextColumn(column)) supported = textOperators.filter((op)=>"in" !== op);
|
|
6
|
+
else if (isNumericColumn(column) || isTemporalColumn(column) || column.serverDataType?.startsWith("scaleddecimal")) supported = [
|
|
7
|
+
...numericOperators,
|
|
8
|
+
"between",
|
|
9
|
+
"between-inclusive"
|
|
10
|
+
];
|
|
11
|
+
else if ("boolean" === column.serverDataType) supported = [
|
|
12
|
+
"=",
|
|
13
|
+
"!="
|
|
14
|
+
];
|
|
15
|
+
else throw Error(`[ColumnFilter] unsupported column type for ${column.name}`);
|
|
16
|
+
if (operators) {
|
|
17
|
+
if (0 === operators.length || operators.some((op)=>!supported.includes(op))) throw Error(`[ColumnFilter] invalid operators for ${column.name}`);
|
|
18
|
+
return operators;
|
|
19
|
+
}
|
|
20
|
+
return supported.filter((op)=>!isBetweenOperator(op));
|
|
21
|
+
};
|
|
22
|
+
const getDefaultColumnFilterOperator = (operators, operator)=>{
|
|
23
|
+
const selected = void 0 === operator ? operators.find((op)=>"=" === op) ?? operators[0] : operators.find((op)=>op === operator);
|
|
24
|
+
if (void 0 === selected) throw Error(`[ColumnFilter] operator ${operator} is not in the configured operators`);
|
|
25
|
+
return selected;
|
|
26
|
+
};
|
|
27
|
+
const getColumnFilterOperator = (column, filter)=>{
|
|
28
|
+
const clause = getColumnFilterClause(column, filter);
|
|
29
|
+
if (isBetweenFilter(clause)) return ">=" === clause.filters[0].op ? "between-inclusive" : "between";
|
|
30
|
+
if (isSingleValueFilter(clause)) return clause.op;
|
|
31
|
+
};
|
|
32
|
+
const getColumnFilterClause = (column, filter)=>{
|
|
33
|
+
const clause = extractFilterForColumn(filter, column.name);
|
|
34
|
+
return isSingleValueFilter(clause) || isBetweenFilter(clause) ? clause : void 0;
|
|
35
|
+
};
|
|
36
|
+
const normalizeColumnFilterValue = (operator, value = "")=>isBetweenOperator(operator) ? Array.isArray(value) ? value : [
|
|
37
|
+
`${value}`,
|
|
38
|
+
""
|
|
39
|
+
] : Array.isArray(value) ? value[0] : value;
|
|
40
|
+
export { getColumnFilterClause, getColumnFilterOperator, getColumnFilterOperators, getDefaultColumnFilterOperator, normalizeColumnFilterValue };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useControlled } from "@salt-ds/core";
|
|
2
2
|
import { useCallback, useMemo, useState } from "react";
|
|
3
3
|
import { isBetweenOperator, isValidRange } from "@vuu-ui/vuu-utils";
|
|
4
|
+
import { getColumnFilterOperators, getDefaultColumnFilterOperator, normalizeColumnFilterValue } from "./column-filter-utils.js";
|
|
4
5
|
const injectInputProps = (InputProps, inputProps)=>{
|
|
5
6
|
if (void 0 === InputProps) return {
|
|
6
7
|
inputProps
|
|
@@ -13,20 +14,59 @@ const injectInputProps = (InputProps, inputProps)=>{
|
|
|
13
14
|
}
|
|
14
15
|
};
|
|
15
16
|
};
|
|
16
|
-
const useColumnFilter = ({ InputProps: InputPropsProp, column, defaultValue, extendedFilterOptions, onColumnFilterChange, onColumnRangeFilterChange, onCommit, operator =
|
|
17
|
+
const useColumnFilter = ({ InputProps: InputPropsProp, column, defaultValue, defaultOperator, extendedFilterOptions, onColumnFilterChange, onColumnRangeFilterChange, onCommit, onOperatorChange, operator: operatorProp, operators: operatorsProp, showOperatorPicker = false, value: valueProp })=>{
|
|
17
18
|
const [isInvalid, setIsInvalid] = useState(false);
|
|
18
|
-
const
|
|
19
|
+
const operators = useMemo(()=>showOperatorPicker ? getColumnFilterOperators(column, operatorsProp) : void 0, [
|
|
20
|
+
column,
|
|
21
|
+
operatorsProp,
|
|
22
|
+
showOperatorPicker
|
|
23
|
+
]);
|
|
24
|
+
const [selectedOperator, setOperator] = useControlled({
|
|
25
|
+
controlled: operatorProp,
|
|
26
|
+
default: operators ? getDefaultColumnFilterOperator(operators, defaultOperator) : defaultOperator ?? "=",
|
|
27
|
+
name: "ColumnFilter",
|
|
28
|
+
state: "operator"
|
|
29
|
+
});
|
|
30
|
+
const operator = operators ? getDefaultColumnFilterOperator(operators, selectedOperator) : selectedOperator;
|
|
31
|
+
const [rawValue, setValue] = useControlled({
|
|
19
32
|
controlled: valueProp,
|
|
20
33
|
default: defaultValue,
|
|
21
|
-
name: "
|
|
34
|
+
name: "ColumnFilter",
|
|
22
35
|
state: "value"
|
|
23
36
|
});
|
|
37
|
+
const value = showOperatorPicker ? normalizeColumnFilterValue(operator, rawValue) : rawValue;
|
|
38
|
+
const handleOperatorSelect = useCallback((event, newOperator)=>{
|
|
39
|
+
if (newOperator === operator) return;
|
|
40
|
+
const newValue = normalizeColumnFilterValue(newOperator, value);
|
|
41
|
+
setOperator(newOperator);
|
|
42
|
+
setValue(newValue);
|
|
43
|
+
setIsInvalid(false);
|
|
44
|
+
onOperatorChange?.(event, newOperator);
|
|
45
|
+
const hadValue = Array.isArray(value) ? value.some((v)=>"" !== v) : void 0 !== value && "" !== value;
|
|
46
|
+
if (hadValue) {
|
|
47
|
+
const valid = !Array.isArray(newValue) || isValidRange(newValue);
|
|
48
|
+
setIsInvalid(!valid);
|
|
49
|
+
if (valid) onCommit(column, newOperator, newValue, extendedFilterOptions);
|
|
50
|
+
}
|
|
51
|
+
}, [
|
|
52
|
+
column,
|
|
53
|
+
extendedFilterOptions,
|
|
54
|
+
onCommit,
|
|
55
|
+
onOperatorChange,
|
|
56
|
+
operator,
|
|
57
|
+
setOperator,
|
|
58
|
+
setValue,
|
|
59
|
+
value
|
|
60
|
+
]);
|
|
24
61
|
const handleCommit = useCallback((_e, newValue = "")=>{
|
|
25
62
|
if (Array.isArray(value)) if (isBetweenOperator(operator)) {
|
|
26
|
-
|
|
63
|
+
const newRange = [
|
|
27
64
|
`${newValue}`,
|
|
28
65
|
value[1]
|
|
29
|
-
]
|
|
66
|
+
];
|
|
67
|
+
setValue(newRange);
|
|
68
|
+
if (showOperatorPicker && !isValidRange(newRange)) return void setIsInvalid(true);
|
|
69
|
+
setIsInvalid(false);
|
|
30
70
|
onCommit?.(column, operator, [
|
|
31
71
|
`${newValue}`,
|
|
32
72
|
value[1]
|
|
@@ -42,7 +82,8 @@ const useColumnFilter = ({ InputProps: InputPropsProp, column, defaultValue, ext
|
|
|
42
82
|
setValue,
|
|
43
83
|
onCommit,
|
|
44
84
|
column,
|
|
45
|
-
extendedFilterOptions
|
|
85
|
+
extendedFilterOptions,
|
|
86
|
+
showOperatorPicker
|
|
46
87
|
]);
|
|
47
88
|
const handleRangeCommit = useCallback((_e, newValue = "")=>{
|
|
48
89
|
if (Array.isArray(value)) {
|
|
@@ -79,29 +120,37 @@ const useColumnFilter = ({ InputProps: InputPropsProp, column, defaultValue, ext
|
|
|
79
120
|
]);
|
|
80
121
|
const onChange = useCallback((e)=>{
|
|
81
122
|
const { value = "" } = e.target;
|
|
82
|
-
setValue((v)=>
|
|
123
|
+
setValue((v)=>{
|
|
124
|
+
const current = showOperatorPicker ? normalizeColumnFilterValue(operator, v) : v;
|
|
125
|
+
return Array.isArray(current) ? [
|
|
83
126
|
value,
|
|
84
|
-
|
|
85
|
-
] : value
|
|
127
|
+
current[1]
|
|
128
|
+
] : value;
|
|
129
|
+
});
|
|
86
130
|
onColumnFilterChange?.(e.target.value, column, operator);
|
|
87
131
|
}, [
|
|
88
132
|
column,
|
|
89
133
|
onColumnFilterChange,
|
|
90
134
|
operator,
|
|
91
|
-
setValue
|
|
135
|
+
setValue,
|
|
136
|
+
showOperatorPicker
|
|
92
137
|
]);
|
|
93
138
|
const onRangeInputChange = useCallback((e)=>{
|
|
94
139
|
const { value = "" } = e.target;
|
|
95
|
-
setValue((v)=>
|
|
96
|
-
|
|
140
|
+
setValue((v)=>{
|
|
141
|
+
const current = showOperatorPicker ? normalizeColumnFilterValue(operator, v) : v;
|
|
142
|
+
return Array.isArray(current) ? [
|
|
143
|
+
current[0],
|
|
97
144
|
value
|
|
98
|
-
] : value
|
|
145
|
+
] : value;
|
|
146
|
+
});
|
|
99
147
|
onColumnRangeFilterChange?.(value, column, operator);
|
|
100
148
|
}, [
|
|
101
149
|
setValue,
|
|
102
150
|
onColumnRangeFilterChange,
|
|
103
151
|
column,
|
|
104
|
-
operator
|
|
152
|
+
operator,
|
|
153
|
+
showOperatorPicker
|
|
105
154
|
]);
|
|
106
155
|
const InputProps = useMemo(()=>injectInputProps(InputPropsProp, {
|
|
107
156
|
onChange,
|
|
@@ -122,9 +171,12 @@ const useColumnFilter = ({ InputProps: InputPropsProp, column, defaultValue, ext
|
|
|
122
171
|
return {
|
|
123
172
|
InputProps,
|
|
124
173
|
InputPropsRange,
|
|
125
|
-
isInvalid,
|
|
174
|
+
isInvalid: showOperatorPicker ? Array.isArray(value) && !isValidRange(value) : isInvalid,
|
|
126
175
|
onCommit: handleCommit,
|
|
127
|
-
onCommitRange: handleRangeCommit
|
|
176
|
+
onCommitRange: handleRangeCommit,
|
|
177
|
+
operator,
|
|
178
|
+
operators,
|
|
179
|
+
onOperatorSelect: handleOperatorSelect
|
|
128
180
|
};
|
|
129
181
|
};
|
|
130
182
|
export { useColumnFilter };
|
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Option, useForkRef } from "@salt-ds/core";
|
|
2
|
+
import { Dropdown, Option, useForkRef } from "@salt-ds/core";
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import { ExpandoCombobox } from "../ExpandoCombobox.js";
|
|
5
5
|
import { getOperators } from "./operator-utils.js";
|
|
6
6
|
import { useOperatorPicker } from "./useOperatorPicker.js";
|
|
7
|
-
const OperatorPicker_OperatorPicker = /*#__PURE__*/ forwardRef(function(
|
|
7
|
+
const OperatorPicker_OperatorPicker = /*#__PURE__*/ forwardRef(function(props, forwardedRef) {
|
|
8
|
+
if ("dropdown" === props.variant) {
|
|
9
|
+
const { operators, value, onSelect, variant: _variant, ...dropdownProps } = props;
|
|
10
|
+
return /*#__PURE__*/ jsx("div", {
|
|
11
|
+
ref: forwardedRef,
|
|
12
|
+
"data-field": "operator",
|
|
13
|
+
children: /*#__PURE__*/ jsx(Dropdown, {
|
|
14
|
+
...dropdownProps,
|
|
15
|
+
value: value,
|
|
16
|
+
selected: [
|
|
17
|
+
value
|
|
18
|
+
],
|
|
19
|
+
onSelectionChange: (evt, [operator])=>{
|
|
20
|
+
if (void 0 !== operator) onSelect(evt, operator);
|
|
21
|
+
},
|
|
22
|
+
children: operators.map((op)=>/*#__PURE__*/ jsx(Option, {
|
|
23
|
+
value: op,
|
|
24
|
+
children: op
|
|
25
|
+
}, op))
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return /*#__PURE__*/ jsx(OperatorPicker_OperatorCombobox, {
|
|
30
|
+
...props,
|
|
31
|
+
ref: forwardedRef
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
const OperatorPicker_OperatorCombobox = /*#__PURE__*/ forwardRef(function({ className, column, dropdownOnAutofocus = true, inputProps, onSelect, operators, value: valueProp }, forwardedRef) {
|
|
8
35
|
const { ref, ...comboProps } = useOperatorPicker({
|
|
9
36
|
onSelect,
|
|
10
37
|
value: valueProp?.toString() ?? ""
|
|
@@ -17,7 +44,7 @@ const OperatorPicker_OperatorPicker = /*#__PURE__*/ forwardRef(function({ classN
|
|
|
17
44
|
inputProps: inputProps,
|
|
18
45
|
ref: useForkRef(forwardedRef, ref),
|
|
19
46
|
title: "operator",
|
|
20
|
-
children: getOperators(column).filter((op)=>op.toLowerCase().includes(comboProps.value.toLowerCase())).map((op)=>/*#__PURE__*/ jsx(Option, {
|
|
47
|
+
children: (operators ?? getOperators(column)).filter((op)=>op.toLowerCase().includes(comboProps.value.toLowerCase())).map((op)=>/*#__PURE__*/ jsx(Option, {
|
|
21
48
|
value: op
|
|
22
49
|
}, op))
|
|
23
50
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDefaultTimeZone, isValidTimeString, temporalFilterAsQuery } from "@vuu-ui/vuu-utils";
|
|
1
|
+
import { getDefaultTimeZone, isScaledDecimalFilterOptions, isValidTimeString, scaledDecimalFilterAsQuery, temporalFilterAsQuery } from "@vuu-ui/vuu-utils";
|
|
2
2
|
const isTimeToday = (options)=>"TimeString" === options.type && "today" === options.date;
|
|
3
3
|
const timeStringWithFraction = /^\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?$/;
|
|
4
4
|
class ExtendedSingleValueFilterClause {
|
|
@@ -18,22 +18,30 @@ class ExtendedSingleValueFilterClause {
|
|
|
18
18
|
}
|
|
19
19
|
asQuery() {
|
|
20
20
|
const { column, op, value } = this;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
if (isScaledDecimalFilterOptions(this.#options)) return scaledDecimalFilterAsQuery({
|
|
22
|
+
column,
|
|
23
|
+
op,
|
|
24
|
+
value
|
|
25
|
+
}, this.#options);
|
|
26
|
+
if ("TimeString" === this.#options.type) {
|
|
27
|
+
const { date, encoding = "epochMillis", timeZone } = this.#options;
|
|
28
|
+
if ("string" == typeof value && (isValidTimeString(value) || timeStringWithFraction.test(value))) {
|
|
29
|
+
const temporalInfo = {
|
|
30
|
+
kind: "time",
|
|
31
|
+
encoding,
|
|
32
|
+
precision: "epochNanos" === encoding ? "ns" : "ms",
|
|
33
|
+
timeZone: timeZone ?? getDefaultTimeZone()
|
|
34
|
+
};
|
|
35
|
+
return temporalFilterAsQuery({
|
|
36
|
+
column,
|
|
37
|
+
op,
|
|
38
|
+
value
|
|
39
|
+
}, temporalInfo, {
|
|
40
|
+
date: date
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
throw Error(`[ExtendedSingleValueFilterClause] invalid TimeString ${value}`);
|
|
44
|
+
}
|
|
37
45
|
throw Error("[ExtendedSingleValueFilterClause] unhandled extended filter type");
|
|
38
46
|
}
|
|
39
47
|
toJSON() {
|