@bagelink/vue 1.9.67 → 1.9.71

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.9.67",
4
+ "version": "1.9.71",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts" generic="T extends Record<string, any>">
2
2
  import type { Option } from '@bagelink/vue'
3
3
  import type { ComparisonOperator, LogicalOperator, QueryConditions } from '../utils/queryFilter'
4
- import { Btn, DateInput, Dropdown, Icon, SelectInput, TextInput, useI18n } from '@bagelink/vue'
5
- import { computed } from 'vue'
4
+ import { Btn, DateInput, Dropdown, Icon, SelectInput, TextInput, useI18n, useDebounceFn } from '@bagelink/vue'
5
+ import { computed, ref, useAttrs } from 'vue'
6
6
 
7
7
  type OptionsSource = Option[] | ((query: string) => Promise<Option[]>)
8
8
 
@@ -25,11 +25,28 @@ const props = defineProps<{
25
25
 
26
26
  const emit = defineEmits<{
27
27
  change: [value: QueryConditions<T>]
28
+ save: [value: QueryConditions<T>]
28
29
  }>()
29
30
 
30
31
  const model = defineModel<QueryConditions<T>>({ default: () => [] })
31
32
 
33
+ // Internal working copy — may contain incomplete conditions for UI purposes
34
+ const internalModel = ref<QueryConditions<T>>([...model.value] as QueryConditions<T>)
35
+
32
36
  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))
42
+ }
43
+
44
+ const debouncedFlush = useDebounceFn(() => {
45
+ const complete = internalModel.value.filter(isComplete) as QueryConditions<T>
46
+ console.log('[Filter] flushing - internal:', internalModel.value, '| complete:', complete)
47
+ model.value = complete
48
+ emit('change', complete)
49
+ }, 400)
33
50
 
34
51
  let conditionIdCounter = 0
35
52
  function generateId() {
@@ -76,7 +93,7 @@ const currentTexts = computed(() => ({
76
93
  const conditionIds = new Map<number, string>()
77
94
 
78
95
  const conditions = computed(() => {
79
- const arr = model.value || []
96
+ const arr = internalModel.value || []
80
97
  return arr.map((cond, index) => {
81
98
  // Get or create stable ID for this index
82
99
  if (!conditionIds.has(index)) {
@@ -142,7 +159,7 @@ function parseValue(value: string | number | boolean | null): string | number |
142
159
  }
143
160
 
144
161
  function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value: string | number | boolean | null, connector: LogicalOperator }>) {
145
- const newModel = [...model.value]
162
+ const newModel = [...internalModel.value]
146
163
  const existing = newModel[index]
147
164
  if (!existing) return
148
165
 
@@ -153,8 +170,8 @@ function updateConditionAtIndex(index: number, updates: Partial<{ field: string,
153
170
  if (updates.connector !== undefined) updated.connector = updates.connector
154
171
 
155
172
  newModel[index] = updated
156
- model.value = newModel
157
- emit('change', newModel)
173
+ internalModel.value = newModel as QueryConditions<T>
174
+ debouncedFlush()
158
175
  }
159
176
 
160
177
  function addCondition() {
@@ -165,28 +182,28 @@ function addCondition() {
165
182
  value: '' as any,
166
183
  connector: model.value.length > 0 ? 'and' as LogicalOperator : undefined,
167
184
  }
168
- const newModel = [...model.value, newCondition]
169
- model.value = newModel
170
- emit('change', newModel)
185
+ const newModel = [...internalModel.value, newCondition]
186
+ internalModel.value = newModel as QueryConditions<T>
187
+ console.log('[Filter] addCondition - skipping flush (incomplete)')
171
188
  }
172
189
 
173
190
  function removeCondition(index: number) {
174
- const newModel = [...model.value]
191
+ const newModel = [...internalModel.value]
175
192
  newModel.splice(index, 1)
176
- // Clear connector on new first item
177
193
  if (newModel.length > 0 && newModel[0].connector) {
178
194
  newModel[0] = { ...newModel[0], connector: undefined }
179
195
  }
180
- // Clean up ID mapping
181
196
  conditionIds.delete(index)
182
- model.value = newModel
183
- emit('change', newModel)
197
+ internalModel.value = newModel as QueryConditions<T>
198
+ debouncedFlush()
184
199
  }
185
200
 
186
201
  function clearAll() {
187
202
  conditionIds.clear()
188
- model.value = []
189
- emit('change', [])
203
+ internalModel.value = [] as unknown as QueryConditions<T>
204
+ model.value = [] as unknown as QueryConditions<T>
205
+ console.log('[Filter] clearAll')
206
+ emit('change', [] as unknown as QueryConditions<T>)
190
207
  }
191
208
 
192
209
  function getFieldType(fieldValue: string): string {
@@ -245,9 +262,11 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
245
262
  <TransitionGroup name="condition">
246
263
  <div
247
264
  v-for="(condition, index) in conditions" :key="condition.id"
248
- class="grid filter-row gap-025 align-items-center pt-025" :class="{
265
+ class="grid gap-025 align-items-center pt-025" :class="{
249
266
  'mt-025': index > 0 && condition.connector === 'or',
250
267
  'pt-075 border-top-or': index > 0 && condition.connector === 'or',
268
+ 'first-filter-row': index === 0,
269
+ 'filter-row': index > 0,
251
270
  }"
252
271
  >
253
272
  <!-- Connector (AND/OR) -->
@@ -260,7 +279,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
260
279
  @update:model-value="(v: LogicalOperator) => onConnectorChange(condition.id, v)"
261
280
  />
262
281
  </div>
263
- <div v-else />
282
+ <!-- <div v-else /> -->
264
283
 
265
284
  <!-- Field selector -->
266
285
  <SelectInput
@@ -324,10 +343,16 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
324
343
 
325
344
  <div class="flex gap-1 space-between">
326
345
  <Btn icon="add" flat thin :value="currentTexts.buttons.addCondition" @click="addCondition" />
327
- <Btn
328
- v-if="conditions.length > 0" icon="delete_sweep" flat thin color="gray"
329
- :value="currentTexts.buttons.clearAll" @click="clearAll"
330
- />
346
+ <div class="flex gap-05">
347
+ <Btn
348
+ v-if="conditions.length > 0" icon="delete_sweep" flat thin color="gray"
349
+ :value="currentTexts.buttons.clearAll" @click="clearAll"
350
+ />
351
+ <Btn
352
+ v-if="hasSaveListener" icon="save" thin color="primary" :value="$t('filter.buttons.save')"
353
+ @click="emit('save', internalModel.filter(isComplete))"
354
+ />
355
+ </div>
331
356
  </div>
332
357
  </div>
333
358
  </Dropdown>
@@ -347,6 +372,10 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
347
372
  font-size: 12px !important;
348
373
  }
349
374
 
375
+ .first-filter-row {
376
+ grid-template-columns: minmax(90px, 0.5fr) minmax(170px, 0.75fr) minmax(120px, 0.5fr) auto;
377
+ }
378
+
350
379
  .filter-row {
351
380
  grid-template-columns: 50px minmax(90px, 0.5fr) minmax(170px, 0.75fr) minmax(120px, 0.5fr) auto;
352
381
  }