@vuu-ui/vuu-filters 0.7.0-debug → 0.7.1-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.
package/esm/index.js CHANGED
@@ -93,1491 +93,1957 @@ var require_classnames = __commonJS({
93
93
  }
94
94
  });
95
95
 
96
- // src/filter-input/FilterInput.tsx
97
- import { Button } from "@salt-ds/core";
98
-
99
- // src/filter-input/useCodeMirrorEditor.ts
100
- var import_classnames = __toESM(require_classnames(), 1);
101
- import {
102
- autocompletion,
103
- defaultKeymap,
104
- EditorState as EditorState2,
105
- EditorView as EditorView2,
106
- ensureSyntaxTree,
107
- keymap,
108
- minimalSetup,
109
- startCompletion
110
- } from "@vuu-ui/vuu-codemirror";
111
- import { useEffect, useMemo, useRef } from "react";
112
-
113
- // src/filter-input/filter-language-parser/FilterLanguage.ts
96
+ // src/column-filter/ColumnFilter.tsx
114
97
  import {
115
- LanguageSupport,
116
- LRLanguage,
117
- styleTags,
118
- tags as tag
119
- } from "@vuu-ui/vuu-codemirror";
98
+ Dropdown,
99
+ Toolbar,
100
+ ToolbarButton,
101
+ ToolbarField as ToolbarField2
102
+ } from "@heswell/salt-lab";
103
+ import { Text } from "@salt-ds/core";
120
104
 
121
- // src/filter-input/filter-language-parser/generated/filter-parser.js
122
- import { LRParser } from "@vuu-ui/vuu-codemirror";
123
- var parser = LRParser.deserialize({
124
- version: 14,
125
- states: "%QOVQPOOOOQO'#C_'#C_O_QQO'#C^OOQO'#DO'#DOOvQQO'#C|OOQO'#DR'#DROVQPO'#CuOOQO'#C}'#C}QOQPOOOOQO'#C`'#C`O!UQQO,58xO!dQPO,59VOVQPO,59]OVQPO,59_O!iQPO,59hO!nQQO,59aOOQO'#DQ'#DQOOQO1G.d1G.dO!UQQO1G.qO!yQQO1G.wOOQO1G.y1G.yOOQO'#Cw'#CwOOQO1G/S1G/SOOQO1G.{1G.{O#[QPO'#CnO#dQPO7+$]O!UQQO'#CxO#iQPO,59YOOQO<<Gw<<GwOOQO,59d,59dOOQO-E6v-E6v",
126
- stateData: "#q~OoOS~OsPOvUO~OTXOUXOVXOWXOXXOYXO`ZO~Of[Oh]Oj^OmpX~OZ`O[`O]`O^`O~OabO~OseO~Of[Oh]OwgO~Oh]Ofeijeimeiwei~OcjOdbX~OdlO~OcjOdba~O",
127
- goto: "#YvPPw}!TPPPPPPPPPPwPP!WPP!ZP!ZP!aP!g!jPPP!p!s!aP#P!aXROU[]XQOU[]RYQRibXTOU[]XVOU[]Rf^QkhRnkRWOQSOQ_UQc[Rd]QaYQhbRmj",
128
- nodeNames: "\u26A0 Filter ColumnValueExpression Column Operator Eq NotEq Gt Lt Starts Ends Number String True False ColumnSetExpression In LBrack Values Comma RBrack AndExpression And OrExpression Or ParenthesizedExpression As FilterName",
129
- maxTerm: 39,
130
- skippedNodes: [0],
131
- repeatNodeCount: 1,
132
- tokenData: "6p~RnXY#PYZ#P]^#Ppq#Pqr#brs#mxy$eyz$j|}$o!O!P$t!Q![%S!^!_%_!_!`%d!`!a%i!c!}%n!}#O&V#P#Q&[#R#S%n#T#U&a#U#X%n#X#Y(w#Y#Z+]#Z#]%n#]#^.]#^#c%n#c#d/e#d#g%n#g#h0m#h#i4[#i#o%n~#USo~XY#PYZ#P]^#Ppq#P~#eP!_!`#h~#mOU~~#pWOX#mZ]#m^r#mrs$Ys#O#m#P;'S#m;'S;=`$_<%lO#m~$_O[~~$bP;=`<%l#m~$jOv~~$oOw~~$tOc~~$wP!Q![$z~%PPZ~!Q![$z~%XQZ~!O!P$t!Q![%S~%dOW~~%iOT~~%nOV~P%sUsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%n~&[Oa~~&aOd~R&fYsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#b%n#b#c'U#c#g%n#g#h(^#h#o%nR'ZWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#W%n#W#X's#X#o%nR'zUfQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR(eUjQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR(|WsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#b%n#b#c)f#c#o%nR)kWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#W%n#W#X*T#X#o%nR*YWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#g%n#g#h*r#h#o%nR*yUYQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR+bVsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#U+w#U#o%nR+|WsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#`%n#`#a,f#a#o%nR,kWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#g%n#g#h-T#h#o%nR-YWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#X%n#X#Y-r#Y#o%nR-yU^QsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR.bWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#b%n#b#c.z#c#o%nR/RU`QsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR/jWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#f%n#f#g0S#g#o%nR0ZUhQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR0rWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#h%n#h#i1[#i#o%nR1aVsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#U1v#U#o%nR1{WsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#f%n#f#g2e#g#o%nR2jWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#h%n#h#i3S#i#o%nR3XWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#g%n#g#h3q#h#o%nR3xUXQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR4aWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#f%n#f#g4y#g#o%nR5OWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#i%n#i#j5h#j#o%nR5mWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#X%n#X#Y6V#Y#o%nR6^U]QsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%n",
133
- tokenizers: [0, 1],
134
- topRules: { "Filter": [0, 1] },
135
- tokenPrec: 0
136
- });
105
+ // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/icon/Icon.js
106
+ import { jsx } from "react/jsx-runtime";
107
+ import { forwardRef } from "react";
137
108
 
138
- // src/filter-input/filter-language-parser/FilterLanguage.ts
139
- var filterLanguage = LRLanguage.define({
140
- name: "VuuFilterQuery",
141
- parser: parser.configure({
142
- props: [
143
- styleTags({
144
- Identifier: tag.variableName,
145
- String: tag.string,
146
- Or: tag.emphasis,
147
- Operator: tag.operator
148
- })
149
- ]
150
- })
151
- });
152
- var filterLanguageSupport = () => {
153
- return new LanguageSupport(filterLanguage);
154
- };
109
+ // ../../node_modules/clsx/dist/clsx.m.js
110
+ function r(e) {
111
+ var t, f, n = "";
112
+ if ("string" == typeof e || "number" == typeof e)
113
+ n += e;
114
+ else if ("object" == typeof e)
115
+ if (Array.isArray(e))
116
+ for (t = 0; t < e.length; t++)
117
+ e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
118
+ else
119
+ for (t in e)
120
+ e[t] && (n && (n += " "), n += t);
121
+ return n;
122
+ }
123
+ function clsx() {
124
+ for (var e, t, f = 0, n = ""; f < arguments.length; )
125
+ (e = arguments[f++]) && (t = r(e)) && (n && (n += " "), n += t);
126
+ return n;
127
+ }
155
128
 
156
- // src/filter-input/filter-language-parser/FilterTreeWalker.ts
157
- import {
158
- isMultiClauseFilter,
159
- isMultiValueFilter,
160
- isSingleValueFilter
161
- } from "@vuu-ui/vuu-utils";
162
- var _filter;
163
- var FilterExpression = class {
164
- constructor() {
165
- __privateAdd(this, _filter, void 0);
129
+ // ../../node_modules/@salt-ds/icons/dist-es/node_modules/style-inject/dist/style-inject.es.js
130
+ function styleInject(css, ref) {
131
+ if (ref === void 0)
132
+ ref = {};
133
+ var insertAt = ref.insertAt;
134
+ if (!css || typeof document === "undefined") {
135
+ return;
166
136
  }
167
- setFilterCombinatorOp(op, filter = __privateGet(this, _filter)) {
168
- if (isMultiClauseFilter(filter) && filter.op === op) {
169
- return;
137
+ var head = document.head || document.getElementsByTagName("head")[0];
138
+ var style = document.createElement("style");
139
+ style.type = "text/css";
140
+ if (insertAt === "top") {
141
+ if (head.firstChild) {
142
+ head.insertBefore(style, head.firstChild);
170
143
  } else {
171
- __privateSet(this, _filter, {
172
- op,
173
- filters: [__privateGet(this, _filter)]
174
- });
144
+ head.appendChild(style);
175
145
  }
146
+ } else {
147
+ head.appendChild(style);
176
148
  }
177
- add(filter) {
178
- if (__privateGet(this, _filter) === void 0) {
179
- __privateSet(this, _filter, filter);
180
- } else if (isMultiClauseFilter(__privateGet(this, _filter))) {
181
- __privateGet(this, _filter).filters.push(filter);
182
- } else {
183
- throw Error(`Invalid filter passed to FilterExpression`);
184
- }
149
+ if (style.styleSheet) {
150
+ style.styleSheet.cssText = css;
151
+ } else {
152
+ style.appendChild(document.createTextNode(css));
185
153
  }
186
- setColumn(column, filter = __privateGet(this, _filter)) {
154
+ }
155
+
156
+ // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/icon/Icon.css.js
157
+ var css_248z = "/* Style applied to the root element */\n.saltIcon {\n --icon-color: var(--saltIcon-color, var(--salt-text-secondary-foreground));\n --icon-size-multiplier: var(--saltIcon-size-multiplier, 1);\n --icon-base-size: var(--salt-size-icon-base, 12px);\n /**\n * Icon size will be the multiplier (an integer from the size prop) * the base size (set by the theme per density)\n * Icons should never be smaller than 12px for readability so we've added a max() to enforce this\n */\n --icon-size: max(calc(var(--icon-base-size) * var(--icon-size-multiplier)), 12px);\n}\n\n.saltIcon {\n fill: var(--saltIcon-color, var(--icon-color));\n display: inline-block;\n margin: var(--saltIcon-margin, 0);\n position: relative;\n width: var(--icon-size);\n height: var(--icon-size);\n min-width: var(--icon-size);\n min-height: var(--icon-size);\n}\n\n.saltIcon:hover {\n --icon-color: var(--saltIcon-color-hover, var(--salt-text-secondary-foreground));\n}\n\n.saltIcon:active {\n --icon-color: var(--saltIcon-color-active, var(--salt-text-secondary-foreground));\n}\n";
158
+ styleInject(css_248z);
159
+
160
+ // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/icon/Icon.js
161
+ var makePrefixer = (prefix) => (...names) => [prefix, ...names].join("-");
162
+ var withBaseName = makePrefixer("saltIcon");
163
+ var DEFAULT_ICON_SIZE = 1;
164
+ var Icon = forwardRef(function Icon2({ children, className, size = DEFAULT_ICON_SIZE, style: styleProp, ...rest }, ref) {
165
+ const style = {
166
+ ...styleProp,
167
+ "--saltIcon-size-multiplier": `${size}`
168
+ };
169
+ return /* @__PURE__ */ jsx("svg", {
170
+ className: clsx(withBaseName(), className),
171
+ style,
172
+ role: "img",
173
+ ...rest,
174
+ ref,
175
+ children: /* @__PURE__ */ jsx("g", {
176
+ "aria-hidden": true,
177
+ children
178
+ })
179
+ });
180
+ });
181
+
182
+ // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/components/Delete.js
183
+ import { jsxs, jsx as jsx2 } from "react/jsx-runtime";
184
+ import { forwardRef as forwardRef2 } from "react";
185
+ var DeleteIcon = forwardRef2(
186
+ function DeleteIcon2(props, ref) {
187
+ return /* @__PURE__ */ jsxs(Icon, {
188
+ "data-testid": "DeleteIcon",
189
+ "aria-label": "delete",
190
+ viewBox: "0 0 12 12",
191
+ ref,
192
+ ...props,
193
+ children: [
194
+ /* @__PURE__ */ jsx2("path", {
195
+ d: "M5 4v6H4V4h1Zm2 0v6H6V4h1Z"
196
+ }),
197
+ /* @__PURE__ */ jsx2("path", {
198
+ fillRule: "evenodd",
199
+ d: "M4 0a1 1 0 0 0-1 1v1H0v1h1v7a2 2 0 0 0 2 2h5.25A1.75 1.75 0 0 0 10 10.25V3h1V2H8V1a1 1 0 0 0-1-1H4Zm5 3H2v7a1 1 0 0 0 1 1h5.25a.75.75 0 0 0 .75-.75V3ZM7 2H4v-.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5V2Z",
200
+ clipRule: "evenodd"
201
+ })
202
+ ]
203
+ });
204
+ }
205
+ );
206
+
207
+ // src/column-filter/RangeFilter.tsx
208
+ import { Input, ToolbarField } from "@heswell/salt-lab";
209
+
210
+ // src/filter-utils.ts
211
+ import { extractFilterForColumn, partition } from "@vuu-ui/vuu-utils";
212
+
213
+ // src/filterTypes.ts
214
+ var singleValueFilterOps = /* @__PURE__ */ new Set([
215
+ "=",
216
+ "!=",
217
+ ">",
218
+ ">=",
219
+ "<",
220
+ "<=",
221
+ "starts",
222
+ "ends"
223
+ ]);
224
+ var isNamedFilter = (f) => f !== void 0 && f.name !== void 0;
225
+ var isSingleValueFilter = (f) => f !== void 0 && singleValueFilterOps.has(f.op);
226
+ var isFilterClause = (f) => f !== void 0 && (isSingleValueFilter(f) || isMultiValueFilter(f));
227
+ var isMultiValueFilter = (f) => f !== void 0 && f.op === "in";
228
+ var isInFilter = (f) => f.op === "in";
229
+ var isAndFilter = (f) => f.op === "and";
230
+ var isOrFilter = (f) => f.op === "or";
231
+ function isMultiClauseFilter(f) {
232
+ return f !== void 0 && (f.op === "and" || f.op === "or");
233
+ }
234
+
235
+ // src/filter-utils.ts
236
+ var AND = "and";
237
+ var EQUALS = "=";
238
+ var GREATER_THAN = ">";
239
+ var LESS_THAN = "<";
240
+ var OR = "or";
241
+ var STARTS_WITH = "starts";
242
+ var ENDS_WITH = "ends";
243
+ var IN = "in";
244
+ var filterClauses = (filter, clauses = []) => {
245
+ if (filter) {
187
246
  if (isMultiClauseFilter(filter)) {
188
- const target = filter.filters.at(-1);
189
- if (target) {
190
- this.setColumn(column, target);
191
- }
192
- } else if (filter) {
193
- filter.column = column;
247
+ filter.filters.forEach((f) => clauses.push(...filterClauses(f)));
248
+ } else {
249
+ clauses.push(filter);
194
250
  }
195
251
  }
196
- setOp(value, filter = __privateGet(this, _filter)) {
252
+ return clauses;
253
+ };
254
+ var DEFAULT_ADD_FILTER_OPTS = {
255
+ combineWith: "and"
256
+ };
257
+ var addFilter = (existingFilter, filter, { combineWith = AND } = DEFAULT_ADD_FILTER_OPTS) => {
258
+ var _a;
259
+ if (includesNoValues(filter)) {
197
260
  if (isMultiClauseFilter(filter)) {
198
- const target = filter.filters.at(-1);
199
- if (target) {
200
- this.setOp(value, target);
201
- }
202
- } else if (filter) {
203
- filter.op = value;
261
+ } else {
262
+ existingFilter = removeFilterForColumn(existingFilter, {
263
+ name: filter.column
264
+ });
204
265
  }
205
- }
206
- setValue(value, filter = __privateGet(this, _filter)) {
207
- var _a;
266
+ } else if (includesAllValues(filter)) {
208
267
  if (isMultiClauseFilter(filter)) {
209
- const target = filter.filters.at(-1);
210
- if (target) {
211
- this.setValue(value, target);
212
- }
213
- } else if (isMultiValueFilter(filter)) {
214
- (_a = filter.values) != null ? _a : filter.values = [];
215
- filter.values.push(value);
216
- } else if (isSingleValueFilter(filter)) {
217
- filter.value = value;
218
268
  }
269
+ return removeFilterForColumn(existingFilter, { name: (_a = filter.column) != null ? _a : "" });
219
270
  }
220
- toJSON(filter = __privateGet(this, _filter)) {
221
- if (this.name) {
222
- return {
223
- ...filter,
224
- name: this.name
225
- };
226
- } else {
227
- return filter;
228
- }
271
+ if (!existingFilter) {
272
+ return filter;
273
+ }
274
+ if (!filter) {
275
+ return existingFilter;
276
+ }
277
+ if (existingFilter.op === AND && filter.op === AND) {
278
+ return {
279
+ op: AND,
280
+ filters: combine(existingFilter.filters, filter.filters)
281
+ };
282
+ }
283
+ if (existingFilter.op === AND) {
284
+ const filters = replaceOrInsert(existingFilter.filters, filter);
285
+ return filters.length > 1 ? { op: AND, filters } : filters[0];
286
+ }
287
+ if (filter.op === AND) {
288
+ return { op: AND, filters: filter.filters.concat(existingFilter) };
289
+ }
290
+ if (filterEquals(existingFilter, filter, true)) {
291
+ return filter;
229
292
  }
293
+ if (canMerge(existingFilter, filter)) {
294
+ return merge(existingFilter, filter);
295
+ }
296
+ return { op: combineWith, filters: [existingFilter, filter] };
230
297
  };
231
- _filter = new WeakMap();
232
- var walkTree = (tree, source) => {
233
- const filterExpression = new FilterExpression();
234
- const cursor = tree.cursor();
235
- do {
236
- const { name, from, to } = cursor;
237
- switch (name) {
238
- case "ColumnValueExpression":
239
- filterExpression.add({});
240
- break;
241
- case "ColumnSetExpression":
242
- filterExpression.add({ op: "in" });
243
- break;
244
- case "Or":
245
- case "And":
246
- filterExpression.setFilterCombinatorOp(source.substring(from, to));
247
- break;
248
- case "Column":
249
- filterExpression.setColumn(source.substring(from, to));
250
- break;
251
- case "Operator":
252
- filterExpression.setOp(source.substring(from, to));
253
- break;
254
- case "String":
255
- filterExpression.setValue(source.substring(from + 1, to - 1));
256
- break;
257
- case "Number":
258
- filterExpression.setValue(parseFloat(source.substring(from, to)));
259
- break;
260
- case "True":
261
- filterExpression.setValue(true);
262
- break;
263
- case "False":
264
- filterExpression.setValue(false);
265
- break;
266
- case "FilterName":
267
- filterExpression.name = source.substring(from, to);
268
- break;
269
- default:
270
- }
271
- } while (cursor.next());
272
- return filterExpression.toJSON();
298
+ var includesNoValues = (filter) => {
299
+ if (!filter) {
300
+ return false;
301
+ }
302
+ if (isInFilter(filter) && filter.values.length === 0) {
303
+ return true;
304
+ }
305
+ return isAndFilter(filter) && filter.filters.some((f) => includesNoValues(f));
273
306
  };
274
-
275
- // src/filter-input/filter-language-parser/FilterParser.ts
276
- var strictParser = parser.configure({ strict: true });
277
- var parseFilter = (filterQuery) => {
278
- const parseTree = strictParser.parse(filterQuery);
279
- const filter = walkTree(parseTree, filterQuery);
280
- return filter;
281
- };
282
-
283
- // src/filter-input/highlighting.ts
284
- import {
285
- HighlightStyle,
286
- syntaxHighlighting,
287
- tags
288
- } from "@vuu-ui/vuu-codemirror";
289
- var myHighlightStyle = HighlightStyle.define([
290
- { tag: tags.variableName, color: "var(--vuuFilterEditor-variableColor)" },
291
- { tag: tags.comment, color: "green", fontStyle: "italic" }
292
- ]);
293
- var vuuHighlighting = syntaxHighlighting(myHighlightStyle);
294
-
295
- // src/filter-input/theme.ts
296
- import { EditorView } from "@vuu-ui/vuu-codemirror";
297
- var vuuTheme = EditorView.theme(
298
- {
299
- "&": {
300
- color: "var(--vuuFilterEditor-color)",
301
- backgroundColor: "var(--vuuFilterEditor-background)",
302
- fontSize: "var(--vuuFilterEditor-fontSize)"
303
- },
304
- ".cm-content": {
305
- caretColor: "var(--vuuFilterEditor-cursorColor)",
306
- padding: 0
307
- },
308
- ".cm-line": {
309
- lineHeight: "var(--vuuFilterEditor-lineHeight)"
310
- },
311
- "&.cm-focused .cm-cursor": {
312
- borderLeftColor: "var(--vuuFilterEditor-cursorColor)"
313
- },
314
- "&.cm-focused .cm-selectionBackground, ::selection": {
315
- backgroundColor: "var(--vuuFilterEditor-selectionBackground)"
316
- },
317
- ".cm-selectionBackground, ::selection": {
318
- backgroundColor: "var(--vuuFilterEditor-selectionBackground)"
319
- },
320
- ".cm-scroller": {
321
- fontFamily: "var(--vuuFilterEditor-fontFamily)"
322
- },
323
- ".cm-tooltip": {
324
- background: "var(--vuuFilterEditor-tooltipBackground)",
325
- border: "var(--vuuFilterEditor-tooltipBorder)",
326
- boxShadow: "var(--vuuFilterEditor-tooltipElevation)",
327
- "&.cm-tooltip-autocomplete > ul": {
328
- fontFamily: "var(--vuuFilterEditor-fontFamily)",
329
- fontSize: "var(--vuuFilterEditor-fontSize)",
330
- maxHeight: "240px"
331
- },
332
- "&.cm-tooltip-autocomplete > ul > li": {
333
- alignItems: "center",
334
- display: "flex",
335
- height: "var(--vuuFilterEditor-suggestion-height)",
336
- padding: "0 3px",
337
- lineHeight: "var(--vuuFilterEditor-suggestion-height)"
338
- },
339
- "&.cm-tooltip-autocomplete li[aria-selected]": {
340
- background: "var(--vuuFilterEditor-suggestion-selectedBackground)",
341
- color: "var(--vuuFilterEditor-suggestion-selectedColor)"
342
- }
343
- },
344
- ".cm-completionIcon": {
345
- height: "18px",
346
- flex: "0 0 16px"
347
- },
348
- ".cm-completionLabel": {
349
- flex: "1 1 auto"
350
- },
351
- ".cm-completionIcon-filter": {
352
- position: "relative",
353
- "&:after": {
354
- background: "var(--salt-text-secondary-foreground)",
355
- content: "''",
356
- "-webkit-mask": "var(--svg-filter) center center/13px 13px",
357
- "-webkit-mask-repeat": "no-repeat",
358
- position: "absolute",
359
- height: "18px",
360
- left: "0px",
361
- top: "0px",
362
- width: "16px"
363
- }
364
- }
365
- },
366
- { dark: false }
367
- );
368
-
369
- // src/filter-input/useFilterAutoComplete.ts
370
- import {
371
- getNodeByName,
372
- getValue,
373
- syntaxTree
374
- } from "@vuu-ui/vuu-codemirror";
375
- import { useCallback } from "react";
376
- var getOperator = (node, state) => {
377
- let maybeColumnNode = node.prevSibling || node.parent;
378
- while (maybeColumnNode && !["Column", "Operator", "In"].includes(maybeColumnNode.name)) {
379
- maybeColumnNode = maybeColumnNode.prevSibling || maybeColumnNode.parent;
380
- }
381
- if ((maybeColumnNode == null ? void 0 : maybeColumnNode.name) === "In" || (maybeColumnNode == null ? void 0 : maybeColumnNode.name) === "Operator") {
382
- return getValue(maybeColumnNode, state);
307
+ var filterValue = (value) => typeof value === "string" ? `"${value}"` : value;
308
+ var filterAsQuery = (f) => {
309
+ if (isMultiClauseFilter(f)) {
310
+ return f.filters.map((filter) => filterAsQuery(filter)).join(` ${f.op} `);
311
+ } else if (isMultiValueFilter(f)) {
312
+ return `${f.column} ${f.op} [${f.values.join(",")}]`;
383
313
  } else {
384
- return void 0;
314
+ return `${f.column} ${f.op} ${filterValue(f.value)}`;
385
315
  }
386
316
  };
387
- var getPartialOperator = (maybeOperatorNode, state, columnName) => {
388
- const value = getValue(maybeOperatorNode, state);
389
- if (columnName === void 0 || value === columnName) {
390
- return;
317
+ var includesAllValues = (filter) => {
318
+ if (!filter) {
319
+ return false;
391
320
  }
392
- if (["contains", "ends", "starts"].some(
393
- (val) => val.startsWith(value.toLowerCase())
394
- )) {
395
- return value;
396
- } else {
397
- return void 0;
321
+ if (filter.op === STARTS_WITH && filter.value === "") {
322
+ return true;
398
323
  }
324
+ return filter.op === STARTS_WITH && filter.value === "";
399
325
  };
400
- var getClauseOperator = (node, state) => {
401
- let maybeTargetNode = node.prevSibling || node.parent || node.lastChild;
402
- while (maybeTargetNode && maybeTargetNode.name === "\u26A0")
403
- maybeTargetNode = maybeTargetNode.prevSibling;
404
- if (maybeTargetNode && ["As", "Or", "And"].includes(maybeTargetNode.name)) {
405
- return getValue(maybeTargetNode, state);
406
- } else {
407
- return void 0;
326
+ var replaceOrInsert = (filters, filter) => {
327
+ return filters.concat(filter);
328
+ };
329
+ var merge = (f1, f2) => {
330
+ if (includesNoValues(f2)) {
331
+ return f2;
332
+ }
333
+ if (isInFilter(f1) && isInFilter(f2)) {
334
+ return {
335
+ ...f1,
336
+ values: [
337
+ ...f1.values,
338
+ ...f2.values.filter(
339
+ (v) => !f1.values.includes(v)
340
+ )
341
+ ]
342
+ };
343
+ } else if (isInFilter(f1) && f2.op === EQUALS) {
344
+ return {
345
+ ...f1,
346
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
347
+ // @ts-ignore
348
+ values: f1.values.concat([f2.value])
349
+ };
350
+ } else if (f1.op === EQUALS && f2.op === EQUALS) {
351
+ return {
352
+ column: f1.column,
353
+ op: IN,
354
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
355
+ // @ts-ignore
356
+ values: [f1.value, f2.value]
357
+ };
408
358
  }
359
+ return f2;
409
360
  };
410
- var getFilterName = (node, state) => {
411
- if (node.name === "FilterName") {
412
- return getValue(node, state);
413
- } else {
414
- let maybeTargetNode = node.prevSibling || node.parent || node.lastChild;
415
- while (maybeTargetNode && maybeTargetNode.name !== "FilterName")
416
- maybeTargetNode = maybeTargetNode.prevSibling;
417
- if (maybeTargetNode && maybeTargetNode.name === "FilterName") {
418
- return getValue(node, state);
361
+ var combine = (existingFilters, replacementFilters) => {
362
+ const equivalentType = ({ op: t1 }, { op: t2 }) => {
363
+ return t1 === t2 || t1[0] === t2[0];
364
+ };
365
+ const replaces = (existingFilter, replacementFilter) => {
366
+ return existingFilter.column === replacementFilter.column && equivalentType(existingFilter, replacementFilter);
367
+ };
368
+ const stillApplicable = (existingFilter) => replacementFilters.some(
369
+ (replacementFilter) => replaces(existingFilter, replacementFilter)
370
+ ) === false;
371
+ return existingFilters.filter(stillApplicable).concat(replacementFilters);
372
+ };
373
+ var removeColumnFromFilter = (column, filter) => {
374
+ if (isMultiClauseFilter(filter)) {
375
+ const [clause1, clause2] = filter.filters;
376
+ if (clause1.column === column.name) {
377
+ return [clause2, filterAsQuery(clause2)];
378
+ }
379
+ if (clause2.column === column.name) {
380
+ return [clause1, filterAsQuery(clause1)];
419
381
  }
420
382
  }
383
+ return [void 0, ""];
421
384
  };
422
- var getColumnName = (node, state) => {
423
- const prevNode = node.prevSibling;
424
- if ((prevNode == null ? void 0 : prevNode.name) === "Column") {
425
- return getValue(prevNode, state);
426
- } else if ((prevNode == null ? void 0 : prevNode.name) === "Operator") {
427
- return getColumnName(prevNode, state);
385
+ var removeFilter = (sourceFilter, filterToRemove) => {
386
+ if (filterEquals(sourceFilter, filterToRemove, true)) {
387
+ return null;
428
388
  }
429
- };
430
- var getSetValues = (node, state) => {
431
- let maybeTargetNode = node.lastChild;
432
- const values = [];
433
- while (maybeTargetNode && maybeTargetNode.name !== "In") {
434
- const value = getValue(maybeTargetNode, state);
435
- if (value) {
436
- values.push(value);
437
- } else {
438
- break;
439
- }
440
- maybeTargetNode = maybeTargetNode.prevSibling;
389
+ if (sourceFilter.op !== AND) {
390
+ throw Error(
391
+ `removeFilter cannot remove ${JSON.stringify(
392
+ filterToRemove
393
+ )} from ${JSON.stringify(sourceFilter)}`
394
+ );
441
395
  }
442
- return values;
396
+ const filters = sourceFilter.filters.filter(
397
+ (f) => !filterEquals(f, filterToRemove)
398
+ );
399
+ return filters.length > 0 ? { type: AND, filters } : null;
443
400
  };
444
- var useAutoComplete = (suggestionProvider, onSubmit, existingFilter) => {
445
- const makeSuggestions = useCallback(
446
- async (context, suggestionType, optionalArgs = {}) => {
447
- const { startsWith = "" } = optionalArgs;
448
- const options = await suggestionProvider.getSuggestions(
449
- suggestionType,
450
- optionalArgs
451
- );
452
- return { from: context.pos - startsWith.length, options };
453
- },
454
- [suggestionProvider]
455
- );
456
- return useCallback(
457
- async (context) => {
458
- var _a, _b;
459
- const { state, pos } = context;
460
- const word = (_a = context.matchBefore(/\w*/)) != null ? _a : {
461
- from: 0,
462
- to: 0,
463
- text: void 0
464
- };
465
- const tree = syntaxTree(state);
466
- const nodeBefore = tree.resolveInner(pos, -1);
467
- console.log({ nodeBeforeName: nodeBefore.name });
468
- switch (nodeBefore.name) {
469
- case "Filter":
470
- if (context.pos === 0) {
471
- return makeSuggestions(context, "column");
472
- } else {
473
- const clauseOperator = getClauseOperator(nodeBefore, state);
474
- if (clauseOperator === "as") {
475
- return makeSuggestions(context, "name");
476
- } else {
477
- const filterName = getFilterName(nodeBefore, state);
478
- return makeSuggestions(context, "save", {
479
- onSubmit: onSubmit.current,
480
- existingFilter,
481
- filterName
482
- });
483
- }
484
- }
485
- case "String":
486
- {
487
- const operator = getOperator(nodeBefore, state);
488
- const columnName = getColumnName(nodeBefore, state);
489
- const { from, to } = nodeBefore;
490
- if (to - from === 2 && context.pos === from + 1) {
491
- if (columnName && operator) {
492
- return makeSuggestions(context, "columnValue", {
493
- columnName,
494
- operator,
495
- quoted: true,
496
- startsWith: word.text
497
- });
498
- }
499
- } else {
500
- console.log(
501
- `we have a string, column is ${columnName} ${from} ${to}`
502
- );
503
- }
504
- }
505
- break;
506
- case "As":
507
- return makeSuggestions(context, "name");
508
- case "FilterName":
509
- return makeSuggestions(context, "save", {
510
- onSubmit: onSubmit.current,
511
- existingFilter,
512
- filterName: getFilterName(nodeBefore, state)
513
- });
514
- case "Column": {
515
- const columnName = getValue(nodeBefore, state);
516
- const isPartialMatch = await suggestionProvider.isPartialMatch(
517
- "column",
518
- void 0,
519
- columnName
520
- );
521
- if (isPartialMatch) {
522
- return makeSuggestions(context, "column", {
523
- startsWith: columnName
524
- });
525
- } else {
526
- return makeSuggestions(context, "operator", { columnName });
527
- }
528
- }
529
- case "\u26A0": {
530
- const columnName = getNodeByName(nodeBefore, state);
531
- const operator = getOperator(nodeBefore, state);
532
- const partialOperator = operator ? void 0 : getPartialOperator(nodeBefore, state, columnName);
533
- if (partialOperator) {
534
- return makeSuggestions(context, "operator", {
535
- columnName,
536
- startsWith: partialOperator
537
- });
538
- } else {
539
- return makeSuggestions(context, "columnValue", {
540
- columnName,
541
- operator,
542
- startsWith: word.text
543
- });
544
- }
545
- }
546
- case "Identifier":
547
- {
548
- const clauseOperator = getClauseOperator(nodeBefore, state);
549
- if (clauseOperator === "as") {
550
- return {
551
- from: context.pos,
552
- options: [
553
- {
554
- label: "press ENTER to apply filter and save",
555
- apply: () => onSubmit.current(),
556
- boost: 5
557
- }
558
- ]
559
- };
560
- }
561
- }
562
- break;
563
- case "ColumnSetExpression":
564
- case "Values": {
565
- const columnName = getNodeByName(nodeBefore, state);
566
- const selection = getSetValues(nodeBefore, state);
567
- return makeSuggestions(context, "columnValue", {
568
- columnName,
569
- selection
570
- });
571
- }
572
- case "Comma":
573
- case "LBrack": {
574
- const columnName = getNodeByName(nodeBefore, state);
575
- return makeSuggestions(context, "columnValue", { columnName });
576
- }
577
- case "ColumnValueExpression":
578
- {
579
- const lastToken = (_b = nodeBefore.lastChild) == null ? void 0 : _b.prevSibling;
580
- if ((lastToken == null ? void 0 : lastToken.name) === "Column") {
581
- return makeSuggestions(context, "operator", {
582
- columnName: getNodeByName(nodeBefore, state)
583
- });
584
- } else if ((lastToken == null ? void 0 : lastToken.name) === "Operator") {
585
- return makeSuggestions(context, "columnValue", {
586
- columnName: getNodeByName(lastToken, state),
587
- operator: getValue(lastToken, state)
588
- });
589
- }
590
- }
591
- break;
592
- case "In": {
593
- return {
594
- from: context.pos,
595
- options: [{ label: "[", apply: " [", type: "text" }]
596
- };
597
- }
598
- case "Eq": {
599
- return makeSuggestions(context, "columnValue", {
600
- columnName: getNodeByName(nodeBefore, state)
601
- });
602
- }
603
- case "AndExpression":
604
- case "OrExpression": {
605
- return makeSuggestions(context, "column");
606
- }
607
- default:
608
- }
609
- },
610
- [existingFilter, makeSuggestions, onSubmit, suggestionProvider]
401
+ var splitFilterOnColumn = (filter, columnName) => {
402
+ if (!filter) {
403
+ return [null, null];
404
+ }
405
+ if (filter.column === columnName) {
406
+ return [filter, null];
407
+ }
408
+ if (filter.op !== AND) {
409
+ return [null, filter];
410
+ }
411
+ const [[columnFilter = null], filters] = partition(
412
+ filter.filters,
413
+ (f) => f.column === columnName
611
414
  );
415
+ return filters.length === 1 ? [columnFilter, filters[0]] : [columnFilter, { op: AND, filters }];
612
416
  };
613
-
614
- // src/filter-input/useCodeMirrorEditor.ts
615
- var getView = (ref) => {
616
- if (ref.current == void 0) {
617
- throw Error("EditorView not defined");
417
+ var overrideColName = (filter, column) => {
418
+ if (isMultiClauseFilter(filter)) {
419
+ return {
420
+ op: filter.op,
421
+ filters: filter.filters.map((f) => overrideColName(f, column))
422
+ };
618
423
  }
619
- return ref.current;
424
+ return { ...filter, column };
620
425
  };
621
- var getOptionClass = (completion) => {
622
- return (0, import_classnames.default)("vuuSuggestion", {
623
- vuuIllustration: completion.isIllustration
624
- });
426
+ var filterIncludesColumn = (filter, column) => {
427
+ if (!filter) {
428
+ return false;
429
+ }
430
+ const { op, column: filterColName } = filter;
431
+ switch (op) {
432
+ case AND:
433
+ case OR:
434
+ return filter.filters != null && filter.filters.some((f) => filterIncludesColumn(f, column));
435
+ default:
436
+ return filterColName === column.name;
437
+ }
625
438
  };
626
- var stripName = (filterQuery) => {
627
- const pos = filterQuery.lastIndexOf(" as ");
628
- if (pos !== -1) {
629
- return filterQuery.slice(0, pos);
630
- } else {
631
- return filterQuery;
439
+ var removeFilterForColumn = (sourceFilter, column) => {
440
+ const colName = column.name;
441
+ if (!sourceFilter) {
442
+ return void 0;
443
+ }
444
+ if (sourceFilter.column === colName) {
445
+ return void 0;
632
446
  }
447
+ if (isAndFilter(sourceFilter) || isOrFilter(sourceFilter)) {
448
+ const { op } = sourceFilter;
449
+ const filters = sourceFilter.filters;
450
+ const otherColFilters = filters.filter((f) => f.column !== colName);
451
+ switch (otherColFilters.length) {
452
+ case 0:
453
+ return void 0;
454
+ case 1:
455
+ return otherColFilters[0];
456
+ default:
457
+ return { op, filters: otherColFilters };
458
+ }
459
+ }
460
+ return sourceFilter;
633
461
  };
634
- var noop = () => console.log("noooop");
635
- var useCodeMirrorEditor = ({
636
- existingFilter,
637
- onSubmitFilter,
638
- suggestionProvider
639
- }) => {
640
- const editorRef = useRef(null);
641
- const onSubmit = useRef(noop);
642
- const viewRef = useRef();
643
- const completionFn = useAutoComplete(
644
- suggestionProvider,
645
- onSubmit,
646
- existingFilter
647
- );
648
- const [createState, clearInput] = useMemo(() => {
649
- const parseFilter2 = () => {
650
- const view = getView(viewRef);
651
- const source = view.state.doc.toString();
652
- const tree = ensureSyntaxTree(view.state, view.state.doc.length, 5e3);
653
- if (tree) {
654
- const filter = walkTree(tree, source);
655
- return [filter, stripName(source), filter.name];
656
- } else {
657
- return [void 0, "", void 0];
462
+ var canMerge = (f1, f2) => f1.column === f2.column && (f1.op === "=" || f1.op === "in") && (f2.op === "=" || f2.op === "in");
463
+ var sameValues = (arr1, arr2) => {
464
+ if (arr1 === arr2) {
465
+ return true;
466
+ }
467
+ if (arr1.length === arr2.length) {
468
+ const a = arr1.slice().sort();
469
+ const b = arr2.slice().sort();
470
+ return a.join("|") === b.join("|");
471
+ }
472
+ return false;
473
+ };
474
+ var filterEquals = (f1, f2, strict = false) => {
475
+ if (!strict) {
476
+ return true;
477
+ }
478
+ if (f1 && f2 && canMerge(f1, f2)) {
479
+ return f1.op === f2.op && (isSingleValueFilter(f1) && isSingleValueFilter(f2) && f1.value === f2.value || isMultiValueFilter(f1) && isMultiValueFilter(f2) && sameValues(f1.values, f2.values));
480
+ }
481
+ return false;
482
+ };
483
+ var updateFilter = (filter, newFilter, mode) => {
484
+ if (filter && newFilter) {
485
+ if (mode === "replace") {
486
+ return newFilter;
487
+ }
488
+ if (filter.op === "and") {
489
+ return {
490
+ ...filter,
491
+ filters: filter.filters.concat(newFilter)
492
+ };
493
+ }
494
+ const { column: columnName } = newFilter;
495
+ if (columnName) {
496
+ const existingClause = newFilter.column ? extractFilterForColumn(filter, columnName) : void 0;
497
+ if (existingClause && columnName) {
498
+ const result = removeFilterForColumn(filter, { name: columnName });
499
+ return updateFilter(result, newFilter, "add");
658
500
  }
501
+ }
502
+ return {
503
+ op: "and",
504
+ filters: [filter, newFilter]
659
505
  };
660
- const clearInput2 = () => {
661
- getView(viewRef).setState(createState2());
506
+ }
507
+ if (newFilter) {
508
+ return newFilter;
509
+ }
510
+ return filter;
511
+ };
512
+
513
+ // src/column-filter/utils.ts
514
+ var isStartsWithValue = (value) => /\.\.\.$/.test(value);
515
+ var getTypeaheadFilter = (column, filterValues, isStartsWithFilter) => {
516
+ if (filterValues.length === 0) {
517
+ return void 0;
518
+ }
519
+ if (isStartsWithFilter) {
520
+ const startsWith = filterValues[0].substring(0, filterValues[0].length - 3);
521
+ return {
522
+ column,
523
+ op: "starts",
524
+ value: `"${startsWith}"`
662
525
  };
663
- const submitFilterAndClearInput = (mode) => {
664
- const [filter, filterQuery, filterName] = parseFilter2();
665
- onSubmitFilter == null ? void 0 : onSubmitFilter(filter, filterQuery, mode, filterName);
666
- clearInput2();
526
+ }
527
+ return {
528
+ column,
529
+ op: "in",
530
+ values: filterValues.map((value) => `"${value}"`)
531
+ };
532
+ };
533
+ var getRangeFilter = (column, startValue, endValue) => {
534
+ const startFilter = startValue === void 0 ? void 0 : { column, op: ">", value: startValue - 1 };
535
+ const endFilter = endValue === void 0 ? void 0 : { column, op: "<", value: endValue + 1 };
536
+ if (endFilter === void 0)
537
+ return startFilter;
538
+ return addFilter(startFilter, endFilter, { combineWith: "and" });
539
+ };
540
+
541
+ // src/column-filter/RangeFilter.tsx
542
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
543
+ var RangeFilter = ({
544
+ defaultTypeaheadParams,
545
+ filterValues,
546
+ onChange
547
+ }) => {
548
+ var _a, _b;
549
+ const columnName = defaultTypeaheadParams[1];
550
+ const startChangeHandler = (e) => {
551
+ const value = parseFloat(e.target.value);
552
+ const newRange = {
553
+ start: isNaN(value) ? void 0 : value,
554
+ end: filterValues == null ? void 0 : filterValues.end
667
555
  };
668
- const submitFilter = (key) => {
669
- return keymap.of([
670
- {
671
- key,
672
- run() {
673
- submitFilterAndClearInput();
674
- return true;
675
- }
676
- }
677
- ]);
556
+ const filter = getRangeFilter(columnName, newRange.start, newRange.end);
557
+ onChange(newRange, filter);
558
+ };
559
+ const endChangeHandler = (e) => {
560
+ const value = parseFloat(e.target.value);
561
+ const newRange = {
562
+ start: filterValues == null ? void 0 : filterValues.start,
563
+ end: isNaN(value) ? void 0 : value
678
564
  };
679
- const showSuggestions = (key) => {
680
- return keymap.of([
681
- {
682
- key,
683
- run() {
684
- startCompletion(getView(viewRef));
685
- return true;
686
- }
687
- }
688
- ]);
565
+ const filter = getRangeFilter(columnName, newRange.start, newRange.end);
566
+ onChange(newRange, filter);
567
+ };
568
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "row" }, children: [
569
+ /* @__PURE__ */ jsx3(ToolbarField, { label: "From", children: /* @__PURE__ */ jsx3(
570
+ Input,
571
+ {
572
+ onChange: startChangeHandler,
573
+ value: ((_a = filterValues == null ? void 0 : filterValues.start) == null ? void 0 : _a.toString()) || "",
574
+ type: "number"
575
+ }
576
+ ) }),
577
+ /* @__PURE__ */ jsx3(ToolbarField, { label: "To", children: /* @__PURE__ */ jsx3(
578
+ Input,
579
+ {
580
+ onChange: endChangeHandler,
581
+ value: ((_b = filterValues == null ? void 0 : filterValues.end) == null ? void 0 : _b.toString()) || "",
582
+ type: "number"
583
+ }
584
+ ) })
585
+ ] });
586
+ };
587
+
588
+ // src/column-filter/TypeaheadFilter.tsx
589
+ import { useCallback, useEffect, useState } from "react";
590
+ import { useTypeaheadSuggestions } from "@vuu-ui/vuu-data";
591
+ import { ComboBoxDeprecated } from "@heswell/salt-lab";
592
+ import { jsx as jsx4 } from "react/jsx-runtime";
593
+ var TypeaheadFilter = ({
594
+ defaultTypeaheadParams,
595
+ filterValues = [],
596
+ onChange: onFilterChange
597
+ }) => {
598
+ const [tableName, columnName] = defaultTypeaheadParams;
599
+ const [searchValue, setSearchValue] = useState("");
600
+ const [typeaheadValues, setTypeaheadValues] = useState([]);
601
+ const getSuggestions = useTypeaheadSuggestions();
602
+ useEffect(() => {
603
+ const params = searchValue ? [tableName, columnName, searchValue] : defaultTypeaheadParams;
604
+ let isSubscribed = true;
605
+ getSuggestions(params).then((options) => {
606
+ if (!isSubscribed) {
607
+ return;
608
+ }
609
+ if (isStartsWithValue(filterValues[0])) {
610
+ options.unshift(filterValues[0]);
611
+ }
612
+ if (searchValue) {
613
+ options.unshift(`${searchValue}...`);
614
+ }
615
+ options.concat(filterValues);
616
+ setTypeaheadValues(options);
617
+ });
618
+ return () => {
619
+ isSubscribed = false;
689
620
  };
690
- const createState2 = () => EditorState2.create({
691
- doc: "",
692
- extensions: [
693
- minimalSetup,
694
- autocompletion({
695
- override: [completionFn],
696
- optionClass: getOptionClass
697
- }),
698
- filterLanguageSupport(),
699
- keymap.of(defaultKeymap),
700
- submitFilter("Ctrl-Enter"),
701
- showSuggestions("ArrowDown"),
702
- EditorView2.updateListener.of((v) => {
703
- const view = getView(viewRef);
704
- if (v.docChanged) {
705
- startCompletion(view);
621
+ }, [
622
+ filterValues,
623
+ searchValue,
624
+ columnName,
625
+ tableName,
626
+ getSuggestions,
627
+ defaultTypeaheadParams
628
+ ]);
629
+ const onInputChange = useCallback(
630
+ (evt) => {
631
+ const value = evt.target.value;
632
+ setSearchValue(value);
633
+ },
634
+ []
635
+ );
636
+ const onSelectionChange = useCallback(
637
+ (_evt, selected) => {
638
+ setSearchValue("");
639
+ if (selected === null)
640
+ return;
641
+ if (selected.some(isStartsWithValue)) {
642
+ selected = selected.filter(isStartsWithValue).slice(-1);
643
+ }
644
+ const filter = getTypeaheadFilter(
645
+ columnName,
646
+ selected,
647
+ isStartsWithValue(selected[0])
648
+ );
649
+ onFilterChange(selected, filter);
650
+ },
651
+ [columnName, onFilterChange]
652
+ );
653
+ return /* @__PURE__ */ jsx4(
654
+ ComboBoxDeprecated,
655
+ {
656
+ multiSelect: true,
657
+ onInputChange,
658
+ onChange: onSelectionChange,
659
+ source: typeaheadValues,
660
+ style: { minWidth: 200 },
661
+ inputValue: searchValue,
662
+ selectedItem: filterValues
663
+ },
664
+ columnName
665
+ );
666
+ };
667
+
668
+ // src/column-filter/ColumnListItem.tsx
669
+ import { memo } from "react";
670
+ import {
671
+ Highlighter,
672
+ ListItem
673
+ } from "@heswell/salt-lab";
674
+ import { jsx as jsx5 } from "react/jsx-runtime";
675
+ var ColumnListItem = (props) => {
676
+ return /* @__PURE__ */ jsx5(MemoColumnItem, { ...props });
677
+ };
678
+ var MemoColumnItem = memo(function MemoizedItem({
679
+ item,
680
+ itemTextHighlightPattern,
681
+ ...restProps
682
+ }) {
683
+ return /* @__PURE__ */ jsx5(ListItem, { ...restProps, children: /* @__PURE__ */ jsx5("span", { style: { marginLeft: 10 }, children: /* @__PURE__ */ jsx5(
684
+ Highlighter,
685
+ {
686
+ matchPattern: itemTextHighlightPattern,
687
+ text: item == null ? void 0 : item.name
688
+ }
689
+ ) }) });
690
+ });
691
+
692
+ // src/column-filter/useColumnFilterStore.ts
693
+ import { useCallback as useCallback2, useState as useState2 } from "react";
694
+ var addOrReplace = (array, newValue, key) => array.filter((oldValue) => oldValue[key] !== newValue[key]).concat(newValue);
695
+ var useColumnFilterStore = (onFilterSubmit) => {
696
+ var _a, _b;
697
+ const [selectedColumnName, setSelectedColumnName] = useState2("");
698
+ const [savedFilters, setSavedFilters] = useState2([]);
699
+ const [rangeValues, setRangeValues] = useState2([]);
700
+ const [typeaheadValues, setTypeaheadValues] = useState2([]);
701
+ const clear = () => {
702
+ setSelectedColumnName("");
703
+ setRangeValues([]);
704
+ setTypeaheadValues([]);
705
+ setSavedFilters([]);
706
+ onFilterSubmit("");
707
+ };
708
+ const updateFilters = useCallback2(
709
+ (newFilter) => {
710
+ const newSavedFilters = addOrReplace(
711
+ savedFilters,
712
+ { column: selectedColumnName, filter: newFilter },
713
+ "column"
714
+ );
715
+ setSavedFilters(newSavedFilters);
716
+ const combinedFilter = newSavedFilters.map((x) => x.filter).reduce((prev, filter) => {
717
+ if (filter === void 0)
718
+ return prev;
719
+ return addFilter(prev, filter, { combineWith: AND });
720
+ }, void 0);
721
+ const query = combinedFilter === void 0 ? "" : filterAsQuery(combinedFilter);
722
+ onFilterSubmit(query, combinedFilter);
723
+ },
724
+ [selectedColumnName, onFilterSubmit, savedFilters]
725
+ );
726
+ const onTypeaheadChange = useCallback2(
727
+ (newValues, newFilter) => {
728
+ setTypeaheadValues(
729
+ addOrReplace(
730
+ typeaheadValues,
731
+ { column: selectedColumnName, value: newValues },
732
+ "column"
733
+ )
734
+ );
735
+ updateFilters(newFilter);
736
+ },
737
+ [selectedColumnName, typeaheadValues, updateFilters]
738
+ );
739
+ const onRangeChange = useCallback2(
740
+ (newValues, newFilter) => {
741
+ setRangeValues(
742
+ addOrReplace(
743
+ rangeValues,
744
+ { column: selectedColumnName, value: newValues },
745
+ "column"
746
+ )
747
+ );
748
+ updateFilters(newFilter);
749
+ },
750
+ [selectedColumnName, rangeValues, updateFilters]
751
+ );
752
+ const onSelectedColumnChange = useCallback2(
753
+ (column) => setSelectedColumnName((column == null ? void 0 : column.name) || ""),
754
+ []
755
+ );
756
+ const rangeValue = (_a = rangeValues.filter(
757
+ (v) => v.column === selectedColumnName
758
+ )[0]) == null ? void 0 : _a.value;
759
+ const typeaheadValue = (_b = typeaheadValues.filter(
760
+ (v) => v.column === selectedColumnName
761
+ )[0]) == null ? void 0 : _b.value;
762
+ return {
763
+ clear,
764
+ selectedColumnName,
765
+ rangeValue,
766
+ typeaheadValue,
767
+ onSelectedColumnChange,
768
+ onRangeChange,
769
+ onTypeaheadChange
770
+ };
771
+ };
772
+
773
+ // src/column-filter/ColumnFilter.tsx
774
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
775
+ var ColumnFilter = ({
776
+ className,
777
+ table,
778
+ columns,
779
+ onFilterSubmit,
780
+ ...htmlAttributes
781
+ }) => {
782
+ const {
783
+ clear,
784
+ onTypeaheadChange,
785
+ onRangeChange,
786
+ onSelectedColumnChange,
787
+ selectedColumnName,
788
+ rangeValue,
789
+ typeaheadValue
790
+ } = useColumnFilterStore(onFilterSubmit);
791
+ const getFilterComponent = () => {
792
+ var _a;
793
+ const defaultTypeaheadParams = [table, selectedColumnName];
794
+ const selectedColumnType = (_a = columns.find(
795
+ (column) => column.name === selectedColumnName
796
+ )) == null ? void 0 : _a.serverDataType;
797
+ switch (selectedColumnType) {
798
+ case "string":
799
+ case "char":
800
+ return /* @__PURE__ */ jsx6(
801
+ ToolbarField2,
802
+ {
803
+ label: "Start typing to select a filter",
804
+ labelPlacement: "top",
805
+ children: /* @__PURE__ */ jsx6(
806
+ TypeaheadFilter,
807
+ {
808
+ defaultTypeaheadParams,
809
+ filterValues: typeaheadValue,
810
+ onChange: onTypeaheadChange
811
+ }
812
+ )
813
+ }
814
+ );
815
+ case "int":
816
+ case "long":
817
+ case "double":
818
+ return /* @__PURE__ */ jsx6(ToolbarField2, { label: "Select a range", labelPlacement: "top", children: /* @__PURE__ */ jsx6(
819
+ RangeFilter,
820
+ {
821
+ defaultTypeaheadParams,
822
+ filterValues: rangeValue,
823
+ onChange: onRangeChange
824
+ }
825
+ ) });
826
+ default:
827
+ return /* @__PURE__ */ jsx6(ToolbarField2, { children: /* @__PURE__ */ jsx6(Text, { children: "Data type unsupported" }) });
828
+ }
829
+ };
830
+ return /* @__PURE__ */ jsxs3(
831
+ Toolbar,
832
+ {
833
+ ...htmlAttributes,
834
+ style: { alignItems: "center", height: "36px" },
835
+ children: [
836
+ /* @__PURE__ */ jsx6(
837
+ ToolbarField2,
838
+ {
839
+ label: "Select a column to filter",
840
+ labelPlacement: "top",
841
+ style: { width: 180 },
842
+ children: /* @__PURE__ */ jsx6(
843
+ Dropdown,
844
+ {
845
+ source: columns,
846
+ ListItem: ColumnListItem,
847
+ itemToString: (column) => column.name,
848
+ onSelectionChange: (_evt, column) => onSelectedColumnChange(column)
849
+ }
850
+ )
706
851
  }
707
- }),
708
- EditorState2.transactionFilter.of(
709
- (tr) => tr.newDoc.lines > 1 ? [] : tr
710
852
  ),
711
- vuuTheme,
712
- vuuHighlighting
853
+ selectedColumnName && getFilterComponent(),
854
+ /* @__PURE__ */ jsx6(ToolbarButton, { onClick: clear, children: /* @__PURE__ */ jsx6(DeleteIcon, {}) })
713
855
  ]
714
- });
715
- onSubmit.current = (mode) => {
716
- submitFilterAndClearInput(mode);
717
- setTimeout(() => {
718
- getView(viewRef).focus();
719
- }, 100);
720
- };
721
- return [createState2, clearInput2];
722
- }, [completionFn, onSubmitFilter]);
723
- useEffect(() => {
724
- if (!editorRef.current) {
725
- throw Error("editor not in dom");
726
856
  }
727
- viewRef.current = new EditorView2({
728
- state: createState(),
729
- parent: editorRef.current
730
- });
731
- return () => {
732
- var _a;
733
- (_a = viewRef.current) == null ? void 0 : _a.destroy();
734
- };
735
- }, [completionFn, createState]);
736
- return { editorRef, clearInput };
857
+ );
737
858
  };
738
859
 
739
860
  // src/filter-input/FilterInput.tsx
740
- import { jsx, jsxs } from "react/jsx-runtime";
741
- var classBase = "vuuFilterInput";
742
- var FilterInput = ({
743
- existingFilter,
744
- iconName = "filter",
745
- namedFilters,
746
- onSubmitFilter,
747
- suggestionProvider,
748
- ...props
749
- }) => {
750
- const { editorRef, clearInput } = useCodeMirrorEditor({
751
- existingFilter,
752
- onSubmitFilter,
753
- suggestionProvider
754
- });
755
- return /* @__PURE__ */ jsxs("div", { ...props, className: classBase, children: [
756
- /* @__PURE__ */ jsx(
757
- Button,
758
- {
759
- className: `${classBase}-FilterButton`,
760
- "data-icon": iconName,
761
- tabIndex: -1
861
+ import { Button } from "@salt-ds/core";
862
+
863
+ // src/filter-input/useCodeMirrorEditor.ts
864
+ var import_classnames = __toESM(require_classnames(), 1);
865
+ import {
866
+ autocompletion,
867
+ defaultKeymap,
868
+ EditorState as EditorState2,
869
+ EditorView as EditorView2,
870
+ ensureSyntaxTree,
871
+ keymap,
872
+ minimalSetup,
873
+ startCompletion
874
+ } from "@vuu-ui/vuu-codemirror";
875
+ import { useEffect as useEffect2, useMemo, useRef } from "react";
876
+
877
+ // src/filter-input/filter-language-parser/FilterLanguage.ts
878
+ import {
879
+ LanguageSupport,
880
+ LRLanguage,
881
+ styleTags,
882
+ tags as tag
883
+ } from "@vuu-ui/vuu-codemirror";
884
+
885
+ // src/filter-input/filter-language-parser/generated/filter-parser.js
886
+ import { LRParser } from "@vuu-ui/vuu-codemirror";
887
+ var parser = LRParser.deserialize({
888
+ version: 14,
889
+ states: "%QOVQPOOOOQO'#C_'#C_O_QQO'#C^OOQO'#DO'#DOOvQQO'#C|OOQO'#DR'#DROVQPO'#CuOOQO'#C}'#C}QOQPOOOOQO'#C`'#C`O!UQQO,58xO!dQPO,59VOVQPO,59]OVQPO,59_O!iQPO,59hO!nQQO,59aOOQO'#DQ'#DQOOQO1G.d1G.dO!UQQO1G.qO!yQQO1G.wOOQO1G.y1G.yOOQO'#Cw'#CwOOQO1G/S1G/SOOQO1G.{1G.{O#[QPO'#CnO#dQPO7+$]O!UQQO'#CxO#iQPO,59YOOQO<<Gw<<GwOOQO,59d,59dOOQO-E6v-E6v",
890
+ stateData: "#q~OoOS~OsPOvUO~OTXOUXOVXOWXOXXOYXO`ZO~Of[Oh]Oj^OmpX~OZ`O[`O]`O^`O~OabO~OseO~Of[Oh]OwgO~Oh]Ofeijeimeiwei~OcjOdbX~OdlO~OcjOdba~O",
891
+ goto: "#YvPPw}!TPPPPPPPPPPwPP!WPP!ZP!ZP!aP!g!jPPP!p!s!aP#P!aXROU[]XQOU[]RYQRibXTOU[]XVOU[]Rf^QkhRnkRWOQSOQ_UQc[Rd]QaYQhbRmj",
892
+ nodeNames: "\u26A0 Filter ColumnValueExpression Column Operator Eq NotEq Gt Lt Starts Ends Number String True False ColumnSetExpression In LBrack Values Comma RBrack AndExpression And OrExpression Or ParenthesizedExpression As FilterName",
893
+ maxTerm: 39,
894
+ skippedNodes: [0],
895
+ repeatNodeCount: 1,
896
+ tokenData: "6p~RnXY#PYZ#P]^#Ppq#Pqr#brs#mxy$eyz$j|}$o!O!P$t!Q![%S!^!_%_!_!`%d!`!a%i!c!}%n!}#O&V#P#Q&[#R#S%n#T#U&a#U#X%n#X#Y(w#Y#Z+]#Z#]%n#]#^.]#^#c%n#c#d/e#d#g%n#g#h0m#h#i4[#i#o%n~#USo~XY#PYZ#P]^#Ppq#P~#eP!_!`#h~#mOU~~#pWOX#mZ]#m^r#mrs$Ys#O#m#P;'S#m;'S;=`$_<%lO#m~$_O[~~$bP;=`<%l#m~$jOv~~$oOw~~$tOc~~$wP!Q![$z~%PPZ~!Q![$z~%XQZ~!O!P$t!Q![%S~%dOW~~%iOT~~%nOV~P%sUsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%n~&[Oa~~&aOd~R&fYsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#b%n#b#c'U#c#g%n#g#h(^#h#o%nR'ZWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#W%n#W#X's#X#o%nR'zUfQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR(eUjQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR(|WsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#b%n#b#c)f#c#o%nR)kWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#W%n#W#X*T#X#o%nR*YWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#g%n#g#h*r#h#o%nR*yUYQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR+bVsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#U+w#U#o%nR+|WsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#`%n#`#a,f#a#o%nR,kWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#g%n#g#h-T#h#o%nR-YWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#X%n#X#Y-r#Y#o%nR-yU^QsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR.bWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#b%n#b#c.z#c#o%nR/RU`QsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR/jWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#f%n#f#g0S#g#o%nR0ZUhQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR0rWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#h%n#h#i1[#i#o%nR1aVsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#U1v#U#o%nR1{WsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#f%n#f#g2e#g#o%nR2jWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#h%n#h#i3S#i#o%nR3XWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#g%n#g#h3q#h#o%nR3xUXQsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%nR4aWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#f%n#f#g4y#g#o%nR5OWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#i%n#i#j5h#j#o%nR5mWsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#X%n#X#Y6V#Y#o%nR6^U]QsP}!O%n!O!P%n!Q![%n!c!}%n#R#S%n#T#o%n",
897
+ tokenizers: [0, 1],
898
+ topRules: { "Filter": [0, 1] },
899
+ tokenPrec: 0
900
+ });
901
+
902
+ // src/filter-input/filter-language-parser/FilterLanguage.ts
903
+ var filterLanguage = LRLanguage.define({
904
+ name: "VuuFilterQuery",
905
+ parser: parser.configure({
906
+ props: [
907
+ styleTags({
908
+ Identifier: tag.variableName,
909
+ String: tag.string,
910
+ Or: tag.emphasis,
911
+ Operator: tag.operator
912
+ })
913
+ ]
914
+ })
915
+ });
916
+ var filterLanguageSupport = () => {
917
+ return new LanguageSupport(filterLanguage);
918
+ };
919
+
920
+ // src/filter-input/filter-language-parser/FilterTreeWalker.ts
921
+ import {
922
+ isMultiClauseFilter as isMultiClauseFilter2,
923
+ isMultiValueFilter as isMultiValueFilter2,
924
+ isSingleValueFilter as isSingleValueFilter2
925
+ } from "@vuu-ui/vuu-utils";
926
+ var _filter;
927
+ var FilterExpression = class {
928
+ constructor() {
929
+ __privateAdd(this, _filter, void 0);
930
+ }
931
+ setFilterCombinatorOp(op, filter = __privateGet(this, _filter)) {
932
+ if (isMultiClauseFilter2(filter) && filter.op === op) {
933
+ return;
934
+ } else {
935
+ __privateSet(this, _filter, {
936
+ op,
937
+ filters: [__privateGet(this, _filter)]
938
+ });
939
+ }
940
+ }
941
+ add(filter) {
942
+ if (__privateGet(this, _filter) === void 0) {
943
+ __privateSet(this, _filter, filter);
944
+ } else if (isMultiClauseFilter2(__privateGet(this, _filter))) {
945
+ __privateGet(this, _filter).filters.push(filter);
946
+ } else {
947
+ throw Error(`Invalid filter passed to FilterExpression`);
948
+ }
949
+ }
950
+ setColumn(column, filter = __privateGet(this, _filter)) {
951
+ if (isMultiClauseFilter2(filter)) {
952
+ const target = filter.filters.at(-1);
953
+ if (target) {
954
+ this.setColumn(column, target);
955
+ }
956
+ } else if (filter) {
957
+ filter.column = column;
958
+ }
959
+ }
960
+ setOp(value, filter = __privateGet(this, _filter)) {
961
+ if (isMultiClauseFilter2(filter)) {
962
+ const target = filter.filters.at(-1);
963
+ if (target) {
964
+ this.setOp(value, target);
965
+ }
966
+ } else if (filter) {
967
+ filter.op = value;
968
+ }
969
+ }
970
+ setValue(value, filter = __privateGet(this, _filter)) {
971
+ var _a;
972
+ if (isMultiClauseFilter2(filter)) {
973
+ const target = filter.filters.at(-1);
974
+ if (target) {
975
+ this.setValue(value, target);
976
+ }
977
+ } else if (isMultiValueFilter2(filter)) {
978
+ (_a = filter.values) != null ? _a : filter.values = [];
979
+ filter.values.push(value);
980
+ } else if (isSingleValueFilter2(filter)) {
981
+ filter.value = value;
982
+ }
983
+ }
984
+ toJSON(filter = __privateGet(this, _filter)) {
985
+ if (this.name) {
986
+ return {
987
+ ...filter,
988
+ name: this.name
989
+ };
990
+ } else {
991
+ return filter;
992
+ }
993
+ }
994
+ };
995
+ _filter = new WeakMap();
996
+ var walkTree = (tree, source) => {
997
+ const filterExpression = new FilterExpression();
998
+ const cursor = tree.cursor();
999
+ do {
1000
+ const { name, from, to } = cursor;
1001
+ switch (name) {
1002
+ case "ColumnValueExpression":
1003
+ filterExpression.add({});
1004
+ break;
1005
+ case "ColumnSetExpression":
1006
+ filterExpression.add({ op: "in" });
1007
+ break;
1008
+ case "Or":
1009
+ case "And":
1010
+ filterExpression.setFilterCombinatorOp(source.substring(from, to));
1011
+ break;
1012
+ case "Column":
1013
+ filterExpression.setColumn(source.substring(from, to));
1014
+ break;
1015
+ case "Operator":
1016
+ filterExpression.setOp(source.substring(from, to));
1017
+ break;
1018
+ case "String":
1019
+ filterExpression.setValue(source.substring(from + 1, to - 1));
1020
+ break;
1021
+ case "Number":
1022
+ filterExpression.setValue(parseFloat(source.substring(from, to)));
1023
+ break;
1024
+ case "True":
1025
+ filterExpression.setValue(true);
1026
+ break;
1027
+ case "False":
1028
+ filterExpression.setValue(false);
1029
+ break;
1030
+ case "FilterName":
1031
+ filterExpression.name = source.substring(from, to);
1032
+ break;
1033
+ default:
1034
+ }
1035
+ } while (cursor.next());
1036
+ return filterExpression.toJSON();
1037
+ };
1038
+
1039
+ // src/filter-input/filter-language-parser/FilterParser.ts
1040
+ var strictParser = parser.configure({ strict: true });
1041
+ var parseFilter = (filterQuery) => {
1042
+ const parseTree = strictParser.parse(filterQuery);
1043
+ const filter = walkTree(parseTree, filterQuery);
1044
+ return filter;
1045
+ };
1046
+
1047
+ // src/filter-input/highlighting.ts
1048
+ import {
1049
+ HighlightStyle,
1050
+ syntaxHighlighting,
1051
+ tags
1052
+ } from "@vuu-ui/vuu-codemirror";
1053
+ var myHighlightStyle = HighlightStyle.define([
1054
+ { tag: tags.variableName, color: "var(--vuuFilterEditor-variableColor)" },
1055
+ { tag: tags.comment, color: "green", fontStyle: "italic" }
1056
+ ]);
1057
+ var vuuHighlighting = syntaxHighlighting(myHighlightStyle);
1058
+
1059
+ // src/filter-input/theme.ts
1060
+ import { EditorView } from "@vuu-ui/vuu-codemirror";
1061
+ var vuuTheme = EditorView.theme(
1062
+ {
1063
+ "&": {
1064
+ color: "var(--vuuFilterEditor-color)",
1065
+ backgroundColor: "var(--vuuFilterEditor-background)",
1066
+ fontSize: "var(--vuuFilterEditor-fontSize)"
1067
+ },
1068
+ ".cm-content": {
1069
+ caretColor: "var(--vuuFilterEditor-cursorColor)",
1070
+ padding: 0
1071
+ },
1072
+ ".cm-line": {
1073
+ lineHeight: "var(--vuuFilterEditor-lineHeight)"
1074
+ },
1075
+ "&.cm-focused .cm-cursor": {
1076
+ borderLeftColor: "var(--vuuFilterEditor-cursorColor)"
1077
+ },
1078
+ "&.cm-focused .cm-selectionBackground, ::selection": {
1079
+ backgroundColor: "var(--vuuFilterEditor-selectionBackground)"
1080
+ },
1081
+ ".cm-selectionBackground, ::selection": {
1082
+ backgroundColor: "var(--vuuFilterEditor-selectionBackground)"
1083
+ },
1084
+ ".cm-scroller": {
1085
+ fontFamily: "var(--vuuFilterEditor-fontFamily)"
1086
+ },
1087
+ ".cm-tooltip": {
1088
+ background: "var(--vuuFilterEditor-tooltipBackground)",
1089
+ border: "var(--vuuFilterEditor-tooltipBorder)",
1090
+ boxShadow: "var(--vuuFilterEditor-tooltipElevation)",
1091
+ "&.cm-tooltip-autocomplete > ul": {
1092
+ fontFamily: "var(--vuuFilterEditor-fontFamily)",
1093
+ fontSize: "var(--vuuFilterEditor-fontSize)",
1094
+ maxHeight: "240px"
1095
+ },
1096
+ "&.cm-tooltip-autocomplete > ul > li": {
1097
+ alignItems: "center",
1098
+ display: "flex",
1099
+ height: "var(--vuuFilterEditor-suggestion-height)",
1100
+ padding: "0 3px",
1101
+ lineHeight: "var(--vuuFilterEditor-suggestion-height)"
1102
+ },
1103
+ "&.cm-tooltip-autocomplete li[aria-selected]": {
1104
+ background: "var(--vuuFilterEditor-suggestion-selectedBackground)",
1105
+ color: "var(--vuuFilterEditor-suggestion-selectedColor)"
762
1106
  }
763
- ),
764
- /* @__PURE__ */ jsx("div", { className: `${classBase}-Editor`, ref: editorRef }),
765
- /* @__PURE__ */ jsx(
766
- Button,
767
- {
768
- className: `${classBase}-ClearButton`,
769
- "data-icon": "close-circle",
770
- onClick: clearInput
1107
+ },
1108
+ ".cm-completionIcon": {
1109
+ height: "18px",
1110
+ flex: "0 0 16px"
1111
+ },
1112
+ ".cm-completionLabel": {
1113
+ flex: "1 1 auto"
1114
+ },
1115
+ ".cm-completionIcon-filter": {
1116
+ position: "relative",
1117
+ "&:after": {
1118
+ background: "var(--salt-text-secondary-foreground)",
1119
+ content: "''",
1120
+ "-webkit-mask": "var(--svg-filter) center center/13px 13px",
1121
+ "-webkit-mask-repeat": "no-repeat",
1122
+ position: "absolute",
1123
+ height: "18px",
1124
+ left: "0px",
1125
+ top: "0px",
1126
+ width: "16px"
771
1127
  }
772
- )
773
- ] });
774
- };
1128
+ }
1129
+ },
1130
+ { dark: false }
1131
+ );
775
1132
 
776
- // src/filter-input/useFilterSuggestionProvider.ts
1133
+ // src/filter-input/useFilterAutoComplete.ts
777
1134
  import {
778
- asNameSuggestion,
779
- booleanJoinSuggestions,
780
- getNamePrompt,
781
- numericOperators,
782
- stringOperators,
783
- toSuggestions
1135
+ getNodeByName,
1136
+ getValue,
1137
+ syntaxTree
784
1138
  } from "@vuu-ui/vuu-codemirror";
785
- import { getTypeaheadParams, useTypeaheadSuggestions } from "@vuu-ui/vuu-data";
786
- import { useCallback as useCallback2, useRef as useRef2 } from "react";
787
-
788
- // src/filter-input/filterInfo.ts
789
- import { createEl } from "@vuu-ui/vuu-utils";
790
- var filterInfo = (filterName, filterQuery) => {
791
- const rootElement = createEl("div", "vuuFunctionDoc");
792
- const headingElement = createEl("div", "function-heading");
793
- const nameElement = createEl("span", "function-name", filterName);
794
- headingElement.appendChild(nameElement);
795
- const child2 = createEl("p", void 0, filterQuery);
796
- rootElement.appendChild(headingElement);
797
- rootElement.appendChild(child2);
798
- return rootElement;
1139
+ import { useCallback as useCallback3 } from "react";
1140
+ var getOperator = (node, state) => {
1141
+ let maybeColumnNode = node.prevSibling || node.parent;
1142
+ while (maybeColumnNode && !["Column", "Operator", "In"].includes(maybeColumnNode.name)) {
1143
+ maybeColumnNode = maybeColumnNode.prevSibling || maybeColumnNode.parent;
1144
+ }
1145
+ if ((maybeColumnNode == null ? void 0 : maybeColumnNode.name) === "In" || (maybeColumnNode == null ? void 0 : maybeColumnNode.name) === "Operator") {
1146
+ return getValue(maybeColumnNode, state);
1147
+ } else {
1148
+ return void 0;
1149
+ }
799
1150
  };
800
-
801
- // src/filter-input/useFilterSuggestionProvider.ts
802
- var NO_NAMED_FILTERS = [];
803
- var NONE = {};
804
- var saveAsTab = (onSubmit) => [
805
- {
806
- label: "Press ENTER to create TAB",
807
- apply: () => onSubmit("tab"),
808
- boost: 6
1151
+ var getPartialOperator = (maybeOperatorNode, state, columnName) => {
1152
+ const value = getValue(maybeOperatorNode, state);
1153
+ if (columnName === void 0 || value === columnName) {
1154
+ return;
1155
+ }
1156
+ if (["contains", "ends", "starts"].some(
1157
+ (val) => val.startsWith(value.toLowerCase())
1158
+ )) {
1159
+ return value;
1160
+ } else {
1161
+ return void 0;
809
1162
  }
810
- ];
811
- var makeSaveOrExtendSuggestions = (onSubmit, existingFilter, withJoinSuggestions = true) => {
812
- const result = existingFilter ? [
813
- {
814
- label: "REPLACE existing filter",
815
- apply: () => onSubmit("replace"),
816
- boost: 8
817
- },
818
- {
819
- label: "AND existing filter",
820
- apply: () => onSubmit("and"),
821
- boost: 7
822
- },
823
- {
824
- label: "OR existing filter",
825
- apply: () => onSubmit("or"),
826
- boost: 7
827
- }
828
- ] : [
829
- {
830
- label: "Press ENTER to submit",
831
- apply: () => onSubmit(),
832
- boost: 6
833
- }
834
- ];
835
- return withJoinSuggestions ? result.concat(booleanJoinSuggestions).concat(asNameSuggestion) : result;
836
1163
  };
837
- var promptToSaveOrExtend = (onSubmit, existingFilter) => makeSaveOrExtendSuggestions(onSubmit, existingFilter, true);
838
- var promptToSave = (onSubmit) => makeSaveOrExtendSuggestions(onSubmit, void 0);
839
- var getSaveSuggestions = ({
840
- existingFilter,
841
- filterName,
842
- onSubmit,
843
- saveOptions
844
- }) => {
845
- const includeTabSuggestion = filterName && saveOptions.allowSaveAsTab;
846
- const result = existingFilter ? promptToSaveOrExtend(onSubmit, existingFilter) : promptToSave(onSubmit);
847
- if (includeTabSuggestion) {
848
- return result.concat(saveAsTab(onSubmit));
1164
+ var getClauseOperator = (node, state) => {
1165
+ let maybeTargetNode = node.prevSibling || node.parent || node.lastChild;
1166
+ while (maybeTargetNode && maybeTargetNode.name === "\u26A0")
1167
+ maybeTargetNode = maybeTargetNode.prevSibling;
1168
+ if (maybeTargetNode && ["As", "Or", "And"].includes(maybeTargetNode.name)) {
1169
+ return getValue(maybeTargetNode, state);
849
1170
  } else {
850
- return result;
1171
+ return void 0;
851
1172
  }
852
1173
  };
853
- var suggestColumns = (columns) => columns.map((column) => ({
854
- boost: 5,
855
- label: column.name
856
- }));
857
- var suggestNamedFilters = (namedFilters) => namedFilters ? Array.from(namedFilters.entries()).map(([filterName, filterQuery]) => ({
858
- info: () => filterInfo(filterName, filterQuery),
859
- label: filterName,
860
- type: "filter"
861
- })) : NO_NAMED_FILTERS;
862
- var doneCommand = {
863
- label: "Done",
864
- apply: "] ",
865
- type: "keyword",
866
- boost: 10
1174
+ var getFilterName = (node, state) => {
1175
+ if (node.name === "FilterName") {
1176
+ return getValue(node, state);
1177
+ } else {
1178
+ let maybeTargetNode = node.prevSibling || node.parent || node.lastChild;
1179
+ while (maybeTargetNode && maybeTargetNode.name !== "FilterName")
1180
+ maybeTargetNode = maybeTargetNode.prevSibling;
1181
+ if (maybeTargetNode && maybeTargetNode.name === "FilterName") {
1182
+ return getValue(node, state);
1183
+ }
1184
+ }
867
1185
  };
868
- var withApplySpace = (suggestions, startsWith = "") => suggestions.filter((sugg) => startsWith === "" || sugg.label.startsWith(startsWith)).map((suggestion) => ({
869
- ...suggestion,
870
- apply: suggestion.label + " "
871
- }));
872
- var defaultSaveOptions = {
873
- allowReplace: true
1186
+ var getColumnName = (node, state) => {
1187
+ const prevNode = node.prevSibling;
1188
+ if ((prevNode == null ? void 0 : prevNode.name) === "Column") {
1189
+ return getValue(prevNode, state);
1190
+ } else if ((prevNode == null ? void 0 : prevNode.name) === "Operator") {
1191
+ return getColumnName(prevNode, state);
1192
+ }
874
1193
  };
875
- var useFilterSuggestionProvider = ({
876
- columns,
877
- namedFilters,
878
- saveOptions = defaultSaveOptions,
879
- table
880
- }) => {
881
- const latestSuggestionsRef = useRef2();
882
- const getTypeaheadSuggestions = useTypeaheadSuggestions();
883
- const getSuggestions = useCallback2(
884
- async (suggestionType, options = NONE) => {
885
- const {
886
- columnName,
887
- existingFilter,
888
- filterName,
889
- operator,
890
- quoted: autoQuoted,
891
- onSubmit,
892
- startsWith,
893
- selection
894
- } = options;
895
- switch (suggestionType) {
896
- case "operator":
1194
+ var getSetValues = (node, state) => {
1195
+ let maybeTargetNode = node.lastChild;
1196
+ const values = [];
1197
+ while (maybeTargetNode && maybeTargetNode.name !== "In") {
1198
+ const value = getValue(maybeTargetNode, state);
1199
+ if (value) {
1200
+ values.push(value);
1201
+ } else {
1202
+ break;
1203
+ }
1204
+ maybeTargetNode = maybeTargetNode.prevSibling;
1205
+ }
1206
+ return values;
1207
+ };
1208
+ var useAutoComplete = (suggestionProvider, onSubmit, existingFilter) => {
1209
+ const makeSuggestions = useCallback3(
1210
+ async (context, suggestionType, optionalArgs = {}) => {
1211
+ const { startsWith = "" } = optionalArgs;
1212
+ const options = await suggestionProvider.getSuggestions(
1213
+ suggestionType,
1214
+ optionalArgs
1215
+ );
1216
+ return { from: context.pos - startsWith.length, options };
1217
+ },
1218
+ [suggestionProvider]
1219
+ );
1220
+ return useCallback3(
1221
+ async (context) => {
1222
+ var _a, _b;
1223
+ const { state, pos } = context;
1224
+ const word = (_a = context.matchBefore(/\w*/)) != null ? _a : {
1225
+ from: 0,
1226
+ to: 0,
1227
+ text: void 0
1228
+ };
1229
+ const tree = syntaxTree(state);
1230
+ const nodeBefore = tree.resolveInner(pos, -1);
1231
+ console.log({ nodeBeforeName: nodeBefore.name });
1232
+ switch (nodeBefore.name) {
1233
+ case "Filter":
1234
+ if (context.pos === 0) {
1235
+ return makeSuggestions(context, "column");
1236
+ } else {
1237
+ const clauseOperator = getClauseOperator(nodeBefore, state);
1238
+ if (clauseOperator === "as") {
1239
+ return makeSuggestions(context, "name");
1240
+ } else {
1241
+ const filterName = getFilterName(nodeBefore, state);
1242
+ return makeSuggestions(context, "save", {
1243
+ onSubmit: onSubmit.current,
1244
+ existingFilter,
1245
+ filterName
1246
+ });
1247
+ }
1248
+ }
1249
+ case "String":
897
1250
  {
898
- const column = columns.find((col) => col.name === columnName);
899
- if (column) {
900
- switch (column.serverDataType) {
901
- case "string":
902
- case "char":
903
- return withApplySpace(stringOperators, startsWith);
904
- case "int":
905
- case "long":
906
- case "double":
907
- return withApplySpace(numericOperators);
1251
+ const operator = getOperator(nodeBefore, state);
1252
+ const columnName = getColumnName(nodeBefore, state);
1253
+ const { from, to } = nodeBefore;
1254
+ if (to - from === 2 && context.pos === from + 1) {
1255
+ if (columnName && operator) {
1256
+ return makeSuggestions(context, "columnValue", {
1257
+ columnName,
1258
+ operator,
1259
+ quoted: true,
1260
+ startsWith: word.text
1261
+ });
908
1262
  }
909
1263
  } else {
910
- console.warn(`'${columnName}' does not match any column name`);
1264
+ console.log(
1265
+ `we have a string, column is ${columnName} ${from} ${to}`
1266
+ );
911
1267
  }
912
1268
  }
913
1269
  break;
914
- case "column": {
915
- const columnSuggestions = await suggestColumns(columns);
916
- const filterSuggestions = await suggestNamedFilters(namedFilters);
917
- return (latestSuggestionsRef.current = withApplySpace(columnSuggestions)).concat(
918
- withApplySpace(filterSuggestions)
1270
+ case "As":
1271
+ return makeSuggestions(context, "name");
1272
+ case "FilterName":
1273
+ return makeSuggestions(context, "save", {
1274
+ onSubmit: onSubmit.current,
1275
+ existingFilter,
1276
+ filterName: getFilterName(nodeBefore, state)
1277
+ });
1278
+ case "Column": {
1279
+ const columnName = getValue(nodeBefore, state);
1280
+ const isPartialMatch = await suggestionProvider.isPartialMatch(
1281
+ "column",
1282
+ void 0,
1283
+ columnName
919
1284
  );
1285
+ if (isPartialMatch) {
1286
+ return makeSuggestions(context, "column", {
1287
+ startsWith: columnName
1288
+ });
1289
+ } else {
1290
+ return makeSuggestions(context, "operator", { columnName });
1291
+ }
920
1292
  }
921
- case "columnValue":
1293
+ case "\u26A0": {
1294
+ const columnName = getNodeByName(nodeBefore, state);
1295
+ const operator = getOperator(nodeBefore, state);
1296
+ const partialOperator = operator ? void 0 : getPartialOperator(nodeBefore, state, columnName);
1297
+ if (partialOperator) {
1298
+ return makeSuggestions(context, "operator", {
1299
+ columnName,
1300
+ startsWith: partialOperator
1301
+ });
1302
+ } else {
1303
+ return makeSuggestions(context, "columnValue", {
1304
+ columnName,
1305
+ operator,
1306
+ startsWith: word.text
1307
+ });
1308
+ }
1309
+ }
1310
+ case "Identifier":
922
1311
  {
923
- if (columnName) {
924
- const column = columns.find((col) => col.name === columnName);
925
- if (!column) {
926
- throw Error(
927
- `useFilterSUggestionProvider no column ${columnName}`
928
- );
929
- }
930
- const prefix = Array.isArray(selection) ? selection.length === 0 ? "[" : "," : "";
931
- const params = getTypeaheadParams(
932
- table,
933
- columnName,
934
- startsWith
935
- );
936
- const suggestions = await getTypeaheadSuggestions(params);
937
- const isIllustration = operator === "starts";
938
- latestSuggestionsRef.current = toSuggestions(suggestions, {
939
- moveCursorToEnd: autoQuoted,
940
- quoted: (column == null ? void 0 : column.serverDataType) === "string" && !autoQuoted,
941
- suffix: autoQuoted ? "" : " ",
942
- prefix: isIllustration ? startsWith : prefix,
943
- isIllustration
944
- });
945
- if (Array.isArray(selection) && (selection == null ? void 0 : selection.length) > 1) {
946
- return [doneCommand, ...latestSuggestionsRef.current];
947
- }
948
- return latestSuggestionsRef.current;
1312
+ const clauseOperator = getClauseOperator(nodeBefore, state);
1313
+ if (clauseOperator === "as") {
1314
+ return {
1315
+ from: context.pos,
1316
+ options: [
1317
+ {
1318
+ label: "press ENTER to apply filter and save",
1319
+ apply: () => onSubmit.current(),
1320
+ boost: 5
1321
+ }
1322
+ ]
1323
+ };
949
1324
  }
950
1325
  }
951
1326
  break;
952
- case "save": {
953
- if (typeof onSubmit !== "function") {
954
- throw Error(
955
- "useFilterSuggestionProvider, onSubmit must be supplied for 'save' suggestions"
956
- );
957
- }
958
- return await getSaveSuggestions({
959
- existingFilter,
960
- filterName,
961
- onSubmit,
962
- saveOptions
1327
+ case "ColumnSetExpression":
1328
+ case "Values": {
1329
+ const columnName = getNodeByName(nodeBefore, state);
1330
+ const selection = getSetValues(nodeBefore, state);
1331
+ return makeSuggestions(context, "columnValue", {
1332
+ columnName,
1333
+ selection
963
1334
  });
964
1335
  }
965
- case "name":
966
- return await getNamePrompt("filter");
967
- default:
968
- }
969
- return [];
970
- },
971
- [columns, getTypeaheadSuggestions, namedFilters, saveOptions, table]
972
- );
973
- const isPartialMatch = useCallback2(
974
- async (valueType, columnName, pattern) => {
975
- const suggestions = (
976
- // latestSuggestions && latestSuggestions.length > 0
977
- // ? latestSuggestions
978
- await getSuggestions(valueType, { columnName })
979
- );
980
- if (pattern && suggestions) {
981
- for (const option of suggestions) {
982
- if (option.label === pattern) {
983
- return false;
984
- } else if (option.label.startsWith(pattern)) {
985
- return true;
1336
+ case "Comma":
1337
+ case "LBrack": {
1338
+ const columnName = getNodeByName(nodeBefore, state);
1339
+ return makeSuggestions(context, "columnValue", { columnName });
1340
+ }
1341
+ case "ColumnValueExpression":
1342
+ {
1343
+ const lastToken = (_b = nodeBefore.lastChild) == null ? void 0 : _b.prevSibling;
1344
+ if ((lastToken == null ? void 0 : lastToken.name) === "Column") {
1345
+ return makeSuggestions(context, "operator", {
1346
+ columnName: getNodeByName(nodeBefore, state)
1347
+ });
1348
+ } else if ((lastToken == null ? void 0 : lastToken.name) === "Operator") {
1349
+ return makeSuggestions(context, "columnValue", {
1350
+ columnName: getNodeByName(lastToken, state),
1351
+ operator: getValue(lastToken, state)
1352
+ });
1353
+ }
986
1354
  }
1355
+ break;
1356
+ case "In": {
1357
+ return {
1358
+ from: context.pos,
1359
+ options: [{ label: "[", apply: " [", type: "text" }]
1360
+ };
987
1361
  }
988
- }
989
- return false;
990
- },
991
- [getSuggestions]
992
- );
993
- return {
994
- getSuggestions,
995
- isPartialMatch
996
- };
997
- };
998
-
999
- // src/filter-toolbar/FilterToolbar.tsx
1000
- var import_classnames2 = __toESM(require_classnames(), 1);
1001
- import { Toolbar } from "@heswell/salt-lab";
1002
-
1003
- // src/filter-toolbar/useFilterToolbar.tsx
1004
- import { ToggleButton, ToolbarField } from "@heswell/salt-lab";
1005
-
1006
- // src/filter-toolbar/FilterDropdown.tsx
1007
- import { Dropdown } from "@heswell/salt-lab";
1008
- import { useCallback as useCallback3, useState } from "react";
1009
- import { jsx as jsx2 } from "react/jsx-runtime";
1010
- var isString = (s) => typeof s === "string";
1011
- var stripQuotes = (selected) => {
1012
- if (isString(selected)) {
1013
- if (selected.startsWith('"') && selected.endsWith('"')) {
1014
- return selected.slice(1, -1);
1015
- } else {
1016
- return selected;
1017
- }
1018
- } else {
1019
- return selected.map(stripQuotes);
1020
- }
1021
- };
1022
- var FilterDropdown = ({
1023
- column,
1024
- selected: selectedProp,
1025
- suggestionProvider,
1026
- ...props
1027
- }) => {
1028
- const selected = selectedProp != null ? stripQuotes(selectedProp) : void 0;
1029
- const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
1030
- const [values, setValues] = useState(initialValues);
1031
- console.log({ initialValues });
1032
- const handleOpenChange = useCallback3(
1033
- async (isOpen) => {
1034
- if (isOpen) {
1035
- const values2 = await suggestionProvider.getSuggestions("columnValue", {
1036
- columnName: column
1037
- });
1038
- console.log({ values: values2 });
1039
- setValues(values2.map((suggestion) => suggestion.label));
1040
- }
1041
- },
1042
- [column, suggestionProvider]
1043
- );
1044
- return /* @__PURE__ */ jsx2(
1045
- Dropdown,
1046
- {
1047
- ...props,
1048
- onOpenChange: handleOpenChange,
1049
- selected,
1050
- source: values
1051
- }
1362
+ case "Eq": {
1363
+ return makeSuggestions(context, "columnValue", {
1364
+ columnName: getNodeByName(nodeBefore, state)
1365
+ });
1366
+ }
1367
+ case "AndExpression":
1368
+ case "OrExpression": {
1369
+ return makeSuggestions(context, "column");
1370
+ }
1371
+ default:
1372
+ }
1373
+ },
1374
+ [existingFilter, makeSuggestions, onSubmit, suggestionProvider]
1052
1375
  );
1053
1376
  };
1054
1377
 
1055
- // src/filter-toolbar/FilterDropdownMultiSelect.tsx
1056
- import { Dropdown as Dropdown2 } from "@heswell/salt-lab";
1057
- import { useCallback as useCallback4, useState as useState2 } from "react";
1058
- import { jsx as jsx3 } from "react/jsx-runtime";
1059
- var isString2 = (s) => typeof s === "string";
1060
- var stripQuotes2 = (selected) => {
1061
- if (selected === void 0) {
1062
- return void 0;
1063
- } else if (isString2(selected)) {
1064
- if (selected.startsWith('"') && selected.endsWith('"')) {
1065
- return selected.slice(1, -1);
1066
- } else {
1067
- return selected;
1068
- }
1378
+ // src/filter-input/useCodeMirrorEditor.ts
1379
+ var getView = (ref) => {
1380
+ if (ref.current == void 0) {
1381
+ throw Error("EditorView not defined");
1382
+ }
1383
+ return ref.current;
1384
+ };
1385
+ var getOptionClass = (completion) => {
1386
+ return (0, import_classnames.default)("vuuSuggestion", {
1387
+ vuuIllustration: completion.isIllustration
1388
+ });
1389
+ };
1390
+ var stripName = (filterQuery) => {
1391
+ const pos = filterQuery.lastIndexOf(" as ");
1392
+ if (pos !== -1) {
1393
+ return filterQuery.slice(0, pos);
1069
1394
  } else {
1070
- return selected.map(stripQuotes2);
1395
+ return filterQuery;
1071
1396
  }
1072
1397
  };
1073
- var FilterDropdownMultiSelect = ({
1074
- column,
1075
- selected: selectedProp,
1076
- suggestionProvider,
1077
- ...props
1398
+ var noop = () => console.log("noooop");
1399
+ var useCodeMirrorEditor = ({
1400
+ existingFilter,
1401
+ onSubmitFilter,
1402
+ suggestionProvider
1078
1403
  }) => {
1079
- const selected = stripQuotes2(selectedProp);
1080
- const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
1081
- const [values, setValues] = useState2(initialValues);
1082
- const handleOpenChange = useCallback4(
1083
- async (isOpen) => {
1084
- if (isOpen) {
1085
- const values2 = await suggestionProvider.getSuggestions("columnValue", {
1086
- columnName: column
1087
- });
1088
- console.log({ values: values2 });
1089
- setValues(values2.map((suggestion) => suggestion.label));
1090
- }
1091
- },
1092
- [column, suggestionProvider]
1404
+ const editorRef = useRef(null);
1405
+ const onSubmit = useRef(noop);
1406
+ const viewRef = useRef();
1407
+ const completionFn = useAutoComplete(
1408
+ suggestionProvider,
1409
+ onSubmit,
1410
+ existingFilter
1093
1411
  );
1094
- return /* @__PURE__ */ jsx3(
1095
- Dropdown2,
1096
- {
1097
- ...props,
1098
- onOpenChange: handleOpenChange,
1099
- selected,
1100
- selectionStrategy: "multiple",
1101
- source: values
1412
+ const [createState, clearInput] = useMemo(() => {
1413
+ const parseFilter2 = () => {
1414
+ const view = getView(viewRef);
1415
+ const source = view.state.doc.toString();
1416
+ const tree = ensureSyntaxTree(view.state, view.state.doc.length, 5e3);
1417
+ if (tree) {
1418
+ const filter = walkTree(tree, source);
1419
+ return [filter, stripName(source), filter.name];
1420
+ } else {
1421
+ return [void 0, "", void 0];
1422
+ }
1423
+ };
1424
+ const clearInput2 = () => {
1425
+ getView(viewRef).setState(createState2());
1426
+ };
1427
+ const submitFilterAndClearInput = (mode) => {
1428
+ const [filter, filterQuery, filterName] = parseFilter2();
1429
+ onSubmitFilter == null ? void 0 : onSubmitFilter(filter, filterQuery, mode, filterName);
1430
+ clearInput2();
1431
+ };
1432
+ const submitFilter = (key) => {
1433
+ return keymap.of([
1434
+ {
1435
+ key,
1436
+ run() {
1437
+ submitFilterAndClearInput();
1438
+ return true;
1439
+ }
1440
+ }
1441
+ ]);
1442
+ };
1443
+ const showSuggestions = (key) => {
1444
+ return keymap.of([
1445
+ {
1446
+ key,
1447
+ run() {
1448
+ startCompletion(getView(viewRef));
1449
+ return true;
1450
+ }
1451
+ }
1452
+ ]);
1453
+ };
1454
+ const createState2 = () => EditorState2.create({
1455
+ doc: "",
1456
+ extensions: [
1457
+ minimalSetup,
1458
+ autocompletion({
1459
+ override: [completionFn],
1460
+ optionClass: getOptionClass
1461
+ }),
1462
+ filterLanguageSupport(),
1463
+ keymap.of(defaultKeymap),
1464
+ submitFilter("Ctrl-Enter"),
1465
+ showSuggestions("ArrowDown"),
1466
+ EditorView2.updateListener.of((v) => {
1467
+ const view = getView(viewRef);
1468
+ if (v.docChanged) {
1469
+ startCompletion(view);
1470
+ }
1471
+ }),
1472
+ EditorState2.transactionFilter.of(
1473
+ (tr) => tr.newDoc.lines > 1 ? [] : tr
1474
+ ),
1475
+ vuuTheme,
1476
+ vuuHighlighting
1477
+ ]
1478
+ });
1479
+ onSubmit.current = (mode) => {
1480
+ submitFilterAndClearInput(mode);
1481
+ setTimeout(() => {
1482
+ getView(viewRef).focus();
1483
+ }, 100);
1484
+ };
1485
+ return [createState2, clearInput2];
1486
+ }, [completionFn, onSubmitFilter]);
1487
+ useEffect2(() => {
1488
+ if (!editorRef.current) {
1489
+ throw Error("editor not in dom");
1102
1490
  }
1103
- );
1491
+ viewRef.current = new EditorView2({
1492
+ state: createState(),
1493
+ parent: editorRef.current
1494
+ });
1495
+ return () => {
1496
+ var _a;
1497
+ (_a = viewRef.current) == null ? void 0 : _a.destroy();
1498
+ };
1499
+ }, [completionFn, createState]);
1500
+ return { editorRef, clearInput };
1104
1501
  };
1105
1502
 
1106
- // src/filter-toolbar/useFilterToolbar.tsx
1107
- import { jsx as jsx4 } from "react/jsx-runtime";
1108
- var filterToControl = (filter, suggestionProvider) => {
1109
- if (isNamedFilter(filter)) {
1110
- return /* @__PURE__ */ jsx4(
1111
- ToggleButton,
1503
+ // src/filter-input/FilterInput.tsx
1504
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1505
+ var classBase = "vuuFilterInput";
1506
+ var FilterInput = ({
1507
+ existingFilter,
1508
+ iconName = "filter",
1509
+ namedFilters,
1510
+ onSubmitFilter,
1511
+ suggestionProvider,
1512
+ ...props
1513
+ }) => {
1514
+ const { editorRef, clearInput } = useCodeMirrorEditor({
1515
+ existingFilter,
1516
+ onSubmitFilter,
1517
+ suggestionProvider
1518
+ });
1519
+ return /* @__PURE__ */ jsxs4("div", { ...props, className: classBase, children: [
1520
+ /* @__PURE__ */ jsx7(
1521
+ Button,
1112
1522
  {
1113
- className: "vuuToggleButton",
1114
- toggled: true,
1115
- variant: "secondary",
1116
- children: filter.name
1523
+ className: `${classBase}-FilterButton`,
1524
+ "data-icon": iconName,
1525
+ tabIndex: -1
1117
1526
  }
1118
- );
1119
- }
1120
- if (isSingleValueFilter2(filter)) {
1121
- const { column, value } = filter;
1122
- return /* @__PURE__ */ jsx4(
1123
- ToolbarField,
1124
- {
1125
- className: "vuuFilterDropdown",
1126
- label: column,
1127
- labelPlacement: "top",
1128
- children: /* @__PURE__ */ jsx4(
1129
- FilterDropdown,
1130
- {
1131
- column,
1132
- selected: value.toString(),
1133
- selectionStrategy: "default",
1134
- source: [value.toString()],
1135
- suggestionProvider,
1136
- style: { width: 100 }
1137
- }
1138
- )
1139
- },
1140
- column
1141
- );
1142
- }
1143
- if (isMultiValueFilter2(filter)) {
1144
- const values = filter.values.map((v) => v.toString());
1145
- return /* @__PURE__ */ jsx4(
1146
- ToolbarField,
1527
+ ),
1528
+ /* @__PURE__ */ jsx7("div", { className: `${classBase}-Editor`, ref: editorRef }),
1529
+ /* @__PURE__ */ jsx7(
1530
+ Button,
1147
1531
  {
1148
- className: "vuuFilterDropdown",
1149
- label: filter.column,
1150
- labelPlacement: "top",
1151
- children: /* @__PURE__ */ jsx4(
1152
- FilterDropdownMultiSelect,
1153
- {
1154
- column: filter.column,
1155
- selected: values,
1156
- source: values,
1157
- suggestionProvider,
1158
- style: { width: 100 }
1159
- }
1160
- )
1161
- },
1162
- filter.column
1163
- );
1164
- }
1165
- return filter.filters.map(
1166
- (filter2) => filterToControl(filter2, suggestionProvider)
1167
- );
1168
- };
1169
- var useFilterToolbar = ({
1170
- filter,
1171
- suggestionProvider
1172
- }) => {
1173
- if (filter) {
1174
- return filterToControl(filter, suggestionProvider);
1175
- }
1176
- return [];
1532
+ className: `${classBase}-ClearButton`,
1533
+ "data-icon": "close-circle",
1534
+ onClick: clearInput
1535
+ }
1536
+ )
1537
+ ] });
1177
1538
  };
1178
1539
 
1179
- // src/filter-toolbar/FilterToolbar.tsx
1180
- import { jsx as jsx5 } from "react/jsx-runtime";
1181
- var FilterToolbar = ({
1182
- className,
1183
- filter,
1184
- suggestionProvider,
1185
- ...props
1186
- }) => {
1187
- console.log(`FilterToolbar ${JSON.stringify(filter, null, 2)}`);
1188
- const toolbarItems = useFilterToolbar({ filter, suggestionProvider });
1189
- return /* @__PURE__ */ jsx5(Toolbar, { className: (0, import_classnames2.default)("vuuFilterToolbar", className), ...props, children: toolbarItems });
1540
+ // src/filter-input/useFilterSuggestionProvider.ts
1541
+ import {
1542
+ asNameSuggestion,
1543
+ booleanJoinSuggestions,
1544
+ getNamePrompt,
1545
+ numericOperators,
1546
+ stringOperators,
1547
+ toSuggestions
1548
+ } from "@vuu-ui/vuu-codemirror";
1549
+ import {
1550
+ getTypeaheadParams,
1551
+ useTypeaheadSuggestions as useTypeaheadSuggestions2
1552
+ } from "@vuu-ui/vuu-data";
1553
+ import { useCallback as useCallback4, useRef as useRef2 } from "react";
1554
+
1555
+ // src/filter-input/filterInfo.ts
1556
+ import { createEl } from "@vuu-ui/vuu-utils";
1557
+ var filterInfo = (filterName, filterQuery) => {
1558
+ const rootElement = createEl("div", "vuuFunctionDoc");
1559
+ const headingElement = createEl("div", "function-heading");
1560
+ const nameElement = createEl("span", "function-name", filterName);
1561
+ headingElement.appendChild(nameElement);
1562
+ const child2 = createEl("p", void 0, filterQuery);
1563
+ rootElement.appendChild(headingElement);
1564
+ rootElement.appendChild(child2);
1565
+ return rootElement;
1190
1566
  };
1191
1567
 
1192
- // src/filter-evaluation-utils.ts
1193
- var filterPredicateMap = /* @__PURE__ */ new Map();
1194
- var filterReject = () => false;
1195
- var getFilterPredicate = (columnMap, filterQuery) => {
1196
- let predicate = filterPredicateMap.get(filterQuery);
1197
- if (predicate) {
1198
- return predicate;
1199
- }
1200
- try {
1201
- const filter = parseFilter(filterQuery);
1202
- predicate = filterPredicate(columnMap, filter);
1203
- filterPredicateMap.set(filterQuery, predicate);
1204
- return predicate;
1205
- } catch (err) {
1206
- console.warn(
1207
- `filter-evaluation-utils, failed to parse filter "${filterQuery}"`
1208
- );
1209
- return filterReject;
1568
+ // src/filter-input/useFilterSuggestionProvider.ts
1569
+ var NO_NAMED_FILTERS = [];
1570
+ var NONE = {};
1571
+ var saveAsTab = (onSubmit) => [
1572
+ {
1573
+ label: "Press ENTER to create TAB",
1574
+ apply: () => onSubmit("tab"),
1575
+ boost: 6
1210
1576
  }
1577
+ ];
1578
+ var makeSaveOrExtendSuggestions = (onSubmit, existingFilter, withJoinSuggestions = true) => {
1579
+ const result = existingFilter ? [
1580
+ {
1581
+ label: "REPLACE existing filter",
1582
+ apply: () => onSubmit("replace"),
1583
+ boost: 8
1584
+ },
1585
+ {
1586
+ label: "AND existing filter",
1587
+ apply: () => onSubmit("and"),
1588
+ boost: 7
1589
+ },
1590
+ {
1591
+ label: "OR existing filter",
1592
+ apply: () => onSubmit("or"),
1593
+ boost: 7
1594
+ }
1595
+ ] : [
1596
+ {
1597
+ label: "Press ENTER to submit",
1598
+ apply: () => onSubmit(),
1599
+ boost: 6
1600
+ }
1601
+ ];
1602
+ return withJoinSuggestions ? result.concat(booleanJoinSuggestions).concat(asNameSuggestion) : result;
1211
1603
  };
1212
- function filterPredicate(columnMap, filter) {
1213
- switch (filter.op) {
1214
- case "in":
1215
- return testInclude(columnMap, filter);
1216
- case "=":
1217
- return testEQ(columnMap, filter);
1218
- case ">":
1219
- return testGT(columnMap, filter);
1220
- case ">=":
1221
- return testGE(columnMap, filter);
1222
- case "<":
1223
- return testLT(columnMap, filter);
1224
- case "<=":
1225
- return testLE(columnMap, filter);
1226
- case "starts":
1227
- return testSW(columnMap, filter);
1228
- case "and":
1229
- return testAND(columnMap, filter);
1230
- case "or":
1231
- return testOR(columnMap, filter);
1232
- default:
1233
- console.log(`unrecognized filter type ${filter.op}`);
1234
- return () => true;
1604
+ var promptToSaveOrExtend = (onSubmit, existingFilter) => makeSaveOrExtendSuggestions(onSubmit, existingFilter, true);
1605
+ var promptToSave = (onSubmit) => makeSaveOrExtendSuggestions(onSubmit, void 0);
1606
+ var getSaveSuggestions = ({
1607
+ existingFilter,
1608
+ filterName,
1609
+ onSubmit,
1610
+ saveOptions
1611
+ }) => {
1612
+ const includeTabSuggestion = filterName && saveOptions.allowSaveAsTab;
1613
+ const result = existingFilter ? promptToSaveOrExtend(onSubmit, existingFilter) : promptToSave(onSubmit);
1614
+ if (includeTabSuggestion) {
1615
+ return result.concat(saveAsTab(onSubmit));
1616
+ } else {
1617
+ return result;
1235
1618
  }
1236
- }
1237
- var testInclude = (columnMap, filter) => {
1238
- return (row) => filter.values.indexOf(row[columnMap[filter.column]]) !== -1;
1239
- };
1240
- var testEQ = (columnMap, filter) => {
1241
- return (row) => row[columnMap[filter.column]] === filter.value;
1242
- };
1243
- var testGT = (columnMap, filter) => {
1244
- return (row) => row[columnMap[filter.column]] > filter.value;
1245
- };
1246
- var testGE = (columnMap, filter) => {
1247
- return (row) => row[columnMap[filter.column]] >= filter.value;
1248
1619
  };
1249
- var testLT = (columnMap, filter) => {
1250
- return (row) => row[columnMap[filter.column]] < filter.value;
1620
+ var suggestColumns = (columns) => columns.map((column) => ({
1621
+ boost: 5,
1622
+ label: column.name
1623
+ }));
1624
+ var suggestNamedFilters = (namedFilters) => namedFilters ? Array.from(namedFilters.entries()).map(([filterName, filterQuery]) => ({
1625
+ info: () => filterInfo(filterName, filterQuery),
1626
+ label: filterName,
1627
+ type: "filter"
1628
+ })) : NO_NAMED_FILTERS;
1629
+ var doneCommand = {
1630
+ label: "Done",
1631
+ apply: "] ",
1632
+ type: "keyword",
1633
+ boost: 10
1251
1634
  };
1252
- var testLE = (columnMap, filter) => {
1253
- return (row) => row[columnMap[filter.column]] <= filter.value;
1635
+ var withApplySpace = (suggestions, startsWith = "") => suggestions.filter((sugg) => startsWith === "" || sugg.label.startsWith(startsWith)).map((suggestion) => ({
1636
+ ...suggestion,
1637
+ apply: suggestion.label + " "
1638
+ }));
1639
+ var defaultSaveOptions = {
1640
+ allowReplace: true
1254
1641
  };
1255
- var testSW = (columnMap, filter) => {
1256
- const filterValue2 = filter.value;
1257
- if (typeof filterValue2 !== "string") {
1258
- throw Error("string filter applied to value of wrong type");
1259
- }
1260
- return (row) => {
1261
- const rowValue = row[columnMap[filter.column]];
1262
- if (typeof rowValue !== "string") {
1263
- throw Error("string filter applied to value of wrong type");
1264
- }
1265
- return rowValue.toLowerCase().startsWith(filterValue2.toLowerCase());
1642
+ var useFilterSuggestionProvider = ({
1643
+ columns,
1644
+ namedFilters,
1645
+ saveOptions = defaultSaveOptions,
1646
+ table,
1647
+ typeaheadHook: useTypeahead = useTypeaheadSuggestions2
1648
+ }) => {
1649
+ const latestSuggestionsRef = useRef2();
1650
+ const getTypeaheadSuggestions = useTypeahead();
1651
+ const getSuggestions = useCallback4(
1652
+ async (suggestionType, options = NONE) => {
1653
+ const {
1654
+ columnName,
1655
+ existingFilter,
1656
+ filterName,
1657
+ operator,
1658
+ quoted: autoQuoted,
1659
+ onSubmit,
1660
+ startsWith,
1661
+ selection
1662
+ } = options;
1663
+ switch (suggestionType) {
1664
+ case "operator":
1665
+ {
1666
+ const column = columns.find((col) => col.name === columnName);
1667
+ if (column) {
1668
+ switch (column.serverDataType) {
1669
+ case "string":
1670
+ case "char":
1671
+ return withApplySpace(stringOperators, startsWith);
1672
+ case "int":
1673
+ case "long":
1674
+ case "double":
1675
+ return withApplySpace(numericOperators);
1676
+ }
1677
+ } else {
1678
+ console.warn(`'${columnName}' does not match any column name`);
1679
+ }
1680
+ }
1681
+ break;
1682
+ case "column": {
1683
+ const columnSuggestions = await suggestColumns(columns);
1684
+ const filterSuggestions = await suggestNamedFilters(namedFilters);
1685
+ return (latestSuggestionsRef.current = withApplySpace(columnSuggestions)).concat(
1686
+ withApplySpace(filterSuggestions)
1687
+ );
1688
+ }
1689
+ case "columnValue":
1690
+ {
1691
+ if (columnName) {
1692
+ const column = columns.find((col) => col.name === columnName);
1693
+ if (!column) {
1694
+ throw Error(
1695
+ `useFilterSUggestionProvider no column ${columnName}`
1696
+ );
1697
+ }
1698
+ const prefix = Array.isArray(selection) ? selection.length === 0 ? "[" : "," : "";
1699
+ const params = getTypeaheadParams(
1700
+ table,
1701
+ columnName,
1702
+ startsWith
1703
+ );
1704
+ const suggestions = await getTypeaheadSuggestions(params);
1705
+ const isIllustration = operator === "starts";
1706
+ latestSuggestionsRef.current = toSuggestions(suggestions, {
1707
+ moveCursorToEnd: autoQuoted,
1708
+ quoted: (column == null ? void 0 : column.serverDataType) === "string" && !autoQuoted,
1709
+ suffix: autoQuoted ? "" : " ",
1710
+ prefix: isIllustration ? startsWith : prefix,
1711
+ isIllustration
1712
+ });
1713
+ if (Array.isArray(selection) && (selection == null ? void 0 : selection.length) > 1) {
1714
+ return [doneCommand, ...latestSuggestionsRef.current];
1715
+ }
1716
+ return latestSuggestionsRef.current;
1717
+ }
1718
+ }
1719
+ break;
1720
+ case "save": {
1721
+ if (typeof onSubmit !== "function") {
1722
+ throw Error(
1723
+ "useFilterSuggestionProvider, onSubmit must be supplied for 'save' suggestions"
1724
+ );
1725
+ }
1726
+ return await getSaveSuggestions({
1727
+ existingFilter,
1728
+ filterName,
1729
+ onSubmit,
1730
+ saveOptions
1731
+ });
1732
+ }
1733
+ case "name":
1734
+ return await getNamePrompt("filter");
1735
+ default:
1736
+ }
1737
+ return [];
1738
+ },
1739
+ [columns, getTypeaheadSuggestions, namedFilters, saveOptions, table]
1740
+ );
1741
+ const isPartialMatch = useCallback4(
1742
+ async (valueType, columnName, pattern) => {
1743
+ const suggestions = (
1744
+ // latestSuggestions && latestSuggestions.length > 0
1745
+ // ? latestSuggestions
1746
+ await getSuggestions(valueType, { columnName })
1747
+ );
1748
+ if (pattern && suggestions) {
1749
+ for (const option of suggestions) {
1750
+ if (option.label === pattern) {
1751
+ return false;
1752
+ } else if (option.label.startsWith(pattern)) {
1753
+ return true;
1754
+ }
1755
+ }
1756
+ }
1757
+ return false;
1758
+ },
1759
+ [getSuggestions]
1760
+ );
1761
+ return {
1762
+ getSuggestions,
1763
+ isPartialMatch
1266
1764
  };
1267
1765
  };
1268
- var testAND = (columnMap, filter) => {
1269
- const filters = filter.filters.map((f1) => filterPredicate(columnMap, f1));
1270
- return (row) => filters.every((fn) => fn(row));
1271
- };
1272
- function testOR(columnMap, filter) {
1273
- const filters = filter.filters.map((f1) => filterPredicate(columnMap, f1));
1274
- return (row) => filters.some((fn) => fn(row));
1275
- }
1276
1766
 
1277
- // src/filter-utils.ts
1278
- import { extractFilterForColumn, partition } from "@vuu-ui/vuu-utils";
1767
+ // src/filter-toolbar/FilterToolbar.tsx
1768
+ var import_classnames2 = __toESM(require_classnames(), 1);
1769
+ import { Toolbar as Toolbar2 } from "@heswell/salt-lab";
1279
1770
 
1280
- // src/filterTypes.ts
1281
- var singleValueFilterOps = /* @__PURE__ */ new Set([
1282
- "=",
1283
- "!=",
1284
- ">",
1285
- ">=",
1286
- "<",
1287
- "<=",
1288
- "starts",
1289
- "ends"
1290
- ]);
1291
- var isNamedFilter = (f) => f !== void 0 && f.name !== void 0;
1292
- var isSingleValueFilter2 = (f) => f !== void 0 && singleValueFilterOps.has(f.op);
1293
- var isFilterClause = (f) => f !== void 0 && (isSingleValueFilter2(f) || isMultiValueFilter2(f));
1294
- var isMultiValueFilter2 = (f) => f !== void 0 && f.op === "in";
1295
- var isInFilter = (f) => f.op === "in";
1296
- var isAndFilter = (f) => f.op === "and";
1297
- var isOrFilter = (f) => f.op === "or";
1298
- function isMultiClauseFilter2(f) {
1299
- return f !== void 0 && (f.op === "and" || f.op === "or");
1300
- }
1771
+ // src/filter-toolbar/useFilterToolbar.tsx
1772
+ import { ToggleButton, ToolbarField as ToolbarField3 } from "@heswell/salt-lab";
1301
1773
 
1302
- // src/filter-utils.ts
1303
- var AND = "and";
1304
- var EQUALS = "=";
1305
- var GREATER_THAN = ">";
1306
- var LESS_THAN = "<";
1307
- var OR = "or";
1308
- var STARTS_WITH = "starts";
1309
- var ENDS_WITH = "ends";
1310
- var IN = "in";
1311
- var filterClauses = (filter, clauses = []) => {
1312
- if (filter) {
1313
- if (isMultiClauseFilter2(filter)) {
1314
- filter.filters.forEach((f) => clauses.push(...filterClauses(f)));
1774
+ // src/filter-toolbar/FilterDropdown.tsx
1775
+ import { Dropdown as Dropdown2 } from "@heswell/salt-lab";
1776
+ import { useCallback as useCallback5, useState as useState3 } from "react";
1777
+ import { jsx as jsx8 } from "react/jsx-runtime";
1778
+ var isString = (s) => typeof s === "string";
1779
+ var stripQuotes = (selected) => {
1780
+ if (isString(selected)) {
1781
+ if (selected.startsWith('"') && selected.endsWith('"')) {
1782
+ return selected.slice(1, -1);
1315
1783
  } else {
1316
- clauses.push(filter);
1784
+ return selected;
1317
1785
  }
1786
+ } else {
1787
+ return selected.map(stripQuotes);
1318
1788
  }
1319
- return clauses;
1320
1789
  };
1321
- var DEFAULT_ADD_FILTER_OPTS = {
1322
- combineWith: "and"
1790
+ var FilterDropdown = ({
1791
+ column,
1792
+ selected: selectedProp,
1793
+ suggestionProvider,
1794
+ ...props
1795
+ }) => {
1796
+ const selected = selectedProp != null ? stripQuotes(selectedProp) : void 0;
1797
+ const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
1798
+ const [values, setValues] = useState3(initialValues);
1799
+ console.log({ initialValues });
1800
+ const handleOpenChange = useCallback5(
1801
+ async (isOpen) => {
1802
+ if (isOpen) {
1803
+ const values2 = await suggestionProvider.getSuggestions("columnValue", {
1804
+ columnName: column
1805
+ });
1806
+ console.log({ values: values2 });
1807
+ setValues(values2.map((suggestion) => suggestion.label));
1808
+ }
1809
+ },
1810
+ [column, suggestionProvider]
1811
+ );
1812
+ return /* @__PURE__ */ jsx8(
1813
+ Dropdown2,
1814
+ {
1815
+ ...props,
1816
+ onOpenChange: handleOpenChange,
1817
+ selected,
1818
+ source: values
1819
+ }
1820
+ );
1323
1821
  };
1324
- var addFilter = (existingFilter, filter, { combineWith = AND } = DEFAULT_ADD_FILTER_OPTS) => {
1325
- var _a;
1326
- if (includesNoValues(filter)) {
1327
- if (isMultiClauseFilter2(filter)) {
1822
+
1823
+ // src/filter-toolbar/FilterDropdownMultiSelect.tsx
1824
+ import { Dropdown as Dropdown3 } from "@heswell/salt-lab";
1825
+ import { useCallback as useCallback6, useState as useState4 } from "react";
1826
+ import { jsx as jsx9 } from "react/jsx-runtime";
1827
+ var isString2 = (s) => typeof s === "string";
1828
+ var stripQuotes2 = (selected) => {
1829
+ if (selected === void 0) {
1830
+ return void 0;
1831
+ } else if (isString2(selected)) {
1832
+ if (selected.startsWith('"') && selected.endsWith('"')) {
1833
+ return selected.slice(1, -1);
1328
1834
  } else {
1329
- existingFilter = removeFilterForColumn(existingFilter, {
1330
- name: filter.column
1331
- });
1332
- }
1333
- } else if (includesAllValues(filter)) {
1334
- if (isMultiClauseFilter2(filter)) {
1835
+ return selected;
1335
1836
  }
1336
- return removeFilterForColumn(existingFilter, { name: (_a = filter.column) != null ? _a : "" });
1337
- }
1338
- if (!existingFilter) {
1339
- return filter;
1340
- }
1341
- if (!filter) {
1342
- return existingFilter;
1343
- }
1344
- if (existingFilter.op === AND && filter.op === AND) {
1345
- return {
1346
- op: AND,
1347
- filters: combine(existingFilter.filters, filter.filters)
1348
- };
1349
- }
1350
- if (existingFilter.op === AND) {
1351
- const filters = replaceOrInsert(existingFilter.filters, filter);
1352
- return filters.length > 1 ? { op: AND, filters } : filters[0];
1353
- }
1354
- if (filter.op === AND) {
1355
- return { op: AND, filters: filter.filters.concat(existingFilter) };
1356
- }
1357
- if (filterEquals(existingFilter, filter, true)) {
1358
- return filter;
1359
- }
1360
- if (canMerge(existingFilter, filter)) {
1361
- return merge(existingFilter, filter);
1362
- }
1363
- return { op: combineWith, filters: [existingFilter, filter] };
1364
- };
1365
- var includesNoValues = (filter) => {
1366
- if (!filter) {
1367
- return false;
1368
- }
1369
- if (isInFilter(filter) && filter.values.length === 0) {
1370
- return true;
1371
- }
1372
- return isAndFilter(filter) && filter.filters.some((f) => includesNoValues(f));
1373
- };
1374
- var filterValue = (value) => typeof value === "string" ? `"${value}"` : value;
1375
- var filterAsQuery = (f) => {
1376
- if (isMultiClauseFilter2(f)) {
1377
- return f.filters.map((filter) => filterAsQuery(filter)).join(` ${f.op} `);
1378
- } else if (isMultiValueFilter2(f)) {
1379
- return `${f.column} ${f.op} [${f.values.join(",")}]`;
1380
1837
  } else {
1381
- return `${f.column} ${f.op} ${filterValue(f.value)}`;
1382
- }
1383
- };
1384
- var includesAllValues = (filter) => {
1385
- if (!filter) {
1386
- return false;
1387
- }
1388
- if (filter.op === STARTS_WITH && filter.value === "") {
1389
- return true;
1390
- }
1391
- return filter.op === STARTS_WITH && filter.value === "";
1392
- };
1393
- var replaceOrInsert = (filters, filter) => {
1394
- return filters.concat(filter);
1395
- };
1396
- var merge = (f1, f2) => {
1397
- if (includesNoValues(f2)) {
1398
- return f2;
1399
- }
1400
- if (isInFilter(f1) && isInFilter(f2)) {
1401
- return {
1402
- ...f1,
1403
- values: [
1404
- ...f1.values,
1405
- ...f2.values.filter(
1406
- (v) => !f1.values.includes(v)
1407
- )
1408
- ]
1409
- };
1410
- } else if (isInFilter(f1) && f2.op === EQUALS) {
1411
- return {
1412
- ...f1,
1413
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1414
- // @ts-ignore
1415
- values: f1.values.concat([f2.value])
1416
- };
1417
- } else if (f1.op === EQUALS && f2.op === EQUALS) {
1418
- return {
1419
- column: f1.column,
1420
- op: IN,
1421
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1422
- // @ts-ignore
1423
- values: [f1.value, f2.value]
1424
- };
1838
+ return selected.map(stripQuotes2);
1425
1839
  }
1426
- return f2;
1427
- };
1428
- var combine = (existingFilters, replacementFilters) => {
1429
- const equivalentType = ({ op: t1 }, { op: t2 }) => {
1430
- return t1 === t2 || t1[0] === t2[0];
1431
- };
1432
- const replaces = (existingFilter, replacementFilter) => {
1433
- return existingFilter.column === replacementFilter.column && equivalentType(existingFilter, replacementFilter);
1434
- };
1435
- const stillApplicable = (existingFilter) => replacementFilters.some(
1436
- (replacementFilter) => replaces(existingFilter, replacementFilter)
1437
- ) === false;
1438
- return existingFilters.filter(stillApplicable).concat(replacementFilters);
1439
1840
  };
1440
- var removeColumnFromFilter = (column, filter) => {
1441
- if (isMultiClauseFilter2(filter)) {
1442
- const [clause1, clause2] = filter.filters;
1443
- if (clause1.column === column.name) {
1444
- return [clause2, filterAsQuery(clause2)];
1445
- }
1446
- if (clause2.column === column.name) {
1447
- return [clause1, filterAsQuery(clause1)];
1841
+ var FilterDropdownMultiSelect = ({
1842
+ column,
1843
+ selected: selectedProp,
1844
+ suggestionProvider,
1845
+ ...props
1846
+ }) => {
1847
+ const selected = stripQuotes2(selectedProp);
1848
+ const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
1849
+ const [values, setValues] = useState4(initialValues);
1850
+ const handleOpenChange = useCallback6(
1851
+ async (isOpen) => {
1852
+ if (isOpen) {
1853
+ const values2 = await suggestionProvider.getSuggestions("columnValue", {
1854
+ columnName: column
1855
+ });
1856
+ console.log({ values: values2 });
1857
+ setValues(values2.map((suggestion) => suggestion.label));
1858
+ }
1859
+ },
1860
+ [column, suggestionProvider]
1861
+ );
1862
+ return /* @__PURE__ */ jsx9(
1863
+ Dropdown3,
1864
+ {
1865
+ ...props,
1866
+ onOpenChange: handleOpenChange,
1867
+ selected,
1868
+ selectionStrategy: "multiple",
1869
+ source: values
1448
1870
  }
1449
- }
1450
- return [void 0, ""];
1451
- };
1452
- var removeFilter = (sourceFilter, filterToRemove) => {
1453
- if (filterEquals(sourceFilter, filterToRemove, true)) {
1454
- return null;
1455
- }
1456
- if (sourceFilter.op !== AND) {
1457
- throw Error(
1458
- `removeFilter cannot remove ${JSON.stringify(
1459
- filterToRemove
1460
- )} from ${JSON.stringify(sourceFilter)}`
1461
- );
1462
- }
1463
- const filters = sourceFilter.filters.filter(
1464
- (f) => !filterEquals(f, filterToRemove)
1465
1871
  );
1466
- return filters.length > 0 ? { type: AND, filters } : null;
1467
1872
  };
1468
- var splitFilterOnColumn = (filter, columnName) => {
1469
- if (!filter) {
1470
- return [null, null];
1873
+
1874
+ // src/filter-toolbar/useFilterToolbar.tsx
1875
+ import { jsx as jsx10 } from "react/jsx-runtime";
1876
+ var filterToControl = (filter, suggestionProvider) => {
1877
+ if (isNamedFilter(filter)) {
1878
+ return /* @__PURE__ */ jsx10(
1879
+ ToggleButton,
1880
+ {
1881
+ className: "vuuToggleButton",
1882
+ toggled: true,
1883
+ variant: "secondary",
1884
+ children: filter.name
1885
+ }
1886
+ );
1471
1887
  }
1472
- if (filter.column === columnName) {
1473
- return [filter, null];
1888
+ if (isSingleValueFilter(filter)) {
1889
+ const { column, value } = filter;
1890
+ return /* @__PURE__ */ jsx10(
1891
+ ToolbarField3,
1892
+ {
1893
+ className: "vuuFilterDropdown",
1894
+ label: column,
1895
+ labelPlacement: "top",
1896
+ children: /* @__PURE__ */ jsx10(
1897
+ FilterDropdown,
1898
+ {
1899
+ column,
1900
+ selected: value.toString(),
1901
+ selectionStrategy: "default",
1902
+ source: [value.toString()],
1903
+ suggestionProvider,
1904
+ style: { width: 100 }
1905
+ }
1906
+ )
1907
+ },
1908
+ column
1909
+ );
1474
1910
  }
1475
- if (filter.op !== AND) {
1476
- return [null, filter];
1911
+ if (isMultiValueFilter(filter)) {
1912
+ const values = filter.values.map((v) => v.toString());
1913
+ return /* @__PURE__ */ jsx10(
1914
+ ToolbarField3,
1915
+ {
1916
+ className: "vuuFilterDropdown",
1917
+ label: filter.column,
1918
+ labelPlacement: "top",
1919
+ children: /* @__PURE__ */ jsx10(
1920
+ FilterDropdownMultiSelect,
1921
+ {
1922
+ column: filter.column,
1923
+ selected: values,
1924
+ source: values,
1925
+ suggestionProvider,
1926
+ style: { width: 100 }
1927
+ }
1928
+ )
1929
+ },
1930
+ filter.column
1931
+ );
1477
1932
  }
1478
- const [[columnFilter = null], filters] = partition(
1479
- filter.filters,
1480
- (f) => f.column === columnName
1933
+ return filter.filters.map(
1934
+ (filter2) => filterToControl(filter2, suggestionProvider)
1481
1935
  );
1482
- return filters.length === 1 ? [columnFilter, filters[0]] : [columnFilter, { op: AND, filters }];
1483
1936
  };
1484
- var overrideColName = (filter, column) => {
1485
- if (isMultiClauseFilter2(filter)) {
1486
- return {
1487
- op: filter.op,
1488
- filters: filter.filters.map((f) => overrideColName(f, column))
1489
- };
1937
+ var useFilterToolbar = ({
1938
+ filter,
1939
+ suggestionProvider
1940
+ }) => {
1941
+ if (filter) {
1942
+ return filterToControl(filter, suggestionProvider);
1490
1943
  }
1491
- return { ...filter, column };
1944
+ return [];
1492
1945
  };
1493
- var filterIncludesColumn = (filter, column) => {
1494
- if (!filter) {
1495
- return false;
1496
- }
1497
- const { op, column: filterColName } = filter;
1498
- switch (op) {
1499
- case AND:
1500
- case OR:
1501
- return filter.filters != null && filter.filters.some((f) => filterIncludesColumn(f, column));
1502
- default:
1503
- return filterColName === column.name;
1504
- }
1946
+
1947
+ // src/filter-toolbar/FilterToolbar.tsx
1948
+ import { jsx as jsx11 } from "react/jsx-runtime";
1949
+ var FilterToolbar = ({
1950
+ className,
1951
+ filter,
1952
+ suggestionProvider,
1953
+ ...props
1954
+ }) => {
1955
+ console.log(`FilterToolbar ${JSON.stringify(filter, null, 2)}`);
1956
+ const toolbarItems = useFilterToolbar({ filter, suggestionProvider });
1957
+ return /* @__PURE__ */ jsx11(Toolbar2, { className: (0, import_classnames2.default)("vuuFilterToolbar", className), ...props, children: toolbarItems });
1505
1958
  };
1506
- var removeFilterForColumn = (sourceFilter, column) => {
1507
- const colName = column.name;
1508
- if (!sourceFilter) {
1509
- return void 0;
1510
- }
1511
- if (sourceFilter.column === colName) {
1512
- return void 0;
1959
+
1960
+ // src/filter-evaluation-utils.ts
1961
+ var filterPredicateMap = /* @__PURE__ */ new Map();
1962
+ var filterReject = () => false;
1963
+ var getFilterPredicate = (columnMap, filterQuery) => {
1964
+ let predicate = filterPredicateMap.get(filterQuery);
1965
+ if (predicate) {
1966
+ return predicate;
1513
1967
  }
1514
- if (isAndFilter(sourceFilter) || isOrFilter(sourceFilter)) {
1515
- const { op } = sourceFilter;
1516
- const filters = sourceFilter.filters;
1517
- const otherColFilters = filters.filter((f) => f.column !== colName);
1518
- switch (otherColFilters.length) {
1519
- case 0:
1520
- return void 0;
1521
- case 1:
1522
- return otherColFilters[0];
1523
- default:
1524
- return { op, filters: otherColFilters };
1525
- }
1968
+ try {
1969
+ const filter = parseFilter(filterQuery);
1970
+ predicate = filterPredicate(columnMap, filter);
1971
+ filterPredicateMap.set(filterQuery, predicate);
1972
+ return predicate;
1973
+ } catch (err) {
1974
+ console.warn(
1975
+ `filter-evaluation-utils, failed to parse filter "${filterQuery}"`
1976
+ );
1977
+ return filterReject;
1526
1978
  }
1527
- return sourceFilter;
1528
1979
  };
1529
- var canMerge = (f1, f2) => f1.column === f2.column && (f1.op === "=" || f1.op === "in") && (f2.op === "=" || f2.op === "in");
1530
- var sameValues = (arr1, arr2) => {
1531
- if (arr1 === arr2) {
1532
- return true;
1533
- }
1534
- if (arr1.length === arr2.length) {
1535
- const a = arr1.slice().sort();
1536
- const b = arr2.slice().sort();
1537
- return a.join("|") === b.join("|");
1980
+ function filterPredicate(columnMap, filter) {
1981
+ switch (filter.op) {
1982
+ case "in":
1983
+ return testInclude(columnMap, filter);
1984
+ case "=":
1985
+ return testEQ(columnMap, filter);
1986
+ case ">":
1987
+ return testGT(columnMap, filter);
1988
+ case ">=":
1989
+ return testGE(columnMap, filter);
1990
+ case "<":
1991
+ return testLT(columnMap, filter);
1992
+ case "<=":
1993
+ return testLE(columnMap, filter);
1994
+ case "starts":
1995
+ return testSW(columnMap, filter);
1996
+ case "and":
1997
+ return testAND(columnMap, filter);
1998
+ case "or":
1999
+ return testOR(columnMap, filter);
2000
+ default:
2001
+ console.log(`unrecognized filter type ${filter.op}`);
2002
+ return () => true;
1538
2003
  }
1539
- return false;
2004
+ }
2005
+ var testInclude = (columnMap, filter) => {
2006
+ return (row) => filter.values.indexOf(row[columnMap[filter.column]]) !== -1;
1540
2007
  };
1541
- var filterEquals = (f1, f2, strict = false) => {
1542
- if (!strict) {
1543
- return true;
1544
- }
1545
- if (f1 && f2 && canMerge(f1, f2)) {
1546
- return f1.op === f2.op && (isSingleValueFilter2(f1) && isSingleValueFilter2(f2) && f1.value === f2.value || isMultiValueFilter2(f1) && isMultiValueFilter2(f2) && sameValues(f1.values, f2.values));
1547
- }
1548
- return false;
2008
+ var testEQ = (columnMap, filter) => {
2009
+ return (row) => row[columnMap[filter.column]] === filter.value;
1549
2010
  };
1550
- var updateFilter = (filter, newFilter, mode) => {
1551
- if (filter && newFilter) {
1552
- if (mode === "replace") {
1553
- return newFilter;
1554
- }
1555
- if (filter.op === "and") {
1556
- return {
1557
- ...filter,
1558
- filters: filter.filters.concat(newFilter)
1559
- };
1560
- }
1561
- const { column: columnName } = newFilter;
1562
- if (columnName) {
1563
- const existingClause = newFilter.column ? extractFilterForColumn(filter, columnName) : void 0;
1564
- if (existingClause && columnName) {
1565
- const result = removeFilterForColumn(filter, { name: columnName });
1566
- return updateFilter(result, newFilter, "add");
1567
- }
1568
- }
1569
- return {
1570
- op: "and",
1571
- filters: [filter, newFilter]
1572
- };
1573
- }
1574
- if (newFilter) {
1575
- return newFilter;
2011
+ var testGT = (columnMap, filter) => {
2012
+ return (row) => row[columnMap[filter.column]] > filter.value;
2013
+ };
2014
+ var testGE = (columnMap, filter) => {
2015
+ return (row) => row[columnMap[filter.column]] >= filter.value;
2016
+ };
2017
+ var testLT = (columnMap, filter) => {
2018
+ return (row) => row[columnMap[filter.column]] < filter.value;
2019
+ };
2020
+ var testLE = (columnMap, filter) => {
2021
+ return (row) => row[columnMap[filter.column]] <= filter.value;
2022
+ };
2023
+ var testSW = (columnMap, filter) => {
2024
+ const filterValue2 = filter.value;
2025
+ if (typeof filterValue2 !== "string") {
2026
+ throw Error("string filter applied to value of wrong type");
1576
2027
  }
1577
- return filter;
2028
+ return (row) => {
2029
+ const rowValue = row[columnMap[filter.column]];
2030
+ if (typeof rowValue !== "string") {
2031
+ throw Error("string filter applied to value of wrong type");
2032
+ }
2033
+ return rowValue.toLowerCase().startsWith(filterValue2.toLowerCase());
2034
+ };
2035
+ };
2036
+ var testAND = (columnMap, filter) => {
2037
+ const filters = filter.filters.map((f1) => filterPredicate(columnMap, f1));
2038
+ return (row) => filters.every((fn) => fn(row));
1578
2039
  };
2040
+ function testOR(columnMap, filter) {
2041
+ const filters = filter.filters.map((f1) => filterPredicate(columnMap, f1));
2042
+ return (row) => filters.some((fn) => fn(row));
2043
+ }
1579
2044
  export {
1580
2045
  AND,
2046
+ ColumnFilter,
1581
2047
  ENDS_WITH,
1582
2048
  EQUALS,
1583
2049
  FilterInput,
@@ -1597,11 +2063,11 @@ export {
1597
2063
  isAndFilter,
1598
2064
  isFilterClause,
1599
2065
  isInFilter,
1600
- isMultiClauseFilter2 as isMultiClauseFilter,
1601
- isMultiValueFilter2 as isMultiValueFilter,
2066
+ isMultiClauseFilter,
2067
+ isMultiValueFilter,
1602
2068
  isNamedFilter,
1603
2069
  isOrFilter,
1604
- isSingleValueFilter2 as isSingleValueFilter,
2070
+ isSingleValueFilter,
1605
2071
  overrideColName,
1606
2072
  parseFilter,
1607
2073
  removeColumnFromFilter,