@bagelink/vue 1.9.79 → 1.9.81

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.
@@ -27,7 +27,7 @@ type DeepKeyOf<T> = T extends Primitive ? never : {
27
27
  /**
28
28
  * SCIM 2.0 comparison operators
29
29
  */
30
- export type ComparisonOperator = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'co' | 'sw' | 'ew' | 'pr';
30
+ export type ComparisonOperator = 'eq' | 'ne' | 'gt' | 'ge' | 'lt' | 'le' | 'co' | 'sw' | 'ew' | 'pr' | 'in' | 'nin';
31
31
  /**
32
32
  * SCIM 2.0 logical operators
33
33
  */
@@ -38,7 +38,7 @@ export type LogicalOperator = 'and' | 'or';
38
38
  export interface FilterCondition<T extends Record<string, any> = Record<string, any>> {
39
39
  field: DeepKeyOf<T> | string;
40
40
  op: ComparisonOperator;
41
- value?: Primitive;
41
+ value?: Primitive | Primitive[];
42
42
  connector?: LogicalOperator;
43
43
  }
44
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"queryFilter.d.ts","sourceRoot":"","sources":["../../src/utils/queryFilter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"queryFilter.d.ts","sourceRoot":"","sources":["../../src/utils/queryFilter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.9.79",
4
+ "version": "1.9.81",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -2,7 +2,7 @@
2
2
  import type { Option } from '@bagelink/vue'
3
3
  import type { ComparisonOperator, LogicalOperator, QueryConditions } from '../utils/queryFilter'
4
4
  import { Btn, DateInput, Dropdown, Icon, SelectInput, TextInput, useI18n, useDebounceFn } from '@bagelink/vue'
5
- import { computed, ref, useAttrs } from 'vue'
5
+ import { computed, getCurrentInstance, ref } from 'vue'
6
6
 
7
7
  type OptionsSource = Option[] | ((query: string) => Promise<Option[]>)
8
8
 
@@ -10,7 +10,7 @@ interface UICondition {
10
10
  id: string
11
11
  field: string
12
12
  operator: ComparisonOperator
13
- value: string
13
+ value: string | string[]
14
14
  connector: LogicalOperator | ''
15
15
  }
16
16
 
@@ -20,6 +20,7 @@ const props = defineProps<{
20
20
  value: string
21
21
  type?: 'string' | 'number' | 'boolean' | 'date'
22
22
  options?: OptionsSource
23
+ multiple?: boolean
23
24
  }>
24
25
  }>()
25
26
 
@@ -34,11 +35,14 @@ const model = defineModel<QueryConditions<T>>({ default: () => [] })
34
35
  const internalModel = ref<QueryConditions<T>>([...model.value] as QueryConditions<T>)
35
36
 
36
37
  const { $t } = useI18n()
37
- const attrs = useAttrs()
38
- const hasSaveListener = computed(() => !!attrs.onSave)
39
-
40
- function isComplete(c: { field: string, op: ComparisonOperator, value: any }) {
41
- return c.field && c.op && (c.op === 'pr' || (c.value !== '' && c.value !== null && c.value !== undefined))
38
+ const instance = getCurrentInstance()
39
+ const hasSaveListener = computed(() => !!instance?.vnode.props?.onSave)
40
+
41
+ function isComplete(c: { field: string, op: ComparisonOperator, value?: any }) {
42
+ if (!c.field || !c.op) return false
43
+ if (c.op === 'pr') return true
44
+ if (c.op === 'in' || c.op === 'nin') return Array.isArray(c.value) && c.value.length > 0
45
+ return c.value !== '' && c.value !== null && c.value !== undefined
42
46
  }
43
47
 
44
48
  const debouncedFlush = useDebounceFn(() => {
@@ -72,6 +76,8 @@ const currentTexts = computed(() => ({
72
76
  sw: $t('filter.operators.sw'),
73
77
  ew: $t('filter.operators.ew'),
74
78
  pr: $t('filter.operators.pr'),
79
+ in: $t('filter.operators.in'),
80
+ nin: $t('filter.operators.nin'),
75
81
  },
76
82
  placeholders: {
77
83
  selectField: $t('filter.placeholders.selectField'),
@@ -105,7 +111,9 @@ const conditions = computed(() => {
105
111
  id,
106
112
  field: cond.field as string,
107
113
  operator: cond.op,
108
- value: cond.value === null ? 'null' : cond.value === undefined ? '' : String(cond.value),
114
+ value: Array.isArray(cond.value)
115
+ ? cond.value.map(String)
116
+ : (cond.value === null ? 'null' : cond.value === undefined ? '' : String(cond.value)),
109
117
  connector: index > 0 ? (cond.connector || 'and') : '',
110
118
  }
111
119
  return uiCond
@@ -123,9 +131,14 @@ const allOperatorOptions = computed(() => [
123
131
  { label: currentTexts.value.operators.sw, value: 'sw' as ComparisonOperator },
124
132
  { label: currentTexts.value.operators.ew, value: 'ew' as ComparisonOperator },
125
133
  { label: currentTexts.value.operators.pr, value: 'pr' as ComparisonOperator },
134
+ { label: currentTexts.value.operators.in, value: 'in' as ComparisonOperator },
135
+ { label: currentTexts.value.operators.nin, value: 'nin' as ComparisonOperator },
126
136
  ])
127
137
 
128
- function getOperatorsForType(fieldType: string, hasOptions: boolean) {
138
+ function getOperatorsForType(fieldType: string, hasOptions: boolean, isMultiple: boolean) {
139
+ if (isMultiple && hasOptions) {
140
+ return allOperatorOptions.value.filter(o => ['in', 'nin', 'pr'].includes(o.value))
141
+ }
129
142
  if (hasOptions) {
130
143
  return allOperatorOptions.value.filter(o => ['eq', 'ne', 'pr'].includes(o.value))
131
144
  }
@@ -140,8 +153,17 @@ function getOperatorsForType(fieldType: string, hasOptions: boolean) {
140
153
  }
141
154
  }
142
155
 
156
+ function getFieldDef(fieldValue: string) {
157
+ return props.fields.find(f => f.value === fieldValue)
158
+ }
159
+
143
160
  function getFieldOptions(fieldValue: string): OptionsSource | undefined {
144
- return props.fields.find(f => f.value === fieldValue)?.options
161
+ return getFieldDef(fieldValue)?.options
162
+ }
163
+
164
+ function isMultipleField(fieldValue: string): boolean {
165
+ const def = getFieldDef(fieldValue)
166
+ return !!(def?.multiple && def?.options)
145
167
  }
146
168
 
147
169
  const booleanOptions = computed(() => [
@@ -158,7 +180,7 @@ function parseValue(value: string | number | boolean | null): string | number |
158
180
  return value
159
181
  }
160
182
 
161
- function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value: string | number | boolean | null, connector: LogicalOperator }>) {
183
+ function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value: string | number | boolean | null | string[], connector: LogicalOperator }>) {
162
184
  const newModel = [...internalModel.value]
163
185
  const existing = newModel[index]
164
186
  if (!existing) return
@@ -166,7 +188,7 @@ function updateConditionAtIndex(index: number, updates: Partial<{ field: string,
166
188
  const updated = { ...existing }
167
189
  if (updates.field !== undefined) updated.field = updates.field
168
190
  if (updates.op !== undefined) updated.op = updates.op
169
- if (updates.value !== undefined) updated.value = parseValue(updates.value)
191
+ if (updates.value !== undefined) updated.value = Array.isArray(updates.value) ? updates.value : parseValue(updates.value)
170
192
  if (updates.connector !== undefined) updated.connector = updates.connector
171
193
 
172
194
  newModel[index] = updated
@@ -176,10 +198,11 @@ function updateConditionAtIndex(index: number, updates: Partial<{ field: string,
176
198
 
177
199
  function addCondition() {
178
200
  const field = props.fields[0]?.value || 'field'
201
+ const multi = isMultipleField(field)
179
202
  const newCondition = {
180
203
  field,
181
- op: 'eq' as ComparisonOperator,
182
- value: '' as any,
204
+ op: (multi ? 'in' : 'eq') as ComparisonOperator,
205
+ value: (multi ? [] : '') as any,
183
206
  connector: model.value.length > 0 ? 'and' as LogicalOperator : undefined,
184
207
  }
185
208
  const newModel = [...internalModel.value, newCondition]
@@ -214,6 +237,10 @@ function needsValue(operator: ComparisonOperator): boolean {
214
237
  return operator !== 'pr'
215
238
  }
216
239
 
240
+ function isMultiOperator(operator: ComparisonOperator): boolean {
241
+ return operator === 'in' || operator === 'nin'
242
+ }
243
+
217
244
  const activeFiltersCount = computed(() => {
218
245
  return conditions.value.filter((c: UICondition) => Boolean(c.field) && (c.operator === 'pr' || Boolean(c.value))).length
219
246
  })
@@ -229,10 +256,17 @@ function onFieldChange(id: string, newField: string) {
229
256
 
230
257
  function onOperatorChange(id: string, operator: ComparisonOperator) {
231
258
  const index = getConditionIndex(id)
232
- if (index >= 0) updateConditionAtIndex(index, { op: operator })
259
+ if (index < 0) return
260
+ const current = conditions.value[index]
261
+ const wasMulti = current?.operator === 'in' || current?.operator === 'nin'
262
+ const isMulti = operator === 'in' || operator === 'nin'
263
+ const updates: Parameters<typeof updateConditionAtIndex>[1] = { op: operator }
264
+ if (wasMulti && !isMulti) updates.value = ''
265
+ else if (!wasMulti && isMulti) updates.value = []
266
+ updateConditionAtIndex(index, updates)
233
267
  }
234
268
 
235
- function onValueChange(id: string, value: string) {
269
+ function onValueChange(id: string, value: string | string[]) {
236
270
  const index = getConditionIndex(id)
237
271
  if (index >= 0) updateConditionAtIndex(index, { value })
238
272
  }
@@ -258,7 +292,6 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
258
292
  {{ currentTexts.noActiveFilters }}
259
293
  </div>
260
294
  </div>
261
-
262
295
  <TransitionGroup name="condition">
263
296
  <div
264
297
  v-for="(condition, index) in conditions" :key="condition.id"
@@ -291,7 +324,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
291
324
  <!-- Operator selector -->
292
325
  <SelectInput
293
326
  :model-value="condition.operator"
294
- :options="getOperatorsForType(getFieldType(condition.field), !!getFieldOptions(condition.field))"
327
+ :options="getOperatorsForType(getFieldType(condition.field), !!getFieldOptions(condition.field), isMultipleField(condition.field))"
295
328
  class="m-0 light-input borderHover"
296
329
  @update:model-value="(v: ComparisonOperator) => onOperatorChange(condition.id, v)"
297
330
  />
@@ -299,7 +332,15 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
299
332
  <!-- Value input - type-specific -->
300
333
  <template v-if="needsValue(condition.operator)">
301
334
  <SelectInput
302
- v-if="getFieldOptions(condition.field)" :model-value="condition.value"
335
+ v-if="getFieldOptions(condition.field) && isMultiOperator(condition.operator)"
336
+ :model-value="Array.isArray(condition.value) ? condition.value : []"
337
+ :options="getFieldOptions(condition.field)!"
338
+ :placeholder="currentTexts.placeholders.selectOption"
339
+ class="m-0 light-input borderHover" searchable multiselect
340
+ @update:model-value="(v: string[]) => onValueChange(condition.id, v)"
341
+ />
342
+ <SelectInput
343
+ v-else-if="getFieldOptions(condition.field)" :model-value="condition.value"
303
344
  :options="getFieldOptions(condition.field)!"
304
345
  :placeholder="currentTexts.placeholders.selectOption"
305
346
  class="m-0 light-input borderHover" searchable
@@ -314,18 +355,18 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
314
355
  />
315
356
  <DateInput
316
357
  v-else-if="getFieldType(condition.field) === 'date'"
317
- :model-value="condition.value" :placeholder="currentTexts.placeholders.selectDate"
358
+ :model-value="(condition.value as string)" :placeholder="currentTexts.placeholders.selectDate"
318
359
  class="m-0 light-input borderHover"
319
360
  @update:model-value="(v: any) => onValueChange(condition.id, String(v))"
320
361
  />
321
362
  <TextInput
322
363
  v-else-if="getFieldType(condition.field) === 'number'"
323
- :model-value="condition.value" placeholder="0" type="number"
364
+ :model-value="(condition.value as string)" placeholder="0" type="number"
324
365
  class="m-0 light-input borderHover"
325
366
  @update:model-value="(v: string) => onValueChange(condition.id, v)"
326
367
  />
327
368
  <TextInput
328
- v-else :model-value="condition.value"
369
+ v-else :model-value="(condition.value as string)"
329
370
  :placeholder="currentTexts.placeholders.enterValue" class="m-0 light-input borderHover"
330
371
  @update:model-value="(v: string) => onValueChange(condition.id, v)"
331
372
  />
@@ -349,7 +390,8 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
349
390
  :value="currentTexts.buttons.clearAll" @click="clearAll"
350
391
  />
351
392
  <Btn
352
- v-if="hasSaveListener" icon="save" thin color="primary" :value="$t('filter.buttons.save')"
393
+ v-if="hasSaveListener" :disabled="conditions.length === 0" class="color-green" icon="save" thin
394
+ color="green-light" :value="$t('filter.buttons.save')"
353
395
  @click="emit('save', internalModel.filter(isComplete))"
354
396
  />
355
397
  </div>
@@ -72,7 +72,9 @@
72
72
  "co": "Contains",
73
73
  "sw": "Starts with",
74
74
  "ew": "Ends with",
75
- "pr": "Present"
75
+ "pr": "Present",
76
+ "in": "In list",
77
+ "nin": "Not in list"
76
78
  },
77
79
  "placeholders": {
78
80
  "selectField": "Select field",
@@ -82,7 +84,8 @@
82
84
  },
83
85
  "buttons": {
84
86
  "addCondition": "Add condition",
85
- "clearAll": "Clear all"
87
+ "clearAll": "Clear all",
88
+ "save": "Save"
86
89
  }
87
90
  },
88
91
  "multiStep": {
@@ -72,7 +72,9 @@
72
72
  "co": "Contiene",
73
73
  "sw": "Empieza con",
74
74
  "ew": "Termina con",
75
- "pr": "Presente"
75
+ "pr": "Presente",
76
+ "in": "En lista",
77
+ "nin": "No en lista"
76
78
  },
77
79
  "placeholders": {
78
80
  "selectField": "Seleccionar campo",
@@ -82,7 +84,8 @@
82
84
  },
83
85
  "buttons": {
84
86
  "addCondition": "Añadir condición",
85
- "clearAll": "Limpiar todo"
87
+ "clearAll": "Limpiar todo",
88
+ "save": "Guardar"
86
89
  }
87
90
  },
88
91
  "multiStep": {
@@ -72,7 +72,9 @@
72
72
  "co": "Contient",
73
73
  "sw": "Commence par",
74
74
  "ew": "Se termine par",
75
- "pr": "Présent"
75
+ "pr": "Présent",
76
+ "in": "Dans la liste",
77
+ "nin": "Pas dans la liste"
76
78
  },
77
79
  "placeholders": {
78
80
  "selectField": "Sélectionner un champ",
@@ -82,7 +84,8 @@
82
84
  },
83
85
  "buttons": {
84
86
  "addCondition": "Ajouter une condition",
85
- "clearAll": "Tout effacer"
87
+ "clearAll": "Tout effacer",
88
+ "save": "Enregistrer"
86
89
  }
87
90
  },
88
91
  "multiStep": {
@@ -72,7 +72,9 @@
72
72
  "co": "מכיל",
73
73
  "sw": "מתחיל ב",
74
74
  "ew": "מסתיים ב",
75
- "pr": "קיים"
75
+ "pr": "קיים",
76
+ "in": "ברשימה",
77
+ "nin": "לא ברשימה"
76
78
  },
77
79
  "placeholders": {
78
80
  "selectField": "בחר שדה",
@@ -82,7 +84,8 @@
82
84
  },
83
85
  "buttons": {
84
86
  "addCondition": "הוספת תנאי",
85
- "clearAll": "נקה הכל"
87
+ "clearAll": "נקה הכל",
88
+ "save": "שמירה"
86
89
  }
87
90
  },
88
91
  "multiStep": {
@@ -72,7 +72,9 @@
72
72
  "co": "Contiene",
73
73
  "sw": "Inizia con",
74
74
  "ew": "Finisce con",
75
- "pr": "Presente"
75
+ "pr": "Presente",
76
+ "in": "Nella lista",
77
+ "nin": "Non nella lista"
76
78
  },
77
79
  "placeholders": {
78
80
  "selectField": "Seleziona campo",
@@ -82,7 +84,8 @@
82
84
  },
83
85
  "buttons": {
84
86
  "addCondition": "Aggiungi condizione",
85
- "clearAll": "Cancella tutto"
87
+ "clearAll": "Cancella tutto",
88
+ "save": "Salva"
86
89
  }
87
90
  },
88
91
  "multiStep": {
@@ -72,7 +72,9 @@
72
72
  "co": "Содержит",
73
73
  "sw": "Начинается с",
74
74
  "ew": "Заканчивается на",
75
- "pr": "Присутствует"
75
+ "pr": "Присутствует",
76
+ "in": "В списке",
77
+ "nin": "Не в списке"
76
78
  },
77
79
  "placeholders": {
78
80
  "selectField": "Выберите поле",
@@ -82,7 +84,8 @@
82
84
  },
83
85
  "buttons": {
84
86
  "addCondition": "Добавить условие",
85
- "clearAll": "Очистить всё"
87
+ "clearAll": "Очистить всё",
88
+ "save": "Сохранить"
86
89
  }
87
90
  },
88
91
  "multiStep": {
@@ -43,6 +43,8 @@ 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
48
 
47
49
  /**
48
50
  * SCIM 2.0 logical operators
@@ -55,7 +57,7 @@ export type LogicalOperator = 'and' | 'or'
55
57
  export interface FilterCondition<T extends Record<string, any> = Record<string, any>> {
56
58
  field: DeepKeyOf<T> | string
57
59
  op: ComparisonOperator
58
- value?: Primitive // optional for 'pr' operator
60
+ value?: Primitive | Primitive[] // array only used with 'in'/'nin'
59
61
  connector?: LogicalOperator // before this condition, ignored for first
60
62
  }
61
63
 
@@ -107,8 +109,15 @@ export function buildQuery<T extends Record<string, any>>(
107
109
  // Build the condition
108
110
  if (op === 'pr') {
109
111
  parts.push(`${field} pr`)
112
+ } else if (op === 'in' || op === 'nin') {
113
+ const values = Array.isArray(value) ? value : (value != null ? [value] : [])
114
+ 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)
110
119
  } else {
111
- parts.push(`${field} ${op} ${formatValue(value ?? null)}`)
120
+ parts.push(`${field} ${op} ${formatValue(value as Primitive ?? null)}`)
112
121
  }
113
122
  }
114
123
 
@@ -373,7 +382,7 @@ export function search<T extends Record<string, any>>(
373
382
  return buildQuery(conditions)
374
383
  }
375
384
 
376
- const OPERATORS: ComparisonOperator[] = ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'co', 'sw', 'ew', 'pr']
385
+ const OPERATORS: ComparisonOperator[] = ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'co', 'sw', 'ew', 'pr', 'in', 'nin']
377
386
  const CONNECTORS: LogicalOperator[] = ['and', 'or']
378
387
 
379
388
  /**