@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.
- package/dist/components/charts/Funnel.vue.d.ts.map +1 -1
- package/dist/components/charts/RankBars.vue.d.ts.map +1 -1
- package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
- package/dist/form-flow/MultiStepForm.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +9 -2
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/form-flow/schema-fields.d.ts +11 -1
- package/dist/form-flow/schema-fields.d.ts.map +1 -1
- package/dist/index.cjs +177 -150
- package/dist/index.mjs +36021 -42051
- package/dist/style.css +1 -2
- 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/form-flow/FormFlow.vue +26 -1
- package/src/form-flow/MultiStepForm.vue +1 -0
- package/src/form-flow/form-flow.ts +23 -2
- package/src/form-flow/schema-fields.ts +21 -0
- 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 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
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
|
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'
|