@bagelink/vue 1.15.145 → 1.15.151

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.145",
4
+ "version": "1.15.151",
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" :title="s.label">{{ s.label }}</span>
310
+ <span class="bgl-funnel__val txt24">{{ 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
 
@@ -288,6 +288,7 @@ flex-wrap: wrap;
288
288
  @media screen and (max-width: 910px) {
289
289
  .app-header {
290
290
  row-gap: 0.25rem;
291
+ justify-content: space-between;
291
292
  }
292
293
  }
293
294
 
@@ -310,7 +311,7 @@ row-gap: 0.5rem;
310
311
  @media screen and (max-width: 910px) {
311
312
  .app-header-side {
312
313
  row-gap: 0.25rem;
313
- flex-grow: 1;
314
+ flex-grow: unset;
314
315
  }
315
316
  }
316
317
 
@@ -125,6 +125,19 @@ function shouldShowField(field: FieldBuilder): boolean {
125
125
  return evaluateQuery(field._condition, formData.value)
126
126
  }
127
127
 
128
+ // Fields whose options depend on a sibling (via `.if('<dep> …')`) must remount
129
+ // when that sibling changes so a function option-loader re-runs with the new
130
+ // value. `useSearch` only reloads on search-term change, so we fold the
131
+ // referenced field's current value into the field's render key. Extracts the
132
+ // first field name from the condition (e.g. `provider pr` → `provider`).
133
+ function fieldKey(key: string, field: FieldBuilder): string {
134
+ if (field._condition == null) return key
135
+ const dep = field._condition.trim().split(/\s+/)[0]
136
+ if (!dep) return key
137
+ const depVal = getNestedValue(formData.value, dep)
138
+ return depVal == null ? key : `${key}:${String(depVal)}`
139
+ }
140
+
128
141
  const visibleFields = computed(() => {
129
142
  const fields: Array<{ key: string, field: FieldBuilder }> = []
130
143
 
@@ -258,6 +271,16 @@ function getFieldProps(field: FieldBuilder, key: string) {
258
271
  baseProps.multiselect = true
259
272
  }
260
273
 
274
+ // Function option loaders get the live form data as a 2nd arg so options can
275
+ // depend on sibling fields (e.g. `model` filtered by the selected `provider`).
276
+ // SelectInput/useSearch only re-run the loader on search-term change; a change
277
+ // to a dependency is handled by re-keying the field (see fieldKey) so it
278
+ // remounts and reloads.
279
+ if ((field._type === 'select' || field._type === 'multiselect') && typeof baseProps.options === 'function') {
280
+ const loader = baseProps.options
281
+ baseProps.options = (query: string) => loader(query, formData.value)
282
+ }
283
+
261
284
  if (field._type === 'json') {
262
285
  baseProps.language = baseProps.language ?? 'json'
263
286
  }
@@ -375,7 +398,7 @@ defineExpose({
375
398
  <slot name="success" :form-data="formData" />
376
399
  </template>
377
400
  <template v-else>
378
- <div v-for="{ key, field } in visibleFields" :key="key" :class="[field._class, field._config.class]">
401
+ <div v-for="{ key, field } in visibleFields" :key="fieldKey(key, field)" :class="[field._class, field._config.class]">
379
402
  <!-- Custom slot for specific field -->
380
403
  <slot
381
404
  :name="`field:${key}`"
@@ -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>
@@ -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'