@vuu-ui/vuu-filters 0.8.4-debug → 0.8.5-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.js CHANGED
@@ -93,1057 +93,329 @@ var require_classnames = __commonJS({
93
93
  }
94
94
  });
95
95
 
96
- // src/column-filter/ColumnFilter.tsx
97
- import {
98
- Dropdown,
99
- Toolbar,
100
- ToolbarButton,
101
- ToolbarField as ToolbarField2
102
- } from "@heswell/salt-lab";
103
- import { Text } from "@salt-ds/core";
104
-
105
- // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/icon/Icon.js
106
- import { jsx } from "react/jsx-runtime";
107
- import { forwardRef } from "react";
96
+ // src/filter-input/FilterInput.tsx
97
+ import { Button } from "@salt-ds/core";
108
98
 
109
- // ../../node_modules/clsx/dist/clsx.m.js
110
- function r(e) {
111
- var t, f, n = "";
112
- if ("string" == typeof e || "number" == typeof e)
113
- n += e;
114
- else if ("object" == typeof e)
115
- if (Array.isArray(e))
116
- for (t = 0; t < e.length; t++)
117
- e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
118
- else
119
- for (t in e)
120
- e[t] && (n && (n += " "), n += t);
121
- return n;
122
- }
123
- function clsx() {
124
- for (var e, t, f = 0, n = ""; f < arguments.length; )
125
- (e = arguments[f++]) && (t = r(e)) && (n && (n += " "), n += t);
126
- return n;
127
- }
99
+ // src/filter-input/useCodeMirrorEditor.ts
100
+ import {
101
+ autocompletion,
102
+ defaultKeymap,
103
+ EditorState as EditorState2,
104
+ EditorView as EditorView2,
105
+ ensureSyntaxTree,
106
+ keymap,
107
+ minimalSetup,
108
+ startCompletion
109
+ } from "@vuu-ui/vuu-codemirror";
128
110
 
129
- // ../../node_modules/@salt-ds/icons/dist-es/node_modules/style-inject/dist/style-inject.es.js
130
- function styleInject(css, ref) {
131
- if (ref === void 0)
132
- ref = {};
133
- var insertAt = ref.insertAt;
134
- if (!css || typeof document === "undefined") {
135
- return;
136
- }
137
- var head = document.head || document.getElementsByTagName("head")[0];
138
- var style = document.createElement("style");
139
- style.type = "text/css";
140
- if (insertAt === "top") {
141
- if (head.firstChild) {
142
- head.insertBefore(style, head.firstChild);
143
- } else {
144
- head.appendChild(style);
145
- }
146
- } else {
147
- head.appendChild(style);
148
- }
149
- if (style.styleSheet) {
150
- style.styleSheet.cssText = css;
151
- } else {
152
- style.appendChild(document.createTextNode(css));
111
+ // ../../node_modules/@lezer/common/dist/index.js
112
+ var DefaultBufferLength = 1024;
113
+ var nextPropID = 0;
114
+ var Range = class {
115
+ constructor(from, to) {
116
+ this.from = from;
117
+ this.to = to;
153
118
  }
154
- }
155
-
156
- // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/icon/Icon.css.js
157
- var css_248z = "/* Style applied to the root element */\n.saltIcon {\n --icon-color: var(--saltIcon-color, var(--salt-text-secondary-foreground));\n --icon-size-multiplier: var(--saltIcon-size-multiplier, 1);\n --icon-base-size: var(--salt-size-icon-base, 12px);\n /**\n * Icon size will be the multiplier (an integer from the size prop) * the base size (set by the theme per density)\n * Icons should never be smaller than 12px for readability so we've added a max() to enforce this\n */\n --icon-size: max(calc(var(--icon-base-size) * var(--icon-size-multiplier)), 12px);\n}\n\n.saltIcon {\n fill: var(--saltIcon-color, var(--icon-color));\n display: inline-block;\n margin: var(--saltIcon-margin, 0);\n position: relative;\n width: var(--icon-size);\n height: var(--icon-size);\n min-width: var(--icon-size);\n min-height: var(--icon-size);\n}\n\n.saltIcon:hover {\n --icon-color: var(--saltIcon-color-hover, var(--salt-text-secondary-foreground));\n}\n\n.saltIcon:active {\n --icon-color: var(--saltIcon-color-active, var(--salt-text-secondary-foreground));\n}\n";
158
- styleInject(css_248z);
159
-
160
- // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/icon/Icon.js
161
- var makePrefixer = (prefix) => (...names) => [prefix, ...names].join("-");
162
- var withBaseName = makePrefixer("saltIcon");
163
- var DEFAULT_ICON_SIZE = 1;
164
- var Icon = forwardRef(function Icon2({ children, className, size = DEFAULT_ICON_SIZE, style: styleProp, ...rest }, ref) {
165
- const style = {
166
- ...styleProp,
167
- "--saltIcon-size-multiplier": `${size}`
168
- };
169
- return /* @__PURE__ */ jsx("svg", {
170
- className: clsx(withBaseName(), className),
171
- style,
172
- role: "img",
173
- ...rest,
174
- ref,
175
- children: /* @__PURE__ */ jsx("g", {
176
- "aria-hidden": true,
177
- children
178
- })
179
- });
180
- });
181
-
182
- // ../../node_modules/@salt-ds/icons/dist-es/packages/icons/src/components/Delete.js
183
- import { jsxs, jsx as jsx2 } from "react/jsx-runtime";
184
- import { forwardRef as forwardRef2 } from "react";
185
- var DeleteIcon = forwardRef2(
186
- function DeleteIcon2(props, ref) {
187
- return /* @__PURE__ */ jsxs(Icon, {
188
- "data-testid": "DeleteIcon",
189
- "aria-label": "delete",
190
- viewBox: "0 0 12 12",
191
- ref,
192
- ...props,
193
- children: [
194
- /* @__PURE__ */ jsx2("path", {
195
- d: "M5 4v6H4V4h1Zm2 0v6H6V4h1Z"
196
- }),
197
- /* @__PURE__ */ jsx2("path", {
198
- fillRule: "evenodd",
199
- d: "M4 0a1 1 0 0 0-1 1v1H0v1h1v7a2 2 0 0 0 2 2h5.25A1.75 1.75 0 0 0 10 10.25V3h1V2H8V1a1 1 0 0 0-1-1H4Zm5 3H2v7a1 1 0 0 0 1 1h5.25a.75.75 0 0 0 .75-.75V3ZM7 2H4v-.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5V2Z",
200
- clipRule: "evenodd"
201
- })
202
- ]
119
+ };
120
+ var NodeProp = class {
121
+ /// Create a new node prop type.
122
+ constructor(config = {}) {
123
+ this.id = nextPropID++;
124
+ this.perNode = !!config.perNode;
125
+ this.deserialize = config.deserialize || (() => {
126
+ throw new Error("This node type doesn't define a deserialize function");
203
127
  });
204
128
  }
205
- );
206
-
207
- // src/column-filter/RangeFilter.tsx
208
- import { Input, ToolbarField } from "@heswell/salt-lab";
209
-
210
- // src/filter-utils.ts
211
- import {
212
- extractFilterForColumn,
213
- isAndFilter,
214
- isInFilter,
215
- isMultiClauseFilter,
216
- isMultiValueFilter,
217
- isOrFilter,
218
- isSingleValueFilter,
219
- partition
220
- } from "@vuu-ui/vuu-utils";
221
- var AND = "and";
222
- var EQUALS = "=";
223
- var GREATER_THAN = ">";
224
- var LESS_THAN = "<";
225
- var OR = "or";
226
- var STARTS_WITH = "starts";
227
- var ENDS_WITH = "ends";
228
- var IN = "in";
229
- var filterClauses = (filter, clauses = []) => {
230
- if (filter) {
231
- if (isMultiClauseFilter(filter)) {
232
- filter.filters.forEach((f) => clauses.push(...filterClauses(f)));
233
- } else {
234
- clauses.push(filter);
235
- }
129
+ /// This is meant to be used with
130
+ /// [`NodeSet.extend`](#common.NodeSet.extend) or
131
+ /// [`LRParser.configure`](#lr.ParserConfig.props) to compute
132
+ /// prop values for each node type in the set. Takes a [match
133
+ /// object](#common.NodeType^match) or function that returns undefined
134
+ /// if the node type doesn't get this prop, and the prop's value if
135
+ /// it does.
136
+ add(match) {
137
+ if (this.perNode)
138
+ throw new RangeError("Can't add per-node props to node types");
139
+ if (typeof match != "function")
140
+ match = NodeType.match(match);
141
+ return (type) => {
142
+ let result = match(type);
143
+ return result === void 0 ? null : [this, result];
144
+ };
236
145
  }
237
- return clauses;
238
- };
239
- var DEFAULT_ADD_FILTER_OPTS = {
240
- combineWith: "and"
241
146
  };
242
- var addFilter = (existingFilter, filter, { combineWith = AND } = DEFAULT_ADD_FILTER_OPTS) => {
243
- var _a;
244
- if (includesNoValues(filter)) {
245
- if (isMultiClauseFilter(filter)) {
246
- } else {
247
- existingFilter = removeFilterForColumn(existingFilter, {
248
- name: filter.column
249
- });
250
- }
251
- } else if (includesAllValues(filter)) {
252
- if (isMultiClauseFilter(filter)) {
253
- }
254
- return removeFilterForColumn(existingFilter, { name: (_a = filter.column) != null ? _a : "" });
255
- }
256
- if (!existingFilter) {
257
- return filter;
147
+ NodeProp.closedBy = new NodeProp({ deserialize: (str) => str.split(" ") });
148
+ NodeProp.openedBy = new NodeProp({ deserialize: (str) => str.split(" ") });
149
+ NodeProp.group = new NodeProp({ deserialize: (str) => str.split(" ") });
150
+ NodeProp.contextHash = new NodeProp({ perNode: true });
151
+ NodeProp.lookAhead = new NodeProp({ perNode: true });
152
+ NodeProp.mounted = new NodeProp({ perNode: true });
153
+ var noProps = /* @__PURE__ */ Object.create(null);
154
+ var NodeType = class {
155
+ /// @internal
156
+ constructor(name, props, id, flags = 0) {
157
+ this.name = name;
158
+ this.props = props;
159
+ this.id = id;
160
+ this.flags = flags;
258
161
  }
259
- if (!filter) {
260
- return existingFilter;
162
+ /// Define a node type.
163
+ static define(spec) {
164
+ let props = spec.props && spec.props.length ? /* @__PURE__ */ Object.create(null) : noProps;
165
+ let flags = (spec.top ? 1 : 0) | (spec.skipped ? 2 : 0) | (spec.error ? 4 : 0) | (spec.name == null ? 8 : 0);
166
+ let type = new NodeType(spec.name || "", props, spec.id, flags);
167
+ if (spec.props)
168
+ for (let src of spec.props) {
169
+ if (!Array.isArray(src))
170
+ src = src(type);
171
+ if (src) {
172
+ if (src[0].perNode)
173
+ throw new RangeError("Can't store a per-node prop on a node type");
174
+ props[src[0].id] = src[1];
175
+ }
176
+ }
177
+ return type;
261
178
  }
262
- if (existingFilter.op === AND && filter.op === AND) {
263
- return {
264
- op: AND,
265
- filters: combine(existingFilter.filters, filter.filters)
266
- };
179
+ /// Retrieves a node prop for this type. Will return `undefined` if
180
+ /// the prop isn't present on this node.
181
+ prop(prop) {
182
+ return this.props[prop.id];
267
183
  }
268
- if (existingFilter.op === AND) {
269
- const filters = replaceOrInsert(existingFilter.filters, filter);
270
- return filters.length > 1 ? { op: AND, filters } : filters[0];
184
+ /// True when this is the top node of a grammar.
185
+ get isTop() {
186
+ return (this.flags & 1) > 0;
271
187
  }
272
- if (filter.op === AND) {
273
- return { op: AND, filters: filter.filters.concat(existingFilter) };
188
+ /// True when this node is produced by a skip rule.
189
+ get isSkipped() {
190
+ return (this.flags & 2) > 0;
274
191
  }
275
- if (filterEquals(existingFilter, filter, true)) {
276
- return filter;
192
+ /// Indicates whether this is an error node.
193
+ get isError() {
194
+ return (this.flags & 4) > 0;
277
195
  }
278
- if (canMerge(existingFilter, filter)) {
279
- return merge(existingFilter, filter);
196
+ /// When true, this node type doesn't correspond to a user-declared
197
+ /// named node, for example because it is used to cache repetition.
198
+ get isAnonymous() {
199
+ return (this.flags & 8) > 0;
280
200
  }
281
- return { op: combineWith, filters: [existingFilter, filter] };
282
- };
283
- var includesNoValues = (filter) => {
284
- if (!filter) {
285
- return false;
201
+ /// Returns true when this node's name or one of its
202
+ /// [groups](#common.NodeProp^group) matches the given string.
203
+ is(name) {
204
+ if (typeof name == "string") {
205
+ if (this.name == name)
206
+ return true;
207
+ let group = this.prop(NodeProp.group);
208
+ return group ? group.indexOf(name) > -1 : false;
209
+ }
210
+ return this.id == name;
286
211
  }
287
- if (isInFilter(filter) && filter.values.length === 0) {
288
- return true;
212
+ /// Create a function from node types to arbitrary values by
213
+ /// specifying an object whose property names are node or
214
+ /// [group](#common.NodeProp^group) names. Often useful with
215
+ /// [`NodeProp.add`](#common.NodeProp.add). You can put multiple
216
+ /// names, separated by spaces, in a single property name to map
217
+ /// multiple node names to a single value.
218
+ static match(map) {
219
+ let direct = /* @__PURE__ */ Object.create(null);
220
+ for (let prop in map)
221
+ for (let name of prop.split(" "))
222
+ direct[name] = map[prop];
223
+ return (node) => {
224
+ for (let groups = node.prop(NodeProp.group), i = -1; i < (groups ? groups.length : 0); i++) {
225
+ let found = direct[i < 0 ? node.name : groups[i]];
226
+ if (found)
227
+ return found;
228
+ }
229
+ };
289
230
  }
290
- return isAndFilter(filter) && filter.filters.some((f) => includesNoValues(f));
291
231
  };
292
- var includesAllValues = (filter) => {
293
- if (!filter) {
294
- return false;
232
+ NodeType.none = new NodeType(
233
+ "",
234
+ /* @__PURE__ */ Object.create(null),
235
+ 0,
236
+ 8
237
+ /* NodeFlag.Anonymous */
238
+ );
239
+ var NodeSet = class {
240
+ /// Create a set with the given types. The `id` property of each
241
+ /// type should correspond to its position within the array.
242
+ constructor(types) {
243
+ this.types = types;
244
+ for (let i = 0; i < types.length; i++)
245
+ if (types[i].id != i)
246
+ throw new RangeError("Node type ids should correspond to array positions when creating a node set");
295
247
  }
296
- if (filter.op === STARTS_WITH && filter.value === "") {
297
- return true;
248
+ /// Create a copy of this set with some node properties added. The
249
+ /// arguments to this method can be created with
250
+ /// [`NodeProp.add`](#common.NodeProp.add).
251
+ extend(...props) {
252
+ let newTypes = [];
253
+ for (let type of this.types) {
254
+ let newProps = null;
255
+ for (let source of props) {
256
+ let add = source(type);
257
+ if (add) {
258
+ if (!newProps)
259
+ newProps = Object.assign({}, type.props);
260
+ newProps[add[0].id] = add[1];
261
+ }
262
+ }
263
+ newTypes.push(newProps ? new NodeType(type.name, newProps, type.id, type.flags) : type);
264
+ }
265
+ return new NodeSet(newTypes);
298
266
  }
299
- return filter.op === STARTS_WITH && filter.value === "";
300
267
  };
301
- var replaceOrInsert = (filters, filter) => {
302
- return filters.concat(filter);
303
- };
304
- var merge = (f1, f2) => {
305
- if (includesNoValues(f2)) {
306
- return f2;
307
- }
308
- if (isInFilter(f1) && isInFilter(f2)) {
309
- return {
310
- ...f1,
311
- values: [
312
- ...f1.values,
313
- ...f2.values.filter(
314
- (v) => !f1.values.includes(v)
315
- )
316
- ]
317
- };
318
- } else if (isInFilter(f1) && f2.op === EQUALS) {
319
- return {
320
- ...f1,
321
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
322
- // @ts-ignore
323
- values: f1.values.concat([f2.value])
324
- };
325
- } else if (f1.op === EQUALS && f2.op === EQUALS) {
326
- return {
327
- column: f1.column,
328
- op: IN,
329
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
330
- // @ts-ignore
331
- values: [f1.value, f2.value]
332
- };
268
+ var CachedNode = /* @__PURE__ */ new WeakMap();
269
+ var CachedInnerNode = /* @__PURE__ */ new WeakMap();
270
+ var IterMode;
271
+ (function(IterMode2) {
272
+ IterMode2[IterMode2["ExcludeBuffers"] = 1] = "ExcludeBuffers";
273
+ IterMode2[IterMode2["IncludeAnonymous"] = 2] = "IncludeAnonymous";
274
+ IterMode2[IterMode2["IgnoreMounts"] = 4] = "IgnoreMounts";
275
+ IterMode2[IterMode2["IgnoreOverlays"] = 8] = "IgnoreOverlays";
276
+ })(IterMode || (IterMode = {}));
277
+ var Tree = class {
278
+ /// Construct a new tree. See also [`Tree.build`](#common.Tree^build).
279
+ constructor(type, children, positions, length, props) {
280
+ this.type = type;
281
+ this.children = children;
282
+ this.positions = positions;
283
+ this.length = length;
284
+ this.props = null;
285
+ if (props && props.length) {
286
+ this.props = /* @__PURE__ */ Object.create(null);
287
+ for (let [prop, value] of props)
288
+ this.props[typeof prop == "number" ? prop : prop.id] = value;
289
+ }
333
290
  }
334
- return f2;
335
- };
336
- var combine = (existingFilters, replacementFilters) => {
337
- const equivalentType = ({ op: t1 }, { op: t2 }) => {
338
- return t1 === t2 || t1[0] === t2[0];
339
- };
340
- const replaces = (existingFilter, replacementFilter) => {
341
- return existingFilter.column === replacementFilter.column && equivalentType(existingFilter, replacementFilter);
342
- };
343
- const stillApplicable = (existingFilter) => replacementFilters.some(
344
- (replacementFilter) => replaces(existingFilter, replacementFilter)
345
- ) === false;
346
- return existingFilters.filter(stillApplicable).concat(replacementFilters);
347
- };
348
- var removeFilter = (sourceFilter, filterToRemove) => {
349
- if (filterEquals(sourceFilter, filterToRemove, true)) {
350
- return null;
291
+ /// @internal
292
+ toString() {
293
+ let mounted = this.prop(NodeProp.mounted);
294
+ if (mounted && !mounted.overlay)
295
+ return mounted.tree.toString();
296
+ let children = "";
297
+ for (let ch of this.children) {
298
+ let str = ch.toString();
299
+ if (str) {
300
+ if (children)
301
+ children += ",";
302
+ children += str;
303
+ }
304
+ }
305
+ return !this.type.name ? children : (/\W/.test(this.type.name) && !this.type.isError ? JSON.stringify(this.type.name) : this.type.name) + (children.length ? "(" + children + ")" : "");
351
306
  }
352
- if (sourceFilter.op !== AND) {
353
- throw Error(
354
- `removeFilter cannot remove ${JSON.stringify(
355
- filterToRemove
356
- )} from ${JSON.stringify(sourceFilter)}`
357
- );
307
+ /// Get a [tree cursor](#common.TreeCursor) positioned at the top of
308
+ /// the tree. Mode can be used to [control](#common.IterMode) which
309
+ /// nodes the cursor visits.
310
+ cursor(mode = 0) {
311
+ return new TreeCursor(this.topNode, mode);
358
312
  }
359
- const filters = sourceFilter.filters.filter(
360
- (f) => !filterEquals(f, filterToRemove)
361
- );
362
- return filters.length > 0 ? { type: AND, filters } : null;
363
- };
364
- var splitFilterOnColumn = (columnName, filter) => {
365
- if (!filter) {
366
- return [void 0, void 0];
313
+ /// Get a [tree cursor](#common.TreeCursor) pointing into this tree
314
+ /// at the given position and side (see
315
+ /// [`moveTo`](#common.TreeCursor.moveTo).
316
+ cursorAt(pos, side = 0, mode = 0) {
317
+ let scope = CachedNode.get(this) || this.topNode;
318
+ let cursor = new TreeCursor(scope);
319
+ cursor.moveTo(pos, side);
320
+ CachedNode.set(this, cursor._tree);
321
+ return cursor;
367
322
  }
368
- if (filter.column === columnName) {
369
- return [filter, void 0];
323
+ /// Get a [syntax node](#common.SyntaxNode) object for the top of the
324
+ /// tree.
325
+ get topNode() {
326
+ return new TreeNode(this, 0, 0, null);
370
327
  }
371
- if (filter.op !== AND) {
372
- return [void 0, filter];
328
+ /// Get the [syntax node](#common.SyntaxNode) at the given position.
329
+ /// If `side` is -1, this will move into nodes that end at the
330
+ /// position. If 1, it'll move into nodes that start at the
331
+ /// position. With 0, it'll only enter nodes that cover the position
332
+ /// from both sides.
333
+ ///
334
+ /// Note that this will not enter
335
+ /// [overlays](#common.MountedTree.overlay), and you often want
336
+ /// [`resolveInner`](#common.Tree.resolveInner) instead.
337
+ resolve(pos, side = 0) {
338
+ let node = resolveNode(CachedNode.get(this) || this.topNode, pos, side, false);
339
+ CachedNode.set(this, node);
340
+ return node;
373
341
  }
374
- const [[columnFilter = void 0], filters] = partition(
375
- filter.filters,
376
- (f) => f.column === columnName
377
- );
378
- return filters.length === 1 ? [columnFilter, filters[0]] : [columnFilter, { op: AND, filters }];
379
- };
380
- var overrideColName = (filter, column) => {
381
- if (isMultiClauseFilter(filter)) {
382
- return {
383
- op: filter.op,
384
- filters: filter.filters.map((f) => overrideColName(f, column))
385
- };
342
+ /// Like [`resolve`](#common.Tree.resolve), but will enter
343
+ /// [overlaid](#common.MountedTree.overlay) nodes, producing a syntax node
344
+ /// pointing into the innermost overlaid tree at the given position
345
+ /// (with parent links going through all parent structure, including
346
+ /// the host trees).
347
+ resolveInner(pos, side = 0) {
348
+ let node = resolveNode(CachedInnerNode.get(this) || this.topNode, pos, side, true);
349
+ CachedInnerNode.set(this, node);
350
+ return node;
386
351
  }
387
- return { ...filter, column };
388
- };
389
- var filterIncludesColumn = (filter, column) => {
390
- if (!filter) {
391
- return false;
352
+ /// Iterate over the tree and its children, calling `enter` for any
353
+ /// node that touches the `from`/`to` region (if given) before
354
+ /// running over such a node's children, and `leave` (if given) when
355
+ /// leaving the node. When `enter` returns `false`, that node will
356
+ /// not have its children iterated over (or `leave` called).
357
+ iterate(spec) {
358
+ let { enter, leave, from = 0, to = this.length } = spec;
359
+ let mode = spec.mode || 0, anon = (mode & IterMode.IncludeAnonymous) > 0;
360
+ for (let c = this.cursor(mode | IterMode.IncludeAnonymous); ; ) {
361
+ let entered = false;
362
+ if (c.from <= to && c.to >= from && (!anon && c.type.isAnonymous || enter(c) !== false)) {
363
+ if (c.firstChild())
364
+ continue;
365
+ entered = true;
366
+ }
367
+ for (; ; ) {
368
+ if (entered && leave && (anon || !c.type.isAnonymous))
369
+ leave(c);
370
+ if (c.nextSibling())
371
+ break;
372
+ if (!c.parent())
373
+ return;
374
+ entered = true;
375
+ }
376
+ }
392
377
  }
393
- const { op, column: filterColName } = filter;
394
- switch (op) {
395
- case AND:
396
- case OR:
397
- return filter.filters != null && filter.filters.some((f) => filterIncludesColumn(f, column));
398
- default:
399
- return filterColName === column.name;
378
+ /// Get the value of the given [node prop](#common.NodeProp) for this
379
+ /// node. Works with both per-node and per-type props.
380
+ prop(prop) {
381
+ return !prop.perNode ? this.type.prop(prop) : this.props ? this.props[prop.id] : void 0;
400
382
  }
401
- };
402
- var removeFilterForColumn = (sourceFilter, column) => {
403
- const colName = column.name;
404
- if (!sourceFilter) {
405
- return void 0;
383
+ /// Returns the node's [per-node props](#common.NodeProp.perNode) in a
384
+ /// format that can be passed to the [`Tree`](#common.Tree)
385
+ /// constructor.
386
+ get propValues() {
387
+ let result = [];
388
+ if (this.props)
389
+ for (let id in this.props)
390
+ result.push([+id, this.props[id]]);
391
+ return result;
406
392
  }
407
- if (sourceFilter.column === colName) {
408
- return void 0;
393
+ /// Balance the direct children of this tree, producing a copy of
394
+ /// which may have children grouped into subtrees with type
395
+ /// [`NodeType.none`](#common.NodeType^none).
396
+ balance(config = {}) {
397
+ return this.children.length <= 8 ? this : balanceRange(NodeType.none, this.children, this.positions, 0, this.children.length, 0, this.length, (children, positions, length) => new Tree(this.type, children, positions, length, this.propValues), config.makeTree || ((children, positions, length) => new Tree(NodeType.none, children, positions, length)));
409
398
  }
410
- if (isAndFilter(sourceFilter) || isOrFilter(sourceFilter)) {
411
- const { op } = sourceFilter;
412
- const filters = sourceFilter.filters;
413
- const otherColFilters = filters.filter((f) => f.column !== colName);
414
- switch (otherColFilters.length) {
415
- case 0:
416
- return void 0;
417
- case 1:
418
- return otherColFilters[0];
419
- default:
420
- return { op, filters: otherColFilters };
421
- }
399
+ /// Build a tree from a postfix-ordered buffer of node information,
400
+ /// or a cursor over such a buffer.
401
+ static build(data) {
402
+ return buildTree(data);
422
403
  }
423
- return sourceFilter;
424
404
  };
425
- var canMerge = (f1, f2) => f1.column === f2.column && (f1.op === "=" || f1.op === "in") && (f2.op === "=" || f2.op === "in");
426
- var sameValues = (arr1, arr2) => {
427
- if (arr1 === arr2) {
428
- return true;
405
+ Tree.empty = new Tree(NodeType.none, [], [], 0);
406
+ var FlatBufferCursor = class {
407
+ constructor(buffer, index) {
408
+ this.buffer = buffer;
409
+ this.index = index;
429
410
  }
430
- if (arr1.length === arr2.length) {
431
- const a = arr1.slice().sort();
432
- const b = arr2.slice().sort();
433
- return a.join("|") === b.join("|");
411
+ get id() {
412
+ return this.buffer[this.index - 4];
434
413
  }
435
- return false;
436
- };
437
- var filterEquals = (f1, f2, strict = false) => {
438
- if (!strict) {
439
- return true;
414
+ get start() {
415
+ return this.buffer[this.index - 3];
440
416
  }
441
- if (f1 && f2 && canMerge(f1, f2)) {
442
- return f1.op === f2.op && (isSingleValueFilter(f1) && isSingleValueFilter(f2) && f1.value === f2.value || isMultiValueFilter(f1) && isMultiValueFilter(f2) && sameValues(f1.values, f2.values));
443
- }
444
- return false;
445
- };
446
- var updateFilter = (filter, newFilter, mode) => {
447
- if (filter && newFilter) {
448
- if (mode === "replace") {
449
- return newFilter;
450
- }
451
- if (filter.op === "and") {
452
- return {
453
- ...filter,
454
- filters: filter.filters.concat(newFilter)
455
- };
456
- }
457
- const { column: columnName } = newFilter;
458
- if (columnName) {
459
- const existingClause = newFilter.column ? extractFilterForColumn(filter, columnName) : void 0;
460
- if (existingClause && columnName) {
461
- const result = removeFilterForColumn(filter, { name: columnName });
462
- return updateFilter(result, newFilter, "add");
463
- }
464
- }
465
- return {
466
- op: "and",
467
- filters: [filter, newFilter]
468
- };
469
- }
470
- if (newFilter) {
471
- return newFilter;
472
- }
473
- return filter;
474
- };
475
-
476
- // src/column-filter/utils.ts
477
- var isStartsWithValue = (value) => /\.\.\.$/.test(value);
478
- var getTypeaheadFilter = (column, filterValues, isStartsWithFilter) => {
479
- if (filterValues.length === 0) {
480
- return void 0;
481
- }
482
- if (isStartsWithFilter) {
483
- const startsWith = filterValues[0].substring(0, filterValues[0].length - 3);
484
- return {
485
- column,
486
- op: "starts",
487
- value: `"${startsWith}"`
488
- };
489
- }
490
- return {
491
- column,
492
- op: "in",
493
- values: filterValues.map((value) => `"${value}"`)
494
- };
495
- };
496
- var getRangeFilter = (column, startValue, endValue) => {
497
- const startFilter = startValue === void 0 ? void 0 : { column, op: ">", value: startValue - 1 };
498
- const endFilter = endValue === void 0 ? void 0 : { column, op: "<", value: endValue + 1 };
499
- if (endFilter === void 0)
500
- return startFilter;
501
- return addFilter(startFilter, endFilter, { combineWith: "and" });
502
- };
503
-
504
- // src/column-filter/RangeFilter.tsx
505
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
506
- var RangeFilter = ({
507
- defaultTypeaheadParams,
508
- filterValues,
509
- onChange
510
- }) => {
511
- var _a, _b;
512
- const columnName = defaultTypeaheadParams[1];
513
- const startChangeHandler = (e) => {
514
- const value = parseFloat(e.target.value);
515
- const newRange = {
516
- start: isNaN(value) ? void 0 : value,
517
- end: filterValues == null ? void 0 : filterValues.end
518
- };
519
- const filter = getRangeFilter(columnName, newRange.start, newRange.end);
520
- onChange(newRange, filter);
521
- };
522
- const endChangeHandler = (e) => {
523
- const value = parseFloat(e.target.value);
524
- const newRange = {
525
- start: filterValues == null ? void 0 : filterValues.start,
526
- end: isNaN(value) ? void 0 : value
527
- };
528
- const filter = getRangeFilter(columnName, newRange.start, newRange.end);
529
- onChange(newRange, filter);
530
- };
531
- return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "row" }, children: [
532
- /* @__PURE__ */ jsx3(ToolbarField, { label: "From", children: /* @__PURE__ */ jsx3(
533
- Input,
534
- {
535
- onChange: startChangeHandler,
536
- value: ((_a = filterValues == null ? void 0 : filterValues.start) == null ? void 0 : _a.toString()) || "",
537
- type: "number"
538
- }
539
- ) }),
540
- /* @__PURE__ */ jsx3(ToolbarField, { label: "To", children: /* @__PURE__ */ jsx3(
541
- Input,
542
- {
543
- onChange: endChangeHandler,
544
- value: ((_b = filterValues == null ? void 0 : filterValues.end) == null ? void 0 : _b.toString()) || "",
545
- type: "number"
546
- }
547
- ) })
548
- ] });
549
- };
550
-
551
- // src/column-filter/TypeaheadFilter.tsx
552
- import { useTypeaheadSuggestions } from "@vuu-ui/vuu-data-react";
553
- import { ComboBoxDeprecated } from "@heswell/salt-lab";
554
- import { useCallback, useEffect, useState } from "react";
555
- import { jsx as jsx4 } from "react/jsx-runtime";
556
- var TypeaheadFilter = ({
557
- defaultTypeaheadParams,
558
- filterValues = [],
559
- onChange: onFilterChange
560
- }) => {
561
- const [tableName, columnName] = defaultTypeaheadParams;
562
- const [searchValue, setSearchValue] = useState("");
563
- const [typeaheadValues, setTypeaheadValues] = useState([]);
564
- const getSuggestions = useTypeaheadSuggestions();
565
- useEffect(() => {
566
- const params = searchValue ? [tableName, columnName, searchValue] : defaultTypeaheadParams;
567
- let isSubscribed = true;
568
- getSuggestions(params).then((options) => {
569
- if (!isSubscribed) {
570
- return;
571
- }
572
- if (isStartsWithValue(filterValues[0])) {
573
- options.unshift(filterValues[0]);
574
- }
575
- if (searchValue) {
576
- options.unshift(`${searchValue}...`);
577
- }
578
- options.concat(filterValues);
579
- setTypeaheadValues(options);
580
- });
581
- return () => {
582
- isSubscribed = false;
583
- };
584
- }, [
585
- filterValues,
586
- searchValue,
587
- columnName,
588
- tableName,
589
- getSuggestions,
590
- defaultTypeaheadParams
591
- ]);
592
- const onInputChange = useCallback(
593
- (evt) => {
594
- const value = evt.target.value;
595
- setSearchValue(value);
596
- },
597
- []
598
- );
599
- const onSelectionChange = useCallback(
600
- (_evt, selected) => {
601
- setSearchValue("");
602
- if (selected === null)
603
- return;
604
- if (selected.some(isStartsWithValue)) {
605
- selected = selected.filter(isStartsWithValue).slice(-1);
606
- }
607
- const filter = getTypeaheadFilter(
608
- columnName,
609
- selected,
610
- isStartsWithValue(selected[0])
611
- );
612
- onFilterChange(selected, filter);
613
- },
614
- [columnName, onFilterChange]
615
- );
616
- return /* @__PURE__ */ jsx4(
617
- ComboBoxDeprecated,
618
- {
619
- multiSelect: true,
620
- onInputChange,
621
- onChange: onSelectionChange,
622
- source: typeaheadValues,
623
- style: { minWidth: 200 },
624
- inputValue: searchValue,
625
- selectedItem: filterValues
626
- },
627
- columnName
628
- );
629
- };
630
-
631
- // src/column-filter/ColumnListItem.tsx
632
- import { memo } from "react";
633
- import {
634
- Highlighter,
635
- ListItem
636
- } from "@heswell/salt-lab";
637
- import { jsx as jsx5 } from "react/jsx-runtime";
638
- var ColumnListItem = (props) => {
639
- return /* @__PURE__ */ jsx5(MemoColumnItem, { ...props });
640
- };
641
- var MemoColumnItem = memo(function MemoizedItem({
642
- item,
643
- itemTextHighlightPattern,
644
- ...restProps
645
- }) {
646
- return /* @__PURE__ */ jsx5(ListItem, { ...restProps, children: /* @__PURE__ */ jsx5("span", { style: { marginLeft: 10 }, children: /* @__PURE__ */ jsx5(
647
- Highlighter,
648
- {
649
- matchPattern: itemTextHighlightPattern,
650
- text: item == null ? void 0 : item.name
651
- }
652
- ) }) });
653
- });
654
-
655
- // src/column-filter/useColumnFilterStore.ts
656
- import { filterAsQuery as filterAsQuery2 } from "@vuu-ui/vuu-utils";
657
- import { useCallback as useCallback2, useState as useState2 } from "react";
658
- var addOrReplace = (array, newValue, key) => array.filter((oldValue) => oldValue[key] !== newValue[key]).concat(newValue);
659
- var useColumnFilterStore = (onFilterSubmit) => {
660
- var _a, _b;
661
- const [selectedColumnName, setSelectedColumnName] = useState2("");
662
- const [savedFilters, setSavedFilters] = useState2([]);
663
- const [rangeValues, setRangeValues] = useState2([]);
664
- const [typeaheadValues, setTypeaheadValues] = useState2([]);
665
- const clear = () => {
666
- setSelectedColumnName("");
667
- setRangeValues([]);
668
- setTypeaheadValues([]);
669
- setSavedFilters([]);
670
- onFilterSubmit({ filter: "" });
671
- };
672
- const updateFilters = useCallback2(
673
- (newFilter) => {
674
- const newSavedFilters = addOrReplace(
675
- savedFilters,
676
- { column: selectedColumnName, filter: newFilter },
677
- "column"
678
- );
679
- setSavedFilters(newSavedFilters);
680
- const combinedFilter = newSavedFilters.map((x) => x.filter).reduce((prev, filter) => {
681
- if (filter === void 0)
682
- return prev;
683
- return addFilter(prev, filter, { combineWith: AND });
684
- }, void 0);
685
- const query = combinedFilter === void 0 ? "" : filterAsQuery2(combinedFilter);
686
- onFilterSubmit({ filter: query, filterStruct: combinedFilter });
687
- },
688
- [selectedColumnName, onFilterSubmit, savedFilters]
689
- );
690
- const onTypeaheadChange = useCallback2(
691
- (newValues, newFilter) => {
692
- setTypeaheadValues(
693
- addOrReplace(
694
- typeaheadValues,
695
- { column: selectedColumnName, value: newValues },
696
- "column"
697
- )
698
- );
699
- updateFilters(newFilter);
700
- },
701
- [selectedColumnName, typeaheadValues, updateFilters]
702
- );
703
- const onRangeChange = useCallback2(
704
- (newValues, newFilter) => {
705
- setRangeValues(
706
- addOrReplace(
707
- rangeValues,
708
- { column: selectedColumnName, value: newValues },
709
- "column"
710
- )
711
- );
712
- updateFilters(newFilter);
713
- },
714
- [selectedColumnName, rangeValues, updateFilters]
715
- );
716
- const onSelectedColumnChange = useCallback2(
717
- (column) => setSelectedColumnName((column == null ? void 0 : column.name) || ""),
718
- []
719
- );
720
- const rangeValue = (_a = rangeValues.filter(
721
- (v) => v.column === selectedColumnName
722
- )[0]) == null ? void 0 : _a.value;
723
- const typeaheadValue = (_b = typeaheadValues.filter(
724
- (v) => v.column === selectedColumnName
725
- )[0]) == null ? void 0 : _b.value;
726
- return {
727
- clear,
728
- selectedColumnName,
729
- rangeValue,
730
- typeaheadValue,
731
- onSelectedColumnChange,
732
- onRangeChange,
733
- onTypeaheadChange
734
- };
735
- };
736
-
737
- // src/column-filter/ColumnFilter.tsx
738
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
739
- var ColumnFilter = ({
740
- className,
741
- table,
742
- columns,
743
- onFilterSubmit,
744
- ...htmlAttributes
745
- }) => {
746
- const {
747
- clear,
748
- onTypeaheadChange,
749
- onRangeChange,
750
- onSelectedColumnChange,
751
- selectedColumnName,
752
- rangeValue,
753
- typeaheadValue
754
- } = useColumnFilterStore(onFilterSubmit);
755
- const getFilterComponent = () => {
756
- var _a;
757
- const defaultTypeaheadParams = [table, selectedColumnName];
758
- const selectedColumnType = (_a = columns.find(
759
- (column) => column.name === selectedColumnName
760
- )) == null ? void 0 : _a.serverDataType;
761
- switch (selectedColumnType) {
762
- case "string":
763
- case "char":
764
- return /* @__PURE__ */ jsx6(
765
- ToolbarField2,
766
- {
767
- label: "Start typing to select a filter",
768
- labelPlacement: "top",
769
- children: /* @__PURE__ */ jsx6(
770
- TypeaheadFilter,
771
- {
772
- defaultTypeaheadParams,
773
- filterValues: typeaheadValue,
774
- onChange: onTypeaheadChange
775
- }
776
- )
777
- }
778
- );
779
- case "int":
780
- case "long":
781
- case "double":
782
- return /* @__PURE__ */ jsx6(ToolbarField2, { label: "Select a range", labelPlacement: "top", children: /* @__PURE__ */ jsx6(
783
- RangeFilter,
784
- {
785
- defaultTypeaheadParams,
786
- filterValues: rangeValue,
787
- onChange: onRangeChange
788
- }
789
- ) });
790
- default:
791
- return /* @__PURE__ */ jsx6(ToolbarField2, { children: /* @__PURE__ */ jsx6(Text, { children: "Data type unsupported" }) });
792
- }
793
- };
794
- return /* @__PURE__ */ jsxs3(
795
- Toolbar,
796
- {
797
- ...htmlAttributes,
798
- style: { alignItems: "center", height: "36px" },
799
- children: [
800
- /* @__PURE__ */ jsx6(
801
- ToolbarField2,
802
- {
803
- label: "Select a column to filter",
804
- labelPlacement: "top",
805
- style: { width: 180 },
806
- children: /* @__PURE__ */ jsx6(
807
- Dropdown,
808
- {
809
- source: columns,
810
- ListItem: ColumnListItem,
811
- itemToString: (column) => column.name,
812
- onSelectionChange: (_evt, column) => onSelectedColumnChange(column)
813
- }
814
- )
815
- }
816
- ),
817
- selectedColumnName && getFilterComponent(),
818
- /* @__PURE__ */ jsx6(ToolbarButton, { onClick: clear, children: /* @__PURE__ */ jsx6(DeleteIcon, {}) })
819
- ]
820
- }
821
- );
822
- };
823
-
824
- // src/filter-input/FilterInput.tsx
825
- import { Button } from "@salt-ds/core";
826
-
827
- // src/filter-input/useCodeMirrorEditor.ts
828
- import {
829
- autocompletion,
830
- defaultKeymap,
831
- EditorState as EditorState2,
832
- EditorView as EditorView2,
833
- ensureSyntaxTree,
834
- keymap,
835
- minimalSetup,
836
- startCompletion
837
- } from "@vuu-ui/vuu-codemirror";
838
-
839
- // ../../node_modules/@lezer/common/dist/index.js
840
- var DefaultBufferLength = 1024;
841
- var nextPropID = 0;
842
- var Range = class {
843
- constructor(from, to) {
844
- this.from = from;
845
- this.to = to;
846
- }
847
- };
848
- var NodeProp = class {
849
- /// Create a new node prop type.
850
- constructor(config = {}) {
851
- this.id = nextPropID++;
852
- this.perNode = !!config.perNode;
853
- this.deserialize = config.deserialize || (() => {
854
- throw new Error("This node type doesn't define a deserialize function");
855
- });
856
- }
857
- /// This is meant to be used with
858
- /// [`NodeSet.extend`](#common.NodeSet.extend) or
859
- /// [`LRParser.configure`](#lr.ParserConfig.props) to compute
860
- /// prop values for each node type in the set. Takes a [match
861
- /// object](#common.NodeType^match) or function that returns undefined
862
- /// if the node type doesn't get this prop, and the prop's value if
863
- /// it does.
864
- add(match) {
865
- if (this.perNode)
866
- throw new RangeError("Can't add per-node props to node types");
867
- if (typeof match != "function")
868
- match = NodeType.match(match);
869
- return (type) => {
870
- let result = match(type);
871
- return result === void 0 ? null : [this, result];
872
- };
873
- }
874
- };
875
- NodeProp.closedBy = new NodeProp({ deserialize: (str) => str.split(" ") });
876
- NodeProp.openedBy = new NodeProp({ deserialize: (str) => str.split(" ") });
877
- NodeProp.group = new NodeProp({ deserialize: (str) => str.split(" ") });
878
- NodeProp.contextHash = new NodeProp({ perNode: true });
879
- NodeProp.lookAhead = new NodeProp({ perNode: true });
880
- NodeProp.mounted = new NodeProp({ perNode: true });
881
- var noProps = /* @__PURE__ */ Object.create(null);
882
- var NodeType = class {
883
- /// @internal
884
- constructor(name, props, id, flags = 0) {
885
- this.name = name;
886
- this.props = props;
887
- this.id = id;
888
- this.flags = flags;
889
- }
890
- /// Define a node type.
891
- static define(spec) {
892
- let props = spec.props && spec.props.length ? /* @__PURE__ */ Object.create(null) : noProps;
893
- let flags = (spec.top ? 1 : 0) | (spec.skipped ? 2 : 0) | (spec.error ? 4 : 0) | (spec.name == null ? 8 : 0);
894
- let type = new NodeType(spec.name || "", props, spec.id, flags);
895
- if (spec.props)
896
- for (let src of spec.props) {
897
- if (!Array.isArray(src))
898
- src = src(type);
899
- if (src) {
900
- if (src[0].perNode)
901
- throw new RangeError("Can't store a per-node prop on a node type");
902
- props[src[0].id] = src[1];
903
- }
904
- }
905
- return type;
906
- }
907
- /// Retrieves a node prop for this type. Will return `undefined` if
908
- /// the prop isn't present on this node.
909
- prop(prop) {
910
- return this.props[prop.id];
911
- }
912
- /// True when this is the top node of a grammar.
913
- get isTop() {
914
- return (this.flags & 1) > 0;
915
- }
916
- /// True when this node is produced by a skip rule.
917
- get isSkipped() {
918
- return (this.flags & 2) > 0;
919
- }
920
- /// Indicates whether this is an error node.
921
- get isError() {
922
- return (this.flags & 4) > 0;
923
- }
924
- /// When true, this node type doesn't correspond to a user-declared
925
- /// named node, for example because it is used to cache repetition.
926
- get isAnonymous() {
927
- return (this.flags & 8) > 0;
928
- }
929
- /// Returns true when this node's name or one of its
930
- /// [groups](#common.NodeProp^group) matches the given string.
931
- is(name) {
932
- if (typeof name == "string") {
933
- if (this.name == name)
934
- return true;
935
- let group = this.prop(NodeProp.group);
936
- return group ? group.indexOf(name) > -1 : false;
937
- }
938
- return this.id == name;
939
- }
940
- /// Create a function from node types to arbitrary values by
941
- /// specifying an object whose property names are node or
942
- /// [group](#common.NodeProp^group) names. Often useful with
943
- /// [`NodeProp.add`](#common.NodeProp.add). You can put multiple
944
- /// names, separated by spaces, in a single property name to map
945
- /// multiple node names to a single value.
946
- static match(map) {
947
- let direct = /* @__PURE__ */ Object.create(null);
948
- for (let prop in map)
949
- for (let name of prop.split(" "))
950
- direct[name] = map[prop];
951
- return (node) => {
952
- for (let groups = node.prop(NodeProp.group), i = -1; i < (groups ? groups.length : 0); i++) {
953
- let found = direct[i < 0 ? node.name : groups[i]];
954
- if (found)
955
- return found;
956
- }
957
- };
958
- }
959
- };
960
- NodeType.none = new NodeType(
961
- "",
962
- /* @__PURE__ */ Object.create(null),
963
- 0,
964
- 8
965
- /* NodeFlag.Anonymous */
966
- );
967
- var NodeSet = class {
968
- /// Create a set with the given types. The `id` property of each
969
- /// type should correspond to its position within the array.
970
- constructor(types) {
971
- this.types = types;
972
- for (let i = 0; i < types.length; i++)
973
- if (types[i].id != i)
974
- throw new RangeError("Node type ids should correspond to array positions when creating a node set");
975
- }
976
- /// Create a copy of this set with some node properties added. The
977
- /// arguments to this method can be created with
978
- /// [`NodeProp.add`](#common.NodeProp.add).
979
- extend(...props) {
980
- let newTypes = [];
981
- for (let type of this.types) {
982
- let newProps = null;
983
- for (let source of props) {
984
- let add = source(type);
985
- if (add) {
986
- if (!newProps)
987
- newProps = Object.assign({}, type.props);
988
- newProps[add[0].id] = add[1];
989
- }
990
- }
991
- newTypes.push(newProps ? new NodeType(type.name, newProps, type.id, type.flags) : type);
992
- }
993
- return new NodeSet(newTypes);
994
- }
995
- };
996
- var CachedNode = /* @__PURE__ */ new WeakMap();
997
- var CachedInnerNode = /* @__PURE__ */ new WeakMap();
998
- var IterMode;
999
- (function(IterMode2) {
1000
- IterMode2[IterMode2["ExcludeBuffers"] = 1] = "ExcludeBuffers";
1001
- IterMode2[IterMode2["IncludeAnonymous"] = 2] = "IncludeAnonymous";
1002
- IterMode2[IterMode2["IgnoreMounts"] = 4] = "IgnoreMounts";
1003
- IterMode2[IterMode2["IgnoreOverlays"] = 8] = "IgnoreOverlays";
1004
- })(IterMode || (IterMode = {}));
1005
- var Tree = class {
1006
- /// Construct a new tree. See also [`Tree.build`](#common.Tree^build).
1007
- constructor(type, children, positions, length, props) {
1008
- this.type = type;
1009
- this.children = children;
1010
- this.positions = positions;
1011
- this.length = length;
1012
- this.props = null;
1013
- if (props && props.length) {
1014
- this.props = /* @__PURE__ */ Object.create(null);
1015
- for (let [prop, value] of props)
1016
- this.props[typeof prop == "number" ? prop : prop.id] = value;
1017
- }
1018
- }
1019
- /// @internal
1020
- toString() {
1021
- let mounted = this.prop(NodeProp.mounted);
1022
- if (mounted && !mounted.overlay)
1023
- return mounted.tree.toString();
1024
- let children = "";
1025
- for (let ch of this.children) {
1026
- let str = ch.toString();
1027
- if (str) {
1028
- if (children)
1029
- children += ",";
1030
- children += str;
1031
- }
1032
- }
1033
- return !this.type.name ? children : (/\W/.test(this.type.name) && !this.type.isError ? JSON.stringify(this.type.name) : this.type.name) + (children.length ? "(" + children + ")" : "");
1034
- }
1035
- /// Get a [tree cursor](#common.TreeCursor) positioned at the top of
1036
- /// the tree. Mode can be used to [control](#common.IterMode) which
1037
- /// nodes the cursor visits.
1038
- cursor(mode = 0) {
1039
- return new TreeCursor(this.topNode, mode);
1040
- }
1041
- /// Get a [tree cursor](#common.TreeCursor) pointing into this tree
1042
- /// at the given position and side (see
1043
- /// [`moveTo`](#common.TreeCursor.moveTo).
1044
- cursorAt(pos, side = 0, mode = 0) {
1045
- let scope = CachedNode.get(this) || this.topNode;
1046
- let cursor = new TreeCursor(scope);
1047
- cursor.moveTo(pos, side);
1048
- CachedNode.set(this, cursor._tree);
1049
- return cursor;
1050
- }
1051
- /// Get a [syntax node](#common.SyntaxNode) object for the top of the
1052
- /// tree.
1053
- get topNode() {
1054
- return new TreeNode(this, 0, 0, null);
1055
- }
1056
- /// Get the [syntax node](#common.SyntaxNode) at the given position.
1057
- /// If `side` is -1, this will move into nodes that end at the
1058
- /// position. If 1, it'll move into nodes that start at the
1059
- /// position. With 0, it'll only enter nodes that cover the position
1060
- /// from both sides.
1061
- ///
1062
- /// Note that this will not enter
1063
- /// [overlays](#common.MountedTree.overlay), and you often want
1064
- /// [`resolveInner`](#common.Tree.resolveInner) instead.
1065
- resolve(pos, side = 0) {
1066
- let node = resolveNode(CachedNode.get(this) || this.topNode, pos, side, false);
1067
- CachedNode.set(this, node);
1068
- return node;
1069
- }
1070
- /// Like [`resolve`](#common.Tree.resolve), but will enter
1071
- /// [overlaid](#common.MountedTree.overlay) nodes, producing a syntax node
1072
- /// pointing into the innermost overlaid tree at the given position
1073
- /// (with parent links going through all parent structure, including
1074
- /// the host trees).
1075
- resolveInner(pos, side = 0) {
1076
- let node = resolveNode(CachedInnerNode.get(this) || this.topNode, pos, side, true);
1077
- CachedInnerNode.set(this, node);
1078
- return node;
1079
- }
1080
- /// Iterate over the tree and its children, calling `enter` for any
1081
- /// node that touches the `from`/`to` region (if given) before
1082
- /// running over such a node's children, and `leave` (if given) when
1083
- /// leaving the node. When `enter` returns `false`, that node will
1084
- /// not have its children iterated over (or `leave` called).
1085
- iterate(spec) {
1086
- let { enter, leave, from = 0, to = this.length } = spec;
1087
- let mode = spec.mode || 0, anon = (mode & IterMode.IncludeAnonymous) > 0;
1088
- for (let c = this.cursor(mode | IterMode.IncludeAnonymous); ; ) {
1089
- let entered = false;
1090
- if (c.from <= to && c.to >= from && (!anon && c.type.isAnonymous || enter(c) !== false)) {
1091
- if (c.firstChild())
1092
- continue;
1093
- entered = true;
1094
- }
1095
- for (; ; ) {
1096
- if (entered && leave && (anon || !c.type.isAnonymous))
1097
- leave(c);
1098
- if (c.nextSibling())
1099
- break;
1100
- if (!c.parent())
1101
- return;
1102
- entered = true;
1103
- }
1104
- }
1105
- }
1106
- /// Get the value of the given [node prop](#common.NodeProp) for this
1107
- /// node. Works with both per-node and per-type props.
1108
- prop(prop) {
1109
- return !prop.perNode ? this.type.prop(prop) : this.props ? this.props[prop.id] : void 0;
1110
- }
1111
- /// Returns the node's [per-node props](#common.NodeProp.perNode) in a
1112
- /// format that can be passed to the [`Tree`](#common.Tree)
1113
- /// constructor.
1114
- get propValues() {
1115
- let result = [];
1116
- if (this.props)
1117
- for (let id in this.props)
1118
- result.push([+id, this.props[id]]);
1119
- return result;
1120
- }
1121
- /// Balance the direct children of this tree, producing a copy of
1122
- /// which may have children grouped into subtrees with type
1123
- /// [`NodeType.none`](#common.NodeType^none).
1124
- balance(config = {}) {
1125
- return this.children.length <= 8 ? this : balanceRange(NodeType.none, this.children, this.positions, 0, this.children.length, 0, this.length, (children, positions, length) => new Tree(this.type, children, positions, length, this.propValues), config.makeTree || ((children, positions, length) => new Tree(NodeType.none, children, positions, length)));
1126
- }
1127
- /// Build a tree from a postfix-ordered buffer of node information,
1128
- /// or a cursor over such a buffer.
1129
- static build(data) {
1130
- return buildTree(data);
1131
- }
1132
- };
1133
- Tree.empty = new Tree(NodeType.none, [], [], 0);
1134
- var FlatBufferCursor = class {
1135
- constructor(buffer, index) {
1136
- this.buffer = buffer;
1137
- this.index = index;
1138
- }
1139
- get id() {
1140
- return this.buffer[this.index - 4];
1141
- }
1142
- get start() {
1143
- return this.buffer[this.index - 3];
1144
- }
1145
- get end() {
1146
- return this.buffer[this.index - 2];
417
+ get end() {
418
+ return this.buffer[this.index - 2];
1147
419
  }
1148
420
  get size() {
1149
421
  return this.buffer[this.index - 1];
@@ -1412,8 +684,8 @@ var TreeNode = class {
1412
684
  return enterUnfinishedNodesBefore(this, pos);
1413
685
  }
1414
686
  getChild(type, before = null, after = null) {
1415
- let r2 = getChildren(this, type, before, after);
1416
- return r2.length ? r2[0] : null;
687
+ let r = getChildren(this, type, before, after);
688
+ return r.length ? r[0] : null;
1417
689
  }
1418
690
  getChildren(type, before = null, after = null) {
1419
691
  return getChildren(this, type, before, after);
@@ -1591,8 +863,8 @@ var BufferNode = class {
1591
863
  return this.context.buffer.childString(this.index);
1592
864
  }
1593
865
  getChild(type, before = null, after = null) {
1594
- let r2 = getChildren(this, type, before, after);
1595
- return r2.length ? r2[0] : null;
866
+ let r = getChildren(this, type, before, after);
867
+ return r.length ? r[0] : null;
1596
868
  }
1597
869
  getChildren(type, before = null, after = null) {
1598
870
  return getChildren(this, type, before, after);
@@ -2141,7 +1413,7 @@ var Parser = class {
2141
1413
  startParse(input, fragments, ranges) {
2142
1414
  if (typeof input == "string")
2143
1415
  input = new StringInput(input);
2144
- ranges = !ranges ? [new Range(0, input.length)] : ranges.length ? ranges.map((r2) => new Range(r2.from, r2.to)) : [new Range(0, 0)];
1416
+ ranges = !ranges ? [new Range(0, input.length)] : ranges.length ? ranges.map((r) => new Range(r.from, r.to)) : [new Range(0, 0)];
2145
1417
  return this.createParse(input, fragments || [], ranges);
2146
1418
  }
2147
1419
  /// Run a full parse, returning the resulting tree.
@@ -2890,11 +2162,11 @@ var InputStream = class {
2890
2162
  if (from >= this.range.from && to <= this.range.to)
2891
2163
  return this.input.read(from, to);
2892
2164
  let result = "";
2893
- for (let r2 of this.ranges) {
2894
- if (r2.from >= to)
2165
+ for (let r of this.ranges) {
2166
+ if (r.from >= to)
2895
2167
  break;
2896
- if (r2.to > from)
2897
- result += this.input.read(Math.max(r2.from, from), Math.min(r2.to, to));
2168
+ if (r.to > from)
2169
+ result += this.input.read(Math.max(r.from, from), Math.min(r.to, to));
2898
2170
  }
2899
2171
  return result;
2900
2172
  }
@@ -3525,7 +2797,7 @@ var LRParser = class extends Parser {
3525
2797
  this.minRepeatTerm = nodeNames.length;
3526
2798
  for (let i = 0; i < spec.repeatNodeCount; i++)
3527
2799
  nodeNames.push("");
3528
- let topTerms = Object.keys(spec.topRules).map((r2) => spec.topRules[r2][1]);
2800
+ let topTerms = Object.keys(spec.topRules).map((r) => spec.topRules[r][1]);
3529
2801
  let nodeProps = [];
3530
2802
  for (let i = 0; i < nodeNames.length; i++)
3531
2803
  nodeProps.push([]);
@@ -3705,13 +2977,13 @@ var LRParser = class extends Parser {
3705
2977
  }
3706
2978
  if (config.tokenizers)
3707
2979
  copy.tokenizers = this.tokenizers.map((t) => {
3708
- let found = config.tokenizers.find((r2) => r2.from == t);
2980
+ let found = config.tokenizers.find((r) => r.from == t);
3709
2981
  return found ? found.to : t;
3710
2982
  });
3711
2983
  if (config.specializers) {
3712
2984
  copy.specializers = this.specializers.slice();
3713
2985
  copy.specializerSpecs = this.specializerSpecs.map((s, i) => {
3714
- let found = config.specializers.find((r2) => r2.from == s.external);
2986
+ let found = config.specializers.find((r) => r.from == s.external);
3715
2987
  if (!found)
3716
2988
  return s;
3717
2989
  let spec = Object.assign(Object.assign({}, s), { external: found.to });
@@ -3822,9 +3094,9 @@ var parser = LRParser.deserialize({
3822
3094
 
3823
3095
  // ../vuu-filter-parser/src/FilterTreeWalker.ts
3824
3096
  import {
3825
- isMultiClauseFilter as isMultiClauseFilter2,
3826
- isMultiValueFilter as isMultiValueFilter2,
3827
- isSingleValueFilter as isSingleValueFilter2
3097
+ isMultiClauseFilter,
3098
+ isMultiValueFilter,
3099
+ isSingleValueFilter
3828
3100
  } from "@vuu-ui/vuu-utils";
3829
3101
  var _filter;
3830
3102
  var FilterExpression = class {
@@ -3832,7 +3104,7 @@ var FilterExpression = class {
3832
3104
  __privateAdd(this, _filter, void 0);
3833
3105
  }
3834
3106
  setFilterCombinatorOp(op, filter = __privateGet(this, _filter)) {
3835
- if (isMultiClauseFilter2(filter) && filter.op === op) {
3107
+ if (isMultiClauseFilter(filter) && filter.op === op) {
3836
3108
  return;
3837
3109
  } else {
3838
3110
  __privateSet(this, _filter, {
@@ -3844,14 +3116,14 @@ var FilterExpression = class {
3844
3116
  add(filter) {
3845
3117
  if (__privateGet(this, _filter) === void 0) {
3846
3118
  __privateSet(this, _filter, filter);
3847
- } else if (isMultiClauseFilter2(__privateGet(this, _filter))) {
3119
+ } else if (isMultiClauseFilter(__privateGet(this, _filter))) {
3848
3120
  __privateGet(this, _filter).filters.push(filter);
3849
3121
  } else {
3850
3122
  throw Error(`Invalid filter passed to FilterExpression`);
3851
3123
  }
3852
3124
  }
3853
3125
  setColumn(column, filter = __privateGet(this, _filter)) {
3854
- if (isMultiClauseFilter2(filter)) {
3126
+ if (isMultiClauseFilter(filter)) {
3855
3127
  const target = filter.filters.at(-1);
3856
3128
  if (target) {
3857
3129
  this.setColumn(column, target);
@@ -3861,7 +3133,7 @@ var FilterExpression = class {
3861
3133
  }
3862
3134
  }
3863
3135
  setOp(value, filter = __privateGet(this, _filter)) {
3864
- if (isMultiClauseFilter2(filter)) {
3136
+ if (isMultiClauseFilter(filter)) {
3865
3137
  const target = filter.filters.at(-1);
3866
3138
  if (target) {
3867
3139
  this.setOp(value, target);
@@ -3872,15 +3144,15 @@ var FilterExpression = class {
3872
3144
  }
3873
3145
  setValue(value, filter = __privateGet(this, _filter)) {
3874
3146
  var _a;
3875
- if (isMultiClauseFilter2(filter)) {
3147
+ if (isMultiClauseFilter(filter)) {
3876
3148
  const target = filter.filters.at(-1);
3877
3149
  if (target) {
3878
3150
  this.setValue(value, target);
3879
3151
  }
3880
- } else if (isMultiValueFilter2(filter)) {
3152
+ } else if (isMultiValueFilter(filter)) {
3881
3153
  (_a = filter.values) != null ? _a : filter.values = [];
3882
3154
  filter.values.push(value);
3883
- } else if (isSingleValueFilter2(filter)) {
3155
+ } else if (isSingleValueFilter(filter)) {
3884
3156
  filter.value = value;
3885
3157
  }
3886
3158
  }
@@ -3944,7 +3216,7 @@ var strictParser = parser.configure({ strict: true });
3944
3216
 
3945
3217
  // src/filter-input/useCodeMirrorEditor.ts
3946
3218
  var import_classnames = __toESM(require_classnames(), 1);
3947
- import { useEffect as useEffect2, useMemo, useRef } from "react";
3219
+ import { useEffect, useMemo, useRef } from "react";
3948
3220
 
3949
3221
  // src/filter-input/FilterLanguage.ts
3950
3222
  import {
@@ -4062,7 +3334,7 @@ import {
4062
3334
  getValue,
4063
3335
  syntaxTree
4064
3336
  } from "@vuu-ui/vuu-codemirror";
4065
- import { useCallback as useCallback3 } from "react";
3337
+ import { useCallback } from "react";
4066
3338
  var getOperator = (node, state) => {
4067
3339
  let maybeColumnNode = node.prevSibling || node.parent;
4068
3340
  while (maybeColumnNode && !["Column", "Operator", "In"].includes(maybeColumnNode.name)) {
@@ -4132,7 +3404,7 @@ var getSetValues = (node, state) => {
4132
3404
  return values;
4133
3405
  };
4134
3406
  var useAutoComplete = (suggestionProvider, onSubmit, existingFilter) => {
4135
- const makeSuggestions = useCallback3(
3407
+ const makeSuggestions = useCallback(
4136
3408
  async (context, suggestionType, optionalArgs = {}) => {
4137
3409
  const { startsWith = "" } = optionalArgs;
4138
3410
  const options = await suggestionProvider.getSuggestions(
@@ -4143,7 +3415,7 @@ var useAutoComplete = (suggestionProvider, onSubmit, existingFilter) => {
4143
3415
  },
4144
3416
  [suggestionProvider]
4145
3417
  );
4146
- return useCallback3(
3418
+ return useCallback(
4147
3419
  async (context) => {
4148
3420
  var _a, _b;
4149
3421
  const { state, pos } = context;
@@ -4410,7 +3682,7 @@ var useCodeMirrorEditor = ({
4410
3682
  };
4411
3683
  return [createState2, clearInput2];
4412
3684
  }, [completionFn, onSubmitFilter]);
4413
- useEffect2(() => {
3685
+ useEffect(() => {
4414
3686
  if (!editorRef.current) {
4415
3687
  throw Error("editor not in dom");
4416
3688
  }
@@ -4427,7 +3699,7 @@ var useCodeMirrorEditor = ({
4427
3699
  };
4428
3700
 
4429
3701
  // src/filter-input/FilterInput.tsx
4430
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
3702
+ import { jsx, jsxs } from "react/jsx-runtime";
4431
3703
  var classBase = "vuuFilterInput";
4432
3704
  var FilterInput = ({
4433
3705
  existingFilter,
@@ -4442,8 +3714,8 @@ var FilterInput = ({
4442
3714
  onSubmitFilter,
4443
3715
  suggestionProvider
4444
3716
  });
4445
- return /* @__PURE__ */ jsxs4("div", { ...props, className: classBase, children: [
4446
- /* @__PURE__ */ jsx7(
3717
+ return /* @__PURE__ */ jsxs("div", { ...props, className: classBase, children: [
3718
+ /* @__PURE__ */ jsx(
4447
3719
  Button,
4448
3720
  {
4449
3721
  className: `${classBase}-FilterButton`,
@@ -4451,8 +3723,8 @@ var FilterInput = ({
4451
3723
  tabIndex: -1
4452
3724
  }
4453
3725
  ),
4454
- /* @__PURE__ */ jsx7("div", { className: `${classBase}-Editor`, ref: editorRef }),
4455
- /* @__PURE__ */ jsx7(
3726
+ /* @__PURE__ */ jsx("div", { className: `${classBase}-Editor`, ref: editorRef }),
3727
+ /* @__PURE__ */ jsx(
4456
3728
  Button,
4457
3729
  {
4458
3730
  className: `${classBase}-ClearButton`,
@@ -4474,9 +3746,9 @@ import {
4474
3746
  } from "@vuu-ui/vuu-codemirror";
4475
3747
  import {
4476
3748
  getTypeaheadParams,
4477
- useTypeaheadSuggestions as useTypeaheadSuggestions2
3749
+ useTypeaheadSuggestions
4478
3750
  } from "@vuu-ui/vuu-data-react";
4479
- import { useCallback as useCallback4, useRef as useRef2 } from "react";
3751
+ import { useCallback as useCallback2, useRef as useRef2 } from "react";
4480
3752
 
4481
3753
  // src/filter-input/filterInfo.ts
4482
3754
  import { createEl } from "@vuu-ui/vuu-utils";
@@ -4570,11 +3842,11 @@ var useFilterSuggestionProvider = ({
4570
3842
  namedFilters,
4571
3843
  saveOptions = defaultSaveOptions,
4572
3844
  table,
4573
- typeaheadHook: useTypeahead = useTypeaheadSuggestions2
3845
+ typeaheadHook: useTypeahead = useTypeaheadSuggestions
4574
3846
  }) => {
4575
3847
  const latestSuggestionsRef = useRef2();
4576
3848
  const getTypeaheadSuggestions = useTypeahead();
4577
- const getSuggestions = useCallback4(
3849
+ const getSuggestions = useCallback2(
4578
3850
  async (suggestionType, options = NONE) => {
4579
3851
  const {
4580
3852
  columnName,
@@ -4664,7 +3936,7 @@ var useFilterSuggestionProvider = ({
4664
3936
  },
4665
3937
  [columns, getTypeaheadSuggestions, namedFilters, saveOptions, table]
4666
3938
  );
4667
- const isPartialMatch = useCallback4(
3939
+ const isPartialMatch = useCallback2(
4668
3940
  async (valueType, columnName, pattern) => {
4669
3941
  const suggestions = (
4670
3942
  // latestSuggestions && latestSuggestions.length > 0
@@ -4680,216 +3952,506 @@ var useFilterSuggestionProvider = ({
4680
3952
  }
4681
3953
  }
4682
3954
  }
4683
- return false;
3955
+ return false;
3956
+ },
3957
+ [getSuggestions]
3958
+ );
3959
+ return {
3960
+ getSuggestions,
3961
+ isPartialMatch
3962
+ };
3963
+ };
3964
+
3965
+ // src/filter-toolbar/FilterToolbar.tsx
3966
+ var import_classnames2 = __toESM(require_classnames(), 1);
3967
+ import { Toolbar } from "@heswell/salt-lab";
3968
+
3969
+ // src/filter-toolbar/useFilterToolbar.tsx
3970
+ import {
3971
+ isMultiValueFilter as isMultiValueFilter2,
3972
+ isNamedFilter,
3973
+ isSingleValueFilter as isSingleValueFilter2
3974
+ } from "@vuu-ui/vuu-utils";
3975
+ import { ToggleButton, ToolbarField } from "@heswell/salt-lab";
3976
+
3977
+ // src/filter-toolbar/FilterDropdown.tsx
3978
+ import { Dropdown } from "@heswell/salt-lab";
3979
+ import { useCallback as useCallback3, useState } from "react";
3980
+ import { jsx as jsx2 } from "react/jsx-runtime";
3981
+ var isString = (s) => typeof s === "string";
3982
+ var stripQuotes = (selected) => {
3983
+ if (isString(selected)) {
3984
+ if (selected.startsWith('"') && selected.endsWith('"')) {
3985
+ return selected.slice(1, -1);
3986
+ } else {
3987
+ return selected;
3988
+ }
3989
+ } else {
3990
+ return selected.map(stripQuotes);
3991
+ }
3992
+ };
3993
+ var FilterDropdown = ({
3994
+ column,
3995
+ selected: selectedProp,
3996
+ suggestionProvider,
3997
+ ...props
3998
+ }) => {
3999
+ const selected = selectedProp != null ? stripQuotes(selectedProp) : void 0;
4000
+ const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
4001
+ const [values, setValues] = useState(initialValues);
4002
+ console.log({ initialValues });
4003
+ const handleOpenChange = useCallback3(
4004
+ async (isOpen) => {
4005
+ if (isOpen) {
4006
+ const values2 = await suggestionProvider.getSuggestions("columnValue", {
4007
+ columnName: column
4008
+ });
4009
+ console.log({ values: values2 });
4010
+ setValues(values2.map((suggestion) => suggestion.label));
4011
+ }
4012
+ },
4013
+ [column, suggestionProvider]
4014
+ );
4015
+ return /* @__PURE__ */ jsx2(
4016
+ Dropdown,
4017
+ {
4018
+ ...props,
4019
+ onOpenChange: handleOpenChange,
4020
+ selected,
4021
+ source: values
4022
+ }
4023
+ );
4024
+ };
4025
+
4026
+ // src/filter-toolbar/FilterDropdownMultiSelect.tsx
4027
+ import { Dropdown as Dropdown2 } from "@heswell/salt-lab";
4028
+ import { useCallback as useCallback4, useState as useState2 } from "react";
4029
+ import { jsx as jsx3 } from "react/jsx-runtime";
4030
+ var isString2 = (s) => typeof s === "string";
4031
+ var stripQuotes2 = (selected) => {
4032
+ if (selected === void 0) {
4033
+ return void 0;
4034
+ } else if (isString2(selected)) {
4035
+ if (selected.startsWith('"') && selected.endsWith('"')) {
4036
+ return selected.slice(1, -1);
4037
+ } else {
4038
+ return selected;
4039
+ }
4040
+ } else {
4041
+ return selected.map(stripQuotes2);
4042
+ }
4043
+ };
4044
+ var FilterDropdownMultiSelect = ({
4045
+ column,
4046
+ selected: selectedProp,
4047
+ suggestionProvider,
4048
+ ...props
4049
+ }) => {
4050
+ const selected = stripQuotes2(selectedProp);
4051
+ const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
4052
+ const [values, setValues] = useState2(initialValues);
4053
+ const handleOpenChange = useCallback4(
4054
+ async (isOpen) => {
4055
+ if (isOpen) {
4056
+ const values2 = await suggestionProvider.getSuggestions("columnValue", {
4057
+ columnName: column
4058
+ });
4059
+ console.log({ values: values2 });
4060
+ setValues(values2.map((suggestion) => suggestion.label));
4061
+ }
4684
4062
  },
4685
- [getSuggestions]
4063
+ [column, suggestionProvider]
4686
4064
  );
4687
- return {
4688
- getSuggestions,
4689
- isPartialMatch
4690
- };
4065
+ return /* @__PURE__ */ jsx3(
4066
+ Dropdown2,
4067
+ {
4068
+ ...props,
4069
+ onOpenChange: handleOpenChange,
4070
+ selected,
4071
+ selectionStrategy: "multiple",
4072
+ source: values
4073
+ }
4074
+ );
4075
+ };
4076
+
4077
+ // src/filter-toolbar/useFilterToolbar.tsx
4078
+ import { jsx as jsx4 } from "react/jsx-runtime";
4079
+ var filterToControl = (filter, suggestionProvider) => {
4080
+ if (isNamedFilter(filter)) {
4081
+ return /* @__PURE__ */ jsx4(
4082
+ ToggleButton,
4083
+ {
4084
+ className: "vuuToggleButton",
4085
+ toggled: true,
4086
+ variant: "secondary",
4087
+ children: filter.name
4088
+ }
4089
+ );
4090
+ }
4091
+ if (isSingleValueFilter2(filter)) {
4092
+ const { column, value } = filter;
4093
+ return /* @__PURE__ */ jsx4(
4094
+ ToolbarField,
4095
+ {
4096
+ className: "vuuFilterDropdown",
4097
+ label: column,
4098
+ labelPlacement: "top",
4099
+ children: /* @__PURE__ */ jsx4(
4100
+ FilterDropdown,
4101
+ {
4102
+ column,
4103
+ selected: value.toString(),
4104
+ selectionStrategy: "default",
4105
+ source: [value.toString()],
4106
+ suggestionProvider,
4107
+ style: { width: 100 }
4108
+ }
4109
+ )
4110
+ },
4111
+ column
4112
+ );
4113
+ }
4114
+ if (isMultiValueFilter2(filter)) {
4115
+ const values = filter.values.map((v) => v.toString());
4116
+ return /* @__PURE__ */ jsx4(
4117
+ ToolbarField,
4118
+ {
4119
+ className: "vuuFilterDropdown",
4120
+ label: filter.column,
4121
+ labelPlacement: "top",
4122
+ children: /* @__PURE__ */ jsx4(
4123
+ FilterDropdownMultiSelect,
4124
+ {
4125
+ column: filter.column,
4126
+ selected: values,
4127
+ source: values,
4128
+ suggestionProvider,
4129
+ style: { width: 100 }
4130
+ }
4131
+ )
4132
+ },
4133
+ filter.column
4134
+ );
4135
+ }
4136
+ return filter.filters.map(
4137
+ (filter2) => filterToControl(filter2, suggestionProvider)
4138
+ );
4139
+ };
4140
+ var useFilterToolbar = ({
4141
+ filter,
4142
+ suggestionProvider
4143
+ }) => {
4144
+ if (filter) {
4145
+ return filterToControl(filter, suggestionProvider);
4146
+ }
4147
+ return [];
4691
4148
  };
4692
4149
 
4693
4150
  // src/filter-toolbar/FilterToolbar.tsx
4694
- var import_classnames2 = __toESM(require_classnames(), 1);
4695
- import { Toolbar as Toolbar2 } from "@heswell/salt-lab";
4151
+ import { jsx as jsx5 } from "react/jsx-runtime";
4152
+ var FilterToolbar = ({
4153
+ className,
4154
+ filter,
4155
+ suggestionProvider,
4156
+ ...props
4157
+ }) => {
4158
+ console.log(`FilterToolbar ${JSON.stringify(filter, null, 2)}`);
4159
+ const toolbarItems = useFilterToolbar({ filter, suggestionProvider });
4160
+ return /* @__PURE__ */ jsx5(Toolbar, { className: (0, import_classnames2.default)("vuuFilterToolbar", className), ...props, children: toolbarItems });
4161
+ };
4696
4162
 
4697
- // src/filter-toolbar/useFilterToolbar.tsx
4163
+ // src/filter-utils.ts
4698
4164
  import {
4165
+ extractFilterForColumn,
4166
+ isAndFilter,
4167
+ isInFilter,
4168
+ isMultiClauseFilter as isMultiClauseFilter2,
4699
4169
  isMultiValueFilter as isMultiValueFilter3,
4700
- isNamedFilter,
4701
- isSingleValueFilter as isSingleValueFilter3
4170
+ isOrFilter,
4171
+ isSingleValueFilter as isSingleValueFilter3,
4172
+ partition
4702
4173
  } from "@vuu-ui/vuu-utils";
4703
- import { ToggleButton, ToolbarField as ToolbarField3 } from "@heswell/salt-lab";
4704
-
4705
- // src/filter-toolbar/FilterDropdown.tsx
4706
- import { Dropdown as Dropdown2 } from "@heswell/salt-lab";
4707
- import { useCallback as useCallback5, useState as useState3 } from "react";
4708
- import { jsx as jsx8 } from "react/jsx-runtime";
4709
- var isString = (s) => typeof s === "string";
4710
- var stripQuotes = (selected) => {
4711
- if (isString(selected)) {
4712
- if (selected.startsWith('"') && selected.endsWith('"')) {
4713
- return selected.slice(1, -1);
4174
+ var AND = "and";
4175
+ var EQUALS = "=";
4176
+ var GREATER_THAN = ">";
4177
+ var LESS_THAN = "<";
4178
+ var OR = "or";
4179
+ var STARTS_WITH = "starts";
4180
+ var ENDS_WITH = "ends";
4181
+ var IN = "in";
4182
+ var filterClauses = (filter, clauses = []) => {
4183
+ if (filter) {
4184
+ if (isMultiClauseFilter2(filter)) {
4185
+ filter.filters.forEach((f) => clauses.push(...filterClauses(f)));
4714
4186
  } else {
4715
- return selected;
4187
+ clauses.push(filter);
4716
4188
  }
4717
- } else {
4718
- return selected.map(stripQuotes);
4719
4189
  }
4720
- };
4721
- var FilterDropdown = ({
4722
- column,
4723
- selected: selectedProp,
4724
- suggestionProvider,
4725
- ...props
4726
- }) => {
4727
- const selected = selectedProp != null ? stripQuotes(selectedProp) : void 0;
4728
- const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
4729
- const [values, setValues] = useState3(initialValues);
4730
- console.log({ initialValues });
4731
- const handleOpenChange = useCallback5(
4732
- async (isOpen) => {
4733
- if (isOpen) {
4734
- const values2 = await suggestionProvider.getSuggestions("columnValue", {
4735
- columnName: column
4736
- });
4737
- console.log({ values: values2 });
4738
- setValues(values2.map((suggestion) => suggestion.label));
4739
- }
4740
- },
4741
- [column, suggestionProvider]
4190
+ return clauses;
4191
+ };
4192
+ var DEFAULT_ADD_FILTER_OPTS = {
4193
+ combineWith: "and"
4194
+ };
4195
+ var addFilter = (existingFilter, filter, { combineWith = AND } = DEFAULT_ADD_FILTER_OPTS) => {
4196
+ var _a;
4197
+ if (includesNoValues(filter)) {
4198
+ if (isMultiClauseFilter2(filter)) {
4199
+ } else {
4200
+ existingFilter = removeFilterForColumn(existingFilter, {
4201
+ name: filter.column
4202
+ });
4203
+ }
4204
+ } else if (includesAllValues(filter)) {
4205
+ if (isMultiClauseFilter2(filter)) {
4206
+ }
4207
+ return removeFilterForColumn(existingFilter, { name: (_a = filter.column) != null ? _a : "" });
4208
+ }
4209
+ if (!existingFilter) {
4210
+ return filter;
4211
+ }
4212
+ if (!filter) {
4213
+ return existingFilter;
4214
+ }
4215
+ if (existingFilter.op === AND && filter.op === AND) {
4216
+ return {
4217
+ op: AND,
4218
+ filters: combine(existingFilter.filters, filter.filters)
4219
+ };
4220
+ }
4221
+ if (existingFilter.op === AND) {
4222
+ const filters = replaceOrInsert(existingFilter.filters, filter);
4223
+ return filters.length > 1 ? { op: AND, filters } : filters[0];
4224
+ }
4225
+ if (filter.op === AND) {
4226
+ return { op: AND, filters: filter.filters.concat(existingFilter) };
4227
+ }
4228
+ if (filterEquals(existingFilter, filter, true)) {
4229
+ return filter;
4230
+ }
4231
+ if (canMerge(existingFilter, filter)) {
4232
+ return merge(existingFilter, filter);
4233
+ }
4234
+ return { op: combineWith, filters: [existingFilter, filter] };
4235
+ };
4236
+ var includesNoValues = (filter) => {
4237
+ if (!filter) {
4238
+ return false;
4239
+ }
4240
+ if (isInFilter(filter) && filter.values.length === 0) {
4241
+ return true;
4242
+ }
4243
+ return isAndFilter(filter) && filter.filters.some((f) => includesNoValues(f));
4244
+ };
4245
+ var includesAllValues = (filter) => {
4246
+ if (!filter) {
4247
+ return false;
4248
+ }
4249
+ if (filter.op === STARTS_WITH && filter.value === "") {
4250
+ return true;
4251
+ }
4252
+ return filter.op === STARTS_WITH && filter.value === "";
4253
+ };
4254
+ var replaceOrInsert = (filters, filter) => {
4255
+ return filters.concat(filter);
4256
+ };
4257
+ var merge = (f1, f2) => {
4258
+ if (includesNoValues(f2)) {
4259
+ return f2;
4260
+ }
4261
+ if (isInFilter(f1) && isInFilter(f2)) {
4262
+ return {
4263
+ ...f1,
4264
+ values: [
4265
+ ...f1.values,
4266
+ ...f2.values.filter(
4267
+ (v) => !f1.values.includes(v)
4268
+ )
4269
+ ]
4270
+ };
4271
+ } else if (isInFilter(f1) && f2.op === EQUALS) {
4272
+ return {
4273
+ ...f1,
4274
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4275
+ // @ts-ignore
4276
+ values: f1.values.concat([f2.value])
4277
+ };
4278
+ } else if (f1.op === EQUALS && f2.op === EQUALS) {
4279
+ return {
4280
+ column: f1.column,
4281
+ op: IN,
4282
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4283
+ // @ts-ignore
4284
+ values: [f1.value, f2.value]
4285
+ };
4286
+ }
4287
+ return f2;
4288
+ };
4289
+ var combine = (existingFilters, replacementFilters) => {
4290
+ const equivalentType = ({ op: t1 }, { op: t2 }) => {
4291
+ return t1 === t2 || t1[0] === t2[0];
4292
+ };
4293
+ const replaces = (existingFilter, replacementFilter) => {
4294
+ return existingFilter.column === replacementFilter.column && equivalentType(existingFilter, replacementFilter);
4295
+ };
4296
+ const stillApplicable = (existingFilter) => replacementFilters.some(
4297
+ (replacementFilter) => replaces(existingFilter, replacementFilter)
4298
+ ) === false;
4299
+ return existingFilters.filter(stillApplicable).concat(replacementFilters);
4300
+ };
4301
+ var removeFilter = (sourceFilter, filterToRemove) => {
4302
+ if (filterEquals(sourceFilter, filterToRemove, true)) {
4303
+ return null;
4304
+ }
4305
+ if (sourceFilter.op !== AND) {
4306
+ throw Error(
4307
+ `removeFilter cannot remove ${JSON.stringify(
4308
+ filterToRemove
4309
+ )} from ${JSON.stringify(sourceFilter)}`
4310
+ );
4311
+ }
4312
+ const filters = sourceFilter.filters.filter(
4313
+ (f) => !filterEquals(f, filterToRemove)
4742
4314
  );
4743
- return /* @__PURE__ */ jsx8(
4744
- Dropdown2,
4745
- {
4746
- ...props,
4747
- onOpenChange: handleOpenChange,
4748
- selected,
4749
- source: values
4750
- }
4315
+ return filters.length > 0 ? { type: AND, filters } : null;
4316
+ };
4317
+ var splitFilterOnColumn = (columnName, filter) => {
4318
+ if (!filter) {
4319
+ return [void 0, void 0];
4320
+ }
4321
+ if (filter.column === columnName) {
4322
+ return [filter, void 0];
4323
+ }
4324
+ if (filter.op !== AND) {
4325
+ return [void 0, filter];
4326
+ }
4327
+ const [[columnFilter = void 0], filters] = partition(
4328
+ filter.filters,
4329
+ (f) => f.column === columnName
4751
4330
  );
4331
+ return filters.length === 1 ? [columnFilter, filters[0]] : [columnFilter, { op: AND, filters }];
4752
4332
  };
4753
-
4754
- // src/filter-toolbar/FilterDropdownMultiSelect.tsx
4755
- import { Dropdown as Dropdown3 } from "@heswell/salt-lab";
4756
- import { useCallback as useCallback6, useState as useState4 } from "react";
4757
- import { jsx as jsx9 } from "react/jsx-runtime";
4758
- var isString2 = (s) => typeof s === "string";
4759
- var stripQuotes2 = (selected) => {
4760
- if (selected === void 0) {
4333
+ var overrideColName = (filter, column) => {
4334
+ if (isMultiClauseFilter2(filter)) {
4335
+ return {
4336
+ op: filter.op,
4337
+ filters: filter.filters.map((f) => overrideColName(f, column))
4338
+ };
4339
+ }
4340
+ return { ...filter, column };
4341
+ };
4342
+ var filterIncludesColumn = (filter, column) => {
4343
+ if (!filter) {
4344
+ return false;
4345
+ }
4346
+ const { op, column: filterColName } = filter;
4347
+ switch (op) {
4348
+ case AND:
4349
+ case OR:
4350
+ return filter.filters != null && filter.filters.some((f) => filterIncludesColumn(f, column));
4351
+ default:
4352
+ return filterColName === column.name;
4353
+ }
4354
+ };
4355
+ var removeFilterForColumn = (sourceFilter, column) => {
4356
+ const colName = column.name;
4357
+ if (!sourceFilter) {
4761
4358
  return void 0;
4762
- } else if (isString2(selected)) {
4763
- if (selected.startsWith('"') && selected.endsWith('"')) {
4764
- return selected.slice(1, -1);
4765
- } else {
4766
- return selected;
4359
+ }
4360
+ if (sourceFilter.column === colName) {
4361
+ return void 0;
4362
+ }
4363
+ if (isAndFilter(sourceFilter) || isOrFilter(sourceFilter)) {
4364
+ const { op } = sourceFilter;
4365
+ const filters = sourceFilter.filters;
4366
+ const otherColFilters = filters.filter((f) => f.column !== colName);
4367
+ switch (otherColFilters.length) {
4368
+ case 0:
4369
+ return void 0;
4370
+ case 1:
4371
+ return otherColFilters[0];
4372
+ default:
4373
+ return { op, filters: otherColFilters };
4767
4374
  }
4768
- } else {
4769
- return selected.map(stripQuotes2);
4770
4375
  }
4376
+ return sourceFilter;
4771
4377
  };
4772
- var FilterDropdownMultiSelect = ({
4773
- column,
4774
- selected: selectedProp,
4775
- suggestionProvider,
4776
- ...props
4777
- }) => {
4778
- const selected = stripQuotes2(selectedProp);
4779
- const initialValues = Array.isArray(selected) ? selected : selected != null ? [selected] : [];
4780
- const [values, setValues] = useState4(initialValues);
4781
- const handleOpenChange = useCallback6(
4782
- async (isOpen) => {
4783
- if (isOpen) {
4784
- const values2 = await suggestionProvider.getSuggestions("columnValue", {
4785
- columnName: column
4786
- });
4787
- console.log({ values: values2 });
4788
- setValues(values2.map((suggestion) => suggestion.label));
4789
- }
4790
- },
4791
- [column, suggestionProvider]
4792
- );
4793
- return /* @__PURE__ */ jsx9(
4794
- Dropdown3,
4795
- {
4796
- ...props,
4797
- onOpenChange: handleOpenChange,
4798
- selected,
4799
- selectionStrategy: "multiple",
4800
- source: values
4801
- }
4802
- );
4378
+ var canMerge = (f1, f2) => f1.column === f2.column && (f1.op === "=" || f1.op === "in") && (f2.op === "=" || f2.op === "in");
4379
+ var sameValues = (arr1, arr2) => {
4380
+ if (arr1 === arr2) {
4381
+ return true;
4382
+ }
4383
+ if (arr1.length === arr2.length) {
4384
+ const a = arr1.slice().sort();
4385
+ const b = arr2.slice().sort();
4386
+ return a.join("|") === b.join("|");
4387
+ }
4388
+ return false;
4803
4389
  };
4804
-
4805
- // src/filter-toolbar/useFilterToolbar.tsx
4806
- import { jsx as jsx10 } from "react/jsx-runtime";
4807
- var filterToControl = (filter, suggestionProvider) => {
4808
- if (isNamedFilter(filter)) {
4809
- return /* @__PURE__ */ jsx10(
4810
- ToggleButton,
4811
- {
4812
- className: "vuuToggleButton",
4813
- toggled: true,
4814
- variant: "secondary",
4815
- children: filter.name
4816
- }
4817
- );
4390
+ var filterEquals = (f1, f2, strict = false) => {
4391
+ if (!strict) {
4392
+ return true;
4818
4393
  }
4819
- if (isSingleValueFilter3(filter)) {
4820
- const { column, value } = filter;
4821
- return /* @__PURE__ */ jsx10(
4822
- ToolbarField3,
4823
- {
4824
- className: "vuuFilterDropdown",
4825
- label: column,
4826
- labelPlacement: "top",
4827
- children: /* @__PURE__ */ jsx10(
4828
- FilterDropdown,
4829
- {
4830
- column,
4831
- selected: value.toString(),
4832
- selectionStrategy: "default",
4833
- source: [value.toString()],
4834
- suggestionProvider,
4835
- style: { width: 100 }
4836
- }
4837
- )
4838
- },
4839
- column
4840
- );
4394
+ if (f1 && f2 && canMerge(f1, f2)) {
4395
+ return f1.op === f2.op && (isSingleValueFilter3(f1) && isSingleValueFilter3(f2) && f1.value === f2.value || isMultiValueFilter3(f1) && isMultiValueFilter3(f2) && sameValues(f1.values, f2.values));
4841
4396
  }
4842
- if (isMultiValueFilter3(filter)) {
4843
- const values = filter.values.map((v) => v.toString());
4844
- return /* @__PURE__ */ jsx10(
4845
- ToolbarField3,
4846
- {
4847
- className: "vuuFilterDropdown",
4848
- label: filter.column,
4849
- labelPlacement: "top",
4850
- children: /* @__PURE__ */ jsx10(
4851
- FilterDropdownMultiSelect,
4852
- {
4853
- column: filter.column,
4854
- selected: values,
4855
- source: values,
4856
- suggestionProvider,
4857
- style: { width: 100 }
4858
- }
4859
- )
4860
- },
4861
- filter.column
4862
- );
4397
+ return false;
4398
+ };
4399
+ var updateFilter = (filter, newFilter, mode) => {
4400
+ if (filter && newFilter) {
4401
+ if (mode === "replace") {
4402
+ return newFilter;
4403
+ }
4404
+ if (filter.op === "and") {
4405
+ return {
4406
+ ...filter,
4407
+ filters: filter.filters.concat(newFilter)
4408
+ };
4409
+ }
4410
+ const { column: columnName } = newFilter;
4411
+ if (columnName) {
4412
+ const existingClause = newFilter.column ? extractFilterForColumn(filter, columnName) : void 0;
4413
+ if (existingClause && columnName) {
4414
+ const result = removeFilterForColumn(filter, { name: columnName });
4415
+ return updateFilter(result, newFilter, "add");
4416
+ }
4417
+ }
4418
+ return {
4419
+ op: "and",
4420
+ filters: [filter, newFilter]
4421
+ };
4863
4422
  }
4864
- return filter.filters.map(
4865
- (filter2) => filterToControl(filter2, suggestionProvider)
4866
- );
4423
+ if (newFilter) {
4424
+ return newFilter;
4425
+ }
4426
+ return filter;
4867
4427
  };
4868
- var useFilterToolbar = ({
4869
- filter,
4870
- suggestionProvider
4871
- }) => {
4872
- if (filter) {
4873
- return filterToControl(filter, suggestionProvider);
4428
+ var getTypeaheadFilter = (column, filterValues, isStartsWithFilter) => {
4429
+ if (filterValues.length === 0) {
4430
+ return void 0;
4874
4431
  }
4875
- return [];
4432
+ if (isStartsWithFilter) {
4433
+ const startsWith = filterValues[0].substring(0, filterValues[0].length - 3);
4434
+ return {
4435
+ column,
4436
+ op: "starts",
4437
+ value: `"${startsWith}"`
4438
+ };
4439
+ }
4440
+ return {
4441
+ column,
4442
+ op: "in",
4443
+ values: filterValues.map((value) => `"${value}"`)
4444
+ };
4876
4445
  };
4877
-
4878
- // src/filter-toolbar/FilterToolbar.tsx
4879
- import { jsx as jsx11 } from "react/jsx-runtime";
4880
- var FilterToolbar = ({
4881
- className,
4882
- filter,
4883
- suggestionProvider,
4884
- ...props
4885
- }) => {
4886
- console.log(`FilterToolbar ${JSON.stringify(filter, null, 2)}`);
4887
- const toolbarItems = useFilterToolbar({ filter, suggestionProvider });
4888
- return /* @__PURE__ */ jsx11(Toolbar2, { className: (0, import_classnames2.default)("vuuFilterToolbar", className), ...props, children: toolbarItems });
4446
+ var getNumericFilter = (column, operator, value) => {
4447
+ if (operator === void 0)
4448
+ return void 0;
4449
+ if (value === void 0 || isNaN(value))
4450
+ return void 0;
4451
+ return { column, op: operator, value };
4889
4452
  };
4890
4453
  export {
4891
4454
  AND,
4892
- ColumnFilter,
4893
4455
  ENDS_WITH,
4894
4456
  EQUALS,
4895
4457
  FilterInput,
@@ -4903,6 +4465,8 @@ export {
4903
4465
  filterClauses,
4904
4466
  filterEquals,
4905
4467
  filterIncludesColumn,
4468
+ getNumericFilter,
4469
+ getTypeaheadFilter,
4906
4470
  overrideColName,
4907
4471
  removeFilter,
4908
4472
  splitFilterOnColumn,