@vuu-ui/vuu-filters 4.0.0-alpha.17 → 4.0.0-alpha.19

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 (31) hide show
  1. package/package.json +12 -12
  2. package/src/FilterAggregator.js +61 -42
  3. package/src/column-filter/ColumnFilter.css.js +4 -0
  4. package/src/column-filter/ColumnFilter.js +18 -7
  5. package/src/filter-clause/operator-picker/operator-utils.js +2 -2
  6. package/src/filter-clause/value-editors/FilterClauseValueEditor.js +8 -5
  7. package/src/filter-clause/value-editors/FilterClauseValueEditorDate.js +11 -43
  8. package/src/filter-clause/value-editors/FilterClauseValueEditorTime.js +16 -42
  9. package/src/filter-container/ExtendedSingleValueFilterClause.js +21 -7
  10. package/src/filter-container/FilterContainer.js +21 -8
  11. package/src/filter-context-menu/useFilterContextMenu.js +36 -9
  12. package/src/filter-pill/getFilterTooltipText.js +2 -8
  13. package/src/filter-provider/FilterContext.js +20 -1
  14. package/src/filter-provider/FilterProvider.js +20 -2
  15. package/src/filter-utils.js +5 -10
  16. package/src/index.js +1 -1
  17. package/src/quick-filters/useQuickFilters.js +6 -1
  18. package/src/toggle-filter/ToggleFilter.css.js +8 -0
  19. package/src/toggle-filter/ToggleFilter.js +21 -11
  20. package/src/toggle-filter/useToggleFilter.js +57 -33
  21. package/types/column-filter/ColumnFilter.d.ts +14 -1
  22. package/types/filter-clause/value-editors/FilterClauseValueEditorDate.d.ts +13 -2
  23. package/types/filter-clause/value-editors/FilterClauseValueEditorTime.d.ts +15 -4
  24. package/types/filter-container/ExtendedSingleValueFilterClause.d.ts +6 -0
  25. package/types/filter-container/FilterContainer.d.ts +8 -1
  26. package/types/filter-context-menu/useFilterContextMenu.d.ts +11 -1
  27. package/types/filter-provider/FilterContext.d.ts +34 -0
  28. package/types/filter-provider/FilterProvider.d.ts +19 -1
  29. package/types/index.d.ts +3 -3
  30. package/types/toggle-filter/ToggleFilter.d.ts +5 -0
  31. package/types/toggle-filter/useToggleFilter.d.ts +19 -1
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.0-alpha.17",
2
+ "version": "4.0.0-alpha.19",
3
3
  "main": "./src/index.js",
4
4
  "author": "heswell",
5
5
  "license": "Apache-2.0",
@@ -10,19 +10,19 @@
10
10
  "type-defs": "node ../../scripts/build-type-defs.mjs"
11
11
  },
12
12
  "devDependencies": {
13
- "@vuu-ui/vuu-data-types": "4.0.0-alpha.17",
14
- "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.17",
15
- "@vuu-ui/vuu-table-types": "4.0.0-alpha.17",
16
- "@vuu-ui/vuu-filter-types": "4.0.0-alpha.17"
13
+ "@vuu-ui/vuu-data-types": "4.0.0-alpha.19",
14
+ "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.19",
15
+ "@vuu-ui/vuu-table-types": "4.0.0-alpha.19",
16
+ "@vuu-ui/vuu-filter-types": "4.0.0-alpha.19"
17
17
  },
18
18
  "dependencies": {
19
- "@vuu-ui/vuu-context-menu": "4.0.0-alpha.17",
20
- "@vuu-ui/vuu-data-react": "4.0.0-alpha.17",
21
- "@vuu-ui/vuu-filter-parser": "4.0.0-alpha.17",
22
- "@vuu-ui/vuu-popups": "4.0.0-alpha.17",
23
- "@vuu-ui/vuu-ui-controls": "4.0.0-alpha.17",
24
- "@vuu-ui/vuu-table": "4.0.0-alpha.17",
25
- "@vuu-ui/vuu-utils": "4.0.0-alpha.17",
19
+ "@vuu-ui/vuu-context-menu": "4.0.0-alpha.19",
20
+ "@vuu-ui/vuu-data-react": "4.0.0-alpha.19",
21
+ "@vuu-ui/vuu-filter-parser": "4.0.0-alpha.19",
22
+ "@vuu-ui/vuu-popups": "4.0.0-alpha.19",
23
+ "@vuu-ui/vuu-ui-controls": "4.0.0-alpha.19",
24
+ "@vuu-ui/vuu-table": "4.0.0-alpha.19",
25
+ "@vuu-ui/vuu-utils": "4.0.0-alpha.19",
26
26
  "@salt-ds/core": "1.67.0",
27
27
  "@salt-ds/lab": "1.0.0-alpha.99",
28
28
  "@salt-ds/styles": "0.2.1",
@@ -1,4 +1,4 @@
1
- import { getTypedRange, getTypedValue, isAndFilter, isBetweenFilter, isExtendedFilter, isMultiClauseFilter, isSingleValueFilter, isTypeDescriptor } from "@vuu-ui/vuu-utils";
1
+ import { EpochTimestamp, getTemporalInfo, getTypedValueForDescriptor, isAndFilter, isBetweenFilter, isExtendedFilter, isMultiClauseFilter, isSingleValueFilter } 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,6 +12,47 @@ 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)=>{
16
+ if (temporalInfo?.kind === "time") return {
17
+ type: "TimeString",
18
+ date: "today",
19
+ encoding: temporalInfo.encoding,
20
+ timeZone: temporalInfo.timeZone,
21
+ ...extendedFilterOptions
22
+ };
23
+ return extendedFilterOptions;
24
+ };
25
+ const getFilterValue = (column, value, temporalInfo, options, throwIfInvalid)=>{
26
+ if ("" === value) {
27
+ if (throwIfInvalid && !temporalInfo) return getTypedValueForDescriptor(value, column, true);
28
+ return;
29
+ }
30
+ if (options?.type === "TimeString") return asFilterTimeString(value, temporalInfo, throwIfInvalid);
31
+ return getTypedValueForDescriptor(value, column, throwIfInvalid);
32
+ };
33
+ const timeOfDayPattern = /^\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
34
+ const asFilterTimeString = (value, temporalInfo, throwIfInvalid)=>{
35
+ if (timeOfDayPattern.test(value)) return 5 === value.length ? `${value}:00` : value;
36
+ const timestamp = EpochTimestamp.fromWire(value, temporalInfo?.encoding ?? "epochMillis");
37
+ if (timestamp && /^\d+$/.test(value)) {
38
+ const { hour, minute, second } = timestamp.getFields(temporalInfo?.timeZone);
39
+ const pad = (n)=>`${n}`.padStart(2, "0");
40
+ return `${pad(hour)}:${pad(minute)}:${pad(second)}`;
41
+ }
42
+ if (throwIfInvalid) throw Error(`[FilterAggregator] value ${value} is not a valid time`);
43
+ };
44
+ const singleValueOps = [
45
+ "=",
46
+ "!=",
47
+ ">",
48
+ ">=",
49
+ "<",
50
+ "<=",
51
+ "contains",
52
+ "starts",
53
+ "ends"
54
+ ];
55
+ const asSingleValueOp = (op)=>singleValueOps.includes(op) ? op : "=";
15
56
  class FilterAggregator {
16
57
  #filters = new Map();
17
58
  constructor(filter){
@@ -24,51 +65,29 @@ class FilterAggregator {
24
65
  });
25
66
  }
26
67
  add(column, value, op = "=", extendedFilterOptions) {
27
- const { serverDataType = "string", type } = column;
68
+ const temporalInfo = getTemporalInfo(column);
69
+ const options = getExtendedFilterOptions(temporalInfo, extendedFilterOptions);
28
70
  const isInclusive = "between-inclusive" === op;
29
- const dataType = isTypeDescriptor(type) ? type.name : type ?? serverDataType;
30
- if (Array.isArray(value)) {
31
- const [value1, value2] = getTypedRange(value, dataType, extendedFilterOptions);
32
- if (void 0 !== value1 && void 0 !== value2) {
33
- const filter = extendedFilterOptions ? {
34
- op: "and",
35
- filters: [
36
- new ExtendedSingleValueFilterClause(column.name, isInclusive ? ">=" : ">", value1, extendedFilterOptions),
37
- new ExtendedSingleValueFilterClause(column.name, isInclusive ? "<=" : "<", value2, extendedFilterOptions)
38
- ]
39
- } : {
40
- op: "and",
41
- filters: [
42
- {
43
- column: column.name,
44
- op: ">",
45
- value: value1
46
- },
47
- {
48
- column: column.name,
49
- op: "<",
50
- value: value2
51
- }
52
- ]
53
- };
54
- this.#filters.set(column.name, filter);
55
- } else if (void 0 !== value1) this.#filters.set(column.name, {
71
+ const createClause = (op, value)=>options ? new ExtendedSingleValueFilterClause(column.name, op, value, options) : {
56
72
  column: column.name,
57
- op: "=",
58
- value: value1
59
- });
60
- else if (void 0 !== value2) this.#filters.set(column.name, {
61
- column: column.name,
62
- op: "<",
63
- value: value2
73
+ op,
74
+ value
75
+ };
76
+ if (Array.isArray(value)) {
77
+ const [value1, value2] = value.map((v)=>getFilterValue(column, v, temporalInfo, options, false));
78
+ if (void 0 !== value1 && void 0 !== value2) this.#filters.set(column.name, {
79
+ op: "and",
80
+ filters: [
81
+ createClause(isInclusive ? ">=" : ">", value1),
82
+ createClause(isInclusive ? "<=" : "<", value2)
83
+ ]
64
84
  });
85
+ else if (void 0 !== value1) this.#filters.set(column.name, createClause("=", value1));
86
+ else if (void 0 !== value2) this.#filters.set(column.name, createClause(isInclusive ? "<=" : "<", value2));
65
87
  } else {
66
- const typedValue = getTypedValue(value.toString(), dataType, true);
67
- this.#filters.set(column.name, {
68
- column: column.name,
69
- op: "=",
70
- value: typedValue
71
- });
88
+ const typedValue = getFilterValue(column, value.toString(), temporalInfo, options, true);
89
+ if (void 0 === typedValue) this.#filters.delete(column.name);
90
+ else this.#filters.set(column.name, createClause(temporalInfo ? asSingleValueOp(op) : "=", typedValue));
72
91
  }
73
92
  }
74
93
  has({ name }) {
@@ -43,6 +43,10 @@ const css = `
43
43
  --saltButton-padding: var(--salt-spacing-200);
44
44
  }
45
45
 
46
+ .vuuColumnFilter-toggle {
47
+ max-width: none;
48
+ }
49
+
46
50
  .vuuColumnFilter-invalid {
47
51
  border-color: red;
48
52
  }
@@ -2,23 +2,32 @@ import { jsxs } from "react/jsx-runtime";
2
2
  import { SegmentedButtonGroup } 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 } from "react";
5
+ import { forwardRef as external_react_forwardRef, useMemo } 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
- import { isBetweenOperator } from "@vuu-ui/vuu-utils";
10
+ import { isBetweenOperator, withDateTimePattern } from "@vuu-ui/vuu-utils";
11
11
  const classBase = "vuuColumnFilter";
12
- const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(function({ InputProps: InputPropsProp, TypeaheadProps, className, column, defaultValue, extendedFilterOptions, onColumnFilterChange, onColumnRangeFilterChange, onCommit: onCommitProp, operator = "=", table, value: valueProp, values, variant, ...buttonGroupProps }, forwardRef) {
12
+ const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(function({ InputProps: InputPropsProp, TypeaheadProps, className, column, defaultValue, extendedFilterOptions, labels, onColumnFilterChange, onColumnRangeFilterChange, onCommit: onCommitProp, operator = "=", pattern, table, value: valueProp, values, variant, ...buttonGroupProps }, forwardRef) {
13
13
  const targetWindow = useWindow();
14
14
  useComponentCssInjection({
15
15
  testId: "vuu-filter-editor",
16
16
  css: ColumnFilter_0,
17
17
  window: targetWindow
18
18
  });
19
+ const { date, time } = pattern ?? {};
20
+ const controlDescriptor = useMemo(()=>withDateTimePattern(column, date || time ? {
21
+ date,
22
+ time
23
+ } : void 0), [
24
+ column,
25
+ date,
26
+ time
27
+ ]);
19
28
  const { InputProps, InputPropsRange, isInvalid, onCommit, onCommitRange } = useColumnFilter({
20
29
  InputProps: InputPropsProp,
21
- column,
30
+ column: controlDescriptor,
22
31
  defaultValue,
23
32
  extendedFilterOptions,
24
33
  onColumnFilterChange,
@@ -30,7 +39,8 @@ const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(functi
30
39
  return /*#__PURE__*/ jsxs(SegmentedButtonGroup, {
31
40
  ...buttonGroupProps,
32
41
  className: clsx(classBase, className, {
33
- [`${classBase}-invalid`]: isInvalid
42
+ [`${classBase}-invalid`]: isInvalid,
43
+ [`${classBase}-toggle`]: "toggle" === variant
34
44
  }),
35
45
  ref: forwardRef,
36
46
  children: [
@@ -38,7 +48,8 @@ const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(functi
38
48
  InputProps,
39
49
  TypeaheadProps,
40
50
  commitWhenCleared: true,
41
- dataDescriptor: column,
51
+ dataDescriptor: controlDescriptor,
52
+ labels,
42
53
  onCommit,
43
54
  table,
44
55
  values,
@@ -49,7 +60,7 @@ const ColumnFilter_ColumnFilter = /*#__PURE__*/ external_react_forwardRef(functi
49
60
  className: `${classBase}-rangeHigh`,
50
61
  commitWhenCleared: true,
51
62
  variant,
52
- dataDescriptor: column,
63
+ dataDescriptor: controlDescriptor,
53
64
  onCommit: onCommitRange,
54
65
  table
55
66
  }) : null
@@ -1,4 +1,4 @@
1
- import { isNumericColumn, isTextColumn, isTimestampColumn } from "@vuu-ui/vuu-utils";
1
+ import { isNumericColumn, isTemporalColumn, isTextColumn } from "@vuu-ui/vuu-utils";
2
2
  const textOperators = [
3
3
  "=",
4
4
  "in",
@@ -17,7 +17,7 @@ const numericOperators = [
17
17
  ];
18
18
  const getOperators = (column)=>{
19
19
  if (isTextColumn(column)) return textOperators;
20
- if (isNumericColumn(column) || isTimestampColumn(column)) return numericOperators;
20
+ if (isNumericColumn(column) || isTemporalColumn(column)) return numericOperators;
21
21
  throw Error("getOperators only supports text and numeric columns");
22
22
  };
23
23
  export { getOperators, numericOperators, textOperators };
@@ -2,26 +2,29 @@ import { jsx } from "react/jsx-runtime";
2
2
  import clsx from "clsx";
3
3
  import { FilterClauseValueEditorNumber } from "./FilterClauseValueEditorNumber.js";
4
4
  import { FilterClauseValueEditorText } from "./FilterClauseValueEditorText.js";
5
- import { isDateTimeDataValue, isTimeDataValue } from "@vuu-ui/vuu-utils";
5
+ import { getTemporalInfo } from "@vuu-ui/vuu-utils";
6
6
  import { forwardRef } from "react";
7
7
  import { FilterClauseValueEditorDate } from "./FilterClauseValueEditorDate.js";
8
8
  import { FilterClauseValueEditorTime } from "./FilterClauseValueEditorTime.js";
9
9
  const classBase = "vuuFilterClause";
10
10
  const FilterClauseValueEditor_FilterClauseValueEditor = /*#__PURE__*/ forwardRef(function({ selectedColumn, operator, inputProps, onChangeValue, onDeselectValue, onOpenChange, table, value, dropdownOnAutofocus = true }, forwardedRef) {
11
11
  if (void 0 === selectedColumn || void 0 === operator) return null;
12
- if (isDateTimeDataValue(selectedColumn)) return /*#__PURE__*/ jsx(FilterClauseValueEditorDate, {
12
+ const temporalInfo = getTemporalInfo(selectedColumn);
13
+ if (temporalInfo?.kind === "time") return /*#__PURE__*/ jsx(FilterClauseValueEditorTime, {
13
14
  inputProps: inputProps,
14
15
  className: clsx(`${classBase}Field`, `${classBase}Value`),
15
16
  value: value,
16
17
  operator: operator,
17
- onChangeValue: onChangeValue
18
+ onChangeValue: onChangeValue,
19
+ temporalInfo: temporalInfo
18
20
  });
19
- if (isTimeDataValue(selectedColumn)) return /*#__PURE__*/ jsx(FilterClauseValueEditorTime, {
21
+ if (temporalInfo) return /*#__PURE__*/ jsx(FilterClauseValueEditorDate, {
20
22
  inputProps: inputProps,
21
23
  className: clsx(`${classBase}Field`, `${classBase}Value`),
22
24
  value: value,
23
25
  operator: operator,
24
- onChangeValue: onChangeValue
26
+ onChangeValue: onChangeValue,
27
+ temporalInfo: temporalInfo
25
28
  });
26
29
  switch(selectedColumn.serverDataType){
27
30
  case "string":
@@ -1,55 +1,23 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { useCallback, useState } from "react";
3
- import { getLocalTimeZone } from "@internationalized/date";
4
- import { toCalendarDate } from "@vuu-ui/vuu-utils";
2
+ import { useCallback } from "react";
3
+ import { EpochTimestamp } from "@vuu-ui/vuu-utils";
5
4
  import { VuuDatePicker } from "@vuu-ui/vuu-ui-controls";
6
- const FilterClauseValueEditorDate = (props)=>{
7
- const { inputProps, className, onChangeValue, operator, value } = props;
8
- const toEpochMilliS = getEpochMillisConverter(operator);
9
- const [date, setDate] = useState(()=>getInitialState(value));
10
- const handleCommit = useCallback((e, selectedDateInputValue)=>{
11
- console.log("change date");
12
- if (selectedDateInputValue) {
13
- const dateValue = toCalendarDate(new Date(selectedDateInputValue));
14
- setDate(dateValue);
15
- if (selectedDateInputValue) onChangeValue(toEpochMilliS(dateValue));
16
- }
5
+ const FilterClauseValueEditorDate = ({ className, inputProps, onChangeValue, temporalInfo, value })=>{
6
+ const { encoding, timeZone } = temporalInfo;
7
+ const handleCommit = useCallback((_e, startOfDayMillis)=>{
8
+ const timestamp = EpochTimestamp.fromMillis(startOfDayMillis);
9
+ onChangeValue("epochNanos" === encoding ? timestamp.toWire("epochNanos") : timestamp.toWire("epochMillis"));
17
10
  }, [
18
- onChangeValue,
19
- toEpochMilliS
20
- ]);
21
- const onBlur = useCallback(()=>{
22
- date && onChangeValue(toEpochMilliS(date));
23
- }, [
24
- date,
25
- onChangeValue,
26
- toEpochMilliS
11
+ encoding,
12
+ onChangeValue
27
13
  ]);
28
14
  return /*#__PURE__*/ jsx(VuuDatePicker, {
29
15
  "data-field": "value",
30
16
  inputProps: inputProps,
31
17
  className: className,
32
- onBlur: onBlur,
33
18
  onCommit: handleCommit,
34
- selectedDate: date
19
+ timeZone: timeZone,
20
+ value: EpochTimestamp.fromWire(value, encoding)
35
21
  });
36
22
  };
37
- function getInitialState(value) {
38
- return value ? toCalendarDate(new Date(value)) : void 0;
39
- }
40
- const getEpochMillisConverter = (op)=>(date, timezone = getLocalTimeZone())=>{
41
- const d = date.toDate(timezone);
42
- switch(op){
43
- case ">":
44
- case "<=":
45
- d.setHours(23, 59, 59, 999);
46
- return d.getTime();
47
- case ">=":
48
- case "<":
49
- case "=":
50
- case "!=":
51
- d.setHours(0, 0, 0, 0);
52
- return d.getTime();
53
- }
54
- };
55
23
  export { FilterClauseValueEditorDate };
@@ -1,53 +1,27 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { useCallback, useState } from "react";
3
- import { getLocalTimeZone } from "@internationalized/date";
4
- import { toCalendarDate } from "@vuu-ui/vuu-utils";
2
+ import { useCallback } from "react";
3
+ import { EpochTimestamp, isValidTimeString } from "@vuu-ui/vuu-utils";
5
4
  import { VuuTimePicker } from "@vuu-ui/vuu-ui-controls";
6
- const FilterClauseValueEditorTime = (props)=>{
7
- const { className, onChangeValue, operator, value } = props;
8
- const toEpochMilliS = getEpochMillisConverter(operator);
9
- const [date, setDate] = useState(()=>getInitialState(value));
10
- const handleCommit = useCallback((e, selectedDateInputValue)=>{
11
- console.log("change time");
12
- if (selectedDateInputValue) {
13
- const dateValue = toCalendarDate(new Date(selectedDateInputValue));
14
- setDate(dateValue);
15
- if (selectedDateInputValue) onChangeValue(toEpochMilliS(dateValue));
16
- }
17
- }, [
18
- onChangeValue,
19
- toEpochMilliS
20
- ]);
21
- const onBlur = useCallback(()=>{
22
- date && onChangeValue(toEpochMilliS(date));
5
+ const pad = (n)=>`${n}`.padStart(2, "0");
6
+ const toTimeString = (value, { encoding, timeZone })=>{
7
+ if (isValidTimeString(value)) return value;
8
+ const timestamp = EpochTimestamp.fromWire(value, encoding);
9
+ if (timestamp) {
10
+ const { hour, minute, second } = timestamp.getFields(timeZone);
11
+ return `${pad(hour)}:${pad(minute)}:${pad(second)}`;
12
+ }
13
+ };
14
+ const FilterClauseValueEditorTime = ({ className, onChangeValue, temporalInfo, value })=>{
15
+ const handleCommit = useCallback((_e, timeString)=>{
16
+ if (isValidTimeString(timeString)) onChangeValue(timeString);
23
17
  }, [
24
- date,
25
- onChangeValue,
26
- toEpochMilliS
18
+ onChangeValue
27
19
  ]);
28
20
  return /*#__PURE__*/ jsx(VuuTimePicker, {
29
21
  "data-field": "value",
30
22
  className: className,
31
- onBlur: onBlur,
23
+ defaultValue: toTimeString(value, temporalInfo),
32
24
  onCommit: handleCommit
33
25
  });
34
26
  };
35
- function getInitialState(value) {
36
- return value ? toCalendarDate(new Date(value)) : void 0;
37
- }
38
- const getEpochMillisConverter = (op)=>(date, timezone = getLocalTimeZone())=>{
39
- const d = date.toDate(timezone);
40
- switch(op){
41
- case ">":
42
- case "<=":
43
- d.setHours(23, 59, 59, 999);
44
- return d.getTime();
45
- case ">=":
46
- case "<":
47
- case "=":
48
- case "!=":
49
- d.setHours(0, 0, 0, 0);
50
- return d.getTime();
51
- }
52
- };
53
27
  export { FilterClauseValueEditorTime };
@@ -1,5 +1,6 @@
1
- import { Time, isValidTimeString } from "@vuu-ui/vuu-utils";
1
+ import { getDefaultTimeZone, isValidTimeString, temporalFilterAsQuery } from "@vuu-ui/vuu-utils";
2
2
  const isTimeToday = (options)=>"TimeString" === options.type && "today" === options.date;
3
+ const timeStringWithFraction = /^\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?$/;
3
4
  class ExtendedSingleValueFilterClause {
4
5
  column;
5
6
  op;
@@ -12,14 +13,27 @@ class ExtendedSingleValueFilterClause {
12
13
  this.#options = extendedOptions;
13
14
  }
14
15
  name;
16
+ get extendedOptions() {
17
+ return this.#options;
18
+ }
15
19
  asQuery() {
16
20
  const { column, op, value } = this;
17
- if (isTimeToday(this.#options)) if (isValidTimeString(value)) {
18
- const timeValue = +Time(value).asDate();
19
- if (">=" === op) return `${column} > ${timeValue - 1}`;
20
- if ("<=" === op) return `${column} < ${timeValue + 1}`;
21
- return `${column} ${op} ${timeValue}`;
22
- } else throw Error(`[ExtendedSingleValueFilterClause] invalid TimeStrong ${value}`);
21
+ const { date, encoding = "epochMillis", timeZone } = this.#options;
22
+ if ("TimeString" === this.#options.type) if ("string" == typeof value && (isValidTimeString(value) || timeStringWithFraction.test(value))) {
23
+ const temporalInfo = {
24
+ kind: "time",
25
+ encoding,
26
+ precision: "epochNanos" === encoding ? "ns" : "ms",
27
+ timeZone: timeZone ?? getDefaultTimeZone()
28
+ };
29
+ return temporalFilterAsQuery({
30
+ column,
31
+ op,
32
+ value
33
+ }, temporalInfo, {
34
+ date: date
35
+ });
36
+ } else throw Error(`[ExtendedSingleValueFilterClause] invalid TimeString ${value}`);
23
37
  throw Error("[ExtendedSingleValueFilterClause] unhandled extended filter type");
24
38
  }
25
39
  toJSON() {
@@ -1,17 +1,33 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import clsx from "clsx";
3
- import { useCallback, useMemo, useRef, useState } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { ColumnFilterContext, useFilterContainer, useFilterContext } from "./useFilterContainer.js";
5
5
  import { ColumnFilter } from "../column-filter/ColumnFilter.js";
6
- import { filterDescriptorHasFilter, isNullFilter, useSavedFilters } from "../filter-provider/FilterContext.js";
7
- import { filtersAreEqual, getColumnValueFromFilter, isBetweenOperator } from "@vuu-ui/vuu-utils";
6
+ import { filterDescriptorHasFilter, isNullFilter, useColumnFilterRegistry, useSavedFilters } from "../filter-provider/FilterContext.js";
7
+ import { filtersAreEqual, getColumnValueFromFilter, getDefaultTimeRange, isBetweenOperator, isTimeDataValue, withDateTimePattern } from "@vuu-ui/vuu-utils";
8
8
  import { useComponentCssInjection } from "@salt-ds/styles";
9
9
  import { useWindow } from "@salt-ds/window";
10
10
  import FilterContainer from "./FilterContainer.css";
11
11
  const classBase = "vuuFilterContainer";
12
12
  const notEmpty = (value)=>Array.isArray(value) ? "" !== value[0] && "" !== value[1] : "" !== value;
13
- const FilterContainerColumnFilter = ({ column, operator = "=", variant, ...props })=>{
13
+ const FilterContainerColumnFilter = ({ column: columnProp, operator = "=", pattern: patternProp, variant, ...props })=>{
14
+ const { getColumnFilterPattern } = useColumnFilterRegistry();
15
+ const pattern = patternProp ?? getColumnFilterPattern(columnProp);
16
+ const { date, time } = pattern ?? {};
17
+ const column = useMemo(()=>withDateTimePattern(columnProp, date || time ? {
18
+ date,
19
+ time
20
+ } : void 0), [
21
+ columnProp,
22
+ date,
23
+ time
24
+ ]);
14
25
  const { filterProviderKey, onChange: onFilterContextChange, onCommit: onFilterContextCommit, register } = useFilterContext(column, true);
26
+ const { registerColumnFilterColumn } = useColumnFilterRegistry(filterProviderKey);
27
+ useEffect(()=>registerColumnFilterColumn(column), [
28
+ column,
29
+ registerColumnFilterColumn
30
+ ]);
15
31
  const initialValue = useMemo(()=>register(column, operator), [
16
32
  column,
17
33
  operator,
@@ -25,10 +41,7 @@ const FilterContainerColumnFilter = ({ column, operator = "=", variant, ...props
25
41
  if (currentFilterRef.current.id !== currentFilter.id || !filtersAreEqual(currentFilterRef.current.filter, currentFilter.filter)) {
26
42
  currentFilterRef.current = currentFilter;
27
43
  if (isNullFilter(currentFilter) && notEmpty(valueRef.current)) {
28
- if (Array.isArray(valueRef.current)) if ("time" === column.type) valueRef.current = [
29
- "00:00:00",
30
- "23:59:59"
31
- ];
44
+ if (Array.isArray(valueRef.current)) if (isTimeDataValue(column)) valueRef.current = getDefaultTimeRange(column);
32
45
  else valueRef.current = [
33
46
  "",
34
47
  ""
@@ -1,14 +1,39 @@
1
- import { filtersAreEqual } from "@vuu-ui/vuu-utils";
1
+ import { filtersAreEqual, getTemporalCellFilter, getTemporalInfo, withDateTimePattern } from "@vuu-ui/vuu-utils";
2
2
  import { useCallback, useMemo, useRef } from "react";
3
- import { useSavedFilters } from "../filter-provider/FilterContext.js";
3
+ import { useColumnFilterRegistry, useSavedFilters } from "../filter-provider/FilterContext.js";
4
4
  import { FilterAggregator } from "../FilterAggregator.js";
5
5
  const EmptyAggregator = new FilterAggregator();
6
6
  const defaultProps = {
7
7
  filterColumns: "*"
8
8
  };
9
- const useFilterContextMenu = ({ filterColumns = "*", filterProviderKey } = defaultProps)=>{
9
+ const useFilterContextMenu = ({ filterColumns = "*", filterPatterns, filterProviderKey } = defaultProps)=>{
10
10
  const { currentFilter, clearCurrentFilter, setCurrentFilter } = useSavedFilters(filterProviderKey);
11
+ const { getColumnFilterColumn, getColumnFilterPattern } = useColumnFilterRegistry(filterProviderKey);
11
12
  const filterAggregatorRef = useRef(EmptyAggregator);
13
+ const getCellFilter = useCallback((column, dataRow)=>{
14
+ const value = dataRow[column.name];
15
+ if (getTemporalInfo(column)) {
16
+ const pattern = filterPatterns?.[column.name];
17
+ const columnFilterPattern = getColumnFilterPattern(column);
18
+ const columnFilterColumn = pattern ? void 0 : getColumnFilterColumn(column.name) ?? (columnFilterPattern ? withDateTimePattern(column, columnFilterPattern) : void 0);
19
+ const filterColumn = pattern ? withDateTimePattern(column, pattern) : columnFilterColumn;
20
+ const temporalCellFilter = getTemporalCellFilter(filterColumn ?? column, value, void 0 !== columnFilterColumn);
21
+ if (temporalCellFilter) return {
22
+ column: filterColumn ?? column,
23
+ ...temporalCellFilter
24
+ };
25
+ }
26
+ return {
27
+ column,
28
+ label: `${value}`,
29
+ op: "=",
30
+ value
31
+ };
32
+ }, [
33
+ filterPatterns,
34
+ getColumnFilterColumn,
35
+ getColumnFilterPattern
36
+ ]);
12
37
  useMemo(()=>{
13
38
  if (!filtersAreEqual(currentFilter.filter, filterAggregatorRef.current.filter)) filterAggregatorRef.current = currentFilter.filter ? new FilterAggregator(currentFilter.filter) : new FilterAggregator();
14
39
  }, [
@@ -18,13 +43,13 @@ const useFilterContextMenu = ({ filterColumns = "*", filterProviderKey } = defau
18
43
  const { column, dataRow } = options;
19
44
  const { current: fag } = filterAggregatorRef;
20
45
  const { name, label = name } = column;
21
- const value = dataRow[column.name];
22
46
  const ClearFilter = {
23
47
  id: "filter-clear",
24
48
  label: "Clear filter",
25
49
  options
26
50
  };
27
51
  if ("*" === filterColumns || filterColumns.includes(column.name)) {
52
+ const { label: value } = getCellFilter(column, dataRow);
28
53
  const SetFilter = {
29
54
  id: "filter-set",
30
55
  label: `Set filter ${label} '${value}'`,
@@ -60,7 +85,8 @@ const useFilterContextMenu = ({ filterColumns = "*", filterProviderKey } = defau
60
85
  ];
61
86
  return [];
62
87
  }, [
63
- filterColumns
88
+ filterColumns,
89
+ getCellFilter
64
90
  ]);
65
91
  const menuActionHandler = useCallback((menuItemId, options)=>{
66
92
  if (!options) return false;
@@ -73,8 +99,8 @@ const useFilterContextMenu = ({ filterColumns = "*", filterProviderKey } = defau
73
99
  break;
74
100
  case "filter-add":
75
101
  {
76
- const value = dataRow[column.name];
77
- fag.add(column, value, "=");
102
+ const cellFilter = getCellFilter(column, dataRow);
103
+ fag.add(cellFilter.column, cellFilter.value, cellFilter.op);
78
104
  if (fag.filter) setCurrentFilter(fag.filter);
79
105
  }
80
106
  break;
@@ -84,9 +110,9 @@ const useFilterContextMenu = ({ filterColumns = "*", filterProviderKey } = defau
84
110
  break;
85
111
  case "filter-set":
86
112
  {
87
- const value = dataRow[column.name];
113
+ const cellFilter = getCellFilter(column, dataRow);
88
114
  fag.clear();
89
- fag.add(column, value, "=");
115
+ fag.add(cellFilter.column, cellFilter.value, cellFilter.op);
90
116
  if (fag.filter) setCurrentFilter(fag.filter);
91
117
  }
92
118
  break;
@@ -96,6 +122,7 @@ const useFilterContextMenu = ({ filterColumns = "*", filterProviderKey } = defau
96
122
  }
97
123
  }, [
98
124
  clearCurrentFilter,
125
+ getCellFilter,
99
126
  setCurrentFilter
100
127
  ]);
101
128
  return {
@@ -1,4 +1,4 @@
1
- import { dateTimePattern, defaultPatternsByType, filterAsQuery, formatDate, isDateTimeDataValue, isMultiClauseFilter } from "@vuu-ui/vuu-utils";
1
+ import { filterAsQuery, formatTemporalFilterValue, isMultiClauseFilter, isTemporal } from "@vuu-ui/vuu-utils";
2
2
  import { filterClauses } from "../filter-utils.js";
3
3
  function applyFormatter(filter, formatter) {
4
4
  if ("value" in filter) return {
@@ -12,13 +12,7 @@ function applyFormatter(filter, formatter) {
12
12
  }
13
13
  function formatFilterValue(filter, columnsByName) {
14
14
  const column = columnsByName?.[filter.column];
15
- if (column && isDateTimeDataValue(column)) {
16
- const pattern = dateTimePattern(column.type);
17
- const formatter = (n)=>formatDate({
18
- date: pattern.date ?? defaultPatternsByType.date
19
- })(new Date(n));
20
- return applyFormatter(filter, formatter);
21
- }
15
+ if (column && isTemporal(column)) return applyFormatter(filter, (value)=>formatTemporalFilterValue(value, column));
22
16
  return filter;
23
17
  }
24
18
  const getFilterAsFormattedText = (columnsByName)=>(filter)=>{