@bagelink/vue 1.15.267 → 1.15.271

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.
@@ -77,13 +77,17 @@
77
77
  "ew": "Finisce con",
78
78
  "pr": "Presente",
79
79
  "in": "Nella lista",
80
- "nin": "Non nella lista"
80
+ "nin": "Non nella lista",
81
+ "between": "Tra"
81
82
  },
82
83
  "placeholders": {
83
84
  "selectField": "Seleziona campo",
84
85
  "selectOption": "Seleziona opzione",
85
86
  "selectDate": "Seleziona data",
86
- "enterValue": "Valore"
87
+ "enterValue": "Valore",
88
+ "from": "Da",
89
+ "to": "A",
90
+ "commaSeparated": "Valori separati da virgole"
87
91
  },
88
92
  "buttons": {
89
93
  "addCondition": "Aggiungi condizione",
@@ -270,5 +274,8 @@
270
274
  "addToCalendar": {
271
275
  "addTo": "Aggiungi a {name}",
272
276
  "addToCalendar": "Aggiungi al calendario"
277
+ },
278
+ "guidedHelp": {
279
+ "hint": "La guida è sempre disponibile sul punto interrogativo."
273
280
  }
274
281
  }
@@ -77,13 +77,17 @@
77
77
  "ew": "Заканчивается на",
78
78
  "pr": "Присутствует",
79
79
  "in": "В списке",
80
- "nin": "Не в списке"
80
+ "nin": "Не в списке",
81
+ "between": "Между"
81
82
  },
82
83
  "placeholders": {
83
84
  "selectField": "Выберите поле",
84
85
  "selectOption": "Выберите вариант",
85
86
  "selectDate": "Выберите дату",
86
- "enterValue": "Значение"
87
+ "enterValue": "Значение",
88
+ "from": "От",
89
+ "to": "До",
90
+ "commaSeparated": "Значения через запятую"
87
91
  },
88
92
  "buttons": {
89
93
  "addCondition": "Добавить условие",
@@ -270,5 +274,8 @@
270
274
  "addToCalendar": {
271
275
  "addTo": "Добавить в {name}",
272
276
  "addToCalendar": "Добавить в календарь"
277
+ },
278
+ "guidedHelp": {
279
+ "hint": "Обучение всегда доступно по знаку вопроса."
273
280
  }
274
281
  }
@@ -43,8 +43,9 @@ export type ComparisonOperator
43
43
  | 'sw' // starts with
44
44
  | 'ew' // ends with
45
45
  | 'pr' // present (has value)
46
- | 'in' // in list — expands to (field eq "a" or field eq "b" …)
47
- | 'nin' // not in list — expands to (field ne "a" and field ne "b" …)
46
+ | 'in' // in list — native: field in ["a","b"] (scalar membership OR array overlap)
47
+ | 'nin' // not in list — native: field nin ["a","b"]
48
+ | 'between' // inclusive range — field between [lo, hi] (numbers or dates)
48
49
 
49
50
  /**
50
51
  * SCIM 2.0 logical operators
@@ -79,6 +80,14 @@ function formatValue(value: Primitive): string {
79
80
  return String(value)
80
81
  }
81
82
 
83
+ /**
84
+ * Format a list of values into a SCIM bracket literal, e.g. ["a","b",3].
85
+ * Used by list-value operators (in / nin / between).
86
+ */
87
+ function formatList(values: Primitive[]): string {
88
+ return `[${values.map(formatValue).join(', ')}]`
89
+ }
90
+
82
91
  /**
83
92
  * Build a SCIM 2.0 query string from conditions array
84
93
  *
@@ -110,12 +119,15 @@ export function buildQuery<T extends Record<string, any>>(
110
119
  if (op === 'pr') {
111
120
  parts.push(`${field} pr`)
112
121
  } else if (op === 'in' || op === 'nin') {
122
+ // Native list membership / array-overlap: field in ["a","b"].
113
123
  const values = Array.isArray(value) ? value : (value != null ? [value] : [])
114
124
  if (values.length === 0) continue
115
- const innerOp = op === 'in' ? 'eq' : 'ne'
116
- const innerConn = op === 'in' ? 'or' : 'and'
117
- const inner = values.map(v => `${field} ${innerOp} ${formatValue(v)}`).join(` ${innerConn} `)
118
- parts.push(values.length > 1 ? `(${inner})` : inner)
125
+ parts.push(`${field} ${op} ${formatList(values)}`)
126
+ } else if (op === 'between') {
127
+ // Inclusive range: field between [lo, hi]. Requires exactly two bounds.
128
+ const bounds = Array.isArray(value) ? value : []
129
+ if (bounds.length !== 2 || bounds[0] == null || bounds[1] == null) continue
130
+ parts.push(`${field} between ${formatList(bounds)}`)
119
131
  } else {
120
132
  parts.push(`${field} ${op} ${formatValue(value as Primitive ?? null)}`)
121
133
  }
@@ -210,6 +222,30 @@ export class QueryFilter<T extends Record<string, any>> {
210
222
  return this
211
223
  }
212
224
 
225
+ /**
226
+ * In list — native membership / array overlap: field in ["a","b"]
227
+ */
228
+ in<K extends DeepKeyOf<T>>(field: K, values: Primitive[]): this {
229
+ this.parts.push(`${field as string} in ${formatList(values)}`)
230
+ return this
231
+ }
232
+
233
+ /**
234
+ * Not in list: field nin ["a","b"]
235
+ */
236
+ nin<K extends DeepKeyOf<T>>(field: K, values: Primitive[]): this {
237
+ this.parts.push(`${field as string} nin ${formatList(values)}`)
238
+ return this
239
+ }
240
+
241
+ /**
242
+ * Inclusive range: field between [lo, hi]
243
+ */
244
+ between<K extends DeepKeyOf<T>>(field: K, lo: number | string, hi: number | string): this {
245
+ this.parts.push(`${field as string} between ${formatList([lo, hi])}`)
246
+ return this
247
+ }
248
+
213
249
  /**
214
250
  * AND logical operator
215
251
  */
@@ -361,8 +397,7 @@ export function range<T extends Record<string, any>>(
361
397
  max: number | string
362
398
  ): string {
363
399
  return buildQuery<T>([
364
- { field, op: 'ge', value: min },
365
- { field, op: 'le', value: max, connector: 'and' },
400
+ { field, op: 'between', value: [min, max] },
366
401
  ])
367
402
  }
368
403
 
@@ -418,6 +453,9 @@ export function evaluateQuery(query: string, data: Record<string, any>): boolean
418
453
  case 'sw': match = value != null && String(fieldValue).startsWith(String(value)); break
419
454
  case 'ew': match = value != null && String(fieldValue).endsWith(String(value)); break
420
455
  case 'pr': match = fieldValue != null; break
456
+ case 'in': match = matchesIn(fieldValue, value); break
457
+ case 'nin': match = !matchesIn(fieldValue, value); break
458
+ case 'between': match = matchesBetween(fieldValue, value); break
421
459
  default: break
422
460
  }
423
461
 
@@ -440,8 +478,30 @@ function getNestedValue(obj: Record<string, any>, path: string): any {
440
478
  return path.split('.').reduce<any>((acc, key) => acc?.[key], obj)
441
479
  }
442
480
 
443
- const OPERATORS: ComparisonOperator[] = ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'co', 'sw', 'ew', 'pr', 'in', 'nin']
481
+ /** in — scalar membership OR array overlap (any shared element). */
482
+ function matchesIn(fieldValue: any, value: Primitive | Primitive[] | undefined): boolean {
483
+ const candidates = Array.isArray(value) ? value : (value != null ? [value] : [])
484
+ if (candidates.length === 0) return false
485
+ if (Array.isArray(fieldValue)) return fieldValue.some(v => candidates.includes(v))
486
+ return candidates.includes(fieldValue)
487
+ }
488
+
489
+ /** between [lo, hi] — inclusive range (numbers or comparable strings/dates). */
490
+ function matchesBetween(fieldValue: any, value: Primitive | Primitive[] | undefined): boolean {
491
+ if (!Array.isArray(value) || value.length !== 2 || fieldValue == null) return false
492
+ const [lo, hi] = value
493
+ if (lo == null || hi == null) return false
494
+ const nLo = Number(lo); const nHi = Number(hi); const nVal = Number(fieldValue)
495
+ if (!Number.isNaN(nLo) && !Number.isNaN(nHi) && !Number.isNaN(nVal)) {
496
+ return nVal >= nLo && nVal <= nHi
497
+ }
498
+ return String(fieldValue) >= String(lo) && String(fieldValue) <= String(hi)
499
+ }
500
+
501
+ const OPERATORS: ComparisonOperator[] = ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'co', 'sw', 'ew', 'pr', 'in', 'nin', 'between']
444
502
  const CONNECTORS: LogicalOperator[] = ['and', 'or']
503
+ /** Operators whose value is a bracket list literal. */
504
+ const LIST_OPERATORS: ComparisonOperator[] = ['in', 'nin', 'between']
445
505
 
446
506
  /**
447
507
  * Parse a value string into a Primitive
@@ -460,6 +520,37 @@ function parseValue(valueStr: string): Primitive {
460
520
  return trimmed
461
521
  }
462
522
 
523
+ /**
524
+ * Parse a bracket list literal token `["a", "b", 3]` into an array of Primitives.
525
+ * Splits on top-level commas (ignoring commas inside quoted items).
526
+ */
527
+ function parseListLiteral(token: string): Primitive[] {
528
+ let inner = token.trim()
529
+ if (inner.startsWith('[')) inner = inner.slice(1)
530
+ if (inner.endsWith(']')) inner = inner.slice(0, -1)
531
+ if (inner.trim() === '') return []
532
+
533
+ const items: string[] = []
534
+ let current = ''
535
+ let inQuote = false
536
+ for (let i = 0; i < inner.length; i++) {
537
+ const ch = inner[i]
538
+ if (inQuote) {
539
+ if (ch === '\\' && inner[i + 1] === '"') { current += '\\"'; i++; continue }
540
+ if (ch === '"') { current += '"'; inQuote = false; continue }
541
+ current += ch
542
+ } else if (ch === '"') {
543
+ current += '"'; inQuote = true
544
+ } else if (ch === ',') {
545
+ items.push(current); current = ''
546
+ } else {
547
+ current += ch
548
+ }
549
+ }
550
+ if (current.trim() !== '') items.push(current)
551
+ return items.map(s => parseValue(s.trim()))
552
+ }
553
+
463
554
  /**
464
555
  * Parse a SCIM 2.0 query string into conditions array
465
556
  * Supports grouping with parentheses: (a eq 1 or b eq 2) and c eq 3
@@ -551,12 +642,13 @@ function parseTokens<T extends Record<string, any>>(
551
642
  throw new Error(`Invalid operator: ${op}`)
552
643
  }
553
644
 
554
- let value: Primitive | undefined
645
+ let value: Primitive | Primitive[] | undefined
555
646
  if (op !== 'pr') {
556
647
  if (i >= end) {
557
648
  throw new Error(`Missing value for operator ${op}`)
558
649
  }
559
- value = parseValue(tokens[i++])
650
+ const raw = tokens[i++]
651
+ value = LIST_OPERATORS.includes(op) ? parseListLiteral(raw) : parseValue(raw)
560
652
  }
561
653
 
562
654
  const condition: FilterCondition<T> = { field, op, value }
@@ -603,6 +695,31 @@ function tokenize(query: string): string[] {
603
695
  i++
604
696
  continue
605
697
  }
698
+ // Handle a bracket list literal as a SINGLE token: [ ... ].
699
+ // Consume everything up to the matching ], respecting quotes so a
700
+ // quoted item containing ] doesn't terminate the list early.
701
+ if (char === '[') {
702
+ if (current) { tokens.push(current); current = '' }
703
+ let literal = '['
704
+ i++
705
+ let inStr = false
706
+ while (i < query.length) {
707
+ const c = query[i]
708
+ if (inStr) {
709
+ if (c === '\\' && query[i + 1] === '"') { literal += '\\"'; i += 2; continue }
710
+ if (c === '"') inStr = false
711
+ literal += c
712
+ i++
713
+ continue
714
+ }
715
+ if (c === '"') { inStr = true; literal += c; i++; continue }
716
+ literal += c
717
+ i++
718
+ if (c === ']') break
719
+ }
720
+ tokens.push(literal)
721
+ continue
722
+ }
606
723
  // Handle parentheses as separate tokens
607
724
  if (char === '(' || char === ')') {
608
725
  if (current) {