@vuu-ui/vuu-filters 0.8.27-debug → 0.8.28-debug

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 (40) hide show
  1. package/cjs/index.js +1553 -1089
  2. package/cjs/index.js.map +4 -4
  3. package/esm/index.js +1540 -1059
  4. package/esm/index.js.map +4 -4
  5. package/index.css +48 -81
  6. package/index.css.map +3 -3
  7. package/package.json +9 -9
  8. package/types/FilterModel.d.ts +43 -0
  9. package/types/filter-bar/FilterBar.d.ts +3 -4
  10. package/types/filter-bar/filterBarFocusManagement.d.ts +1 -0
  11. package/types/filter-bar/useFilterBar.d.ts +16 -21
  12. package/types/filter-bar/useFilterState.d.ts +1 -1
  13. package/types/filter-clause/FilterClause.d.ts +18 -0
  14. package/types/filter-clause/FilterMenu.d.ts +0 -1
  15. package/types/filter-clause/filterClauseFocusManagement.d.ts +16 -0
  16. package/types/filter-clause/filterClauseTypes.d.ts +3 -2
  17. package/types/filter-clause/index.d.ts +2 -2
  18. package/types/filter-clause/useFilterClause.d.ts +24 -0
  19. package/types/filter-clause/value-editors/FilterClauseValueEditor.d.ts +13 -0
  20. package/types/filter-clause/value-editors/FilterClauseValueEditorDate.d.ts +10 -0
  21. package/types/filter-clause/value-editors/FilterClauseValueEditorNumber.d.ts +10 -0
  22. package/types/filter-clause/value-editors/FilterClauseValueEditorText.d.ts +11 -0
  23. package/types/filter-editor/FilterClauseCombinator.d.ts +10 -0
  24. package/types/filter-editor/FilterEditor.d.ts +17 -0
  25. package/types/filter-editor/index.d.ts +1 -0
  26. package/types/filter-editor/useFilterEditor.d.ts +31 -0
  27. package/types/filter-pill/FilterPill.d.ts +8 -5
  28. package/types/filter-pill/FilterPillMenuOptions.d.ts +12 -0
  29. package/types/filter-pill/getFilterTooltipText.d.ts +2 -0
  30. package/types/filter-pill-menu/FilterPillMenu.d.ts +1 -1
  31. package/types/filter-utils.d.ts +0 -1
  32. package/types/index.d.ts +2 -1
  33. package/types/filter-builder-menu/FilterBuilderMenu.d.ts +0 -10
  34. package/types/filter-builder-menu/index.d.ts +0 -1
  35. package/types/filter-clause/DateInput.d.ts +0 -9
  36. package/types/filter-clause/FilterClauseEditor.d.ts +0 -17
  37. package/types/filter-clause/FilterClauseTextValueEditor.d.ts +0 -11
  38. package/types/filter-clause/FilterClauseValueEditor.d.ts +0 -9
  39. package/types/filter-clause/NumericInput.d.ts +0 -9
  40. package/types/filter-clause/useFilterClauseEditor.d.ts +0 -32
package/esm/index.js CHANGED
@@ -17,10 +17,225 @@ var __privateSet = (obj, member, value, setter) => {
17
17
  return value;
18
18
  };
19
19
 
20
+ // src/FilterModel.ts
21
+ import {
22
+ EventEmitter,
23
+ isMultiClauseFilter,
24
+ isMultiValueFilter,
25
+ isSingleValueFilter
26
+ } from "@vuu-ui/vuu-utils";
27
+ var hasValues = ({ values }) => Array.isArray(values) && values.length > 0;
28
+ var isValidFilterClause = (filterClause) => {
29
+ if (filterClause.op === void 0 || filterClause.column === void 0) {
30
+ return false;
31
+ } else if (isMultiValueFilter(filterClause)) {
32
+ return hasValues(filterClause);
33
+ } else if (isSingleValueFilter(filterClause)) {
34
+ return filterClause.value !== void 0 && filterClause.value !== "";
35
+ }
36
+ throw Error("isValidFilterClause should never reach this far");
37
+ };
38
+ var isValidFilter = (filter) => {
39
+ if (filter === void 0) {
40
+ return false;
41
+ } else if (isMultiClauseFilter(filter)) {
42
+ return filter.filters.every(isValidFilter);
43
+ } else {
44
+ return isValidFilterClause(filter);
45
+ }
46
+ };
47
+ var isValidFilterModel = (filterModel) => {
48
+ if (filterModel.isMultiClauseFilter) {
49
+ return filterModel.filterClauses.every((f) => f.isValid);
50
+ } else {
51
+ return filterModel.filterClauses.length === 1 && filterModel.filterClauses[0].isValid;
52
+ }
53
+ };
54
+ var _filterClause, _isValid;
55
+ var FilterClauseModel = class extends EventEmitter {
56
+ constructor(filterClause) {
57
+ super();
58
+ __privateAdd(this, _filterClause, void 0);
59
+ __privateAdd(this, _isValid, void 0);
60
+ __privateSet(this, _filterClause, filterClause);
61
+ __privateSet(this, _isValid, isValidFilterClause(filterClause));
62
+ }
63
+ get isValid() {
64
+ return __privateGet(this, _isValid);
65
+ }
66
+ setIsValid(isValid) {
67
+ __privateSet(this, _isValid, isValid);
68
+ this.emit("isValid", isValid);
69
+ }
70
+ get column() {
71
+ return __privateGet(this, _filterClause).column;
72
+ }
73
+ set column(column) {
74
+ __privateSet(this, _filterClause, {
75
+ column
76
+ });
77
+ const isValid = isValidFilterClause(__privateGet(this, _filterClause));
78
+ this.emit("filterClause", __privateGet(this, _filterClause), isValid);
79
+ if (isValid !== __privateGet(this, _isValid)) {
80
+ this.setIsValid(isValid);
81
+ }
82
+ }
83
+ get op() {
84
+ return __privateGet(this, _filterClause).op;
85
+ }
86
+ setOp(op) {
87
+ __privateSet(this, _filterClause, {
88
+ ...__privateGet(this, _filterClause),
89
+ op
90
+ });
91
+ const isValid = isValidFilterClause(__privateGet(this, _filterClause));
92
+ this.emit("filterClause", __privateGet(this, _filterClause), isValid);
93
+ if (isValid !== __privateGet(this, _isValid)) {
94
+ this.setIsValid(isValid);
95
+ }
96
+ }
97
+ setValue(value, isFinal = true) {
98
+ console.log(`setValue ${value} isFinal(${isFinal})`);
99
+ if (isSingleValueFilter(__privateGet(this, _filterClause))) {
100
+ __privateSet(this, _filterClause, {
101
+ ...__privateGet(this, _filterClause),
102
+ value
103
+ });
104
+ } else if (Array.isArray(value)) {
105
+ __privateSet(this, _filterClause, {
106
+ ...__privateGet(this, _filterClause),
107
+ values: value
108
+ });
109
+ }
110
+ const isValid = isValidFilterClause(__privateGet(this, _filterClause));
111
+ if (isValid !== __privateGet(this, _isValid)) {
112
+ this.setIsValid(isValid);
113
+ }
114
+ if (isFinal) {
115
+ this.emit("filterClause", __privateGet(this, _filterClause), isValid);
116
+ }
117
+ }
118
+ asFilter(throwIfInvalid = true) {
119
+ if (throwIfInvalid && !__privateGet(this, _isValid)) {
120
+ throw Error("Invalid filter model cannot be returned as Filter");
121
+ }
122
+ return __privateGet(this, _filterClause);
123
+ }
124
+ };
125
+ _filterClause = new WeakMap();
126
+ _isValid = new WeakMap();
127
+ var _children, _isValid2, _op;
128
+ var FilterModel = class extends EventEmitter {
129
+ constructor(filter) {
130
+ super();
131
+ __privateAdd(this, _children, []);
132
+ __privateAdd(this, _isValid2, void 0);
133
+ __privateAdd(this, _op, void 0);
134
+ this.onFilterClauseChange = () => {
135
+ this.emit("filter", this.asFilter(false), __privateGet(this, _isValid2));
136
+ };
137
+ this.onFilterClauseStatusChange = (isValid) => {
138
+ if (!isValid && __privateGet(this, _isValid2)) {
139
+ this.setIsValid(false);
140
+ } else {
141
+ this.checkValidStatus();
142
+ }
143
+ };
144
+ if (isMultiClauseFilter(filter)) {
145
+ __privateSet(this, _op, filter.op);
146
+ filter.filters.forEach((f) => this.addFilterClause(f));
147
+ } else if (filter) {
148
+ this.addFilterClause(filter);
149
+ } else {
150
+ this.addNewFilterClause();
151
+ }
152
+ __privateSet(this, _isValid2, isValidFilter(filter));
153
+ }
154
+ get isValid() {
155
+ return __privateGet(this, _isValid2);
156
+ }
157
+ setIsValid(isValid) {
158
+ __privateSet(this, _isValid2, isValid);
159
+ this.emit("isValid", isValid);
160
+ }
161
+ addNewFilterClause(operator) {
162
+ const count = __privateGet(this, _children).length;
163
+ if (!operator && !__privateGet(this, _op) && count === 1) {
164
+ __privateSet(this, _op, "and");
165
+ } else if (operator && !__privateGet(this, _op) && count === 1) {
166
+ __privateSet(this, _op, operator);
167
+ } else if (operator && __privateGet(this, _op) && operator !== __privateGet(this, _op)) {
168
+ throw Error(
169
+ "FilterModel: use setOp to change the Filter combinator operator"
170
+ );
171
+ }
172
+ const filterClauseModel = new FilterClauseModel({});
173
+ filterClauseModel.on("isValid", this.onFilterClauseStatusChange);
174
+ filterClauseModel.on("filterClause", this.onFilterClauseChange);
175
+ __privateGet(this, _children).push(filterClauseModel);
176
+ this.setIsValid(false);
177
+ }
178
+ addFilterClause(filterClause = {}) {
179
+ const filterClauseModel = new FilterClauseModel(filterClause);
180
+ filterClauseModel.on("isValid", this.onFilterClauseStatusChange);
181
+ filterClauseModel.on("filterClause", this.onFilterClauseChange);
182
+ __privateGet(this, _children).push(filterClauseModel);
183
+ }
184
+ removeFilterClause(filterClause) {
185
+ if (this.isMultiClauseFilter) {
186
+ const doomedFilter = this.filterClauses.indexOf(filterClause);
187
+ if (doomedFilter !== -1) {
188
+ this.filterClauses.splice(doomedFilter, 1);
189
+ if (this.filterClauses.length === 1) {
190
+ __privateSet(this, _op, void 0);
191
+ }
192
+ this.checkValidStatus();
193
+ }
194
+ }
195
+ }
196
+ checkValidStatus() {
197
+ const nowValid = isValidFilterModel(this);
198
+ if (nowValid !== __privateGet(this, _isValid2)) {
199
+ this.setIsValid(nowValid);
200
+ }
201
+ }
202
+ getFilterClause(index) {
203
+ return __privateGet(this, _children)[index];
204
+ }
205
+ get op() {
206
+ return __privateGet(this, _op);
207
+ }
208
+ setOp(op) {
209
+ __privateSet(this, _op, op);
210
+ this.emit("filter", this.asFilter(false), __privateGet(this, _isValid2));
211
+ }
212
+ get filterClauses() {
213
+ return __privateGet(this, _children);
214
+ }
215
+ get isMultiClauseFilter() {
216
+ return __privateGet(this, _op) === "and" || __privateGet(this, _op) === "or";
217
+ }
218
+ asFilter(throwIfInvalid = true) {
219
+ if (throwIfInvalid && !__privateGet(this, _isValid2)) {
220
+ throw Error("Invalid filter model cannot be returned as Filter");
221
+ }
222
+ if (__privateGet(this, _op) === "and" || __privateGet(this, _op) === "or") {
223
+ return {
224
+ op: __privateGet(this, _op),
225
+ filters: __privateGet(this, _children).map((f) => f.asFilter(throwIfInvalid))
226
+ };
227
+ } else {
228
+ return __privateGet(this, _children)[0].asFilter(throwIfInvalid);
229
+ }
230
+ }
231
+ };
232
+ _children = new WeakMap();
233
+ _isValid2 = new WeakMap();
234
+ _op = new WeakMap();
235
+
20
236
  // src/filter-bar/FilterBar.tsx
21
- import { Prompt } from "@vuu-ui/vuu-popups";
22
- import { Toolbar } from "@vuu-ui/vuu-ui-controls";
23
- import { Button as Button2 } from "@salt-ds/core";
237
+ import { PopupComponent as Popup, Portal, Prompt } from "@vuu-ui/vuu-popups";
238
+ import { IconButton } from "@vuu-ui/vuu-ui-controls";
24
239
 
25
240
  // ../../node_modules/clsx/dist/clsx.mjs
26
241
  function r(e) {
@@ -46,55 +261,11 @@ var clsx_default = clsx;
46
261
  // src/filter-bar/FilterBar.tsx
47
262
  import { useRef as useRef6 } from "react";
48
263
 
49
- // src/filter-builder-menu/FilterBuilderMenu.tsx
50
- import { useCallback, useRef } from "react";
51
- import { PopupComponent as Popup, Portal } from "@vuu-ui/vuu-popups";
52
- import { List, ListItem } from "@vuu-ui/vuu-ui-controls";
53
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
54
- var classBase = "vuuFilterBuilderMenu";
55
- var FilterBuilderMenu = ({
56
- ListProps: ListProps2,
57
- onMenuAction
58
- }) => {
59
- const ref = useRef(null);
60
- const listRef = useCallback((el) => {
61
- if (el) {
62
- requestAnimationFrame(() => {
63
- el.focus();
64
- });
65
- }
66
- }, []);
67
- const handleSelect = useCallback(
68
- (evt, selected) => {
69
- const {
70
- props: { "data-action": action }
71
- } = selected;
72
- onMenuAction({ type: "menu-action", menuId: action, options: {} });
73
- },
74
- [onMenuAction]
75
- );
76
- return /* @__PURE__ */ jsxs(Fragment, { children: [
77
- /* @__PURE__ */ jsx("span", { className: `${classBase}-trigger`, ref }),
78
- /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Popup, { anchorElement: ref, placement: "right", children: /* @__PURE__ */ jsxs(
79
- List,
80
- {
81
- ...ListProps2,
82
- className: `${classBase}List`,
83
- defaultHighlightedIndex: 0,
84
- itemHeight: 22,
85
- ref: listRef,
86
- onSelect: handleSelect,
87
- style: { position: "relative" },
88
- width: 100,
89
- children: [
90
- /* @__PURE__ */ jsx(ListItem, { "data-action": "apply-save", children: /* @__PURE__ */ jsx("span", { className: "vuuMenuButton", children: "APPLY AND SAVE" }) }),
91
- /* @__PURE__ */ jsx(ListItem, { "data-action": "and-clause", children: "AND" }),
92
- /* @__PURE__ */ jsx(ListItem, { "data-action": "or-clause", children: "OR" })
93
- ]
94
- }
95
- ) }) })
96
- ] });
97
- };
264
+ // src/filter-editor/FilterEditor.tsx
265
+ import { SplitButton } from "@vuu-ui/vuu-ui-controls";
266
+
267
+ // src/filter-clause/FilterClause.tsx
268
+ import { useMemo as useMemo4 } from "react";
98
269
 
99
270
  // src/filter-clause/ExpandoCombobox.tsx
100
271
  import { itemToString as defaultToString } from "@vuu-ui/vuu-utils";
@@ -103,13 +274,13 @@ import {
103
274
  } from "@vuu-ui/vuu-ui-controls";
104
275
  import {
105
276
  forwardRef,
106
- useCallback as useCallback2,
277
+ useCallback,
107
278
  useMemo,
108
- useRef as useRef2,
279
+ useRef,
109
280
  useState
110
281
  } from "react";
111
- import { jsx as jsx2 } from "react/jsx-runtime";
112
- var classBase2 = "vuuExpandoCombobox";
282
+ import { jsx } from "react/jsx-runtime";
283
+ var classBase = "vuuExpandoCombobox";
113
284
  var NO_INPUT_PROPS = {};
114
285
  var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
115
286
  className: classNameProp,
@@ -126,8 +297,8 @@ var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
126
297
  }, forwardedRef) {
127
298
  const [text, setText] = useState(value);
128
299
  const { itemToString = defaultToString } = props;
129
- const initialValue = useRef2(value);
130
- const itemsToString = useCallback2(
300
+ const initialValue = useRef(value);
301
+ const itemsToString = useCallback(
131
302
  (items) => {
132
303
  const [first, ...rest] = items;
133
304
  if (rest.length) {
@@ -138,7 +309,7 @@ var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
138
309
  },
139
310
  [itemToString]
140
311
  );
141
- const handleInputChange = useCallback2(
312
+ const handleInputChange = useCallback(
142
313
  (evt) => {
143
314
  const { value: value2 } = evt.target;
144
315
  setText(value2);
@@ -146,15 +317,15 @@ var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
146
317
  },
147
318
  [onInputChange]
148
319
  );
149
- const handleSetSelectedText = useCallback2((text2) => {
320
+ const handleSetSelectedText = useCallback((text2) => {
150
321
  setText(text2);
151
322
  }, []);
152
- const [InputProps, ListProps2] = useMemo(() => {
323
+ const [InputProps, ListProps] = useMemo(() => {
153
324
  const { inputProps, ...restInputProps } = InputPropsProp;
154
325
  return [
155
326
  {
156
327
  ...restInputProps,
157
- className: `${classBase2}-Input`,
328
+ className: `${classBase}-Input`,
158
329
  endAdornment: null,
159
330
  inputProps: {
160
331
  ...inputProps,
@@ -176,7 +347,7 @@ var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
176
347
  }
177
348
  ];
178
349
  }, [InputPropsProp, handleInputChange, ListPropsProp]);
179
- const handleSelectionChange = useCallback2(
350
+ const handleSelectionChange = useCallback(
180
351
  (_, selected) => {
181
352
  if (Array.isArray(selected)) {
182
353
  onSelectionChange == null ? void 0 : onSelectionChange(
@@ -193,7 +364,7 @@ var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
193
364
  },
194
365
  [itemToString, onSelectionChange]
195
366
  );
196
- const getDefaultSelected = () => {
367
+ const getSelected = () => {
197
368
  if (initialValue.current === void 0) {
198
369
  return void 0;
199
370
  } else if (Array.isArray(initialValue.current)) {
@@ -209,23 +380,23 @@ var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
209
380
  const popupProps = {
210
381
  minWidth: "fit-content"
211
382
  };
212
- return /* @__PURE__ */ jsx2(
383
+ return /* @__PURE__ */ jsx(
213
384
  "div",
214
385
  {
215
- className: clsx_default(classBase2, classNameProp),
386
+ className: clsx_default(classBase, classNameProp),
216
387
  "data-text": text,
217
388
  ref: forwardedRef,
218
389
  style,
219
- children: /* @__PURE__ */ jsx2(
390
+ children: /* @__PURE__ */ jsx(
220
391
  ComboBox,
221
392
  {
222
393
  ...props,
223
394
  PopupProps: popupProps,
224
- allowEnterCommitsText: true,
225
- defaultSelected: getDefaultSelected(),
395
+ className: "vuuEmbedded",
396
+ defaultSelected: getSelected(),
226
397
  defaultValue: Array.isArray(initialValue.current) ? itemsToString(initialValue.current) : initialValue.current,
227
398
  fullWidth: true,
228
- ListProps: ListProps2,
399
+ ListProps,
229
400
  InputProps,
230
401
  itemsToString,
231
402
  onSelectionChange: handleSelectionChange,
@@ -238,79 +409,65 @@ var ExpandoCombobox = forwardRef(function ExpandoCombobox2({
238
409
  );
239
410
  });
240
411
 
241
- // src/filter-clause/FilterClauseEditor.tsx
242
- import { Button } from "@salt-ds/core";
243
- import { useMemo as useMemo4 } from "react";
244
-
245
- // src/filter-clause/operator-utils.ts
246
- import { isNumericColumn, isTextColumn } from "@vuu-ui/vuu-utils";
247
- var textOperators = ["=", "in", "!=", "starts", "ends"];
248
- var numericperators = ["=", "!=", ">", ">=", "<", "<="];
249
- var getOperators = (column) => {
250
- if (isTextColumn(column)) {
251
- return textOperators;
252
- } else if (isNumericColumn(column)) {
253
- return numericperators;
254
- } else {
255
- throw Error("getOperators only supports text and numeric columns");
256
- }
257
- };
258
-
259
- // src/filter-clause/NumericInput.tsx
412
+ // src/filter-clause/value-editors/FilterClauseValueEditorNumber.tsx
260
413
  import {
261
414
  forwardRef as forwardRef2,
262
415
  useState as useState2,
263
- useCallback as useCallback3
416
+ useCallback as useCallback2
264
417
  } from "react";
265
418
  import { ExpandoInput } from "@vuu-ui/vuu-ui-controls";
266
419
  import { isValidNumber } from "@vuu-ui/vuu-utils";
267
- import { jsx as jsx3 } from "react/jsx-runtime";
268
- var NumericInput = forwardRef2(function NumericInput2({
269
- InputProps,
270
- className,
271
- onInputComplete,
272
- value: valueProp
273
- }, forwardedRef) {
274
- const [value, setValue] = useState2(
275
- isValidNumber(valueProp) ? valueProp.toString() : ""
276
- );
277
- const handleChange = useCallback3((evt) => {
278
- const { value: value2 } = evt.target;
279
- const numericValue = parseFloat(value2);
280
- if (isValidNumber(numericValue)) {
281
- console.log("its valid");
282
- }
283
- setValue(value2);
284
- }, []);
285
- const handleKeyDown = useCallback3(
286
- (evt) => {
287
- if (evt.key === "Enter") {
288
- const { value: value2 } = evt.target;
289
- const numericValue = parseFloat(value2);
290
- if (isValidNumber(numericValue)) {
291
- onInputComplete(numericValue);
420
+ import { jsx as jsx2 } from "react/jsx-runtime";
421
+ var FilterClauseValueEditorNumber = forwardRef2(
422
+ function FilterClauseNumericValueEditor({
423
+ InputProps,
424
+ className,
425
+ "data-field": dataField,
426
+ onChangeValue,
427
+ value: valueProp
428
+ }, forwardedRef) {
429
+ const [value, setValue] = useState2(
430
+ isValidNumber(valueProp) ? valueProp.toString() : ""
431
+ );
432
+ const handleChange = useCallback2((evt) => {
433
+ const { value: value2 } = evt.target;
434
+ const numericValue = parseFloat(value2);
435
+ if (isValidNumber(numericValue)) {
436
+ console.log("its valid");
437
+ }
438
+ setValue(value2);
439
+ }, []);
440
+ const handleKeyDown = useCallback2(
441
+ (evt) => {
442
+ if (evt.key === "Enter" || evt.key === "Tab") {
443
+ const { value: value2 } = evt.target;
444
+ const numericValue = parseFloat(value2);
445
+ if (isValidNumber(numericValue)) {
446
+ onChangeValue(numericValue);
447
+ }
292
448
  }
449
+ },
450
+ [onChangeValue]
451
+ );
452
+ return /* @__PURE__ */ jsx2(
453
+ ExpandoInput,
454
+ {
455
+ ...InputProps,
456
+ className,
457
+ "data-field": dataField,
458
+ onChange: handleChange,
459
+ onKeyDown: handleKeyDown,
460
+ ref: forwardedRef,
461
+ value
293
462
  }
294
- },
295
- [onInputComplete]
296
- );
297
- return /* @__PURE__ */ jsx3(
298
- ExpandoInput,
299
- {
300
- ...InputProps,
301
- className,
302
- value,
303
- ref: forwardedRef,
304
- onChange: handleChange,
305
- onKeyDown: handleKeyDown
306
- }
307
- );
308
- });
463
+ );
464
+ }
465
+ );
309
466
 
310
- // src/filter-clause/FilterClauseTextValueEditor.tsx
467
+ // src/filter-clause/value-editors/FilterClauseValueEditorText.tsx
311
468
  import {
312
469
  forwardRef as forwardRef3,
313
- useCallback as useCallback4,
470
+ useCallback as useCallback3,
314
471
  useEffect,
315
472
  useMemo as useMemo2,
316
473
  useState as useState3
@@ -319,7 +476,7 @@ import { useTypeaheadSuggestions } from "@vuu-ui/vuu-data-react";
319
476
  import {
320
477
  ExpandoInput as ExpandoInput2
321
478
  } from "@vuu-ui/vuu-ui-controls";
322
- import { jsx as jsx4 } from "react/jsx-runtime";
479
+ import { jsx as jsx3 } from "react/jsx-runtime";
323
480
  var selectionKeys = ["Enter", " "];
324
481
  var getVuuTable = (schemaTable) => {
325
482
  if (schemaTable.session) {
@@ -330,170 +487,186 @@ var getVuuTable = (schemaTable) => {
330
487
  }
331
488
  };
332
489
  var NO_DATA_MATCH = ["No matching data"];
333
- var FilterClauseTextValueEditor = forwardRef3(function TextInput({
334
- InputProps: InputPropsProp = {},
335
- className,
336
- column,
337
- "data-field": dataField,
338
- onDeselect,
339
- onInputComplete,
340
- operator,
341
- suggestionProvider = useTypeaheadSuggestions,
342
- table,
343
- value
344
- }, forwardedRef) {
345
- var _a;
346
- const isMultiValue = operator === "in";
347
- const [valueInputValue, setValueInputValue] = useState3(
348
- (_a = value == null ? void 0 : value.toString()) != null ? _a : ""
349
- );
350
- const [typeaheadValues, setTypeaheadValues] = useState3([]);
351
- const getSuggestions = suggestionProvider();
352
- const handleSingleValueSelectionChange = useCallback4(
353
- (_, value2) => onInputComplete(value2),
354
- [onInputComplete]
355
- );
356
- const handleMultiValueSelectionChange = useCallback4(
357
- (_, values) => onInputComplete(values),
358
- [onInputComplete]
359
- );
360
- useEffect(() => {
361
- if (table) {
362
- const vuuTable = getVuuTable(table);
363
- const params = valueInputValue && !isMultiValue ? [vuuTable, column.name, valueInputValue] : [vuuTable, column.name];
364
- getSuggestions(params).then((suggestions) => {
365
- if (suggestions.length === 0 && valueInputValue) {
366
- setTypeaheadValues(NO_DATA_MATCH);
490
+ var FilterClauseValueEditorText = forwardRef3(
491
+ function FilterClauseTextValueEditor({
492
+ InputProps: InputPropsProp = {},
493
+ className,
494
+ column,
495
+ "data-field": dataField,
496
+ onDeselect,
497
+ onChangeValue,
498
+ operator,
499
+ suggestionProvider = useTypeaheadSuggestions,
500
+ table,
501
+ value
502
+ }, forwardedRef) {
503
+ var _a;
504
+ const isMultiValue = operator === "in";
505
+ const [valueInputValue, setValueInputValue] = useState3(
506
+ (_a = value == null ? void 0 : value.toString()) != null ? _a : ""
507
+ );
508
+ const [typeaheadValues, setTypeaheadValues] = useState3([]);
509
+ const getSuggestions = suggestionProvider();
510
+ const handleSingleValueSelectionChange = useCallback3(
511
+ (_, value2) => onChangeValue(value2),
512
+ [onChangeValue]
513
+ );
514
+ const handleMultiValueSelectionChange = useCallback3(
515
+ (_, values) => onChangeValue(values),
516
+ [onChangeValue]
517
+ );
518
+ useEffect(() => {
519
+ if (table) {
520
+ const vuuTable = getVuuTable(table);
521
+ const params = valueInputValue && !isMultiValue ? [vuuTable, column.name, valueInputValue] : [vuuTable, column.name];
522
+ getSuggestions(params).then((suggestions) => {
523
+ if (suggestions.length === 0 && valueInputValue) {
524
+ setTypeaheadValues(NO_DATA_MATCH);
525
+ } else {
526
+ setTypeaheadValues(suggestions);
527
+ }
528
+ }).catch((err) => {
529
+ console.error("Error getting suggestions", err);
530
+ });
531
+ }
532
+ }, [table, column, valueInputValue, getSuggestions, isMultiValue]);
533
+ const handleInputChange = useCallback3(
534
+ (evt) => {
535
+ const { value: value2 } = evt.target;
536
+ console.log(`handleInputChange "${value2}"`);
537
+ setValueInputValue(value2);
538
+ if (operator === "starts" || operator === "ends") {
539
+ onChangeValue(value2, false);
540
+ }
541
+ },
542
+ [onChangeValue, operator]
543
+ );
544
+ const handleKeyDownFreeTextInput = useCallback3(
545
+ (evt) => {
546
+ var _a2, _b;
547
+ console.log(`handleKeyDownFreeTextInput ${valueInputValue}`);
548
+ if ((evt.key === "Enter" || evt.key === "Tab") && valueInputValue !== "") {
549
+ evt.stopPropagation();
550
+ evt.preventDefault();
551
+ console.log(`call onInputComplete ${valueInputValue}`);
552
+ onChangeValue(valueInputValue);
367
553
  } else {
368
- setTypeaheadValues(suggestions);
554
+ (_b = (_a2 = InputPropsProp == null ? void 0 : InputPropsProp.inputProps) == null ? void 0 : _a2.onKeyDown) == null ? void 0 : _b.call(_a2, evt);
369
555
  }
370
- }).catch((err) => {
371
- console.error("Error getting suggestions", err);
372
- });
373
- }
374
- }, [table, column, valueInputValue, getSuggestions, isMultiValue]);
375
- const handleInputChange = useCallback4((evt) => {
376
- const { value: value2 } = evt.target;
377
- setValueInputValue(value2);
378
- }, []);
379
- const InputProps = useMemo2(() => {
380
- if (operator !== "in") {
381
- const { inputProps, ...restInputProps } = InputPropsProp;
382
- return {
383
- ...restInputProps,
384
- inputProps: {
385
- ...inputProps,
386
- onKeyDown: (evt) => {
387
- var _a2;
388
- if (evt.key === "Enter" && valueInputValue !== "") {
389
- evt.stopPropagation();
390
- evt.preventDefault();
391
- onInputComplete(valueInputValue);
392
- } else {
393
- (_a2 = inputProps == null ? void 0 : inputProps.onKeyDown) == null ? void 0 : _a2.call(inputProps, evt);
394
- }
556
+ },
557
+ [InputPropsProp, onChangeValue, valueInputValue]
558
+ );
559
+ const InputProps = useMemo2(() => {
560
+ if (operator === "starts" || operator === "ends") {
561
+ const { inputProps, ...restInputProps } = InputPropsProp;
562
+ return {
563
+ ...restInputProps,
564
+ inputProps: {
565
+ ...inputProps,
566
+ onKeyDown: handleKeyDownFreeTextInput
395
567
  }
568
+ };
569
+ } else {
570
+ return InputPropsProp;
571
+ }
572
+ }, [InputPropsProp, handleKeyDownFreeTextInput, operator]);
573
+ const getValueInputField = useCallback3(() => {
574
+ switch (operator) {
575
+ case "in":
576
+ return /* @__PURE__ */ jsx3(
577
+ ExpandoCombobox,
578
+ {
579
+ InputProps,
580
+ className,
581
+ "data-field": dataField,
582
+ initialHighlightedIndex: 0,
583
+ source: typeaheadValues,
584
+ onInputChange: handleInputChange,
585
+ onSelectionChange: handleMultiValueSelectionChange,
586
+ ref: forwardedRef,
587
+ selectionStrategy: "multiple",
588
+ selectionKeys,
589
+ value
590
+ }
591
+ );
592
+ case "starts": {
593
+ return /* @__PURE__ */ jsx3(
594
+ ExpandoCombobox,
595
+ {
596
+ InputProps,
597
+ ListProps: {
598
+ className: "vuuIllustrationsOnly",
599
+ disabled: true
600
+ },
601
+ allowEnterCommitsText: true,
602
+ allowFreeText: true,
603
+ className,
604
+ "data-field": dataField,
605
+ initialHighlightedIndex: 0,
606
+ disableFilter: typeaheadValues === NO_DATA_MATCH && (valueInputValue == null ? void 0 : valueInputValue.length) > 0,
607
+ source: typeaheadValues,
608
+ onInputChange: handleInputChange,
609
+ onSelectionChange: handleSingleValueSelectionChange,
610
+ ref: forwardedRef,
611
+ value
612
+ }
613
+ );
396
614
  }
397
- };
398
- } else {
399
- return InputPropsProp;
400
- }
401
- }, [InputPropsProp, onInputComplete, operator, valueInputValue]);
402
- const getValueInputField = useCallback4(() => {
403
- switch (operator) {
404
- case "in":
405
- return /* @__PURE__ */ jsx4(
406
- ExpandoCombobox,
407
- {
408
- InputProps,
409
- className,
410
- "data-field": dataField,
411
- initialHighlightedIndex: 0,
412
- source: typeaheadValues,
413
- onInputChange: handleInputChange,
414
- onSelectionChange: handleMultiValueSelectionChange,
415
- ref: forwardedRef,
416
- selectionStrategy: "multiple",
417
- selectionKeys,
418
- value
419
- }
420
- );
421
- case "starts": {
422
- return /* @__PURE__ */ jsx4(
423
- ExpandoCombobox,
424
- {
425
- InputProps,
426
- ListProps: {
427
- className: "vuuIllustrationsOnly",
428
- disabled: true
429
- },
430
- allowFreeText: true,
431
- className,
432
- "data-field": dataField,
433
- initialHighlightedIndex: 0,
434
- disableFilter: typeaheadValues === NO_DATA_MATCH && (valueInputValue == null ? void 0 : valueInputValue.length) > 0,
435
- source: typeaheadValues,
436
- onInputChange: handleInputChange,
437
- onSelectionChange: handleSingleValueSelectionChange,
438
- ref: forwardedRef,
439
- value
440
- }
441
- );
615
+ case "ends":
616
+ return /* @__PURE__ */ jsx3(
617
+ ExpandoInput2,
618
+ {
619
+ ...InputProps,
620
+ className,
621
+ "data-field": dataField,
622
+ value: valueInputValue,
623
+ ref: forwardedRef,
624
+ onChange: handleInputChange
625
+ }
626
+ );
627
+ default:
628
+ return typeaheadValues.length > 0 ? /* @__PURE__ */ jsx3(
629
+ ExpandoCombobox,
630
+ {
631
+ InputProps,
632
+ allowBackspaceClearsSelection: true,
633
+ allowFreeText: true,
634
+ className,
635
+ "data-field": dataField,
636
+ initialHighlightedIndex: 0,
637
+ source: typeaheadValues,
638
+ title: "value",
639
+ onInputChange: handleInputChange,
640
+ onDeselect,
641
+ onSelectionChange: handleSingleValueSelectionChange,
642
+ ref: forwardedRef,
643
+ value
644
+ }
645
+ ) : null;
442
646
  }
443
- case "ends":
444
- return /* @__PURE__ */ jsx4(
445
- ExpandoInput2,
446
- {
447
- ...InputProps,
448
- className,
449
- "data-field": dataField,
450
- value: valueInputValue,
451
- ref: forwardedRef,
452
- onChange: handleInputChange
453
- }
454
- );
455
- default:
456
- return /* @__PURE__ */ jsx4(
457
- ExpandoCombobox,
458
- {
459
- InputProps,
460
- allowBackspaceClearsSelection: true,
461
- allowFreeText: true,
462
- className,
463
- "data-field": dataField,
464
- initialHighlightedIndex: 0,
465
- source: typeaheadValues,
466
- title: "value",
467
- onInputChange: handleInputChange,
468
- onDeselect,
469
- onSelectionChange: handleSingleValueSelectionChange,
470
- ref: forwardedRef,
471
- value
472
- }
473
- );
474
- }
475
- }, [
476
- InputProps,
477
- operator,
478
- className,
479
- dataField,
480
- typeaheadValues,
481
- handleInputChange,
482
- handleMultiValueSelectionChange,
483
- forwardedRef,
484
- value,
485
- valueInputValue,
486
- onDeselect,
487
- handleSingleValueSelectionChange
488
- ]);
489
- return getValueInputField();
490
- });
647
+ }, [
648
+ InputProps,
649
+ operator,
650
+ className,
651
+ dataField,
652
+ typeaheadValues,
653
+ handleInputChange,
654
+ handleMultiValueSelectionChange,
655
+ forwardedRef,
656
+ value,
657
+ valueInputValue,
658
+ onDeselect,
659
+ handleSingleValueSelectionChange
660
+ ]);
661
+ return getValueInputField();
662
+ }
663
+ );
491
664
 
492
- // src/filter-clause/FilterClauseValueEditor.tsx
665
+ // src/filter-clause/value-editors/FilterClauseValueEditor.tsx
493
666
  import { isDateTimeColumn } from "@vuu-ui/vuu-utils";
494
667
 
495
- // src/filter-clause/DateInput.tsx
496
- import { useCallback as useCallback5, useState as useState4 } from "react";
668
+ // src/filter-clause/value-editors/FilterClauseValueEditorDate.tsx
669
+ import { useCallback as useCallback4, useState as useState4 } from "react";
497
670
 
498
671
  // ../../node_modules/@internationalized/date/dist/import.mjs
499
672
  var $14e0f24ef4ac5c92$var$localTimeZone = null;
@@ -520,31 +693,52 @@ var $7c5f6fbf42389787$var$MONTH_DAYS = 29;
520
693
  var $7c5f6fbf42389787$var$MONTH_FRACT = 12 * $7c5f6fbf42389787$var$HOUR_PARTS + 793;
521
694
  var $7c5f6fbf42389787$var$MONTH_PARTS = $7c5f6fbf42389787$var$MONTH_DAYS * $7c5f6fbf42389787$var$DAY_PARTS + $7c5f6fbf42389787$var$MONTH_FRACT;
522
695
 
523
- // src/filter-clause/DateInput.tsx
524
- import { DatePicker } from "@vuu-ui/vuu-ui-controls";
525
- import { toCalendarDate } from "@vuu-ui/vuu-utils";
526
- import { jsx as jsx5 } from "react/jsx-runtime";
527
- var DateInput = (props) => {
528
- const { value, onInputComplete, operator } = props;
696
+ // src/filter-clause/value-editors/FilterClauseValueEditorDate.tsx
697
+ import { DateInput } from "@vuu-ui/vuu-ui-controls";
698
+ import { queryClosest, toCalendarDate } from "@vuu-ui/vuu-utils";
699
+ import { jsx as jsx4 } from "react/jsx-runtime";
700
+ var FilterClauseValueEditorDate = (props) => {
701
+ const { InputProps, className, onChangeValue, operator, value } = props;
529
702
  const toEpochMilliS = getEpochMillisConverter(operator);
530
703
  const [date, setDate] = useState4(
531
704
  () => getInitialState(value)
532
705
  );
533
- const onSelectedDateChange = useCallback5((d) => {
534
- setDate(d);
535
- }, []);
536
- const onBlur = useCallback5(() => {
537
- date && onInputComplete(toEpochMilliS(date));
538
- }, [date, onInputComplete, toEpochMilliS]);
539
- return /* @__PURE__ */ jsx5(
540
- DatePicker,
706
+ const onSelectedDateChange = useCallback4(
707
+ (d, source) => {
708
+ setDate(d);
709
+ if (d && source === "calendar") {
710
+ onChangeValue(toEpochMilliS(d));
711
+ }
712
+ },
713
+ [onChangeValue, toEpochMilliS]
714
+ );
715
+ const onBlur = useCallback4(() => {
716
+ date && onChangeValue(toEpochMilliS(date));
717
+ }, [date, onChangeValue, toEpochMilliS]);
718
+ const handleKeyDown = useCallback4(
719
+ (evt) => {
720
+ if (evt.key === "Enter") {
721
+ if (date) {
722
+ const popup = queryClosest(evt.target, ".vuuDatePopup");
723
+ if (popup === null) {
724
+ onChangeValue(toEpochMilliS(date));
725
+ }
726
+ }
727
+ } else if (evt.key === "Tab") {
728
+ console.log("better handle tab");
729
+ }
730
+ },
731
+ [date, onChangeValue, toEpochMilliS]
732
+ );
733
+ return /* @__PURE__ */ jsx4(
734
+ DateInput,
541
735
  {
542
- className: "vuuFilterClause-DatePicker",
736
+ inputProps: InputProps == null ? void 0 : InputProps.inputProps,
737
+ className: clsx_default("vuuFilterClause-DatePicker", className),
543
738
  selectedDate: date,
544
739
  onBlur,
545
- onSelectedDateChange,
546
- closeOnSelection: true,
547
- hideOutOfRangeDates: true
740
+ onChange: onSelectedDateChange,
741
+ onKeyDown: handleKeyDown
548
742
  }
549
743
  );
550
744
  };
@@ -567,9 +761,9 @@ var getEpochMillisConverter = (op) => (date, timezone = $14e0f24ef4ac5c92$export
567
761
  }
568
762
  };
569
763
 
570
- // src/filter-clause/FilterClauseValueEditor.tsx
571
- import { jsx as jsx6 } from "react/jsx-runtime";
572
- var classBase3 = "vuuFilterClause";
764
+ // src/filter-clause/value-editors/FilterClauseValueEditor.tsx
765
+ import { jsx as jsx5 } from "react/jsx-runtime";
766
+ var classBase2 = "vuuFilterClause";
573
767
  var FilterClauseValueEditor = ({
574
768
  selectedColumn,
575
769
  operator,
@@ -584,27 +778,31 @@ var FilterClauseValueEditor = ({
584
778
  return null;
585
779
  }
586
780
  if (isDateTimeColumn(selectedColumn)) {
587
- return /* @__PURE__ */ jsx6(
588
- DateInput,
781
+ console.log(`return DateInput`);
782
+ return /* @__PURE__ */ jsx5(
783
+ FilterClauseValueEditorDate,
589
784
  {
785
+ InputProps,
786
+ className: clsx_default(`${classBase2}Field`, `${classBase2}Value`),
787
+ "data-field": "value",
590
788
  value,
591
789
  operator,
592
- onInputComplete: onChangeValue
790
+ onChangeValue
593
791
  }
594
792
  );
595
793
  }
596
794
  switch (selectedColumn.serverDataType) {
597
795
  case "string":
598
796
  case "char":
599
- return /* @__PURE__ */ jsx6(
600
- FilterClauseTextValueEditor,
797
+ return /* @__PURE__ */ jsx5(
798
+ FilterClauseValueEditorText,
601
799
  {
602
800
  InputProps,
603
- className: clsx_default(`${classBase3}Field`, `${classBase3}Value`),
801
+ className: clsx_default(`${classBase2}Field`, `${classBase2}Value`),
604
802
  column: selectedColumn,
605
803
  "data-field": "value",
606
804
  onDeselect: onDeselectValue,
607
- onInputComplete: onChangeValue,
805
+ onChangeValue,
608
806
  operator,
609
807
  suggestionProvider,
610
808
  table,
@@ -614,13 +812,14 @@ var FilterClauseValueEditor = ({
614
812
  case "int":
615
813
  case "long":
616
814
  case "double":
617
- return /* @__PURE__ */ jsx6(
618
- NumericInput,
815
+ return /* @__PURE__ */ jsx5(
816
+ FilterClauseValueEditorNumber,
619
817
  {
620
818
  InputProps,
621
- className: clsx_default(`${classBase3}Field`, `${classBase3}Value`),
819
+ className: clsx_default(`${classBase2}Field`, `${classBase2}Value`),
622
820
  column: selectedColumn,
623
- onInputComplete: onChangeValue,
821
+ "data-field": "value",
822
+ onChangeValue,
624
823
  operator
625
824
  }
626
825
  );
@@ -630,34 +829,93 @@ var FilterClauseValueEditor = ({
630
829
  }
631
830
  };
632
831
 
633
- // src/filter-clause/useFilterClauseEditor.ts
634
- import {
635
- isMultiValueFilter,
636
- isSingleValueFilter,
637
- isValidFilterClauseOp
638
- } from "@vuu-ui/vuu-utils";
639
- import {
640
- useCallback as useCallback6,
641
- useEffect as useEffect2,
642
- useMemo as useMemo3,
643
- useRef as useRef3,
832
+ // src/filter-clause/operator-utils.ts
833
+ import { isNumericColumn, isTextColumn } from "@vuu-ui/vuu-utils";
834
+ var textOperators = ["=", "in", "!=", "starts", "ends"];
835
+ var numericperators = ["=", "!=", ">", ">=", "<", "<="];
836
+ var getOperators = (column) => {
837
+ if (isTextColumn(column)) {
838
+ return textOperators;
839
+ } else if (isNumericColumn(column)) {
840
+ return numericperators;
841
+ } else {
842
+ throw Error("getOperators only supports text and numeric columns");
843
+ }
844
+ };
845
+
846
+ // src/filter-clause/useFilterClause.ts
847
+ import { isValidFilterClauseOp } from "@vuu-ui/vuu-utils";
848
+ import {
849
+ useCallback as useCallback5,
850
+ useEffect as useEffect2,
851
+ useMemo as useMemo3,
852
+ useRef as useRef2,
644
853
  useState as useState5
645
854
  } from "react";
646
- var cursorAtTextStart = (input) => input.selectionStart === 0;
647
- var cursorAtTextEnd = (input) => input.selectionStart === input.value.length;
648
- var getFieldName = (field) => (field == null ? void 0 : field.classList.contains("vuuFilterClauseColumn")) ? "column" : (field == null ? void 0 : field.classList.contains("vuuFilterClauseOperator")) ? "operator" : "value";
649
- var getFocusedField = () => {
650
- const activeElement = document.activeElement;
651
- if (activeElement == null ? void 0 : activeElement.classList.contains("vuuFilterClause-clearButton")) {
652
- return activeElement;
855
+
856
+ // src/filter-clause/filterClauseFocusManagement.ts
857
+ import { getElementDataIndex, queryClosest as queryClosest2 } from "@vuu-ui/vuu-utils";
858
+ var getFilterClauseElement = (possiblyDescendant) => possiblyDescendant == null ? void 0 : possiblyDescendant.closest(".vuuFilterClause");
859
+ var getFilterClauseFieldElement = (possiblyDescendant) => possiblyDescendant == null ? void 0 : possiblyDescendant.closest(".vuuFilterClauseField");
860
+ var mapFilterFieldToClassName = {
861
+ column: "vuuFilterClauseColumn",
862
+ operator: "vuuFilterClauseOperator",
863
+ value: "vuuFilterClauseValue"
864
+ };
865
+ var getFilterClauseDetails = ({ classList }) => {
866
+ if (classList.contains("vuuFilterClauseColumn")) {
867
+ return "column";
868
+ } else if (classList.contains("vuuFilterClauseOperator")) {
869
+ return "operator";
870
+ } else if (classList.contains("vuuFilterClauseValue")) {
871
+ return "value";
872
+ } else {
873
+ throw Error(
874
+ "getFilterClauseField, filterClauseElemnent is missing required class name"
875
+ );
876
+ }
877
+ };
878
+ var getFocusedFieldDetails = () => {
879
+ const el = document.activeElement;
880
+ const field = queryClosest2(el, ".vuuFilterClauseField");
881
+ const filterClause = queryClosest2(field, ".vuuFilterClause");
882
+ if (filterClause && field) {
883
+ return [getElementDataIndex(filterClause), getFilterClauseDetails(field)];
653
884
  } else {
654
- return activeElement == null ? void 0 : activeElement.closest(".vuuFilterClauseField");
885
+ return [];
655
886
  }
656
887
  };
888
+ var focusField = (fieldOrClause) => {
889
+ const input = fieldOrClause == null ? void 0 : fieldOrClause.querySelector("input");
890
+ if (input) {
891
+ input.focus();
892
+ requestAnimationFrame(() => {
893
+ input == null ? void 0 : input.select();
894
+ });
895
+ }
896
+ };
897
+ var elementIsFilterCombinator = (element) => element !== null && element.classList.contains("vuuFilterClauseCombinator");
898
+ var elementIsFilterClause = (element) => element !== null && element.classList.contains("vuuFilterClause");
899
+ var focusFilterClauseField = (filterEditor, filterClauseIndex, fieldName = "value") => {
900
+ if (filterEditor) {
901
+ const fieldClassName = mapFilterFieldToClassName[fieldName];
902
+ const field = filterEditor.querySelector(
903
+ `.vuuFilterClause[data-index="${filterClauseIndex}"] .${fieldClassName}`
904
+ );
905
+ focusField(field);
906
+ }
907
+ };
908
+ var focusLastClauseValue = (filterEditor) => {
909
+ console.log("focusLastClauseValue");
910
+ const field = Array.from(
911
+ filterEditor == null ? void 0 : filterEditor.querySelectorAll(".vuuFilterClauseField")
912
+ ).at(-1);
913
+ focusField(field);
914
+ };
657
915
  var focusNextFocusableElement = (direction = "fwd") => {
658
916
  var _a;
659
917
  const activeField = getFocusedField();
660
- const filterClause = activeField == null ? void 0 : activeField.closest(".vuuFilterClause");
918
+ const filterClause = getFilterClauseElement(activeField);
661
919
  if (direction === "fwd" && (filterClause == null ? void 0 : filterClause.lastChild) === activeField) {
662
920
  requestAnimationFrame(() => {
663
921
  focusNextFocusableElement();
@@ -667,20 +925,17 @@ var focusNextFocusableElement = (direction = "fwd") => {
667
925
  (_a = nextField == null ? void 0 : nextField.querySelector("input")) == null ? void 0 : _a.focus();
668
926
  }
669
927
  };
670
- var clauseIsNotFirst = (el) => {
671
- var _a;
672
- const clause = el.closest("[data-index]");
673
- if (clause) {
674
- const index = clause.dataset.index;
675
- const previousClause = (_a = clause == null ? void 0 : clause.parentElement) == null ? void 0 : _a.querySelector(
676
- `[data-index]:has(.vuuFilterClause):has(+[data-index="${index}"])`
677
- );
678
- return previousClause !== null;
928
+ var getFocusedField = () => {
929
+ const activeElement = document.activeElement;
930
+ if (activeElement == null ? void 0 : activeElement.classList.contains("vuuFilterClause-clearButton")) {
931
+ return activeElement;
932
+ } else {
933
+ return getFilterClauseFieldElement(activeElement);
679
934
  }
680
935
  };
681
936
  var focusNextElement = () => {
682
937
  const filterClauseField = getFocusedField();
683
- const filterClause = filterClauseField == null ? void 0 : filterClauseField.closest(".vuuFilterClause");
938
+ const filterClause = getFilterClauseElement(filterClauseField);
684
939
  if (filterClause && filterClauseField) {
685
940
  if (filterClauseField.classList.contains("vuuFilterClauseValue")) {
686
941
  const clearButton = filterClause.querySelector(
@@ -692,119 +947,170 @@ var focusNextElement = () => {
692
947
  }
693
948
  }
694
949
  };
695
- var navigateToNextInputIfAtBoundary = (evt) => {
950
+ var cursorAtTextStart = (input) => input.selectionStart === 0;
951
+ var cursorAtTextEnd = (input) => input.selectionStart === input.value.length;
952
+ var getFieldName = (field) => (field == null ? void 0 : field.classList.contains("vuuFilterClauseColumn")) ? "column" : (field == null ? void 0 : field.classList.contains("vuuFilterClauseOperator")) ? "operator" : "value";
953
+ var clauseIsNotFirst = (el) => {
954
+ const clause = getFilterClauseElement(el);
955
+ if (clause) {
956
+ const index = getElementDataIndex(clause);
957
+ return index > 0;
958
+ }
959
+ };
960
+ var clauseIsNotLast = (el) => {
961
+ const clause = getFilterClauseElement(el);
962
+ const nextClause = clause == null ? void 0 : clause.nextElementSibling;
963
+ return nextClause == null ? void 0 : nextClause.classList.contains("vuuFilterClauseCombinator");
964
+ };
965
+ var tabToPreviousFilterCombinator = (currentElement) => {
966
+ const filterClause = getFilterClauseElement(currentElement);
967
+ const nextItem = filterClause.previousSibling;
968
+ console.log(`tab to previous combinator`);
969
+ nextItem == null ? void 0 : nextItem.focus();
970
+ };
971
+ var navigateToNextFilterClause = (currentElement, direction = "fwd") => {
972
+ if (direction === "bwd") {
973
+ if (elementIsFilterCombinator(currentElement)) {
974
+ const nextClause = currentElement.previousElementSibling;
975
+ if (elementIsFilterClause(nextClause)) {
976
+ const nextField = nextClause.querySelector(
977
+ ".vuuFilterClauseValue"
978
+ );
979
+ console.log(`focus field Value ${nextField == null ? void 0 : nextField.classList}`);
980
+ focusField(nextField);
981
+ }
982
+ } else {
983
+ const filterClause = getFilterClauseElement(currentElement);
984
+ const nextClause = filterClause.previousSibling;
985
+ const nextField = nextClause.querySelector(
986
+ ".vuuFilterClauseValue"
987
+ );
988
+ focusField(nextField);
989
+ }
990
+ } else {
991
+ const nextClause = currentElement.nextSibling;
992
+ focusField(nextClause);
993
+ }
994
+ };
995
+ var navigateToNextItemIfAtBoundary = (evt) => {
696
996
  const input = evt.target;
697
- const cursorAtStart = cursorAtTextStart(input);
698
- const cursorAtEnd = cursorAtTextEnd(input);
699
- const field = input.closest(".vuuFilterClauseField");
997
+ const field = getFilterClauseFieldElement(input);
700
998
  if (evt.key === "ArrowLeft") {
701
- if (cursorAtStart) {
999
+ if (cursorAtTextStart(input)) {
702
1000
  const fieldName = getFieldName(field);
703
1001
  if (fieldName === "column") {
704
- return;
1002
+ if (clauseIsNotFirst(input)) {
1003
+ const filterClause = getFilterClauseElement(field);
1004
+ const combinator = filterClause.previousElementSibling;
1005
+ combinator == null ? void 0 : combinator.focus();
1006
+ }
705
1007
  } else {
706
- const nextField = field.previousSibling;
707
- const nextInput = nextField == null ? void 0 : nextField.querySelector("input");
708
1008
  evt.preventDefault();
709
- nextInput == null ? void 0 : nextInput.focus();
710
- requestAnimationFrame(() => {
711
- nextInput == null ? void 0 : nextInput.select();
712
- });
1009
+ focusField(field.previousSibling);
713
1010
  }
714
1011
  }
715
1012
  evt.stopPropagation();
716
1013
  } else if (evt.key === "ArrowRight") {
717
- if (cursorAtEnd) {
1014
+ if (cursorAtTextEnd(input)) {
718
1015
  const fieldName = getFieldName(field);
719
1016
  if (fieldName === "value") {
1017
+ if (clauseIsNotLast(input)) {
1018
+ const filterClause = getFilterClauseElement(field);
1019
+ const combinator = filterClause.nextElementSibling;
1020
+ combinator == null ? void 0 : combinator.focus();
1021
+ }
720
1022
  return;
721
1023
  } else {
722
- const nextField = field.nextSibling;
723
- const nextInput = nextField == null ? void 0 : nextField.querySelector("input");
724
1024
  evt.preventDefault();
725
- nextInput == null ? void 0 : nextInput.focus();
726
- requestAnimationFrame(() => {
727
- nextInput == null ? void 0 : nextInput.select();
728
- });
1025
+ focusField(field.nextSibling);
729
1026
  }
730
1027
  }
731
1028
  evt.stopPropagation();
732
1029
  }
733
1030
  };
734
- var getFilterClauseValue = (filterClause) => {
735
- if (isMultiValueFilter(filterClause)) {
736
- return filterClause.values;
737
- } else if (isSingleValueFilter(filterClause)) {
738
- return filterClause.value;
739
- } else {
740
- return void 0;
1031
+ var focusFirstClauseIfAllClausesValid = (filterEditor) => {
1032
+ const columInput = Array.from(
1033
+ filterEditor.querySelectorAll(".vuuFilterClauseColumn input")
1034
+ );
1035
+ if (columInput.every((input) => input.value.length > 0)) {
1036
+ setTimeout(() => {
1037
+ const input = columInput.at(0);
1038
+ input == null ? void 0 : input.select();
1039
+ input == null ? void 0 : input.focus();
1040
+ }, 100);
741
1041
  }
742
1042
  };
743
- var useFilterClauseEditor = ({
744
- filterClause,
1043
+
1044
+ // src/filter-clause/useFilterClause.ts
1045
+ var useFilterClause = ({
1046
+ filterClauseModel,
745
1047
  onCancel,
746
- onChange,
747
1048
  columnsByName
748
1049
  }) => {
749
- const columnRef = useRef3(null);
750
- const operatorRef = useRef3(null);
751
- const [selectedColumn, setSelectedColumn] = useState5(filterClause.column ? columnsByName[filterClause.column] : void 0);
752
- const [operator, _setOperator] = useState5(
753
- filterClause.op
754
- );
755
- const setOperator = useCallback6((op) => {
756
- _setOperator(op);
757
- }, []);
758
- const [value, setValue] = useState5(
759
- getFilterClauseValue(filterClause)
760
- );
761
- const handleColumnSelect = useCallback6(
762
- (_, column) => {
763
- setSelectedColumn(column != null ? column : void 0);
764
- setOperator(void 0);
765
- setValue(void 0);
766
- setTimeout(() => {
767
- focusNextElement();
768
- }, 100);
769
- },
770
- [setOperator]
1050
+ var _a;
1051
+ const [filterClause, setFilterClause] = useState5(
1052
+ filterClauseModel.isValid ? filterClauseModel.asFilter() : {}
771
1053
  );
772
- const removeAndNavigateToNextInputIfAtBoundary = useCallback6(
1054
+ useMemo3(() => {
1055
+ filterClauseModel.on("filterClause", (filterClause2) => {
1056
+ setFilterClause(filterClause2);
1057
+ });
1058
+ }, [filterClauseModel]);
1059
+ const columnRef = useRef2(null);
1060
+ const operatorRef = useRef2(null);
1061
+ const removeAndNavigateToNextInputIfAtBoundary = useCallback5(
773
1062
  (evt) => {
774
- var _a;
1063
+ var _a2;
775
1064
  const input = evt.target;
776
1065
  if (input.value === "") {
777
- const field = input.closest(
778
- ".vuuFilterClauseField,[data-field]"
779
- );
780
- switch ((_a = field == null ? void 0 : field.dataset) == null ? void 0 : _a.field) {
1066
+ const field = input.closest("[data-field]");
1067
+ switch ((_a2 = field == null ? void 0 : field.dataset) == null ? void 0 : _a2.field) {
781
1068
  case "operator": {
782
- setOperator(void 0);
783
- setSelectedColumn(void 0);
1069
+ filterClauseModel.column = void 0;
784
1070
  focusNextFocusableElement("bwd");
785
1071
  break;
786
1072
  }
787
1073
  case "value": {
788
- setOperator(void 0);
1074
+ filterClauseModel.setOp(void 0);
789
1075
  focusNextFocusableElement("bwd");
790
1076
  break;
791
1077
  }
792
1078
  case "column": {
793
1079
  if (clauseIsNotFirst(input)) {
794
- console.log("This is NOT the first clause");
795
- onCancel == null ? void 0 : onCancel("Backspace");
1080
+ evt.preventDefault();
1081
+ onCancel == null ? void 0 : onCancel(filterClauseModel, "Backspace");
796
1082
  }
797
1083
  }
798
1084
  }
799
1085
  }
800
1086
  },
801
- [onCancel, setOperator]
1087
+ [filterClauseModel, onCancel]
802
1088
  );
803
- const handleOperatorSelect = useCallback6(
1089
+ const handleColumnSelect = useCallback5(
1090
+ (evt, column) => {
1091
+ var _a2;
1092
+ if ((evt == null ? void 0 : evt.type) === "keydown") {
1093
+ const { key } = evt;
1094
+ if (key === "Tab") {
1095
+ if (filterClauseModel.column === column.name) {
1096
+ return;
1097
+ } else {
1098
+ evt.preventDefault();
1099
+ }
1100
+ }
1101
+ }
1102
+ filterClauseModel.column = (_a2 = column == null ? void 0 : column.name) != null ? _a2 : void 0;
1103
+ setTimeout(() => {
1104
+ focusNextElement();
1105
+ }, 100);
1106
+ },
1107
+ [filterClauseModel]
1108
+ );
1109
+ const handleOperatorSelect = useCallback5(
804
1110
  (_, selected) => {
805
1111
  const op = selected;
806
1112
  if (op === void 0 || isValidFilterClauseOp(op)) {
807
- setOperator(op);
1113
+ filterClauseModel.setOp(op);
808
1114
  focusNextElement();
809
1115
  } else {
810
1116
  throw Error(
@@ -812,198 +1118,515 @@ var useFilterClauseEditor = ({
812
1118
  );
813
1119
  }
814
1120
  },
815
- [setOperator]
1121
+ [filterClauseModel]
816
1122
  );
817
- const handleChangeValue = useCallback6(
818
- (value2) => {
819
- setValue(value2);
820
- if (value2 !== null && value2 !== "") {
821
- if (Array.isArray(value2)) {
822
- onChange({
823
- column: selectedColumn == null ? void 0 : selectedColumn.name,
824
- op: operator,
825
- values: value2
826
- });
827
- } else {
828
- onChange({
829
- column: selectedColumn == null ? void 0 : selectedColumn.name,
830
- op: operator,
831
- value: value2
832
- });
833
- }
834
- }
835
- },
836
- [onChange, operator, selectedColumn == null ? void 0 : selectedColumn.name]
1123
+ const handleChangeValue = useCallback5(
1124
+ (value, isFinal) => filterClauseModel.setValue(value, isFinal),
1125
+ [filterClauseModel]
837
1126
  );
838
- const handleDeselectValue = useCallback6(() => {
839
- setValue(void 0);
840
- }, []);
841
- const handleKeyDownCaptureInput = useCallback6(
1127
+ const handleDeselectValue = useCallback5(
1128
+ () => filterClauseModel.setValue(void 0),
1129
+ [filterClauseModel]
1130
+ );
1131
+ const handleKeyDownCaptureNavigation = useCallback5(
842
1132
  (evt) => {
843
1133
  if (["ArrowLeft", "ArrowRight"].includes(evt.key)) {
844
- navigateToNextInputIfAtBoundary(evt);
1134
+ navigateToNextItemIfAtBoundary(evt);
845
1135
  } else if (evt.key === "Backspace") {
846
1136
  removeAndNavigateToNextInputIfAtBoundary(evt);
1137
+ } else if (evt.key === "Escape") {
1138
+ onCancel == null ? void 0 : onCancel(filterClauseModel, "Escape");
1139
+ } else if (evt.key === "Tab" && evt.shiftKey) {
1140
+ evt.preventDefault();
1141
+ tabToPreviousFilterCombinator(evt.target);
847
1142
  }
848
1143
  },
849
- [removeAndNavigateToNextInputIfAtBoundary]
850
- );
851
- const handleClear = useCallback6(
852
- (e) => {
853
- var _a;
854
- const button = e.target;
855
- const firstInput = (_a = button.closest(".vuuFilterClause")) == null ? void 0 : _a.querySelector("input");
856
- setSelectedColumn(void 0);
857
- setOperator(void 0);
858
- setValue(void 0);
859
- setTimeout(() => {
860
- firstInput.select();
861
- firstInput == null ? void 0 : firstInput.focus();
862
- }, 100);
863
- },
864
- [setOperator]
1144
+ [filterClauseModel, onCancel, removeAndNavigateToNextInputIfAtBoundary]
865
1145
  );
866
- const handleClearKeyDown = useCallback6((e) => {
867
- e.stopPropagation();
868
- if (e.key === "Backspace") {
869
- focusNextFocusableElement("bwd");
1146
+ const handleFocus = useCallback5((evt) => {
1147
+ if (elementIsFilterClause(evt.target)) {
1148
+ focusField(evt.target);
870
1149
  }
871
1150
  }, []);
872
1151
  const InputProps = useMemo3(
873
1152
  () => ({
874
1153
  inputProps: {
875
- onKeyDownCapture: handleKeyDownCaptureInput
1154
+ onKeyDownCapture: handleKeyDownCaptureNavigation,
1155
+ tabIndex: -1
876
1156
  }
877
1157
  }),
878
- [handleKeyDownCaptureInput]
1158
+ [handleKeyDownCaptureNavigation]
879
1159
  );
880
1160
  useEffect2(() => {
881
- var _a;
882
- const columnInput = (_a = columnRef.current) == null ? void 0 : _a.querySelector("input");
883
- columnInput == null ? void 0 : columnInput.focus();
884
- }, []);
1161
+ if (filterClauseModel.column === void 0) {
1162
+ requestAnimationFrame(() => {
1163
+ var _a2;
1164
+ const columnInput = (_a2 = columnRef == null ? void 0 : columnRef.current) == null ? void 0 : _a2.querySelector("input");
1165
+ columnInput == null ? void 0 : columnInput.focus();
1166
+ });
1167
+ }
1168
+ }, [filterClauseModel]);
885
1169
  return {
886
1170
  InputProps,
887
1171
  columnRef,
1172
+ filterClause,
888
1173
  onChangeValue: handleChangeValue,
889
- onClear: handleClear,
890
- onClearKeyDown: handleClearKeyDown,
891
1174
  onDeselectValue: handleDeselectValue,
892
1175
  onColumnSelect: handleColumnSelect,
1176
+ onFocus: handleFocus,
893
1177
  onOperatorSelect: handleOperatorSelect,
894
- operator,
895
1178
  operatorRef,
896
- selectedColumn,
897
- value
1179
+ selectedColumn: columnsByName[(_a = filterClause.column) != null ? _a : ""]
898
1180
  };
899
1181
  };
900
1182
 
901
- // src/filter-clause/FilterClauseEditor.tsx
902
- import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
903
- var classBase4 = "vuuFilterClause";
904
- var FilterClauseEditor = ({
1183
+ // src/filter-clause/FilterClause.tsx
1184
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
1185
+ var classBase3 = "vuuFilterClause";
1186
+ var FilterClause = ({
905
1187
  className,
906
1188
  columnsByName,
907
1189
  onCancel,
908
- onChange,
909
1190
  onDropdownClose,
910
1191
  onDropdownOpen,
911
- filterClause,
1192
+ filterClauseModel,
912
1193
  suggestionProvider,
913
1194
  tableSchema,
914
1195
  ...htmlAttributes
915
1196
  }) => {
916
- var _a;
1197
+ var _a, _b;
917
1198
  const {
918
1199
  InputProps,
919
1200
  columnRef,
1201
+ filterClause,
920
1202
  onChangeValue,
921
- onClear,
922
- onClearKeyDown,
923
- onDeselectValue,
924
1203
  onColumnSelect,
1204
+ onFocus,
1205
+ onDeselectValue,
925
1206
  onOperatorSelect,
926
- operator,
927
1207
  operatorRef,
928
- selectedColumn,
929
- value
930
- } = useFilterClauseEditor({
931
- filterClause,
1208
+ selectedColumn
1209
+ } = useFilterClause({
1210
+ filterClauseModel,
932
1211
  onCancel,
933
- onChange,
934
1212
  columnsByName
935
1213
  });
936
1214
  const columns = useMemo4(() => Object.values(columnsByName), [columnsByName]);
937
- return /* @__PURE__ */ jsxs2("div", { className: clsx_default(classBase4, className), ...htmlAttributes, tabIndex: 0, children: [
938
- /* @__PURE__ */ jsx7(
939
- ExpandoCombobox,
940
- {
941
- InputProps,
942
- allowBackspaceClearsSelection: true,
943
- className: clsx_default(`${classBase4}Field`, `${classBase4}Column`),
944
- "data-field": "column",
945
- initialHighlightedIndex: 0,
946
- itemToString: (column) => column.name,
947
- onListItemSelect: onColumnSelect,
948
- ref: columnRef,
949
- source: columns,
950
- title: "column",
951
- value: (_a = selectedColumn == null ? void 0 : selectedColumn.name) != null ? _a : ""
1215
+ return /* @__PURE__ */ jsxs(
1216
+ "div",
1217
+ {
1218
+ className: clsx_default(classBase3, className),
1219
+ ...htmlAttributes,
1220
+ onFocus,
1221
+ tabIndex: 0,
1222
+ children: [
1223
+ /* @__PURE__ */ jsx6(
1224
+ ExpandoCombobox,
1225
+ {
1226
+ InputProps,
1227
+ allowBackspaceClearsSelection: true,
1228
+ className: clsx_default(`${classBase3}Field`, `${classBase3}Column`),
1229
+ "data-field": "column",
1230
+ initialHighlightedIndex: 0,
1231
+ itemToString: (column) => column.name,
1232
+ onListItemSelect: onColumnSelect,
1233
+ ref: columnRef,
1234
+ source: columns,
1235
+ title: "column",
1236
+ value: filterClause.column
1237
+ },
1238
+ "column-field"
1239
+ ),
1240
+ (selectedColumn == null ? void 0 : selectedColumn.name) ? /* @__PURE__ */ jsx6(
1241
+ ExpandoCombobox,
1242
+ {
1243
+ InputProps,
1244
+ allowBackspaceClearsSelection: true,
1245
+ className: clsx_default(`${classBase3}Field`, `${classBase3}Operator`, {
1246
+ [`${classBase3}Operator-hidden`]: selectedColumn === null
1247
+ }),
1248
+ "data-field": "operator",
1249
+ initialHighlightedIndex: 0,
1250
+ onListItemSelect: onOperatorSelect,
1251
+ ref: operatorRef,
1252
+ source: getOperators(selectedColumn),
1253
+ title: "operator",
1254
+ value: (_a = filterClause.op) != null ? _a : ""
1255
+ },
1256
+ "operator-field"
1257
+ ) : null,
1258
+ /* @__PURE__ */ jsx6(
1259
+ FilterClauseValueEditor,
1260
+ {
1261
+ InputProps,
1262
+ onChangeValue,
1263
+ onDeselectValue,
1264
+ operator: filterClause.op,
1265
+ selectedColumn,
1266
+ suggestionProvider,
1267
+ table: tableSchema.table,
1268
+ value: (_b = filterClause == null ? void 0 : filterClause.values) != null ? _b : filterClause == null ? void 0 : filterClause.value
1269
+ },
1270
+ "value-field"
1271
+ )
1272
+ ]
1273
+ }
1274
+ );
1275
+ };
1276
+
1277
+ // src/filter-editor/FilterClauseCombinator.tsx
1278
+ import { CycleStateButton } from "@vuu-ui/vuu-ui-controls";
1279
+ import { useCallback as useCallback6 } from "react";
1280
+ import { jsx as jsx7 } from "react/jsx-runtime";
1281
+ var classBase4 = "vuuFilterClauseCombinator";
1282
+ var FilterClauseCombinator = ({
1283
+ onChange,
1284
+ onKeyDown,
1285
+ operator
1286
+ }) => {
1287
+ const handleChange = useCallback6(
1288
+ (value) => {
1289
+ onChange(value);
1290
+ },
1291
+ [onChange]
1292
+ );
1293
+ return /* @__PURE__ */ jsx7(
1294
+ CycleStateButton,
1295
+ {
1296
+ className: classBase4,
1297
+ onChange: handleChange,
1298
+ onKeyDown,
1299
+ value: operator,
1300
+ values: ["and", "or"],
1301
+ variant: "primary"
1302
+ }
1303
+ );
1304
+ };
1305
+
1306
+ // src/filter-editor/useFilterEditor.ts
1307
+ import {
1308
+ useCallback as useCallback7,
1309
+ useMemo as useMemo5,
1310
+ useRef as useRef3,
1311
+ useState as useState6
1312
+ } from "react";
1313
+ var useFilterEditor = ({
1314
+ columnDescriptors,
1315
+ filter,
1316
+ onCancel,
1317
+ onSave
1318
+ }) => {
1319
+ const filterModel = useMemo5(() => {
1320
+ return new FilterModel(filter);
1321
+ }, [filter]);
1322
+ const [_, forceRefresh] = useState6({});
1323
+ const [isValid, setIsValid] = useState6(filterModel.isValid);
1324
+ const clauseCombinatorRef = useRef3(void 0);
1325
+ const saveButtonRef = useRef3(null);
1326
+ const containerRef = useRef3(null);
1327
+ const setContainer = useCallback7((el) => {
1328
+ containerRef.current = el;
1329
+ if (el) {
1330
+ focusFirstClauseIfAllClausesValid(el);
1331
+ }
1332
+ }, []);
1333
+ const saveButtonMenuBuilder = useCallback7((_2, options) => {
1334
+ switch (clauseCombinatorRef.current) {
1335
+ case "and":
1336
+ return [{ action: "and-clause", label: "AND", options }];
1337
+ case "or":
1338
+ return [{ action: "or-clause", label: "OR", options }];
1339
+ default:
1340
+ return [
1341
+ { action: "and-clause", label: "AND", options },
1342
+ { action: "or-clause", label: "OR", options }
1343
+ ];
1344
+ }
1345
+ }, []);
1346
+ const columnsByName = useMemo5(
1347
+ () => columnDescriptorsByName(columnDescriptors),
1348
+ [columnDescriptors]
1349
+ );
1350
+ const isLastFilterClause = useCallback7(
1351
+ (index) => index !== void 0 && filterModel.filterClauses.length === index + 1,
1352
+ [filterModel]
1353
+ );
1354
+ useMemo5(() => {
1355
+ const setValid = (isValid2) => {
1356
+ setIsValid(isValid2);
1357
+ };
1358
+ const valueChanged = (_filter2, isValid2) => {
1359
+ if (isValid2) {
1360
+ const [filterClauseIndex, fieldName] = getFocusedFieldDetails();
1361
+ if (fieldName === "value" && isLastFilterClause(filterClauseIndex)) {
1362
+ requestAnimationFrame(() => {
1363
+ var _a;
1364
+ (_a = saveButtonRef.current) == null ? void 0 : _a.focus();
1365
+ });
1366
+ }
952
1367
  }
953
- ),
954
- (selectedColumn == null ? void 0 : selectedColumn.name) ? /* @__PURE__ */ jsx7(
955
- ExpandoCombobox,
956
- {
957
- InputProps,
958
- allowBackspaceClearsSelection: true,
959
- className: clsx_default(`${classBase4}Field`, `${classBase4}Operator`, {
960
- [`${classBase4}Operator-hidden`]: selectedColumn === null
961
- }),
962
- "data-field": "operator",
963
- initialHighlightedIndex: 0,
964
- onListItemSelect: onOperatorSelect,
965
- ref: operatorRef,
966
- source: getOperators(selectedColumn),
967
- title: "operator",
968
- value: operator != null ? operator : ""
1368
+ };
1369
+ filterModel.on("isValid", setValid);
1370
+ filterModel.on("filter", valueChanged);
1371
+ }, [filterModel, isLastFilterClause]);
1372
+ const handleCancelFilterClause = useCallback7(
1373
+ (filterClause, reason) => {
1374
+ if (reason === "Backspace") {
1375
+ const indexOfFilterClause = filterModel.filterClauses.indexOf(filterClause);
1376
+ filterModel.removeFilterClause(filterClause);
1377
+ forceRefresh({});
1378
+ if (reason === "Backspace" && containerRef.current) {
1379
+ if (indexOfFilterClause > 0) {
1380
+ focusFilterClauseField(
1381
+ containerRef.current,
1382
+ indexOfFilterClause - 1
1383
+ );
1384
+ }
1385
+ }
1386
+ } else {
1387
+ console.log(
1388
+ `cancel because of Escape valid clause ${filterClause.isValid}`
1389
+ );
1390
+ onCancel(filter);
969
1391
  }
970
- ) : null,
971
- /* @__PURE__ */ jsx7(
972
- FilterClauseValueEditor,
973
- {
974
- InputProps,
975
- onChangeValue,
976
- onDeselectValue,
977
- operator,
978
- selectedColumn,
979
- suggestionProvider,
980
- table: tableSchema == null ? void 0 : tableSchema.table,
981
- value
1392
+ },
1393
+ [filter, filterModel, onCancel]
1394
+ );
1395
+ const invokeMenuAction = useCallback7(
1396
+ ({ menuId }) => {
1397
+ switch (menuId) {
1398
+ case "save": {
1399
+ const savedFilter = filterModel.asFilter();
1400
+ const newOrUpdatedFilter = (filter == null ? void 0 : filter.name) ? {
1401
+ ...savedFilter,
1402
+ name: filter.name
1403
+ } : savedFilter;
1404
+ onSave(newOrUpdatedFilter);
1405
+ return true;
1406
+ }
1407
+ case "and-clause": {
1408
+ clauseCombinatorRef.current = "and";
1409
+ filterModel.addNewFilterClause("and");
1410
+ return true;
1411
+ }
1412
+ case "or-clause":
1413
+ clauseCombinatorRef.current = "or";
1414
+ filterModel.addNewFilterClause("or");
1415
+ return true;
1416
+ default:
1417
+ return false;
982
1418
  }
983
- ),
984
- value !== void 0 ? /* @__PURE__ */ jsx7(
985
- Button,
986
- {
987
- className: `${classBase4}-clearButton`,
988
- onClick: onClear,
989
- onKeyDown: onClearKeyDown,
990
- "data-icon": "close"
1419
+ },
1420
+ [filter == null ? void 0 : filter.name, filterModel, onSave]
1421
+ );
1422
+ const handleKeyDownSaveButton = useCallback7(
1423
+ (evt) => {
1424
+ if (evt.key === "Tab" && evt.shiftKey) {
1425
+ evt.preventDefault();
1426
+ const target = evt.target;
1427
+ const filterEditor = target.closest(".vuuFilterEditor");
1428
+ focusLastClauseValue(filterEditor);
1429
+ } else if (evt.key === "Escape") {
1430
+ onCancel(filter);
991
1431
  }
992
- ) : null
1432
+ },
1433
+ [filter, onCancel]
1434
+ );
1435
+ const handleKeyDownNavigationFromCombinator = useCallback7((evt) => {
1436
+ const { target, key, shiftKey } = evt;
1437
+ if (key === "ArrowLeft") {
1438
+ evt.preventDefault();
1439
+ navigateToNextFilterClause(target, "bwd");
1440
+ } else if (key === "ArrowRight") {
1441
+ evt.preventDefault();
1442
+ navigateToNextFilterClause(target, "fwd");
1443
+ } else if (key === "Tab" && shiftKey) {
1444
+ evt.preventDefault();
1445
+ navigateToNextFilterClause(target, "bwd");
1446
+ }
1447
+ }, []);
1448
+ const handleClickSaveButton = useMemo5(
1449
+ () => () => invokeMenuAction({
1450
+ menuId: "save",
1451
+ options: {},
1452
+ type: "menu-action"
1453
+ }),
1454
+ [invokeMenuAction]
1455
+ );
1456
+ const saveButtonProps = {
1457
+ PopupMenuProps: {
1458
+ icon: "more-vert",
1459
+ menuBuilder: saveButtonMenuBuilder,
1460
+ menuActionHandler: invokeMenuAction,
1461
+ menuLocation: "filter-save-menu"
1462
+ },
1463
+ onClick: handleClickSaveButton,
1464
+ onKeyDown: handleKeyDownSaveButton
1465
+ };
1466
+ const onChangeFilterCombinator = useCallback7(
1467
+ (op) => {
1468
+ filterModel.setOp(op);
1469
+ clauseCombinatorRef.current = op;
1470
+ forceRefresh({});
1471
+ },
1472
+ [filterModel]
1473
+ );
1474
+ const handleCancelFilterEdit = useCallback7(() => {
1475
+ onCancel(filter);
1476
+ }, [filter, onCancel]);
1477
+ return {
1478
+ columnsByName,
1479
+ filterModel,
1480
+ isValid,
1481
+ onCancelFilterClause: handleCancelFilterClause,
1482
+ onCancelFilterEdit: handleCancelFilterEdit,
1483
+ onChangeFilterCombinator,
1484
+ onKeyDownCombinator: handleKeyDownNavigationFromCombinator,
1485
+ saveButtonProps,
1486
+ saveButtonRef,
1487
+ setContainer
1488
+ };
1489
+ };
1490
+ function columnDescriptorsByName(columns) {
1491
+ return columns.reduce((m, col) => ({ ...m, [col.name]: col }), {});
1492
+ }
1493
+
1494
+ // src/filter-editor/FilterEditor.tsx
1495
+ import { Button } from "@salt-ds/core";
1496
+ import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
1497
+ import { createElement } from "react";
1498
+ var classBase5 = "vuuFilterEditor";
1499
+ var FilterEditor = ({
1500
+ FilterClauseEditorProps,
1501
+ columnDescriptors,
1502
+ filter,
1503
+ onCancel,
1504
+ onSave,
1505
+ tableSchema,
1506
+ ...htmlAttributes
1507
+ }) => {
1508
+ const {
1509
+ columnsByName,
1510
+ filterModel,
1511
+ setContainer,
1512
+ onCancelFilterClause,
1513
+ onCancelFilterEdit,
1514
+ onChangeFilterCombinator,
1515
+ onKeyDownCombinator,
1516
+ saveButtonRef,
1517
+ saveButtonProps
1518
+ } = useFilterEditor({
1519
+ columnDescriptors,
1520
+ filter,
1521
+ onCancel,
1522
+ onSave
1523
+ });
1524
+ const getContents = () => {
1525
+ const { op } = filterModel;
1526
+ const content = [];
1527
+ filterModel.filterClauses.forEach((filterClauseModel, i) => {
1528
+ if (i > 0 && op) {
1529
+ content.push(
1530
+ /* @__PURE__ */ jsx8(
1531
+ FilterClauseCombinator,
1532
+ {
1533
+ onChange: onChangeFilterCombinator,
1534
+ onKeyDown: onKeyDownCombinator,
1535
+ operator: op
1536
+ },
1537
+ `filter-operator-${i}`
1538
+ )
1539
+ );
1540
+ }
1541
+ content.push(
1542
+ /* @__PURE__ */ createElement(
1543
+ FilterClause,
1544
+ {
1545
+ ...FilterClauseEditorProps,
1546
+ columnsByName,
1547
+ "data-index": i,
1548
+ filterClauseModel,
1549
+ key: `editor-${i}`,
1550
+ onCancel: onCancelFilterClause,
1551
+ tableSchema
1552
+ }
1553
+ )
1554
+ );
1555
+ });
1556
+ return content;
1557
+ };
1558
+ return /* @__PURE__ */ jsxs2("div", { ...htmlAttributes, className: classBase5, ref: setContainer, children: [
1559
+ getContents(),
1560
+ /* @__PURE__ */ createElement(
1561
+ SplitButton,
1562
+ {
1563
+ ...saveButtonProps,
1564
+ disabled: !filterModel.isValid,
1565
+ key: "save-button",
1566
+ ref: saveButtonRef
1567
+ },
1568
+ "Save"
1569
+ ),
1570
+ /* @__PURE__ */ jsx8(Button, { onClick: onCancelFilterEdit, variant: "secondary", children: "Cancel" })
993
1571
  ] });
994
1572
  };
995
1573
 
996
1574
  // src/filter-pill/FilterPill.tsx
997
- import { Tooltip, useTooltip } from "@vuu-ui/vuu-popups";
998
- import { EditableLabel } from "@vuu-ui/vuu-ui-controls";
1575
+ import {
1576
+ Tooltip,
1577
+ useTooltip
1578
+ } from "@vuu-ui/vuu-popups";
1579
+ import {
1580
+ EditableLabel,
1581
+ SplitStateButton
1582
+ } from "@vuu-ui/vuu-ui-controls";
999
1583
  import { useId } from "@vuu-ui/vuu-utils";
1000
- import { useCallback as useCallback7, useMemo as useMemo6, useRef as useRef4 } from "react";
1584
+ import { useCallback as useCallback8, useMemo as useMemo6, useRef as useRef4 } from "react";
1001
1585
 
1002
- // src/filter-pill-menu/FilterPillMenu.tsx
1003
- import { PopupMenu } from "@vuu-ui/vuu-popups";
1004
- import { useMemo as useMemo5 } from "react";
1586
+ // src/filter-pill/filterAsReactNode.tsx
1587
+ import { isMultiClauseFilter as isMultiClauseFilter3, isMultiValueFilter as isMultiValueFilter2 } from "@vuu-ui/vuu-utils";
1588
+
1589
+ // src/filter-pill/getFilterLabel.ts
1590
+ import { isMultiClauseFilter as isMultiClauseFilter2 } from "@vuu-ui/vuu-utils";
1591
+ var getColumnLabel = (filter, columnsByName) => {
1592
+ var _a;
1593
+ const column = columnsByName == null ? void 0 : columnsByName[filter.column];
1594
+ if (column) {
1595
+ return (_a = column.label) != null ? _a : column.name;
1596
+ } else {
1597
+ return filter.column;
1598
+ }
1599
+ };
1600
+ var getFilterLabel = (columnsByName) => (filter) => {
1601
+ if (isMultiClauseFilter2(filter)) {
1602
+ return filter.filters.map((f) => getFilterLabel(columnsByName)(f)).join(", ");
1603
+ } else {
1604
+ return getColumnLabel(filter, columnsByName);
1605
+ }
1606
+ };
1607
+
1608
+ // src/filter-pill/filterAsReactNode.tsx
1609
+ import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
1610
+ var filterAsReactNode = (f, getLabel = getFilterLabel()) => {
1611
+ if (isMultiClauseFilter3(f)) {
1612
+ const heading = f.op === "and" ? "Match all ..." : "Match any ...";
1613
+ return /* @__PURE__ */ jsxs3("ul", { children: [
1614
+ /* @__PURE__ */ jsx9("span", { children: heading }),
1615
+ f.filters.map((f2, i) => /* @__PURE__ */ jsx9("li", { children: filterAsReactNode(f2, getLabel) }, i))
1616
+ ] });
1617
+ } else if (isMultiValueFilter2(f)) {
1618
+ if (f.values.length > 3) {
1619
+ const values = f.values.slice(0, 3);
1620
+ return `${getLabel({ ...f, values }).slice(0, -1)},...]`;
1621
+ } else {
1622
+ return getLabel(f);
1623
+ }
1624
+ } else {
1625
+ return getLabel(f);
1626
+ }
1627
+ };
1005
1628
 
1006
- // src/filter-pill-menu/FilterPillMenuOptions.ts
1629
+ // src/filter-pill/FilterPillMenuOptions.ts
1007
1630
  var closeCommand = (options) => ({
1008
1631
  label: `Close`,
1009
1632
  location: "filter",
@@ -1012,83 +1635,29 @@ var closeCommand = (options) => ({
1012
1635
  });
1013
1636
  var deleteCommand = (options) => ({
1014
1637
  label: `Delete`,
1015
- location: "filter",
1016
- action: `delete-filter`,
1017
- options
1018
- });
1019
- var renameCommand = (options) => ({
1020
- label: `Rename`,
1021
- location: "filter",
1022
- action: `rename-filter`,
1023
- options
1024
- });
1025
- var editCommand = (options) => ({
1026
- label: `Edit`,
1027
- location: "filter",
1028
- action: "edit-filter",
1029
- options
1030
- });
1031
-
1032
- // src/filter-pill-menu/FilterPillMenu.tsx
1033
- import { jsx as jsx8 } from "react/jsx-runtime";
1034
- var classBase5 = "vuuFilterPillMenu";
1035
- var FilterPillMenu = ({
1036
- allowClose = true,
1037
- allowDelete = true,
1038
- allowEdit = true,
1039
- allowRename = true,
1040
- filter,
1041
- location,
1042
- onMenuAction,
1043
- onMenuClose
1044
- }) => {
1045
- const [menuBuilder, menuOptions] = useMemo5(
1046
- () => [
1047
- (_location, options) => {
1048
- const menuItems = [];
1049
- if (allowRename) {
1050
- menuItems.push(renameCommand(options));
1051
- }
1052
- if (allowEdit) {
1053
- menuItems.push(editCommand(options));
1054
- }
1055
- if (allowClose) {
1056
- menuItems.push(closeCommand(options));
1057
- }
1058
- if (allowDelete) {
1059
- menuItems.push(deleteCommand(options));
1060
- }
1061
- return menuItems;
1062
- },
1063
- {
1064
- filter
1065
- }
1066
- ],
1067
- [allowClose, allowDelete, allowEdit, allowRename, filter]
1068
- );
1069
- return /* @__PURE__ */ jsx8(
1070
- PopupMenu,
1071
- {
1072
- className: classBase5,
1073
- menuBuilder,
1074
- menuActionHandler: onMenuAction,
1075
- menuLocation: clsx_default("filter", location),
1076
- menuOptions,
1077
- onMenuClose,
1078
- tabIndex: -1
1079
- }
1080
- );
1081
- };
1082
-
1083
- // src/filter-pill/filterAsReactNode.tsx
1084
- import { isMultiClauseFilter as isMultiClauseFilter3, isMultiValueFilter as isMultiValueFilter3 } from "@vuu-ui/vuu-utils";
1638
+ location: "filter",
1639
+ action: `delete-filter`,
1640
+ options
1641
+ });
1642
+ var renameCommand = (options) => ({
1643
+ label: `Rename`,
1644
+ location: "filter",
1645
+ action: `rename-filter`,
1646
+ options
1647
+ });
1648
+ var editCommand = (options) => ({
1649
+ label: `Edit`,
1650
+ location: "filter",
1651
+ action: "edit-filter",
1652
+ options
1653
+ });
1085
1654
 
1086
- // src/filter-pill/getFilterLabel.ts
1655
+ // src/filter-pill/getFilterTooltipText.ts
1087
1656
  import {
1088
1657
  filterAsQuery,
1089
1658
  formatDate,
1090
1659
  isDateTimeColumn as isDateTimeColumn2,
1091
- isMultiClauseFilter as isMultiClauseFilter2,
1660
+ isMultiClauseFilter as isMultiClauseFilter5,
1092
1661
  dateTimePattern,
1093
1662
  defaultPatternsByType
1094
1663
  } from "@vuu-ui/vuu-utils";
@@ -1098,9 +1667,8 @@ import {
1098
1667
  extractFilterForColumn,
1099
1668
  isAndFilter,
1100
1669
  isInFilter,
1101
- isMultiClauseFilter,
1102
- isMultiValueFilter as isMultiValueFilter2,
1103
- isNotNullOrUndefined,
1670
+ isMultiClauseFilter as isMultiClauseFilter4,
1671
+ isMultiValueFilter as isMultiValueFilter3,
1104
1672
  isOrFilter,
1105
1673
  isSingleValueFilter as isSingleValueFilter2,
1106
1674
  partition
@@ -1115,7 +1683,7 @@ var ENDS_WITH = "ends";
1115
1683
  var IN = "in";
1116
1684
  var filterClauses = (filter, clauses = []) => {
1117
1685
  if (filter) {
1118
- if (isMultiClauseFilter(filter)) {
1686
+ if (isMultiClauseFilter4(filter)) {
1119
1687
  filter.filters.forEach((f) => clauses.push(...filterClauses(f)));
1120
1688
  } else {
1121
1689
  clauses.push(filter);
@@ -1138,7 +1706,7 @@ var removeLastClause = (filter) => {
1138
1706
  }
1139
1707
  };
1140
1708
  var addClause = (existingFilter, clause, { combineWith = AND } = DEFAULT_ADD_FILTER_OPTS) => {
1141
- if (isMultiClauseFilter(existingFilter) && existingFilter.op === combineWith) {
1709
+ if (isMultiClauseFilter4(existingFilter) && existingFilter.op === combineWith) {
1142
1710
  return {
1143
1711
  ...existingFilter,
1144
1712
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -1152,41 +1720,17 @@ var addClause = (existingFilter, clause, { combineWith = AND } = DEFAULT_ADD_FIL
1152
1720
  };
1153
1721
  }
1154
1722
  };
1155
- var replaceClause = (existingFilter, clause, idx) => {
1156
- if (existingFilter === void 0)
1157
- return clause;
1158
- return findAndReplaceClauseAtGivenIndex(existingFilter, clause, idx).filter;
1159
- };
1160
- function findAndReplaceClauseAtGivenIndex(existingFilter, clause, idx, currIdx = 0) {
1161
- if (isMultiClauseFilter(existingFilter)) {
1162
- let i = currIdx;
1163
- const filters = existingFilter.filters.map((f) => {
1164
- const res = findAndReplaceClauseAtGivenIndex(f, clause, idx, i);
1165
- i = res.nextIdx;
1166
- return res.filter;
1167
- });
1168
- return { filter: { ...existingFilter, filters }, nextIdx: i };
1169
- } else if (idx !== currIdx) {
1170
- return { filter: existingFilter, nextIdx: currIdx + 1 };
1171
- } else {
1172
- const { name } = existingFilter;
1173
- return {
1174
- filter: { ...isNotNullOrUndefined(name) ? { name } : {}, ...clause },
1175
- nextIdx: currIdx + 1
1176
- };
1177
- }
1178
- }
1179
1723
  var addFilter = (existingFilter, filter, { combineWith = AND } = DEFAULT_ADD_FILTER_OPTS) => {
1180
1724
  var _a;
1181
1725
  if (includesNoValues(filter)) {
1182
- if (isMultiClauseFilter(filter)) {
1726
+ if (isMultiClauseFilter4(filter)) {
1183
1727
  } else {
1184
1728
  existingFilter = removeFilterForColumn(existingFilter, {
1185
1729
  name: filter.column
1186
1730
  });
1187
1731
  }
1188
1732
  } else if (includesAllValues(filter)) {
1189
- if (isMultiClauseFilter(filter)) {
1733
+ if (isMultiClauseFilter4(filter)) {
1190
1734
  }
1191
1735
  return removeFilterForColumn(existingFilter, { name: (_a = filter.column) != null ? _a : "" });
1192
1736
  }
@@ -1315,7 +1859,7 @@ var splitFilterOnColumn = (columnName, filter) => {
1315
1859
  return filters.length === 1 ? [columnFilter, filters[0]] : [columnFilter, { op: AND, filters }];
1316
1860
  };
1317
1861
  var overrideColName = (filter, column) => {
1318
- if (isMultiClauseFilter(filter)) {
1862
+ if (isMultiClauseFilter4(filter)) {
1319
1863
  return {
1320
1864
  op: filter.op,
1321
1865
  filters: filter.filters.map((f) => overrideColName(f, column))
@@ -1376,7 +1920,7 @@ var filterEquals = (f1, f2, strict = false) => {
1376
1920
  return true;
1377
1921
  }
1378
1922
  if (f1 && f2 && canMerge(f1, f2)) {
1379
- return f1.op === f2.op && (isSingleValueFilter2(f1) && isSingleValueFilter2(f2) && f1.value === f2.value || isMultiValueFilter2(f1) && isMultiValueFilter2(f2) && sameValues(f1.values, f2.values));
1923
+ return f1.op === f2.op && (isSingleValueFilter2(f1) && isSingleValueFilter2(f2) && f1.value === f2.value || isMultiValueFilter3(f1) && isMultiValueFilter3(f2) && sameValues(f1.values, f2.values));
1380
1924
  }
1381
1925
  return false;
1382
1926
  };
@@ -1435,7 +1979,7 @@ var getNumericFilter = (column, op, value) => {
1435
1979
  return { column, op, value };
1436
1980
  };
1437
1981
 
1438
- // src/filter-pill/getFilterLabel.ts
1982
+ // src/filter-pill/getFilterTooltipText.ts
1439
1983
  function applyFormatter(filter, formatter) {
1440
1984
  if ("value" in filter) {
1441
1985
  return { ...filter, value: formatter(filter.value) };
@@ -1460,8 +2004,8 @@ function formatFilterValue(filter, columnsByName) {
1460
2004
  }
1461
2005
  return filter;
1462
2006
  }
1463
- var getFilterLabel = (columnsByName) => (filter) => {
1464
- if (isMultiClauseFilter2(filter)) {
2007
+ var getFilterTooltipText = (columnsByName) => (filter) => {
2008
+ if (isMultiClauseFilter5(filter)) {
1465
2009
  const [firstClause] = filterClauses(filter);
1466
2010
  const formattedFilter = formatFilterValue(
1467
2011
  firstClause,
@@ -1473,44 +2017,28 @@ var getFilterLabel = (columnsByName) => (filter) => {
1473
2017
  }
1474
2018
  };
1475
2019
 
1476
- // src/filter-pill/filterAsReactNode.tsx
1477
- import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
1478
- var filterAsReactNode = (f, getLabel = getFilterLabel()) => {
1479
- if (isMultiClauseFilter3(f)) {
1480
- const heading = f.op === "and" ? "Match all ..." : "Match any ...";
1481
- return /* @__PURE__ */ jsxs3("ul", { children: [
1482
- /* @__PURE__ */ jsx9("span", { children: heading }),
1483
- f.filters.map((f2, i) => /* @__PURE__ */ jsx9("li", { children: filterAsReactNode(f2, getLabel) }, i))
1484
- ] });
1485
- } else if (isMultiValueFilter3(f)) {
1486
- if (f.values.length > 3) {
1487
- const values = f.values.slice(0, 3);
1488
- return `${getLabel({ ...f, values }).slice(0, -1)},...]`;
1489
- } else {
1490
- return getLabel(f);
1491
- }
1492
- } else {
1493
- return getLabel(f);
1494
- }
1495
- };
1496
-
1497
2020
  // src/filter-pill/FilterPill.tsx
1498
2021
  import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
1499
2022
  var classBase6 = "vuuFilterPill";
1500
2023
  var FilterPill = ({
2024
+ allowClose = true,
2025
+ allowDelete = true,
2026
+ allowEdit = true,
2027
+ allowRename = true,
1501
2028
  className: classNameProp,
1502
2029
  columnsByName,
1503
2030
  editable = true,
2031
+ editing = false,
2032
+ editLabelApiRef,
1504
2033
  filter,
1505
2034
  id: idProp,
1506
2035
  onBeginEdit,
1507
2036
  onExitEditMode,
1508
2037
  onMenuAction,
1509
- showMenu = true,
1510
2038
  ...htmlAttributes
1511
2039
  }) => {
1512
2040
  const rootRef = useRef4(null);
1513
- const handleEnterEditMode = useCallback7(() => {
2041
+ const handleEnterEditMode = useCallback8(() => {
1514
2042
  onBeginEdit == null ? void 0 : onBeginEdit(filter);
1515
2043
  }, [filter, onBeginEdit]);
1516
2044
  const getLabel = getFilterLabel(columnsByName);
@@ -1521,8 +2049,9 @@ var FilterPill = ({
1521
2049
  },
1522
2050
  [getLabel, filter]
1523
2051
  );
2052
+ const getTooltipTextl = getFilterTooltipText(columnsByName);
1524
2053
  const id = useId(idProp);
1525
- const handleMenuClose = useCallback7((reason) => {
2054
+ const handleMenuClose = useCallback8((reason) => {
1526
2055
  if ((reason == null ? void 0 : reason.type) === "escape") {
1527
2056
  requestAnimationFrame(() => {
1528
2057
  if (rootRef.current) {
@@ -1531,16 +2060,71 @@ var FilterPill = ({
1531
2060
  });
1532
2061
  }
1533
2062
  }, []);
1534
- const { anchorProps, tooltipProps } = useTooltip({
2063
+ const popupMenuProps = useMemo6(
2064
+ () => ({
2065
+ icon: "more-vert",
2066
+ menuBuilder: (_location, options) => {
2067
+ const menuItems = [];
2068
+ if (allowRename) {
2069
+ menuItems.push(renameCommand(options));
2070
+ }
2071
+ if (allowEdit) {
2072
+ menuItems.push(editCommand(options));
2073
+ }
2074
+ if (allowClose) {
2075
+ menuItems.push(closeCommand(options));
2076
+ }
2077
+ if (allowDelete) {
2078
+ menuItems.push(deleteCommand(options));
2079
+ }
2080
+ return menuItems;
2081
+ },
2082
+ menuActionHandler: onMenuAction,
2083
+ menuLocation: "filter-pill-menu",
2084
+ menuOptions: {
2085
+ filter
2086
+ },
2087
+ onMenuClose: handleMenuClose
2088
+ }),
2089
+ [
2090
+ allowClose,
2091
+ allowDelete,
2092
+ allowEdit,
2093
+ allowRename,
2094
+ filter,
2095
+ handleMenuClose,
2096
+ onMenuAction
2097
+ ]
2098
+ );
2099
+ const handleExitEditMode = useCallback8(
2100
+ (originalValue, newValue) => {
2101
+ onExitEditMode == null ? void 0 : onExitEditMode(originalValue, newValue);
2102
+ requestAnimationFrame(() => {
2103
+ var _a, _b;
2104
+ (_b = (_a = rootRef.current) == null ? void 0 : _a.querySelector("button")) == null ? void 0 : _b.focus();
2105
+ });
2106
+ },
2107
+ [onExitEditMode]
2108
+ );
2109
+ const { anchorProps, hideTooltip, showTooltip, tooltipProps } = useTooltip({
2110
+ anchorQuery: ".vuuFilterPill",
1535
2111
  id,
1536
- placement: "below",
1537
- tooltipContent: filterAsReactNode(filter, getLabel)
2112
+ placement: ["above", "below"],
2113
+ tooltipContent: filterAsReactNode(filter, getTooltipTextl)
1538
2114
  });
2115
+ const buttonProps = {
2116
+ onBlur: hideTooltip,
2117
+ onFocus: useCallback8(() => {
2118
+ showTooltip(rootRef);
2119
+ }, [showTooltip])
2120
+ };
1539
2121
  return /* @__PURE__ */ jsxs4(
1540
- "div",
2122
+ SplitStateButton,
1541
2123
  {
1542
2124
  ...anchorProps,
1543
2125
  ...htmlAttributes,
2126
+ ButtonProps: buttonProps,
2127
+ PopupMenuProps: popupMenuProps,
1544
2128
  className: clsx_default(classBase6, classNameProp),
1545
2129
  "data-text": label,
1546
2130
  ref: rootRef,
@@ -1549,45 +2133,58 @@ var FilterPill = ({
1549
2133
  EditableLabel,
1550
2134
  {
1551
2135
  defaultValue: label,
2136
+ editing,
2137
+ editLabelApiRef,
1552
2138
  onEnterEditMode: handleEnterEditMode,
1553
- onExitEditMode
2139
+ onExitEditMode: handleExitEditMode
1554
2140
  },
1555
2141
  label
1556
- ) : /* @__PURE__ */ jsx10("span", { className: `${classBase6}-label`, children: label }),
1557
- showMenu && onMenuAction ? /* @__PURE__ */ jsx10(
1558
- FilterPillMenu,
1559
- {
1560
- filter,
1561
- onMenuAction,
1562
- onMenuClose: handleMenuClose
1563
- }
1564
- ) : null,
1565
- tooltipProps && /* @__PURE__ */ jsx10(Tooltip, { ...tooltipProps })
2142
+ ) : label,
2143
+ tooltipProps && /* @__PURE__ */ jsx10(Tooltip, { className: `${classBase6}-tooltip`, ...tooltipProps })
1566
2144
  ]
1567
2145
  }
1568
2146
  );
1569
2147
  };
1570
2148
 
1571
2149
  // src/filter-bar/FilterBarMenu.tsx
1572
- import { PopupMenu as PopupMenu2 } from "@vuu-ui/vuu-popups";
2150
+ import { PopupMenu } from "@vuu-ui/vuu-popups";
1573
2151
  import { jsx as jsx11 } from "react/jsx-runtime";
1574
2152
  var FilterBarMenu = () => {
1575
2153
  const classBase9 = "vuuFilterBarMenu";
1576
- return /* @__PURE__ */ jsx11("div", { className: classBase9, children: /* @__PURE__ */ jsx11(PopupMenu2, { icon: "tune", menuLocation: "filter-bar-menu", tabIndex: -1 }) });
2154
+ return /* @__PURE__ */ jsx11("div", { className: classBase9, children: /* @__PURE__ */ jsx11(PopupMenu, { icon: "tune", menuLocation: "filter-bar-menu", tabIndex: -1 }) });
1577
2155
  };
1578
2156
 
1579
2157
  // src/filter-bar/useFilterBar.ts
1580
- import { dispatchMouseEvent, isMultiClauseFilter as isMultiClauseFilter4 } from "@vuu-ui/vuu-utils";
1581
2158
  import {
1582
- useCallback as useCallback10,
2159
+ NullEditAPI
2160
+ } from "@vuu-ui/vuu-ui-controls";
2161
+ import { getElementDataIndex as getElementDataIndex3, queryClosest as queryClosest4 } from "@vuu-ui/vuu-utils";
2162
+ import {
2163
+ useCallback as useCallback11,
1583
2164
  useMemo as useMemo7,
1584
2165
  useRef as useRef5,
1585
- useState as useState6
2166
+ useState as useState7
1586
2167
  } from "react";
1587
2168
 
1588
2169
  // src/filter-bar/useFilterState.ts
1589
- import { useCallback as useCallback8 } from "react";
2170
+ import { useCallback as useCallback9 } from "react";
1590
2171
  import { useControlled } from "@vuu-ui/vuu-ui-controls";
2172
+ var getActiveIndices = (indices, toggledIndex, preserveExisting) => {
2173
+ const isActive = indices.includes(toggledIndex);
2174
+ if (isActive) {
2175
+ if (preserveExisting) {
2176
+ return indices.filter((i) => i !== toggledIndex);
2177
+ } else {
2178
+ return [];
2179
+ }
2180
+ } else {
2181
+ if (preserveExisting) {
2182
+ return indices.concat(toggledIndex);
2183
+ } else {
2184
+ return [toggledIndex];
2185
+ }
2186
+ }
2187
+ };
1591
2188
  var useFilterState = ({
1592
2189
  defaultFilterState,
1593
2190
  onFilterDeleted,
@@ -1601,14 +2198,14 @@ var useFilterState = ({
1601
2198
  name: "useFilterState",
1602
2199
  state: "FilterState"
1603
2200
  });
1604
- const handleFilterStateChange = useCallback8(
2201
+ const handleFilterStateChange = useCallback9(
1605
2202
  (s) => {
1606
2203
  setFilterState(s);
1607
2204
  onFilterStateChanged == null ? void 0 : onFilterStateChanged(s);
1608
2205
  },
1609
2206
  [onFilterStateChanged, setFilterState]
1610
2207
  );
1611
- const handleAddFilter = useCallback8(
2208
+ const handleAddFilter = useCallback9(
1612
2209
  (filter) => {
1613
2210
  const index = filterState.filters.length;
1614
2211
  const newFilters = filterState.filters.concat(filter);
@@ -1621,7 +2218,7 @@ var useFilterState = ({
1621
2218
  },
1622
2219
  [filterState, handleFilterStateChange]
1623
2220
  );
1624
- const handleDeleteFilter = useCallback8(
2221
+ const handleDeleteFilter = useCallback9(
1625
2222
  (filter) => {
1626
2223
  let index = -1;
1627
2224
  const newFilters = filterState.filters.filter((f, i) => {
@@ -1650,7 +2247,7 @@ var useFilterState = ({
1650
2247
  onFilterDeleted
1651
2248
  ]
1652
2249
  );
1653
- const handleRenameFilter = useCallback8(
2250
+ const handleRenameFilter = useCallback9(
1654
2251
  (filter, name) => {
1655
2252
  let index = -1;
1656
2253
  const newFilters = filterState.filters.map((f, i) => {
@@ -1667,7 +2264,7 @@ var useFilterState = ({
1667
2264
  },
1668
2265
  [filterState, handleFilterStateChange, onFilterRenamed]
1669
2266
  );
1670
- const handleChangeFilter = useCallback8(
2267
+ const handleChangeFilter = useCallback9(
1671
2268
  (oldFilter, newFilter) => {
1672
2269
  let index = -1;
1673
2270
  const newFilters = filterState.filters.map((f, i) => {
@@ -1683,14 +2280,23 @@ var useFilterState = ({
1683
2280
  },
1684
2281
  [filterState, handleFilterStateChange]
1685
2282
  );
1686
- const handleActiveIndicesChange = useCallback8(
1687
- (indices) => handleFilterStateChange({ ...filterState, activeIndices: indices }),
2283
+ const handleToggleFilterActive = useCallback9(
2284
+ (filterIndex, preserveRemainingFilters = false) => {
2285
+ handleFilterStateChange({
2286
+ ...filterState,
2287
+ activeIndices: getActiveIndices(
2288
+ filterState.activeIndices,
2289
+ filterIndex,
2290
+ preserveRemainingFilters
2291
+ )
2292
+ });
2293
+ },
1688
2294
  [filterState, handleFilterStateChange]
1689
2295
  );
1690
2296
  return {
1691
2297
  activeFilterIndex: filterState.activeIndices,
1692
2298
  filters: filterState.filters,
1693
- onChangeActiveFilterIndex: handleActiveIndicesChange,
2299
+ onToggleFilterActive: handleToggleFilterActive,
1694
2300
  onAddFilter: handleAddFilter,
1695
2301
  onChangeFilter: handleChangeFilter,
1696
2302
  onDeleteFilter: handleDeleteFilter,
@@ -1707,7 +2313,7 @@ var removeIndexAndDecrementLarger = (indices, idxToRemove) => {
1707
2313
  };
1708
2314
 
1709
2315
  // src/filter-bar/useApplyFilterOnChange.ts
1710
- import { useCallback as useCallback9, useEffect as useEffect3 } from "react";
2316
+ import { useCallback as useCallback10, useEffect as useEffect3 } from "react";
1711
2317
  import { filterAsQuery as filterAsQuery2 } from "@vuu-ui/vuu-utils";
1712
2318
  function useApplyFilterOnChange({
1713
2319
  activeFilterIndex,
@@ -1715,7 +2321,7 @@ function useApplyFilterOnChange({
1715
2321
  filters,
1716
2322
  onApplyFilter
1717
2323
  }) {
1718
- const applyFilter = useCallback9(
2324
+ const applyFilter = useCallback10(
1719
2325
  (filter) => {
1720
2326
  const query = filter ? filterAsQuery2(filter, { columnsByName }) : "";
1721
2327
  onApplyFilter({ filter: query, filterStruct: filter });
@@ -1735,8 +2341,42 @@ function useApplyFilterOnChange({
1735
2341
  }, [activeFilterIndex, applyFilter, filters]);
1736
2342
  }
1737
2343
 
2344
+ // src/filter-bar/filterBarFocusManagement.ts
2345
+ import {
2346
+ getElementByDataIndex,
2347
+ getElementDataIndex as getElementDataIndex2,
2348
+ queryClosest as queryClosest3
2349
+ } from "@vuu-ui/vuu-utils";
2350
+ var navigateToNextItem = (el, direction = "fwd") => {
2351
+ const pill = queryClosest3(el, ".vuuFilterPill");
2352
+ if (pill) {
2353
+ const index = getElementDataIndex2(pill);
2354
+ if (typeof index === "number") {
2355
+ const target = direction === "fwd" ? getElementByDataIndex(pill.parentElement, index + 1) : getElementByDataIndex(pill.parentElement, index - 1);
2356
+ if (target) {
2357
+ target.focus();
2358
+ } else if (direction === "fwd") {
2359
+ console.log("to the button");
2360
+ const filterBar = queryClosest3(el, ".vuuFilterBar");
2361
+ const addButton = filterBar == null ? void 0 : filterBar.querySelector(
2362
+ ".vuuFilterBar-add"
2363
+ );
2364
+ addButton == null ? void 0 : addButton.focus();
2365
+ }
2366
+ }
2367
+ } else {
2368
+ const button = queryClosest3(el, ".vuuFilterBar-add");
2369
+ if (button) {
2370
+ const filterBar = queryClosest3(el, ".vuuFilterBar");
2371
+ const target = filterBar == null ? void 0 : filterBar.querySelector(
2372
+ ".vuuFilterPill:last-child"
2373
+ );
2374
+ target == null ? void 0 : target.focus();
2375
+ }
2376
+ }
2377
+ };
2378
+
1738
2379
  // src/filter-bar/useFilterBar.ts
1739
- var EMPTY_FILTER_CLAUSE = {};
1740
2380
  var useFilterBar = ({
1741
2381
  columnDescriptors,
1742
2382
  containerRef,
@@ -1745,16 +2385,14 @@ var useFilterBar = ({
1745
2385
  onApplyFilter,
1746
2386
  onFilterDeleted,
1747
2387
  onFilterRenamed,
1748
- onFilterStateChanged,
1749
- showMenu: showMenuProp
2388
+ onFilterStateChanged
1750
2389
  }) => {
1751
2390
  const addButtonRef = useRef5(null);
1752
- const editingFilter = useRef5();
1753
- const [showMenu, setShowMenu] = useState6(showMenuProp);
1754
- const [editFilter, setEditFilter] = useState6();
1755
- const [promptProps, setPromptProps] = useState6(null);
2391
+ const [interactedFilterState, setInteractedFilterState] = useState7();
2392
+ const [promptProps, setPromptProps] = useState7(null);
2393
+ const editPillLabelAPI = useRef5(NullEditAPI);
1756
2394
  const columnsByName = useMemo7(
1757
- () => columnDescriptorsByName(columnDescriptors),
2395
+ () => columnDescriptorsByName2(columnDescriptors),
1758
2396
  [columnDescriptors]
1759
2397
  );
1760
2398
  const {
@@ -1764,7 +2402,7 @@ var useFilterBar = ({
1764
2402
  onChangeFilter,
1765
2403
  onDeleteFilter,
1766
2404
  onRenameFilter,
1767
- onChangeActiveFilterIndex
2405
+ onToggleFilterActive
1768
2406
  } = useFilterState({
1769
2407
  defaultFilterState,
1770
2408
  filterState,
@@ -1778,47 +2416,21 @@ var useFilterBar = ({
1778
2416
  filters,
1779
2417
  onApplyFilter
1780
2418
  });
1781
- const editPillLabel = useCallback10(
1782
- (index) => {
1783
- requestAnimationFrame(() => {
1784
- var _a;
1785
- const pills = (_a = containerRef.current) == null ? void 0 : _a.querySelectorAll(
1786
- ".vuuFilterPill"
1787
- );
1788
- if (pills == null ? void 0 : pills[index]) {
1789
- const editableLabel = pills[index].querySelector(
1790
- ".vuuEditableLabel"
1791
- );
1792
- if (editableLabel) {
1793
- dispatchMouseEvent(editableLabel, "dblclick");
1794
- }
1795
- }
2419
+ const editPillLabel = useCallback11((index, filter) => {
2420
+ setTimeout(() => {
2421
+ setInteractedFilterState({
2422
+ filter,
2423
+ index,
2424
+ state: "rename"
1796
2425
  });
1797
- },
1798
- [containerRef]
1799
- );
1800
- const focusFilterClause = useCallback10(
1801
- (_ = 0) => {
2426
+ }, 100);
2427
+ }, []);
2428
+ const focusFilterPill = useCallback11(
2429
+ (index = 0) => {
1802
2430
  requestAnimationFrame(() => {
1803
2431
  var _a;
1804
- const input = (_a = containerRef.current) == null ? void 0 : _a.querySelector(
1805
- ".vuuFilterClause .saltInput-input"
1806
- );
1807
- if (input) {
1808
- input.focus();
1809
- }
1810
- });
1811
- },
1812
- [containerRef]
1813
- );
1814
- const focusFilterPill = useCallback10(
1815
- (index) => {
1816
- requestAnimationFrame(() => {
1817
- var _a, _b;
1818
- const target = typeof index === "number" ? (_a = containerRef.current) == null ? void 0 : _a.querySelector(
1819
- `.vuuOverflowContainer-item[data-index="${index}"] .vuuFilterPill`
1820
- ) : (_b = containerRef.current) == null ? void 0 : _b.querySelector(
1821
- ".vuuFilterPill[tabindex]"
2432
+ const target = (_a = containerRef.current) == null ? void 0 : _a.querySelector(
2433
+ `.vuuFilterPill[data-index="${index}"] button`
1822
2434
  );
1823
2435
  if (target) {
1824
2436
  target.focus();
@@ -1827,7 +2439,7 @@ var useFilterBar = ({
1827
2439
  },
1828
2440
  [containerRef]
1829
2441
  );
1830
- const deleteConfirmed = useCallback10(
2442
+ const deleteConfirmed = useCallback11(
1831
2443
  (filter) => {
1832
2444
  onDeleteFilter(filter);
1833
2445
  requestAnimationFrame(() => {
@@ -1860,7 +2472,7 @@ var useFilterBar = ({
1860
2472
  },
1861
2473
  [deleteConfirmed, focusFilterPill]
1862
2474
  );
1863
- const deleteFilter = useCallback10(
2475
+ const deleteFilter = useCallback11(
1864
2476
  (filter, withPrompt) => {
1865
2477
  if (withPrompt) {
1866
2478
  setPromptProps(getDeletePrompt(filter));
@@ -1870,23 +2482,19 @@ var useFilterBar = ({
1870
2482
  },
1871
2483
  [deleteConfirmed, getDeletePrompt]
1872
2484
  );
1873
- const handleBeginEditFilterName = useCallback10((filter) => {
1874
- editingFilter.current = filter;
1875
- }, []);
1876
- const handleExitEditFilterName = useCallback10(
2485
+ const handleExitEditFilterName = useCallback11(
1877
2486
  (_, editedValue = "") => {
1878
- if (editingFilter.current) {
1879
- const indexOfEditedFilter = onRenameFilter(
1880
- editingFilter.current,
1881
- editedValue
1882
- );
1883
- editingFilter.current = void 0;
2487
+ if ((interactedFilterState == null ? void 0 : interactedFilterState.state) === "rename" && interactedFilterState.filter) {
2488
+ const { filter } = interactedFilterState;
2489
+ const indexOfEditedFilter = onRenameFilter(filter, editedValue);
2490
+ setInteractedFilterState(void 0);
1884
2491
  focusFilterPill(indexOfEditedFilter);
1885
2492
  }
2493
+ setInteractedFilterState(void 0);
1886
2494
  },
1887
- [focusFilterPill, onRenameFilter]
2495
+ [focusFilterPill, interactedFilterState, onRenameFilter]
1888
2496
  );
1889
- const handlePillMenuAction = useCallback10(
2497
+ const handlePillMenuAction = useCallback11(
1890
2498
  ({ menuId, options }) => {
1891
2499
  switch (menuId) {
1892
2500
  case "delete-filter": {
@@ -1897,201 +2505,125 @@ var useFilterBar = ({
1897
2505
  case "rename-filter": {
1898
2506
  const { filter } = options;
1899
2507
  const index = filters.indexOf(filter);
1900
- editPillLabel(index);
2508
+ editPillLabel(index, filter);
1901
2509
  return true;
1902
2510
  }
1903
2511
  case "edit-filter": {
1904
2512
  const { filter } = options;
1905
- editingFilter.current = filter;
1906
- setEditFilter(filter);
1907
- focusFilterClause();
2513
+ setInteractedFilterState({
2514
+ filter,
2515
+ index: filters.indexOf(filter),
2516
+ state: "edit"
2517
+ });
1908
2518
  return true;
1909
2519
  }
1910
2520
  default:
1911
2521
  return false;
1912
2522
  }
1913
2523
  },
1914
- [deleteFilter, editPillLabel, filters, focusFilterClause]
2524
+ [deleteFilter, editPillLabel, filters]
1915
2525
  );
1916
- const addIfNewElseUpdate = useCallback10(
1917
- (edited, existing) => {
2526
+ const handlePillKeyDown = useCallback11((e) => {
2527
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
2528
+ navigateToNextItem(e.target, e.key === "ArrowLeft" ? "bwd" : "fwd");
2529
+ }
2530
+ }, []);
2531
+ const addIfNewElseUpdate = useCallback11(
2532
+ (newOrUpdatedFilter, existing) => {
1918
2533
  if (existing === void 0) {
1919
- const idx = onAddFilter(edited);
1920
- editPillLabel(idx);
2534
+ const idx = onAddFilter(newOrUpdatedFilter);
2535
+ focusFilterPill(idx);
2536
+ editPillLabel(idx, newOrUpdatedFilter);
1921
2537
  } else {
1922
- onChangeFilter(existing, edited);
2538
+ const idx = onChangeFilter(existing, newOrUpdatedFilter);
2539
+ focusFilterPill(idx);
1923
2540
  }
1924
2541
  },
1925
- [editPillLabel, onAddFilter, onChangeFilter]
2542
+ [editPillLabel, focusFilterPill, onAddFilter, onChangeFilter]
1926
2543
  );
1927
- const handleMenuAction = useCallback10(
1928
- ({ menuId }) => {
1929
- switch (menuId) {
1930
- case "apply-save": {
1931
- const editedFilter = editFilter;
1932
- addIfNewElseUpdate(editedFilter, editingFilter.current);
1933
- setEditFilter(void 0);
1934
- editingFilter.current = void 0;
1935
- setShowMenu(false);
1936
- return true;
1937
- }
1938
- case "and-clause": {
1939
- const newFilter = addClause(
1940
- editFilter,
1941
- EMPTY_FILTER_CLAUSE
1942
- );
1943
- setEditFilter(newFilter);
1944
- setShowMenu(false);
1945
- return true;
2544
+ const filterSaveHandler = useCallback11(
2545
+ (filter) => {
2546
+ if (interactedFilterState) {
2547
+ const { filter: existingFilter } = interactedFilterState;
2548
+ setInteractedFilterState(void 0);
2549
+ addIfNewElseUpdate(filter, existingFilter);
2550
+ }
2551
+ },
2552
+ [addIfNewElseUpdate, interactedFilterState]
2553
+ );
2554
+ const handlePillClick = useCallback11(
2555
+ (e) => {
2556
+ const isEditing = e.target.querySelector(
2557
+ ".vuuEditableLabel-editing"
2558
+ );
2559
+ if (!isEditing) {
2560
+ const pill = queryClosest4(e.target, ".vuuFilterPill");
2561
+ if (pill) {
2562
+ const index = getElementDataIndex3(pill);
2563
+ onToggleFilterActive(index, e.shiftKey);
1946
2564
  }
1947
- case "or-clause":
1948
- setEditFilter(
1949
- (filter) => addClause(filter, EMPTY_FILTER_CLAUSE, {
1950
- combineWith: "or"
1951
- })
1952
- );
1953
- setShowMenu(false);
1954
- return true;
1955
- default:
1956
- return false;
1957
2565
  }
1958
2566
  },
1959
- [editFilter, addIfNewElseUpdate]
2567
+ [onToggleFilterActive]
1960
2568
  );
1961
- const handleClickAddFilter = useCallback10(() => {
1962
- setEditFilter({});
1963
- }, [setEditFilter]);
1964
- const handleClickRemoveFilter = useCallback10(() => {
1965
- setEditFilter(void 0);
1966
- }, []);
1967
2569
  const pillProps = {
1968
- onBeginEdit: handleBeginEditFilterName,
2570
+ editLabelApiRef: editPillLabelAPI,
2571
+ // onBeginEdit: handleBeginEditFilterName,
2572
+ onClick: handlePillClick,
2573
+ onKeyDown: handlePillKeyDown,
1969
2574
  onMenuAction: handlePillMenuAction,
1970
2575
  onExitEditMode: handleExitEditFilterName
1971
2576
  };
1972
- const handleChangeFilterClause = useCallback10(
1973
- (idx) => (filterClause) => {
1974
- if (filterClause !== void 0) {
1975
- setEditFilter((ef) => replaceClause(ef, filterClause, idx));
1976
- setShowMenu(true);
1977
- }
1978
- },
1979
- []
1980
- );
1981
- const handleCancelFilterClause = useCallback10(
1982
- (reason) => {
1983
- if (reason === "Backspace" && isMultiClauseFilter4(editFilter)) {
1984
- setEditFilter(removeLastClause(editFilter));
1985
- }
1986
- },
1987
- [editFilter]
1988
- );
1989
- const handleBlurFilterClause = useCallback10((e) => {
1990
- const target = e.target;
1991
- const relatedTarget = e.relatedTarget;
1992
- const filterClause = target.closest(".vuuFilterClause");
1993
- if (filterClause == null ? void 0 : filterClause.contains(relatedTarget)) {
1994
- } else {
1995
- const dropdownId = target.getAttribute("aria-owns");
1996
- const dropDown = dropdownId ? document.getElementById(dropdownId) : null;
1997
- if (dropDown == null ? void 0 : dropDown.contains(relatedTarget)) {
1998
- } else {
1999
- setShowMenu(true);
2000
- }
2001
- }
2002
- }, []);
2003
- const handleFocusFilterClause = useCallback10(() => {
2004
- setShowMenu(false);
2577
+ const handleClickAddButton = useCallback11(() => {
2578
+ setInteractedFilterState({
2579
+ index: -1,
2580
+ state: "create"
2581
+ });
2005
2582
  }, []);
2006
- const handleKeyDownFilterbar = useCallback10(
2007
- (evt) => {
2008
- if (evt.key === "Escape" && editFilter !== void 0) {
2009
- setEditFilter(void 0);
2010
- requestAnimationFrame(() => {
2011
- });
2012
- }
2013
- },
2014
- [editFilter]
2015
- );
2016
- const handleKeyDownMenu = useCallback10(
2017
- (evt) => {
2018
- var _a;
2019
- const { current: container } = containerRef;
2020
- if (evt.key === "Backspace" && container) {
2021
- evt.preventDefault();
2022
- const fields = Array.from(
2023
- container.querySelectorAll(".vuuFilterClauseField")
2024
- );
2025
- if (fields.length > 0) {
2026
- const field = fields.at(-1);
2027
- (_a = field == null ? void 0 : field.querySelector("input")) == null ? void 0 : _a.focus();
2028
- }
2029
- setShowMenu(false);
2030
- } else if (evt.key === "Tab") {
2031
- if (evt.shiftKey && container) {
2032
- const clearButtons = Array.from(
2033
- container.querySelectorAll(".vuuFilterClause-clearButton")
2034
- );
2035
- if (clearButtons.length > 0) {
2036
- const clearButton = clearButtons.at(-1);
2037
- setTimeout(() => {
2038
- clearButton.focus();
2039
- }, 100);
2040
- }
2041
- } else {
2042
- console.log("apply current selection");
2043
- }
2044
- }
2045
- },
2046
- [containerRef]
2047
- );
2048
- const handleAddButtonKeyDown = useCallback10((evt) => {
2583
+ const handleKeyDownAddButton = useCallback11((evt) => {
2049
2584
  if (evt.key === "ArrowLeft") {
2050
- console.log("navgiate to the Toolbar");
2585
+ navigateToNextItem(evt.target, "bwd");
2051
2586
  }
2052
2587
  }, []);
2053
- const handlePillNavigationOutOfBounds = useCallback10((direction) => {
2588
+ const handleCancelEdit = useCallback11(() => {
2054
2589
  var _a;
2055
- if (direction === "end") {
2056
- (_a = addButtonRef.current) == null ? void 0 : _a.focus();
2590
+ if (interactedFilterState) {
2591
+ const { index } = interactedFilterState;
2592
+ if (index === -1) {
2593
+ (_a = addButtonRef.current) == null ? void 0 : _a.focus();
2594
+ } else {
2595
+ focusFilterPill(index);
2596
+ }
2597
+ setInteractedFilterState(void 0);
2057
2598
  }
2058
- }, []);
2599
+ }, [focusFilterPill, interactedFilterState]);
2059
2600
  const addButtonProps = {
2060
2601
  ref: addButtonRef,
2061
- onKeyDown: handleAddButtonKeyDown
2602
+ onClick: handleClickAddButton,
2603
+ onKeyDown: handleKeyDownAddButton
2062
2604
  };
2063
2605
  return {
2064
2606
  activeFilterIndex,
2065
2607
  addButtonProps,
2066
2608
  columnsByName,
2067
- editFilter,
2068
2609
  filters,
2069
- onBlurFilterClause: handleBlurFilterClause,
2070
- onCancelFilterClause: handleCancelFilterClause,
2071
- onChangeActiveFilterIndex,
2072
- onClickAddFilter: handleClickAddFilter,
2073
- onClickRemoveFilter: handleClickRemoveFilter,
2074
- onChangeFilterClause: handleChangeFilterClause,
2075
- onFocusFilterClause: handleFocusFilterClause,
2076
- onKeyDownFilterbar: handleKeyDownFilterbar,
2077
- onKeyDownMenu: handleKeyDownMenu,
2078
- onMenuAction: handleMenuAction,
2079
- onNavigateOutOfBounds: handlePillNavigationOutOfBounds,
2610
+ interactedFilterState,
2611
+ onCancelEdit: handleCancelEdit,
2612
+ onSave: filterSaveHandler,
2080
2613
  pillProps,
2081
- promptProps,
2082
- showMenu
2614
+ promptProps
2083
2615
  };
2084
2616
  };
2085
- function columnDescriptorsByName(columns) {
2617
+ function columnDescriptorsByName2(columns) {
2086
2618
  return columns.reduce((m, col) => ({ ...m, [col.name]: col }), {});
2087
2619
  }
2088
2620
 
2089
2621
  // src/filter-bar/FilterBar.tsx
2090
- import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
2091
- import { createElement } from "react";
2622
+ import { Fragment, jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
2623
+ import { createElement as createElement2 } from "react";
2092
2624
  var classBase7 = "vuuFilterBar";
2093
2625
  var FilterBar = ({
2094
- FilterClauseEditorProps: FilterClauseEditorProps2,
2626
+ FilterClauseEditorProps,
2095
2627
  className: classNameProp,
2096
2628
  columnDescriptors,
2097
2629
  defaultFilterState,
@@ -2100,7 +2632,6 @@ var FilterBar = ({
2100
2632
  onFilterDeleted,
2101
2633
  onFilterRenamed,
2102
2634
  onFilterStateChanged,
2103
- showMenu: showMenuProp = false,
2104
2635
  tableSchema,
2105
2636
  ...htmlAttributes
2106
2637
  }) => {
@@ -2109,22 +2640,12 @@ var FilterBar = ({
2109
2640
  activeFilterIndex,
2110
2641
  addButtonProps,
2111
2642
  columnsByName,
2112
- editFilter,
2113
2643
  filters,
2114
- onBlurFilterClause,
2115
- onCancelFilterClause,
2116
- onClickAddFilter,
2117
- onClickRemoveFilter,
2118
- onChangeFilterClause,
2119
- onChangeActiveFilterIndex,
2120
- onFocusFilterClause,
2121
- onNavigateOutOfBounds,
2122
- onKeyDownFilterbar,
2123
- onKeyDownMenu,
2124
- onMenuAction,
2644
+ interactedFilterState,
2645
+ onCancelEdit,
2646
+ onSave,
2125
2647
  pillProps,
2126
- promptProps,
2127
- showMenu
2648
+ promptProps
2128
2649
  } = useFilterBar({
2129
2650
  containerRef: rootRef,
2130
2651
  columnDescriptors,
@@ -2133,126 +2654,85 @@ var FilterBar = ({
2133
2654
  onApplyFilter,
2134
2655
  onFilterStateChanged,
2135
2656
  onFilterDeleted,
2136
- onFilterRenamed,
2137
- showMenu: showMenuProp
2138
- });
2139
- const className = clsx_default(classBase7, classNameProp, {
2140
- [`${classBase7}-display`]: editFilter === void 0,
2141
- [`${classBase7}-edit`]: editFilter !== void 0
2657
+ onFilterRenamed
2142
2658
  });
2659
+ const className = clsx_default(classBase7, classNameProp);
2660
+ const indexOfFilterBeingRenamed = (interactedFilterState == null ? void 0 : interactedFilterState.state) === "rename" ? interactedFilterState.index : -1;
2661
+ const filterModel = (interactedFilterState == null ? void 0 : interactedFilterState.state) === "edit" || (interactedFilterState == null ? void 0 : interactedFilterState.state) === "create" ? new FilterModel(interactedFilterState.filter) : void 0;
2143
2662
  const getChildren2 = () => {
2144
2663
  const items = [];
2145
- if (editFilter === void 0) {
2146
- filters.forEach((filter, i) => {
2147
- items.push(
2148
- /* @__PURE__ */ createElement(
2149
- FilterPill,
2150
- {
2151
- ...pillProps,
2152
- columnsByName,
2153
- filter,
2154
- key: `filter-${i}`
2155
- }
2156
- )
2157
- );
2158
- });
2159
- return items;
2160
- } else if (editFilter) {
2161
- const filterClauses2 = filterClauses(editFilter);
2664
+ filters.forEach((filter, i) => {
2162
2665
  items.push(
2163
- /* @__PURE__ */ jsx12("div", { className: `${classBase7}-Editor`, children: filterClauses2.map((f, i) => /* @__PURE__ */ createElement(
2164
- FilterClauseEditor,
2666
+ /* @__PURE__ */ createElement2(
2667
+ FilterPill,
2165
2668
  {
2166
- ...FilterClauseEditorProps2,
2669
+ ...pillProps,
2670
+ editing: indexOfFilterBeingRenamed === i,
2167
2671
  columnsByName,
2168
- filterClause: f,
2169
- key: `editor-${i}`,
2170
- onCancel: onCancelFilterClause,
2171
- onChange: onChangeFilterClause(i),
2172
- onBlur: onBlurFilterClause,
2173
- onFocus: onFocusFilterClause,
2174
- tableSchema
2672
+ "data-index": i,
2673
+ filter,
2674
+ key: `filter-${i}`,
2675
+ selected: activeFilterIndex.includes(i)
2175
2676
  }
2176
- )) }, `editor`)
2177
- );
2178
- if (showMenu) {
2179
- items.push(
2180
- /* @__PURE__ */ jsx12(
2181
- FilterBuilderMenu,
2182
- {
2183
- onMenuAction,
2184
- ListProps: { onKeyDownCapture: onKeyDownMenu }
2185
- },
2186
- "menu"
2187
- )
2188
- );
2189
- }
2190
- items.push(
2191
- /* @__PURE__ */ jsx12(
2192
- Button2,
2193
- {
2194
- className: `${classBase7}-remove`,
2195
- "data-align": "right",
2196
- "data-icon": "cross",
2197
- onClick: onClickRemoveFilter,
2198
- variant: "primary"
2199
- },
2200
- "filter-remove"
2201
2677
  )
2202
2678
  );
2203
- return items;
2204
- }
2679
+ });
2680
+ return items;
2205
2681
  };
2206
- return /* @__PURE__ */ jsxs5(
2207
- "div",
2208
- {
2209
- ...htmlAttributes,
2210
- className,
2211
- onKeyDown: onKeyDownFilterbar,
2212
- ref: rootRef,
2213
- children: [
2214
- /* @__PURE__ */ jsx12(FilterBarMenu, {}),
2215
- /* @__PURE__ */ jsx12(
2216
- Toolbar,
2217
- {
2218
- activeItemIndex: activeFilterIndex,
2219
- onActiveChange: onChangeActiveFilterIndex,
2220
- onNavigateOutOfBounds,
2221
- selectionStrategy: "multiple-special-key",
2222
- children: getChildren2()
2223
- }
2224
- ),
2225
- editFilter === void 0 ? /* @__PURE__ */ createElement(
2226
- Button2,
2227
- {
2228
- ...addButtonProps,
2229
- className: `${classBase7}-add`,
2230
- "data-icon": "plus",
2231
- "data-selectable": false,
2232
- key: "filter-add",
2233
- onClick: onClickAddFilter,
2234
- tabIndex: 0,
2235
- variant: "primary"
2236
- }
2237
- ) : null,
2238
- promptProps ? /* @__PURE__ */ jsx12(
2239
- Prompt,
2682
+ return /* @__PURE__ */ jsxs5("div", { ...htmlAttributes, className, ref: rootRef, children: [
2683
+ /* @__PURE__ */ jsx12(FilterBarMenu, {}),
2684
+ /* @__PURE__ */ jsxs5(Fragment, { children: [
2685
+ /* @__PURE__ */ jsx12("div", { className: `${classBase7}-filters`, children: getChildren2() }),
2686
+ /* @__PURE__ */ createElement2(
2687
+ IconButton,
2688
+ {
2689
+ ...addButtonProps,
2690
+ className: clsx_default("vuuIconButton", `${classBase7}-add`),
2691
+ "data-selectable": false,
2692
+ icon: "plus",
2693
+ key: "filter-add",
2694
+ tabIndex: 0,
2695
+ variant: "primary"
2696
+ }
2697
+ )
2698
+ ] }),
2699
+ filterModel && tableSchema && /* @__PURE__ */ jsx12(Portal, { children: /* @__PURE__ */ jsx12(
2700
+ Popup,
2701
+ {
2702
+ anchorElement: rootRef,
2703
+ offsetTop: -10,
2704
+ offsetLeft: 20,
2705
+ placement: "below",
2706
+ children: /* @__PURE__ */ jsx12(
2707
+ FilterEditor,
2240
2708
  {
2241
- ...promptProps,
2242
- PopupProps: {
2243
- anchorElement: rootRef,
2244
- offsetTop: 16,
2245
- placement: "below-center"
2246
- }
2247
- }
2248
- ) : null
2249
- ]
2250
- }
2251
- );
2709
+ FilterClauseEditorProps,
2710
+ columnDescriptors,
2711
+ onCancel: onCancelEdit,
2712
+ onSave,
2713
+ filter: interactedFilterState == null ? void 0 : interactedFilterState.filter,
2714
+ tableSchema
2715
+ },
2716
+ "filter-editor"
2717
+ )
2718
+ }
2719
+ ) }),
2720
+ promptProps ? /* @__PURE__ */ jsx12(
2721
+ Prompt,
2722
+ {
2723
+ ...promptProps,
2724
+ PopupProps: {
2725
+ anchorElement: rootRef,
2726
+ offsetTop: 16,
2727
+ placement: "below-center"
2728
+ }
2729
+ }
2730
+ ) : null
2731
+ ] });
2252
2732
  };
2253
2733
 
2254
2734
  // src/filter-input/FilterInput.tsx
2255
- import { Button as Button3 } from "@salt-ds/core";
2735
+ import { Button as Button2 } from "@salt-ds/core";
2256
2736
 
2257
2737
  // src/filter-input/useCodeMirrorEditor.ts
2258
2738
  import {
@@ -5252,7 +5732,7 @@ var parser = LRParser.deserialize({
5252
5732
 
5253
5733
  // ../vuu-filter-parser/src/FilterTreeWalker.ts
5254
5734
  import {
5255
- isMultiClauseFilter as isMultiClauseFilter5,
5735
+ isMultiClauseFilter as isMultiClauseFilter6,
5256
5736
  isMultiValueFilter as isMultiValueFilter4,
5257
5737
  isSingleValueFilter as isSingleValueFilter3
5258
5738
  } from "@vuu-ui/vuu-utils";
@@ -5262,7 +5742,7 @@ var FilterExpression = class {
5262
5742
  __privateAdd(this, _filter, void 0);
5263
5743
  }
5264
5744
  setFilterCombinatorOp(op, filter = __privateGet(this, _filter)) {
5265
- if (isMultiClauseFilter5(filter) && filter.op === op) {
5745
+ if (isMultiClauseFilter6(filter) && filter.op === op) {
5266
5746
  return;
5267
5747
  } else {
5268
5748
  __privateSet(this, _filter, {
@@ -5274,14 +5754,14 @@ var FilterExpression = class {
5274
5754
  add(filter) {
5275
5755
  if (__privateGet(this, _filter) === void 0) {
5276
5756
  __privateSet(this, _filter, filter);
5277
- } else if (isMultiClauseFilter5(__privateGet(this, _filter))) {
5757
+ } else if (isMultiClauseFilter6(__privateGet(this, _filter))) {
5278
5758
  __privateGet(this, _filter).filters.push(filter);
5279
5759
  } else {
5280
5760
  throw Error(`Invalid filter passed to FilterExpression`);
5281
5761
  }
5282
5762
  }
5283
5763
  setColumn(column, filter = __privateGet(this, _filter)) {
5284
- if (isMultiClauseFilter5(filter)) {
5764
+ if (isMultiClauseFilter6(filter)) {
5285
5765
  const target = filter.filters.at(-1);
5286
5766
  if (target) {
5287
5767
  this.setColumn(column, target);
@@ -5291,7 +5771,7 @@ var FilterExpression = class {
5291
5771
  }
5292
5772
  }
5293
5773
  setOp(value, filter = __privateGet(this, _filter)) {
5294
- if (isMultiClauseFilter5(filter)) {
5774
+ if (isMultiClauseFilter6(filter)) {
5295
5775
  const target = filter.filters.at(-1);
5296
5776
  if (target) {
5297
5777
  this.setOp(value, target);
@@ -5302,7 +5782,7 @@ var FilterExpression = class {
5302
5782
  }
5303
5783
  setValue(value, filter = __privateGet(this, _filter)) {
5304
5784
  var _a;
5305
- if (isMultiClauseFilter5(filter)) {
5785
+ if (isMultiClauseFilter6(filter)) {
5306
5786
  const target = filter.filters.at(-1);
5307
5787
  if (target) {
5308
5788
  this.setValue(value, target);
@@ -5492,7 +5972,7 @@ import {
5492
5972
  getValue,
5493
5973
  syntaxTree
5494
5974
  } from "@vuu-ui/vuu-codemirror";
5495
- import { useCallback as useCallback11 } from "react";
5975
+ import { useCallback as useCallback12 } from "react";
5496
5976
  var getOperator = (node, state) => {
5497
5977
  let maybeColumnNode = node.prevSibling || node.parent;
5498
5978
  while (maybeColumnNode && !["Column", "Operator", "In"].includes(maybeColumnNode.name)) {
@@ -5562,7 +6042,7 @@ var getSetValues = (node, state) => {
5562
6042
  return values;
5563
6043
  };
5564
6044
  var useAutoComplete = (suggestionProvider, onSubmit, existingFilter) => {
5565
- const makeSuggestions = useCallback11(
6045
+ const makeSuggestions = useCallback12(
5566
6046
  async (context, suggestionType, optionalArgs = {}) => {
5567
6047
  const { startsWith = "" } = optionalArgs;
5568
6048
  const options = await suggestionProvider.getSuggestions(
@@ -5573,7 +6053,7 @@ var useAutoComplete = (suggestionProvider, onSubmit, existingFilter) => {
5573
6053
  },
5574
6054
  [suggestionProvider]
5575
6055
  );
5576
- return useCallback11(
6056
+ return useCallback12(
5577
6057
  async (context) => {
5578
6058
  var _a, _b;
5579
6059
  const { state, pos } = context;
@@ -5874,7 +6354,7 @@ var FilterInput = ({
5874
6354
  });
5875
6355
  return /* @__PURE__ */ jsxs6("div", { ...props, className: classBase8, children: [
5876
6356
  /* @__PURE__ */ jsx13(
5877
- Button3,
6357
+ Button2,
5878
6358
  {
5879
6359
  className: `${classBase8}-FilterButton`,
5880
6360
  "data-icon": iconName,
@@ -5883,7 +6363,7 @@ var FilterInput = ({
5883
6363
  ),
5884
6364
  /* @__PURE__ */ jsx13("div", { className: `${classBase8}-Editor`, ref: editorRef }),
5885
6365
  /* @__PURE__ */ jsx13(
5886
- Button3,
6366
+ Button2,
5887
6367
  {
5888
6368
  className: `${classBase8}-ClearButton`,
5889
6369
  "data-icon": "close-circle",
@@ -5906,7 +6386,7 @@ import {
5906
6386
  getTypeaheadParams,
5907
6387
  useTypeaheadSuggestions as useTypeaheadSuggestions2
5908
6388
  } from "@vuu-ui/vuu-data-react";
5909
- import { useCallback as useCallback12, useRef as useRef8 } from "react";
6389
+ import { useCallback as useCallback13, useRef as useRef8 } from "react";
5910
6390
 
5911
6391
  // src/filter-input/filterInfo.ts
5912
6392
  import { createEl } from "@vuu-ui/vuu-utils";
@@ -6004,7 +6484,7 @@ var useFilterSuggestionProvider = ({
6004
6484
  }) => {
6005
6485
  const latestSuggestionsRef = useRef8();
6006
6486
  const getTypeaheadSuggestions = useTypeahead();
6007
- const getSuggestions = useCallback12(
6487
+ const getSuggestions = useCallback13(
6008
6488
  async (suggestionType, options = NONE) => {
6009
6489
  const {
6010
6490
  columnName,
@@ -6094,7 +6574,7 @@ var useFilterSuggestionProvider = ({
6094
6574
  },
6095
6575
  [columns, getTypeaheadSuggestions, namedFilters, saveOptions, table]
6096
6576
  );
6097
- const isPartialMatch = useCallback12(
6577
+ const isPartialMatch = useCallback13(
6098
6578
  async (valueType, columnName, pattern) => {
6099
6579
  const suggestions = (
6100
6580
  // latestSuggestions && latestSuggestions.length > 0
@@ -6140,10 +6620,12 @@ export {
6140
6620
  EQUALS,
6141
6621
  ExpandoCombobox,
6142
6622
  FilterBar,
6143
- FilterBuilderMenu,
6144
- FilterClauseEditor,
6145
- FilterClauseTextValueEditor,
6623
+ FilterClause,
6624
+ FilterClauseModel,
6625
+ FilterClauseValueEditorText,
6626
+ FilterEditor,
6146
6627
  FilterInput,
6628
+ FilterModel,
6147
6629
  FilterPill,
6148
6630
  GREATER_THAN,
6149
6631
  IN,
@@ -6162,7 +6644,6 @@ export {
6162
6644
  overrideColName,
6163
6645
  removeFilter,
6164
6646
  removeLastClause,
6165
- replaceClause,
6166
6647
  saveLocalEntity,
6167
6648
  splitFilterOnColumn,
6168
6649
  updateFilter,