@spaethtech/svelte-ui 0.19.0 → 0.19.1-dev.98.32fa323

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.
@@ -242,8 +242,8 @@
242
242
  <tr><td>{"> >= < <="}</td><td>greater / less than (numbers &amp; dates)</td></tr>
243
243
  <tr><td>~= !~</td><td>contains / not — or regex if value is <code>/…/</code></td></tr>
244
244
  <tr><td>^= $=</td><td>starts / ends with</td></tr>
245
- <tr><td>|=</td><td>any of a set <code>[a, b]</code> or range <code>[1..9)</code></td></tr>
246
- <tr><td>&=</td><td>all of a set (multi-value columns)</td></tr>
245
+ <tr><td>|=</td><td>any of a set <code>[a, b]</code> (a member may be a regex <code>/…/</code>) or range <code>[1..9)</code></td></tr>
246
+ <tr><td>&=</td><td>all of a set (members may be regex <code>/…/</code>; multi-value columns)</td></tr>
247
247
  </tbody>
248
248
  </table>
249
249
  </div>
@@ -297,6 +297,7 @@
297
297
  <tr><td>$age |= [21..40)</td><td>range — 21 ≤ age &lt; 40</td></tr>
298
298
  <tr><td>$role |= [admin, ops]</td><td>any of a set</td></tr>
299
299
  <tr><td>$tags &amp;= [urgent, vip]</td><td>all of (multi-value column)</td></tr>
300
+ <tr><td>$services |= [/^Managed/, "Static IP"]</td><td>set member as a regex (per element)</td></tr>
300
301
  <tr><td>$created &gt;= now()-7d</td><td>dynamic date (last 7 days)</td></tr>
301
302
  <tr><td>$ip|inet &gt;= 10.0.0.0|inet</td><td>typed IP compare</td></tr>
302
303
  <tr><td>!($role == admin)</td><td>negate a group</td></tr>
@@ -27,6 +27,14 @@ export declare function resolveNow(v: NowValue, at?: Date): Date;
27
27
  export type Cast = "number" | "string" | "bool" | "date" | "inet";
28
28
  /** Membership mode: `|=` matches ANY of the operand, `&=` matches ALL (e.g. array contains-all). */
29
29
  export type MemberMode = "any" | "all";
30
+ /** A regex member inside a `|=`/`&=` set (`$col |= [/re/i]`). Reuses the `match`-node shape
31
+ * (`source`/`flags`). Evaluated per array ELEMENT: a member matches if the regex tests true against
32
+ * any element's string form (or the cell itself for a scalar column). A cast + a regex member is a
33
+ * parse error, same as a bare `~=` regex. */
34
+ export interface RegexMember {
35
+ source: string;
36
+ flags: string;
37
+ }
30
38
  /** One end of an interval. `inclusive` reflects the bracket: `[`/`]` inclusive, `(`/`)` exclusive. */
31
39
  export type Bound = {
32
40
  value: Value;
@@ -63,6 +71,7 @@ export type QueryNode = {
63
71
  col: string;
64
72
  mode: MemberMode;
65
73
  values: Value[];
74
+ patterns?: RegexMember[];
66
75
  cast?: Cast;
67
76
  } | {
68
77
  kind: "range";
@@ -126,12 +126,19 @@ export function matchesRow(row, ast, opts = {}) {
126
126
  }
127
127
  case "in": {
128
128
  const cell = get(row, node.col);
129
- const members = node.values.map((v) => resolveVal(v, now));
130
- if (Array.isArray(cell)) {
131
- const inCell = (m) => cell.some((c) => eq(c, m, node.cast, ci));
132
- return node.mode === "all" ? members.every(inCell) : members.some(inCell);
133
- }
134
- const hits = members.map((m) => eq(cell, m, node.cast, ci));
129
+ // One predicate per member. Scalars → exact-equality (honours cast + ci); regexes → test the
130
+ // string form. A member is satisfied if SOME array element matches it (per-element — the point
131
+ // of a regex member), or the cell itself for a scalar column.
132
+ const scalarTests = node.values.map((v) => {
133
+ const m = resolveVal(v, now);
134
+ return (x) => eq(x, m, node.cast, ci);
135
+ });
136
+ const regexTests = (node.patterns ?? []).map((p) => {
137
+ const re = safeRegExp(p.source, p.flags);
138
+ return (x) => re != null && re.test(toStr(x));
139
+ });
140
+ const memberHit = (test) => Array.isArray(cell) ? cell.some(test) : test(cell);
141
+ const hits = [...scalarTests, ...regexTests].map(memberHit);
135
142
  return node.mode === "all" ? hits.every(Boolean) : hits.some(Boolean);
136
143
  }
137
144
  case "range": {
@@ -125,7 +125,7 @@ class Parser {
125
125
  const node = this.parseClauseBody(col);
126
126
  const cast = this.foldCast();
127
127
  if (cast) {
128
- if (node.kind === "match")
128
+ if (node.kind === "match" || (node.kind === "in" && node.patterns?.length))
129
129
  throw new QueryError("A cast cannot be used with a regex", colTok.start);
130
130
  node.cast = cast;
131
131
  }
@@ -224,6 +224,17 @@ class Parser {
224
224
  }
225
225
  return value;
226
226
  }
227
+ /** One member of a `[...]` set: a regex literal (→ per-element pattern) or a scalar (exact-equality).
228
+ * A regex is only valid in a SET, never as an interval bound (a pattern can't order). */
229
+ parseSetMember() {
230
+ if (this.is("regex")) {
231
+ const r = this.next();
232
+ if (r.type !== "regex")
233
+ throw new QueryError("Expected a regex", r.start);
234
+ return { pattern: { source: r.source, flags: r.flags } };
235
+ }
236
+ return { value: this.parseScalar() };
237
+ }
227
238
  parseMemberOperand(col, mode) {
228
239
  const open = this.next();
229
240
  if (open.type !== "lbracket" && open.type !== "lparen") {
@@ -232,21 +243,28 @@ class Parser {
232
243
  const lowerInclusive = open.type === "lbracket";
233
244
  let lower = null;
234
245
  if (!this.is("dotdot")) {
235
- const first = this.parseScalar();
236
- if (this.is("dotdot")) {
237
- lower = { value: first, inclusive: lowerInclusive };
246
+ const first = this.parseSetMember();
247
+ // Interval only when the first member is a SCALAR immediately followed by `..`; a regex first
248
+ // member (or a scalar not followed by `..`) is a set.
249
+ if (!first.pattern && this.is("dotdot")) {
250
+ lower = { value: first.value, inclusive: lowerInclusive };
238
251
  }
239
252
  else {
240
- // SET (must use square brackets).
253
+ // SET (must use square brackets). Scalars → exact-equality members; regexes → per-element.
241
254
  if (open.type !== "lbracket")
242
255
  throw new QueryError("A set must use '[' ']'", open.start);
243
- const values = [first];
256
+ const values = [];
257
+ const patterns = [];
258
+ const add = (m) => m.pattern ? patterns.push(m.pattern) : values.push(m.value);
259
+ add(first);
244
260
  while (this.is("comma")) {
245
261
  this.next();
246
- values.push(this.parseScalar());
262
+ add(this.parseSetMember());
247
263
  }
248
264
  this.expect("rbracket");
249
- return { kind: "in", col, mode, values };
265
+ return patterns.length
266
+ ? { kind: "in", col, mode, values, patterns }
267
+ : { kind: "in", col, mode, values };
250
268
  }
251
269
  }
252
270
  // interval
@@ -400,7 +400,9 @@ Config-driven, responsive 12-column data table over a `DataGrid`.
400
400
 
401
401
  Filter bar over a `DataSet` — monospace input with `$column` + enum-value autocomplete and a help
402
402
  dialog (operators, casts, dynamic `now()` dates, and a worked examples table). The DSL supports
403
- `now()` / `now()±<n><unit>` dynamic dates — see the help dialog + the data spec.
403
+ `now()` / `now()±<n><unit>` dynamic dates, and **regex members inside `|=`/`&=` sets** —
404
+ `$services |= [/^Managed/, "Static IP"]` matches a set member as a regex, per array element (scalars
405
+ still exact-match; mix freely). See the help dialog + the data spec.
404
406
 
405
407
  - **Location**: `src/lib/components/Query.svelte`
406
408
  - **Props**: `dataset` (`DataSet`), `placeholder`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.19.0",
3
+ "version": "0.19.1-dev.98.32fa323",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"