@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.
- package/dist/components/charts/Funnel.vue.d.ts.map +1 -1
- package/dist/components/charts/RankBars.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +4 -2
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/index.cjs +44 -44
- package/dist/index.mjs +3832 -3796
- package/dist/style.css +1 -1
- package/dist/utils/index.d.ts +30 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/charts/Funnel.vue +28 -27
- package/src/components/charts/RankBars.vue +10 -8
- package/src/components/layout/AppContent.vue +2 -1
- package/src/form-flow/FormFlow.vue +24 -1
- package/src/form-flow/form-flow.ts +4 -2
- package/src/utils/index.ts +58 -0
package/dist/utils/index.d.ts
CHANGED
|
@@ -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
|
@@ -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
|
|
310
|
-
<span class="bgl-funnel__val
|
|
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
|
|
342
|
-
.bgl-
|
|
343
|
-
.bgl-
|
|
344
|
-
.bgl-
|
|
345
|
-
.bgl-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/*
|
|
369
|
-
.bgl-funnel--bars
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
<span class="bgl-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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:
|
|
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
|
-
|
|
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>
|
package/src/utils/index.ts
CHANGED
|
@@ -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'
|