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