@bagelink/vue 1.15.366 → 1.15.371

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.15.366",
4
+ "version": "1.15.371",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -128,14 +128,14 @@ outline-color: transparent;
128
128
  outline: -webkit-focus-ring-color auto 1px;
129
129
  outline-offset: 2px
130
130
  }
131
- .bgl-toggle :checked{
132
- background: var(--bgl-input-label-active-color, var(--bgl-primary));
131
+ .bgl-toggle input:checked{
132
+ background: var(--bgl-input-label-active-color) !important;
133
133
  }
134
134
  .bgl-toggle :checked::before{
135
135
  margin-inline-start:calc(var(--bgl-input-height) / 2 - 2px) ;
136
136
  }
137
137
  .bgl-toggle input:checked + label {
138
- color: var(--bgl-input-label-active-color, var(--bgl-primary)) !important;
138
+ color: var(--bgl-input-label-active-color) !important;
139
139
  }
140
140
 
141
141
  /* ── bgl-outline variant ── */
@@ -20,6 +20,7 @@ import type { Slots } from 'vue'
20
20
  import { computed, ref, useSlots, watch } from 'vue'
21
21
  import { useI18n } from '../i18n'
22
22
  import { fromJSONSchema } from './form-flow'
23
+ import type { JSONSchemaOptionsSourceLoader } from './form-flow'
23
24
  import Btn from '../components/Btn.vue'
24
25
  import FormFlow from './FormFlow.vue'
25
26
  import type { FormStepEventPayload } from './structure'
@@ -52,6 +53,8 @@ export interface Props {
52
53
  hideCounter?: boolean
53
54
  /** Optional async submit handler; awaited before `complete` is emitted. */
54
55
  onComplete?: (data: Record<string, any>) => Promise<void> | void
56
+ /** Resolve declarative x-options-source fields in stored JSON Schemas. */
57
+ loadOptionsSource?: JSONSchemaOptionsSourceLoader
55
58
  }
56
59
 
57
60
  const props = withDefaults(defineProps<Props>(), {
@@ -142,7 +145,11 @@ watch(currentKey, (key, prev) => {
142
145
  // Convert the current step's stored JSON Schema into a renderable schema.
143
146
  const currentSchema = computed(() => {
144
147
  const step = currentStep.value
145
- return step ? fromJSONSchema(step.schema as any) : null
148
+ return step
149
+ ? fromJSONSchema(step.schema as any, {
150
+ loadOptionsSource: props.loadOptionsSource,
151
+ })
152
+ : null
146
153
  })
147
154
 
148
155
  // The *next* step given current answers (reactive: recomputes as fields change).
@@ -774,6 +774,7 @@ interface JSONSchemaProperty {
774
774
  'additionalProperties'?: boolean | JSONSchemaProperty
775
775
  'x-ui'?: {
776
776
  fieldType?: string
777
+ required?: boolean
777
778
  placeholder?: string
778
779
  disabled?: boolean
779
780
  readonly?: boolean
@@ -795,6 +796,7 @@ interface JSONSchemaProperty {
795
796
  // designer-built schema renders identically in FormFlow/FormFlowPlayer.
796
797
  'x-field-type'?: string
797
798
  'x-options'?: { label: string, value: any }[]
799
+ 'x-options-source'?: JSONSchemaOptionsSource
798
800
  'x-placeholder'?: string
799
801
  'x-select-multiselect'?: boolean
800
802
  'x-radio-inline'?: boolean
@@ -802,6 +804,51 @@ interface JSONSchemaProperty {
802
804
  'x-radio-text-align'?: 'left' | 'center' | 'right'
803
805
  }
804
806
 
807
+ export interface JSONSchemaOptionsSource {
808
+ endpoint: string
809
+ itemsPath?: string
810
+ valuePath?: string
811
+ labelPath?: string
812
+ subtitlePath?: string
813
+ searchParam?: string
814
+ limitParam?: string
815
+ limit?: number
816
+ }
817
+
818
+ export type JSONSchemaOptionsSourceLoader = (
819
+ source: JSONSchemaOptionsSource,
820
+ query: string,
821
+ ) => Promise<unknown>
822
+
823
+ function valueAtPath(value: unknown, path?: string): unknown {
824
+ if (!path) return value
825
+ return path.split('.').reduce<unknown>((current, key) => {
826
+ if (current == null || typeof current !== 'object') return undefined
827
+ return (current as Record<string, unknown>)[key]
828
+ }, value)
829
+ }
830
+
831
+ function normalizeOptionsSourceResponse(
832
+ response: unknown,
833
+ source: JSONSchemaOptionsSource,
834
+ ): { label: string, value: string, subtitle?: string }[] {
835
+ const items = valueAtPath(response, source.itemsPath)
836
+ if (!Array.isArray(items)) return []
837
+ return items.flatMap((item) => {
838
+ const value = valueAtPath(item, source.valuePath ?? 'id')
839
+ const label = valueAtPath(item, source.labelPath ?? 'name')
840
+ if (value == null || label == null) return []
841
+ const subtitle = source.subtitlePath
842
+ ? valueAtPath(item, source.subtitlePath)
843
+ : undefined
844
+ return [{
845
+ label: String(label),
846
+ value: String(value),
847
+ ...(subtitle == null ? {} : { subtitle: String(subtitle) }),
848
+ }]
849
+ })
850
+ }
851
+
805
852
  interface JSONSchemaObject {
806
853
  'type'?: string
807
854
  'title'?: string
@@ -832,13 +879,22 @@ interface JSONSchemaObject {
832
879
  * // Use in component
833
880
  * <FormFlow v-model="data" :schema="loadedSchema" />
834
881
  */
835
- export function fromJSONSchema(jsonSchema: JSONSchemaObject): SchemaDefinition {
882
+ export function fromJSONSchema(
883
+ jsonSchema: JSONSchemaObject,
884
+ options?: { loadOptionsSource?: JSONSchemaOptionsSourceLoader },
885
+ ): SchemaDefinition {
836
886
  const fields: Record<string, FieldBuilder> = {}
837
887
  const requiredSet = new Set(jsonSchema.required || [])
838
888
 
839
889
  if (jsonSchema.properties) {
840
890
  for (const [key, prop] of Object.entries(jsonSchema.properties)) {
841
- fields[key] = createFieldFromProperty(key, prop, requiredSet.has(key), jsonSchema)
891
+ fields[key] = createFieldFromProperty(
892
+ key,
893
+ prop,
894
+ requiredSet.has(key),
895
+ jsonSchema,
896
+ options?.loadOptionsSource,
897
+ )
842
898
  }
843
899
  }
844
900
 
@@ -916,7 +972,8 @@ function createFieldFromProperty(
916
972
  key: string,
917
973
  prop: JSONSchemaProperty,
918
974
  isRequired: boolean,
919
- rootSchema: JSONSchemaObject
975
+ rootSchema: JSONSchemaObject,
976
+ loadOptionsSource?: JSONSchemaOptionsSourceLoader,
920
977
  ): FieldBuilder {
921
978
  // Resolve $ref if present
922
979
  let resolvedProp = prop
@@ -958,7 +1015,7 @@ function createFieldFromProperty(
958
1015
  if (resolvedProp.title) config.label = resolvedProp.title
959
1016
  if (resolvedProp.description) config.helpText = resolvedProp.description
960
1017
  if (resolvedProp.default !== undefined) config.default = resolvedProp.default
961
- if (isRequired) config.required = true
1018
+ if (isRequired || xUi.required === true) config.required = true
962
1019
 
963
1020
  // UI hints from x-ui / flat x-* (designer)
964
1021
  if (xUi.placeholder) config.placeholder = xUi.placeholder
@@ -969,6 +1026,31 @@ function createFieldFromProperty(
969
1026
  // designer renders with the same options in the player. Only the props each
970
1027
  // input actually reads are forwarded.
971
1028
  applyDesignerConfig(config, resolvedProp)
1029
+ if (resolvedProp['x-options-source']) {
1030
+ if (!loadOptionsSource) {
1031
+ throw new Error(`Field "${key}" requires an x-options-source loader`)
1032
+ }
1033
+ const source = resolvedProp['x-options-source']
1034
+ let cachedOptions: { label: string, value: string, subtitle?: string }[] | undefined
1035
+ config.options = async (query: string) => {
1036
+ const options = source.searchParam
1037
+ ? normalizeOptionsSourceResponse(
1038
+ await loadOptionsSource(source, query),
1039
+ source,
1040
+ )
1041
+ : cachedOptions ??= normalizeOptionsSourceResponse(
1042
+ await loadOptionsSource(source, source.searchParam ? query : ''),
1043
+ source,
1044
+ )
1045
+ if (source.searchParam || !query.trim()) return options
1046
+ const term = query.trim().toLocaleLowerCase()
1047
+ return options.filter(option =>
1048
+ [option.label, option.subtitle, option.value]
1049
+ .some(value => value?.toLocaleLowerCase().includes(term)),
1050
+ )
1051
+ }
1052
+ config.searchable = true
1053
+ }
972
1054
  if (xUi.radioAlign !== undefined) config.align = xUi.radioAlign
973
1055
  if (xUi.radioTextAlign !== undefined) config.textAlign = xUi.radioTextAlign
974
1056
  if (xUi.disabled) config.disabled = xUi.disabled
@@ -982,6 +1064,9 @@ function createFieldFromProperty(
982
1064
  if (resolvedProp.maximum !== undefined) config.max = resolvedProp.maximum
983
1065
  if (resolvedProp.minLength !== undefined) config.min = resolvedProp.minLength
984
1066
  if (resolvedProp.maxLength !== undefined) config.max = resolvedProp.maxLength
1067
+ if ((resolvedProp as Record<string, any>).pattern !== undefined) {
1068
+ config.pattern = (resolvedProp as Record<string, any>).pattern
1069
+ }
985
1070
 
986
1071
  // Create the appropriate field builder
987
1072
  let field: FieldBuilder
@@ -1031,10 +1116,10 @@ function createFieldFromProperty(
1031
1116
  field = $.radio(xOptions ?? resolvedProp.enum ?? [], config)
1032
1117
  break
1033
1118
  case 'select':
1034
- field = $.select(xOptions ?? resolvedProp.enum ?? [], config)
1119
+ field = $.select(config.options ?? xOptions ?? resolvedProp.enum ?? [], config)
1035
1120
  break
1036
1121
  case 'multiselect':
1037
- field = $.multiselect(xOptions ?? resolvedProp.enum ?? [], config)
1122
+ field = $.multiselect(config.options ?? xOptions ?? resolvedProp.enum ?? [], config)
1038
1123
  break
1039
1124
  case 'icon':
1040
1125
  field = $.icon(config)
@@ -1086,6 +1171,14 @@ function createFieldFromProperty(
1086
1171
  : config.requiredMessage || 'form.required'
1087
1172
  )
1088
1173
  }
1174
+ if ((resolvedProp as Record<string, any>).pattern) {
1175
+ const pattern = new RegExp((resolvedProp as Record<string, any>).pattern)
1176
+ field.validate(value =>
1177
+ value == null || value === '' || pattern.test(String(value))
1178
+ ? true
1179
+ : 'form.pattern',
1180
+ )
1181
+ }
1089
1182
 
1090
1183
  // Apply class
1091
1184
  if (xUi.class) {
@@ -1131,7 +1224,16 @@ function applyDesignerConfig(config: Record<string, any>, prop: JSONSchemaProper
1131
1224
  set('layout', 'x-number-layout')
1132
1225
  if (p['x-number-spinner'] === false) config.spinner = false
1133
1226
  // Phone
1134
- set('onlyCountries', 'x-phone-only-countries')
1227
+ if (typeof p['x-phone-only-countries'] === 'string') {
1228
+ config.onlyCountries = p['x-phone-only-countries']
1229
+ .split(',')
1230
+ .map((country: string) => country.trim())
1231
+ .filter(Boolean)
1232
+ }
1233
+ else if (Array.isArray(p['x-phone-only-countries'])) {
1234
+ config.onlyCountries = p['x-phone-only-countries']
1235
+ }
1236
+ set('defaultCountry', 'x-phone-default-country')
1135
1237
  // Range
1136
1238
  if (p['x-range-multi']) config.multiRange = true
1137
1239
  // File / upload