@bagelink/vue 1.15.147 → 1.15.154

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.
@@ -56,5 +56,35 @@ export { d, DateChain, dateDiff, DAY, fmtDate, formatDate, getDatePartsMap, hand
56
56
  export * from './fetch';
57
57
  export * from './string';
58
58
  export { formatString } from './strings';
59
+ /** An option produced by `createOptionsGetter`: `{ label, value }` plus the full source item. */
60
+ export type ItemOption<T> = {
61
+ label: string;
62
+ value: any;
63
+ } & T;
64
+ /** A memoized async options loader compatible with `$.select`/`SelectInput`. */
65
+ export type OptionsGetter<T> = ((filter?: string) => Promise<ItemOption<T>[]>) & {
66
+ clearCache: () => void;
67
+ };
68
+ /**
69
+ * Build a memoized async options loader for select inputs from a list fetcher.
70
+ *
71
+ * - Fetches once and caches; `clearCache()` invalidates.
72
+ * - When called with a `filter` string, fuzzy-matches on the option label and
73
+ * returns the best matches first (so async `SelectInput`s search client-side).
74
+ * - The default mapper derives `label` from `name`/`title`/`id` and `value` from
75
+ * `id`; pass `mapper` for anything else (e.g. value = name). The full source
76
+ * item is spread onto the option so callers can read extra fields.
77
+ *
78
+ * @example
79
+ * const getCityOptions = createOptionsGetter(() => api.cities.get())
80
+ * const getModelOptions = createOptionsGetter(
81
+ * () => api.models(),
82
+ * (m) => ({ label: m.name, value: m.name, ...m }),
83
+ * )
84
+ */
85
+ export declare function createOptionsGetter<T extends Record<string, any>>(fetcher: (filter?: string) => Promise<T[] | {
86
+ data?: T[];
87
+ items?: T[];
88
+ }>, mapper?: (item: T) => ItemOption<T>): OptionsGetter<T>;
59
89
  export { useDebounceFn } from '@vueuse/core';
60
90
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.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.15.147",
4
+ "version": "1.15.154",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -303,11 +303,11 @@ const tooltipStyle = computed(() => ({ left: `${tipX.value}px`, top: `${tipY.val
303
303
  <!-- Axis labels: left gutter (vertical) or below each stage (horizontal) -->
304
304
  <div class="bgl-funnel__axis">
305
305
  <div
306
- v-for="(s, i) in segments" :key="i" class="bgl-funnel__tick"
306
+ v-for="(s, i) in segments" :key="i" class="bgl-funnel__tick p-05"
307
307
  :style="horizontal ? { flex: '1 1 0' } : { height: `${bandHeight}px` }"
308
308
  >
309
- <span class="bgl-funnel__name ellipsis-1 txt14" :title="s.label">{{ s.label }}</span>
310
- <span class="bgl-funnel__val txt13">{{ s.display }}</span>
309
+ <span class="bgl-funnel__name ellipsis-1 txt12 light opacity-6" :title="s.label">{{ s.label }}</span>
310
+ <span class="bgl-funnel__val txt24 bold">{{ s.display }}</span>
311
311
  </div>
312
312
  </div>
313
313
  </div>
@@ -337,41 +337,42 @@ const tooltipStyle = computed(() => ({ left: `${tipX.value}px`, top: `${tipY.val
337
337
  </template>
338
338
 
339
339
  <style scoped>
340
- /* Flow */
341
- .bgl-funnel__seg { transition: opacity 0.15s ease; outline: none; }
342
- .bgl-funnel__seg:hover { opacity: 0.85; cursor: default; }
343
- .bgl-funnel__seg:focus, .bgl-funnel__sub:focus, .bgl-funnel__hit:focus { outline: none; }
344
- .bgl-funnel__sub { stroke: var(--bgl-box-bg); stroke-width: 0.4; outline: none; }
345
- .bgl-funnel__hit { cursor: default; outline: none; }
340
+ /* ── Flow: SVG segments ──────────────────────────── */
341
+ .bgl-funnel__seg,
342
+ .bgl-funnel__sub,
343
+ .bgl-funnel__hit { outline: none; cursor: default; }
344
+ .bgl-funnel__seg { transition: opacity 0.15s ease; }
345
+ .bgl-funnel__seg:hover { opacity: 0.85; }
346
+ .bgl-funnel__sub { stroke: var(--bgl-box-bg); stroke-width: 0.4; }
346
347
  .bgl-funnel__hit:hover { fill: rgba(255, 255, 255, 0.06); }
347
- .bgl-funnel__legend { list-style: none; margin: 0.75rem 0 0; padding: 0; gap: 0.5rem 1rem; font-size: 0.8rem; }
348
+ .bgl-funnel__val { font-variant-numeric: tabular-nums; }
349
+
350
+ /* ── Legend ───────────────────────────────────────── */
351
+ .bgl-funnel__legend { list-style: none; margin: 0.75rem 0 0; padding: 0; gap: 0.5rem 1rem; font-size: 0.8rem; }
348
352
  .bgl-funnel__legend li { gap: 0.4rem; }
349
- .bgl-funnel__dot { width: 10px; height: 10px; border-radius: 3px; }
350
- .bgl-funnel__val { font-variant-numeric: tabular-nums; }
353
+ .bgl-funnel__dot { width: 10px; height: 10px; border-radius: 3px; }
351
354
 
352
- /* Vertical flow: silhouette (fills) + right axis gutter */
355
+ /* ── Vertical flow ───────────────────────────────── */
353
356
  .bgl-funnel--vertical .bgl-funnel__flow { display: flex; align-items: stretch; gap: 0.75rem; width: 100%; }
354
- .bgl-funnel--vertical .bgl-funnel__svg { flex: 1 1 auto; width: auto; min-width: 0; }
357
+ .bgl-funnel--vertical .bgl-funnel__svg { flex: 1 1 auto; width: auto; min-width: 0; }
355
358
  .bgl-funnel--vertical .bgl-funnel__axis { flex: 0 0 auto; display: flex; flex-direction: column; }
356
- .bgl-funnel--vertical .bgl-funnel__tick {
357
- display: flex; flex-direction: column; justify-content: center; line-height: 1.3; max-width: 12rem;
358
- }
359
+ .bgl-funnel--vertical .bgl-funnel__tick { display: flex; flex-direction: column; justify-content: center; line-height: 1.3; max-width: 12rem; }
359
360
 
360
- /* Horizontal flow: silhouette on top, labels below each stage */
361
+ /* ── Horizontal flow ─────────────────────────────── */
361
362
  .bgl-funnel--horizontal .bgl-funnel__flow { display: flex; flex-direction: column; gap: 0.4rem; }
362
- .bgl-funnel--horizontal .bgl-funnel__svg { width: 100%; }
363
+ .bgl-funnel--horizontal .bgl-funnel__svg { width: 100%; }
363
364
  .bgl-funnel--horizontal .bgl-funnel__axis { display: flex; }
364
- .bgl-funnel--horizontal .bgl-funnel__tick {
365
- display: flex; flex-direction: column; align-items: center; text-align: center; line-height: 1.15; overflow: hidden; padding: 0 0.25rem;
366
- }
365
+ .bgl-funnel--horizontal .bgl-funnel__tick { display: flex; flex-direction: column; align-items: center; text-align: center; line-height: 1.15; overflow: hidden; padding: 0 0.25rem; }
367
366
 
368
- /* Bars */
369
- .bgl-funnel--bars { display: grid; gap: 0.5rem; }
370
- .bgl-funnel__stage { display: grid; grid-template-columns: minmax(60px, 22%) 1fr; align-items: center; gap: 0.75rem; }
367
+ /* ── Discrete bars ───────────────────────────────── */
368
+ .bgl-funnel--bars { display: grid; gap: 0.5rem; }
371
369
  .bgl-funnel--bars .bgl-funnel__name { color: var(--bgl-text); font-weight: 500; }
370
+ .bgl-funnel__stage { display: grid; grid-template-columns: minmax(60px, 22%) 1fr; align-items: center; gap: 0.75rem; }
372
371
  .bgl-funnel__bar {
373
- height: 38px; border-radius: 8px; color: var(--bgl-white); font-weight: 600; font-size: 0.85rem;
372
+ height: 38px; border-radius: 8px; min-width: 2.5rem;
373
+ color: var(--bgl-white); font-weight: 600; font-size: 0.85rem;
374
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
374
375
  transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1);
375
- min-width: 2.5rem; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); cursor: default;
376
+ cursor: default;
376
377
  }
377
378
  </style>
@@ -54,14 +54,16 @@ const rows = computed(() => {
54
54
  </script>
55
55
 
56
56
  <template>
57
- <div class="bgl-rank grid w-100p gap-05">
58
- <div v-for="(r, i) in rows" :key="i" class="bgl-rank__row align-items-center grid gap-075">
59
- <span class="bgl-rank__label ellipsis-1 txt14" :title="r.label">{{ r.label }}</span>
60
- <span class="bgl-rank__track overflow-hidden round">
61
- <span class="bgl-rank__bar display-block round" :style="{ width: `${r.pct}%`, background: r.color }" />
62
- </span>
63
- <span class="txt13 tabular-nums semibold">{{ r.display }}</span>
64
- </div>
57
+ <div class="bgl-rank grid w-100p gap-05 overflow">
58
+ <div>
59
+ <div v-for="(r, i) in rows" :key="i" class="bgl-rank__row align-items-center grid gap-075">
60
+ <span class="bgl-rank__label ellipsis-1 txt14" :title="r.label">{{ r.label }}</span>
61
+ <span class="bgl-rank__track overflow-hidden round">
62
+ <span class="bgl-rank__bar display-block round" :style="{ width: `${r.pct}%`, background: r.color }" />
63
+ </span>
64
+ <span class="txt13 tabular-nums semibold">{{ r.display }}</span>
65
+ </div>
66
+ </div>
65
67
  </div>
66
68
  </template>
67
69
 
@@ -121,10 +121,25 @@ watch(formData, (newVal) => {
121
121
  }, { deep: true })
122
122
 
123
123
  function shouldShowField(field: FieldBuilder): boolean {
124
+ // Hidden fields keep their value (submitted) but are never displayed.
125
+ if (field._hidden) return false
124
126
  if (field._condition == null) return true
125
127
  return evaluateQuery(field._condition, formData.value)
126
128
  }
127
129
 
130
+ // Fields whose options depend on a sibling (via `.if('<dep> …')`) must remount
131
+ // when that sibling changes so a function option-loader re-runs with the new
132
+ // value. `useSearch` only reloads on search-term change, so we fold the
133
+ // referenced field's current value into the field's render key. Extracts the
134
+ // first field name from the condition (e.g. `provider pr` → `provider`).
135
+ function fieldKey(key: string, field: FieldBuilder): string {
136
+ if (field._condition == null) return key
137
+ const dep = field._condition.trim().split(/\s+/)[0]
138
+ if (!dep) return key
139
+ const depVal = getNestedValue(formData.value, dep)
140
+ return depVal == null ? key : `${key}:${String(depVal)}`
141
+ }
142
+
128
143
  const visibleFields = computed(() => {
129
144
  const fields: Array<{ key: string, field: FieldBuilder }> = []
130
145
 
@@ -258,6 +273,16 @@ function getFieldProps(field: FieldBuilder, key: string) {
258
273
  baseProps.multiselect = true
259
274
  }
260
275
 
276
+ // Function option loaders get the live form data as a 2nd arg so options can
277
+ // depend on sibling fields (e.g. `model` filtered by the selected `provider`).
278
+ // SelectInput/useSearch only re-run the loader on search-term change; a change
279
+ // to a dependency is handled by re-keying the field (see fieldKey) so it
280
+ // remounts and reloads.
281
+ if ((field._type === 'select' || field._type === 'multiselect') && typeof baseProps.options === 'function') {
282
+ const loader = baseProps.options
283
+ baseProps.options = (query: string) => loader(query, formData.value)
284
+ }
285
+
261
286
  if (field._type === 'json') {
262
287
  baseProps.language = baseProps.language ?? 'json'
263
288
  }
@@ -375,7 +400,7 @@ defineExpose({
375
400
  <slot name="success" :form-data="formData" />
376
401
  </template>
377
402
  <template v-else>
378
- <div v-for="{ key, field } in visibleFields" :key="key" :class="[field._class, field._config.class]">
403
+ <div v-for="{ key, field } in visibleFields" :key="fieldKey(key, field)" :class="[field._class, field._config.class]">
379
404
  <!-- Custom slot for specific field -->
380
405
  <slot
381
406
  :name="`field:${key}`"
@@ -197,6 +197,7 @@ const stepErrors = ref<Record<string, string>>({})
197
197
 
198
198
  // Check if a field should be validated (visible based on condition)
199
199
  function shouldValidateField(field: FieldBuilder): boolean {
200
+ if (field._hidden) return false
200
201
  if (field._condition == null) return true
201
202
  return evaluateCondition(field._condition, formData.value)
202
203
  }
@@ -25,8 +25,10 @@ export interface SelectOption<T extends string = string> {
25
25
  icon?: IconType
26
26
  }
27
27
 
28
- /** Async options loader function */
29
- export type AsyncOptionsLoader<T extends string = string> = (query: string) => Promise<(T | SelectOption<T>)[]>
28
+ /** Async options loader function. Receives the search query and, when rendered
29
+ * inside FormFlow, the current form data — so options can depend on sibling
30
+ * fields (e.g. a `model` list filtered by the selected `provider`). */
31
+ export type AsyncOptionsLoader<T extends string = string> = (query: string, form: Record<string, any>) => Promise<(T | SelectOption<T>)[]>
30
32
 
31
33
  /** Options can be string array, object array, or async loader function */
32
34
  export type SelectOptions<T extends string = string>
@@ -91,6 +93,8 @@ export interface FieldBuilder<TValue = any> {
91
93
  _type: string
92
94
  _config: BaseFieldConfig & Record<string, any>
93
95
  _condition?: string
96
+ /** When true the field is never rendered but its value is still submitted. */
97
+ _hidden?: boolean
94
98
  _validations?: Array<(value: TValue) => true | string>
95
99
  _onUpdate?: (value: TValue, formData: Record<string, any>) => void
96
100
  _class?: string
@@ -99,6 +103,8 @@ export interface FieldBuilder<TValue = any> {
99
103
 
100
104
  // Chainable methods
101
105
  if: (condition: string) => FieldBuilder<TValue>
106
+ /** Mark the field as hidden — kept in form data / submitted but not displayed. */
107
+ hidden: (hidden?: boolean) => FieldBuilder<TValue>
102
108
  validate: (fn: (value: TValue) => true | string) => FieldBuilder<TValue>
103
109
  onUpdate: (fn: (value: TValue, formData: Record<string, any>) => void) => FieldBuilder<TValue>
104
110
  class: (className: string) => FieldBuilder<TValue>
@@ -146,6 +152,7 @@ class Field<TValue = any> implements FieldBuilder<TValue> {
146
152
  _type: string
147
153
  _config: BaseFieldConfig & Record<string, any>
148
154
  _condition?: string
155
+ _hidden?: boolean
149
156
  _validations?: Array<(value: TValue) => true | string>
150
157
  _onUpdate?: (value: TValue, formData: Record<string, any>) => void
151
158
  _class?: string
@@ -162,6 +169,11 @@ class Field<TValue = any> implements FieldBuilder<TValue> {
162
169
  return this
163
170
  }
164
171
 
172
+ hidden(hidden = true): this {
173
+ this._hidden = hidden
174
+ return this
175
+ }
176
+
165
177
  validate(fn: (value: TValue) => true | string): this {
166
178
  if (!this._validations) {
167
179
  this._validations = []
@@ -572,6 +584,9 @@ class Schema<T extends Record<string, any> = Record<string, any>> implements Sch
572
584
  // Conditional visibility
573
585
  if (field._condition) xUi.condition = field._condition
574
586
 
587
+ // Hidden (submitted but not displayed)
588
+ if (field._hidden) xUi.hidden = true
589
+
575
590
  // Array field options
576
591
  if (field._type === 'array') {
577
592
  if (config.allowAdd !== undefined) xUi.allowAdd = config.allowAdd
@@ -761,6 +776,7 @@ interface JSONSchemaProperty {
761
776
  cols?: number
762
777
  class?: string
763
778
  condition?: string
779
+ hidden?: boolean
764
780
  allowAdd?: boolean
765
781
  allowDelete?: boolean
766
782
  component?: string
@@ -1017,6 +1033,11 @@ function createFieldFromProperty(
1017
1033
  field.if(xUi.condition)
1018
1034
  }
1019
1035
 
1036
+ // Apply hidden
1037
+ if (xUi.hidden) {
1038
+ field.hidden()
1039
+ }
1040
+
1020
1041
  return field
1021
1042
  }
1022
1043
 
@@ -21,6 +21,7 @@ export type FormFieldType =
21
21
  | 'number' | 'date' | 'email' | 'phone' | 'url'
22
22
  | 'select' | 'radio' | 'checkbox' | 'toggle'
23
23
  | 'file' | 'signature' | 'range' | 'color' | 'json'
24
+ | 'hidden'
24
25
  | 'heading' | 'divider'
25
26
 
26
27
  export interface SelectOption {
@@ -55,6 +56,10 @@ export interface FormField {
55
56
  /** User-authored (non-grid) classes preserved alongside layout classes. */
56
57
  class?: string
57
58
  hideLabel?: boolean
59
+ /** SCIM 2.0 filter expression controlling conditional visibility. */
60
+ condition?: string
61
+ /** When true the field is never displayed but its value is still submitted. */
62
+ hidden?: boolean
58
63
  richTextVariant?: 'full' | 'basic' | 'simple'
59
64
  markdownShowFormatting?: boolean
60
65
  textareaRows?: number
@@ -99,6 +104,10 @@ export interface JSONSchemaProperty {
99
104
  /** UI extension object — layout classes + render hints (shared with form-flow). */
100
105
  'x-ui'?: {
101
106
  class?: string
107
+ /** SCIM 2.0 filter expression controlling conditional visibility. */
108
+ condition?: string
109
+ /** Field kept in form data / submitted but never displayed. */
110
+ hidden?: boolean
102
111
  [key: string]: any
103
112
  }
104
113
  'x-field-type'?: FormFieldType
@@ -145,6 +154,9 @@ export function defaultRowSpan(type: FormFieldType): number {
145
154
  return 1
146
155
  }
147
156
 
157
+ /** Field types that never render an input in the live form. */
158
+ export const NON_INPUT_TYPES: FormFieldType[] = ['heading', 'divider', 'hidden']
159
+
148
160
  export function inferFieldType(prop: JSONSchemaProperty): FormFieldType {
149
161
  if (prop.format) {
150
162
  switch (prop.format) {
@@ -204,6 +216,8 @@ export function schemaToFields(schema: JSONSchemaObject | undefined): FormField[
204
216
  mColSpan: classToMColSpan(uiClass) ?? 12,
205
217
  class: stripGridClasses(uiClass) || undefined,
206
218
  hideLabel: prop['x-hide-label'] ?? false,
219
+ condition: prop['x-ui']?.condition || undefined,
220
+ hidden: prop['x-ui']?.hidden || (type === 'hidden') || undefined,
207
221
  richTextVariant: prop['x-rich-text-variant'],
208
222
  markdownShowFormatting: prop['x-markdown-show-formatting'],
209
223
  textareaRows: prop['x-textarea-rows'],
@@ -266,6 +280,7 @@ export function fieldsToSchema(fields: FormField[]): JSONSchemaObject {
266
280
  case 'file':
267
281
  case 'signature':
268
282
  case 'color':
283
+ case 'hidden':
269
284
  prop.type = 'string'
270
285
  break
271
286
  case 'number':
@@ -332,6 +347,10 @@ export function fieldsToSchema(fields: FormField[]): JSONSchemaObject {
332
347
  })
333
348
  if (layoutClass) prop['x-ui'] = { ...prop['x-ui'], class: layoutClass }
334
349
 
350
+ // Conditional visibility + hidden — stored in x-ui (consumed by FormFlow)
351
+ if (field.condition) prop['x-ui'] = { ...prop['x-ui'], condition: field.condition }
352
+ if (field.hidden === true) prop['x-ui'] = { ...prop['x-ui'], hidden: true }
353
+
335
354
  // Boolean/enum x- extensions — only write when non-default
336
355
  if (field.hideLabel === true) prop['x-hide-label'] = true
337
356
  if (field.richTextVariant) prop['x-rich-text-variant'] = field.richTextVariant
@@ -412,6 +431,8 @@ export function validateFormData(fields: FormField[], data: Record<string, any>)
412
431
 
413
432
  for (const field of fields) {
414
433
  if (field.type === 'heading' || field.type === 'divider') continue
434
+ // Hidden fields are never shown, so they can't be user-corrected — skip.
435
+ if (field.type === 'hidden' || field.hidden === true) continue
415
436
 
416
437
  const value = data[field.key]
417
438
 
@@ -379,4 +379,62 @@ export * from './fetch'
379
379
  export * from './string'
380
380
 
381
381
  export { formatString } from './strings'
382
+
383
+ // ─── Async select options ─────────────────────────────────────────────────────
384
+
385
+ import { fuzzyMatch } from './string'
386
+
387
+ /** An option produced by `createOptionsGetter`: `{ label, value }` plus the full source item. */
388
+ export type ItemOption<T> = { label: string, value: any } & T
389
+
390
+ /** A memoized async options loader compatible with `$.select`/`SelectInput`. */
391
+ export type OptionsGetter<T> = ((filter?: string) => Promise<ItemOption<T>[]>) & { clearCache: () => void }
392
+
393
+ /**
394
+ * Build a memoized async options loader for select inputs from a list fetcher.
395
+ *
396
+ * - Fetches once and caches; `clearCache()` invalidates.
397
+ * - When called with a `filter` string, fuzzy-matches on the option label and
398
+ * returns the best matches first (so async `SelectInput`s search client-side).
399
+ * - The default mapper derives `label` from `name`/`title`/`id` and `value` from
400
+ * `id`; pass `mapper` for anything else (e.g. value = name). The full source
401
+ * item is spread onto the option so callers can read extra fields.
402
+ *
403
+ * @example
404
+ * const getCityOptions = createOptionsGetter(() => api.cities.get())
405
+ * const getModelOptions = createOptionsGetter(
406
+ * () => api.models(),
407
+ * (m) => ({ label: m.name, value: m.name, ...m }),
408
+ * )
409
+ */
410
+ export function createOptionsGetter<T extends Record<string, any>>(
411
+ fetcher: (filter?: string) => Promise<T[] | { data?: T[], items?: T[] }>,
412
+ mapper?: (item: T) => ItemOption<T>,
413
+ ): OptionsGetter<T> {
414
+ let cache: ItemOption<T>[] | null = null
415
+ const getter = (async (filter?: string) => {
416
+ try {
417
+ if (!cache) {
418
+ const result = await fetcher(filter)
419
+ const items: T[] = Array.isArray(result) ? result : (result.data || result.items || [])
420
+ cache = items.map(item => mapper
421
+ ? mapper(item)
422
+ : ({ label: item.name || item.title || `${item.id}`, value: item.id, ...item }))
423
+ }
424
+ if (filter && cache) {
425
+ return cache
426
+ .map(item => ({ item, match: fuzzyMatch(item.label, filter) }))
427
+ .filter(({ match }) => match.matches)
428
+ .sort((a, b) => b.match.score - a.match.score)
429
+ .map(({ item }) => item)
430
+ }
431
+ return cache || []
432
+ } catch (error) {
433
+ console.error('Error fetching options:', error)
434
+ return []
435
+ }
436
+ }) as OptionsGetter<T>
437
+ getter.clearCache = () => { cache = null }
438
+ return getter
439
+ }
382
440
  export { useDebounceFn } from '@vueuse/core'