@bagelink/vue 1.14.15 → 1.15.0

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.
Files changed (102) hide show
  1. package/dist/components/Alert.vue.d.ts.map +1 -1
  2. package/dist/components/Badge.vue.d.ts.map +1 -1
  3. package/dist/components/Btn.vue.d.ts.map +1 -1
  4. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  5. package/dist/components/Image.vue.d.ts.map +1 -1
  6. package/dist/components/ListItem.vue.d.ts.map +1 -1
  7. package/dist/components/MapEmbed/Index.vue.d.ts.map +1 -1
  8. package/dist/components/Pagination.vue.d.ts.map +1 -1
  9. package/dist/components/Swiper.vue.d.ts.map +1 -1
  10. package/dist/components/Toast.vue.d.ts.map +1 -1
  11. package/dist/components/form/index.d.ts.map +1 -1
  12. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  13. package/dist/components/index.d.ts.map +1 -1
  14. package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
  15. package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
  16. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  17. package/dist/components/layout/Panel.vue.d.ts.map +1 -1
  18. package/dist/components/layout/Resizable.vue.d.ts.map +1 -1
  19. package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
  20. package/dist/components/layout/appLayoutContext.d.ts +24 -0
  21. package/dist/components/layout/appLayoutContext.d.ts.map +1 -0
  22. package/dist/components/layout/index.d.ts.map +1 -1
  23. package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  24. package/dist/composables/index.d.ts.map +1 -1
  25. package/dist/composables/useDevice.d.ts.map +1 -1
  26. package/dist/composables/useEscapeKey.d.ts +12 -0
  27. package/dist/composables/useEscapeKey.d.ts.map +1 -0
  28. package/dist/composables/useSchemaField.d.ts.map +1 -1
  29. package/dist/composables/useTheme.d.ts.map +1 -1
  30. package/dist/form-flow/FormFlow.vue.d.ts.map +1 -1
  31. package/dist/form-flow/form-flow.d.ts.map +1 -1
  32. package/dist/index.cjs +203 -207
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.mjs +25819 -28870
  35. package/dist/style.css +1 -1
  36. package/dist/types/BagelForm.d.ts.map +1 -1
  37. package/dist/types/BtnOptions.d.ts.map +1 -1
  38. package/dist/utils/constants.d.ts.map +1 -1
  39. package/dist/utils/index.d.ts.map +1 -1
  40. package/package.json +3 -6
  41. package/src/components/Alert.vue +34 -14
  42. package/src/components/Badge.vue +145 -22
  43. package/src/components/Btn.vue +43 -31
  44. package/src/components/Dropdown.vue +5 -12
  45. package/src/components/FilterQuery.vue +1 -1
  46. package/src/components/Image.vue +3 -2
  47. package/src/components/JSONSchema.vue +2 -2
  48. package/src/components/JsonBuilder.vue +1 -1
  49. package/src/components/ListItem.vue +1 -3
  50. package/src/components/MapEmbed/Index.vue +10 -9
  51. package/src/components/NavBar.vue +2 -2
  52. package/src/components/Spreadsheet/Index.vue +1 -1
  53. package/src/components/Swiper.vue +3 -1
  54. package/src/components/Toast.vue +23 -8
  55. package/src/components/calendar/Index.vue +4 -4
  56. package/src/components/calendar/views/MonthView.vue +3 -3
  57. package/src/components/form/index.ts +0 -4
  58. package/src/components/form/inputs/EmailInput.vue +1 -1
  59. package/src/components/form/inputs/NumberInput.vue +1 -1
  60. package/src/components/form/inputs/OTP.vue +2 -2
  61. package/src/components/form/inputs/SelectInput.vue +3 -3
  62. package/src/components/form/inputs/TelInput.vue +2 -2
  63. package/src/components/form/inputs/TextInput.vue +1 -1
  64. package/src/components/form/inputs/Upload/upload.css +2 -2
  65. package/src/components/index.ts +2 -6
  66. package/src/components/layout/AppContent.vue +5 -19
  67. package/src/components/layout/AppLayout.vue +47 -18
  68. package/src/components/layout/AppSidebar.vue +16 -33
  69. package/src/components/layout/Resizable.vue +5 -2
  70. package/src/components/layout/TabsNav.vue +5 -5
  71. package/src/components/layout/appLayoutContext.ts +44 -0
  72. package/src/components/layout/index.ts +2 -0
  73. package/src/components/lightbox/Lightbox.vue +3 -9
  74. package/src/composables/index.ts +1 -0
  75. package/src/composables/useDevice.ts +2 -1
  76. package/src/composables/useEscapeKey.ts +56 -0
  77. package/src/composables/useSchemaField.ts +2 -17
  78. package/src/composables/useTheme.ts +23 -19
  79. package/src/form-flow/FormFlow.vue +2 -0
  80. package/src/form-flow/form-flow.ts +7 -0
  81. package/src/index.ts +0 -2
  82. package/src/styles/inputs.css +1 -1
  83. package/src/types/BagelForm.ts +46 -151
  84. package/src/types/BtnOptions.ts +5 -3
  85. package/src/utils/constants.ts +7 -0
  86. package/src/utils/index.ts +19 -3
  87. package/src/utils/sizeParsing.ts +5 -5
  88. package/vite.config.ts +5 -1
  89. package/src/components/Carousel.vue +0 -724
  90. package/src/components/ImportData.vue +0 -1749
  91. package/src/components/Pill.vue +0 -150
  92. package/src/components/Slider.vue +0 -1446
  93. package/src/components/Title.vue +0 -23
  94. package/src/components/ToolBar.vue +0 -9
  95. package/src/components/form/BagelForm.vue +0 -219
  96. package/src/components/form/BglFieldSet.vue +0 -14
  97. package/src/components/form/BglMultiStepForm.vue +0 -469
  98. package/src/components/form/FieldArray.vue +0 -422
  99. package/src/components/form/useBagelFormState.ts +0 -76
  100. package/src/composables/useFormField.ts +0 -38
  101. package/src/dialog/DialogOLD.vue +0 -358
  102. package/src/utils/BagelFormUtils.ts +0 -684
@@ -1,422 +0,0 @@
1
- <script lang="ts" setup generic="T extends {[key: string]: any}, P extends Path<T>">
2
- import type {
3
- AttributeFn,
4
- AttributeValue,
5
- Attributes,
6
- BagelFieldOptions,
7
- BaseBagelField,
8
- BglFormSchemaT,
9
- Field,
10
- Path
11
- } from '@bagelink/vue'
12
- import type { MaybeRefOrGetter } from 'vue'
13
- import { BagelForm, Btn, Loading, Icon, Card, useI18n } from '@bagelink/vue'
14
- import { ref, computed, watch, toValue, onMounted } from 'vue'
15
-
16
- export interface FieldArrayProps<T, P extends Path<T>> {
17
- el?: any
18
- id?: string
19
- label?: string
20
- placeholder?: string
21
- children?: Field<T>[]
22
- class?: AttributeValue | AttributeFn<T, P>
23
- attrs?: Attributes<T, P>
24
- required?: boolean
25
- disabled?: boolean
26
- helptext?: string
27
- options?: BagelFieldOptions<T, P>
28
- defaultValue?: T[]
29
- add?: boolean
30
- delete?: boolean
31
- collapsed?: boolean
32
- transform?: (value: T) => T
33
- schema?: MaybeRefOrGetter<BglFormSchemaT<T>>
34
- modelValue?: T[]
35
- type?: 'object' | 'number' | 'text' | 'array'
36
- }
37
-
38
- export interface PrimitiveArrFieldT extends BaseBagelField<{ value: string }, 'value'> {
39
- type: FieldArrayProps<{ value: string }, 'value'>['type']
40
- }
41
-
42
- const props = withDefaults(
43
- defineProps<FieldArrayProps<T, P>>(),
44
- {
45
- delete: true,
46
- add: true,
47
- el: 'div',
48
- type: 'object',
49
- collapsed: false,
50
- }
51
- )
52
-
53
- const emit = defineEmits(['update:modelValue'])
54
-
55
- const { $t } = useI18n()
56
-
57
- const BagelFormFA = BagelForm<T, P>
58
-
59
- // State
60
- const minimizedItems = ref<boolean[]>([])
61
- const internalData = ref<any[]>(props.modelValue || [])
62
- const schemaState = ref<'loading' | 'loaded' | 'error'>('loaded')
63
- let lastEmittedValue = '' // Event handlers state
64
-
65
- // Generate schema for primitive types
66
- const primitiveSchema = computed<PrimitiveArrFieldT[]>(() => {
67
- if (props.type === 'text') {
68
- return [{ id: 'value', type: 'text', label: '', $el: 'text' }]
69
- } if (props.type === 'number') {
70
- return [{ id: 'value', type: 'number', label: '', $el: 'number' }]
71
- }
72
- return []
73
- })
74
-
75
- const resolvedSchemaData = computed(() => {
76
- // Handle 'array' type as equivalent to 'object' type
77
- const isObjectType = props.type === 'object' || props.type === 'array'
78
-
79
- if (!isObjectType) { return primitiveSchema.value as BglFormSchemaT<T> }
80
-
81
- // Use children prop first, then schema prop, then attrs.schema
82
- const attrsSchema = props.attrs?.schema !== undefined ? toValue(props.attrs.schema) : null
83
- const schemaToUse = props.children || toValue(props.schema) || attrsSchema
84
-
85
- // // If no schema but we have defaultValue, try to infer schema from first item
86
- // const noSchema = null === schemaToUse || schemaToUse === undefined
87
- // || (Array.isArray(schemaToUse) && 0 === schemaToUse.length)
88
- // const hasDefaultValue = props.defaultValue !== undefined
89
- // && null !== props.defaultValue
90
- // && 0 < props.defaultValue.length
91
-
92
- // Don't infer schema from defaultValue - this causes unwanted restoration
93
- // Let the component work with empty arrays
94
- return schemaToUse as BglFormSchemaT<T>
95
- })// Watch for external changes to modelValue - simple and straightforward
96
- watch(() => props.modelValue, (newValue) => {
97
- // Simply sync with external changes, no automatic defaultValue restoration
98
- if (newValue && Array.isArray(newValue)) {
99
- internalData.value = [...newValue]
100
- } else {
101
- internalData.value = []
102
- }
103
- }, { deep: true, immediate: false })
104
-
105
- // Watch for changes to internalData length to sync minimizedItems
106
- watch(() => internalData.value.length, (newLength) => {
107
- // Resize minimizedItems array to match internalData length
108
- if (minimizedItems.value.length !== newLength) {
109
- // If array grew, add new items with collapsed state
110
- if (minimizedItems.value.length < newLength) {
111
- const itemsToAdd = newLength - minimizedItems.value.length
112
- for (let i = 0; i < itemsToAdd; i++) {
113
- minimizedItems.value.push(props.collapsed || false)
114
- }
115
- } else {
116
- // If array shrank, remove excess items
117
- minimizedItems.value = minimizedItems.value.slice(0, newLength)
118
- }
119
- }
120
- })
121
-
122
- // Initialize with existing data or defaultValue on mount - only once!
123
- onMounted(() => {
124
- // If no modelValue provided, use defaultValue as initial data (one time only)
125
- if (!props.modelValue || props.modelValue.length === 0) {
126
- if (props.defaultValue && props.defaultValue.length > 0) {
127
- internalData.value = [...props.defaultValue]
128
- emitValue() // This will set the initial value and then it becomes "regular" data
129
- }
130
- }
131
-
132
- // Initialize minimized state based on collapsed prop
133
- if (props.collapsed) {
134
- minimizedItems.value = Array.from({ length: internalData.value.length }, () => true)
135
- } else {
136
- minimizedItems.value = Array.from({ length: internalData.value.length }, () => false)
137
- }
138
- })
139
-
140
- // Function to load default values (explicit user action)
141
- function loadDefaults() {
142
- if (props.defaultValue && props.defaultValue.length > 0) {
143
- internalData.value = [...props.defaultValue]
144
- emitValue()
145
- } else {
146
- // If no defaultValue in props, add a single empty item
147
- addItem()
148
- }
149
- }
150
-
151
- // Resolve schema
152
- // async function resolveSchema() {
153
- // // For primitive types, use the predefined schemaks
154
- // if (props.type !== 'object') {
155
- // resolvedSchemaData.value = primitiveSchema.value
156
- // schemaState.value = 'loaded'
157
- // return
158
- // }
159
-
160
- // // For object type without schema
161
- // if (!props.schema) {
162
- // resolvedSchemaData.value = []
163
- // schemaState.value = 'loaded'
164
- // return
165
- // }
166
-
167
- // // For object type with schema
168
- // try {
169
- // // schemaState.value = 'loading'
170
- // const isPromise = (obj: any) => obj && typeof obj.then === 'function'
171
-
172
- // let result: any
173
- // if (typeof props.schema === 'function') {
174
- // result = props.schema()
175
- // result = isPromise(result) ? await result : result
176
- // } else {
177
- // result = isPromise(props.schema) ? await props.schema : props.schema
178
- // }
179
-
180
- // resolvedSchemaData.value = result
181
- // schemaState.value = 'loaded'
182
- // } catch (error) {
183
- // console.error('Schema error:', error)
184
- // schemaState.value = 'error'
185
- // resolvedSchemaData.value = []
186
- // }
187
- // }
188
-
189
- // // Initialize schema on mount
190
- // onMounted(resolveSchema)
191
-
192
- // Event handlers
193
-
194
- function emitValue() {
195
- // Only emit if the value has actually changed to prevent recursive updates
196
- const currentValue = JSON.stringify(props.modelValue || [])
197
- const newValue = JSON.stringify(internalData.value || [])
198
-
199
- // Double-check against last emitted value to prevent loops
200
- if (currentValue !== newValue && lastEmittedValue !== newValue) {
201
- lastEmittedValue = newValue
202
- emit('update:modelValue', internalData.value)
203
- // Reset after a short delay to allow for new changes
204
- setTimeout(() => {
205
- lastEmittedValue = ''
206
- }, 50)
207
- }
208
- }
209
-
210
- function deleteItem(i: number) {
211
- internalData.value.splice(i, 1)
212
- minimizedItems.value.splice(i, 1)
213
- emitValue()
214
- }
215
-
216
- function addItem() {
217
- // Add appropriate default value based on type
218
- const defaultValues: { [key: string]: any } = {
219
- text: '',
220
- number: 0,
221
- object: {},
222
- array: {}
223
- }
224
- const defaultValue = defaultValues[props.type]
225
- internalData.value.push(defaultValue !== undefined ? defaultValue : {})
226
-
227
- // Add minimized state for new item - always open (false) when newly added
228
- minimizedItems.value.push(false)
229
-
230
- emitValue()
231
- }
232
-
233
- function toggleMinimized(index: number) {
234
- minimizedItems.value[index] = !minimizedItems.value[index]
235
- }
236
-
237
- function updateItem(index: number, value: any) {
238
- // Handle primitive types by extracting the value property
239
- if (props.type === 'text' || props.type === 'number') {
240
- internalData.value[index] = props.type === 'number' ? Number(value.value) : value.value
241
- } else {
242
- internalData.value[index] = value
243
- }
244
- emitValue()
245
- }
246
-
247
- // Computed properties for rendering
248
- const isPrimitiveType = computed(() => props.type === 'text' || props.type === 'number')
249
- const canRenderItems = computed(() => {
250
- const schemaData = resolvedSchemaData.value
251
- const hasValidSchema = (Array.isArray(schemaData) && schemaData.length > 0)
252
- || (props.children && props.children.length > 0)
253
- const hasDefaultValue = props.defaultValue && props.defaultValue.length > 0
254
-
255
- // For primitive types or when we have schema or defaultValue
256
- const isObjectType = props.type === 'object' || props.type === 'array'
257
- const shouldRender = isPrimitiveType.value
258
- || (isObjectType && (hasValidSchema === true || hasDefaultValue === true))
259
- return shouldRender
260
- })
261
- const showMinimizeButton = computed(() => {
262
- const schemaData = resolvedSchemaData.value
263
- const hasLongSchema = Array.isArray(schemaData) && schemaData.length > 2
264
- const hasRichText = Array.isArray(schemaData)
265
- && schemaData.some((schema: any) => schema.$el === 'richtext')
266
- return hasLongSchema || hasRichText
267
- })
268
- </script>
269
-
270
- <template>
271
- <div :class="props.class">
272
- <!-- Label -->
273
- <p v-if="label" class="label mb-05">
274
- {{ label }}
275
- </p>
276
- <div class="ps-025 mb-05">
277
- <!-- Loading/Error States -->
278
- <div v-if="schemaState !== 'loaded'" class="flex-center h-300px">
279
- <Loading v-if="schemaState === 'loading'" />
280
- <Icon v-else-if="schemaState === 'error'" icon="error" color="red" />
281
- </div>
282
-
283
- <!-- No Schema Available -->
284
- <Card v-else-if="!canRenderItems" class="py-1 border mb-05">
285
- <p class="opacity-7">
286
- No schema available
287
- </p>
288
- </Card>
289
-
290
- <!-- Items Rendering -->
291
- <template v-else>
292
- <!-- Empty Array Message -->
293
- <Card v-if="internalData.length === 0" class="py-1 border mb-05">
294
- <p class="opacity-7 txt-center txt14">
295
- No {{ label?.toLowerCase() || 'items' }} added yet
296
- </p>
297
- <!-- Load defaults button if defaultValue exists -->
298
- <Btn
299
- v-if="props.defaultValue && props.defaultValue.length > 0" thin color="primary"
300
- class="txt12 mb-05" @click="loadDefaults"
301
- >
302
- {{ $t('fieldArray.loadDefault', { label: label || 'Items' }) }}
303
- </Btn>
304
- </Card>
305
-
306
- <!-- Array Items -->
307
- <div
308
- v-for="(item, i) in internalData" :key="i" outline thin
309
- class="mb-05 itemBox transition radius-05 overflow-hidden txt12 border"
310
- style="--bgl-input-font-size: 12px" :class="{ minimized: minimizedItems[i] }"
311
- >
312
- <div>
313
- <!-- Minimized View -->
314
- <div class="flex pointer">
315
- <p class="minimizedText txt12 p-05 opacity-7 flex-grow-2" @click="toggleMinimized(i)">
316
- {{ label }} {{ i + 1 }}
317
- </p>
318
- <Btn
319
- v-if="showMinimizeButton" class="rotate-180 txt10 opacity-7 p-025" flat thin
320
- icon="keyboard_arrow_down" @click="toggleMinimized(i)"
321
- />
322
- </div>
323
-
324
- <!-- Form View -->
325
- <BagelFormFA
326
- v-if="!minimizedItems[i]" :model-value="isPrimitiveType ? { value: item } : item"
327
- :schema="resolvedSchemaData" class="bginputbg p-05 grid gap-05"
328
- @update:model-value="val => updateItem(i, val)"
329
- />
330
- <Btn
331
- v-if="props.delete" icon="delete" class="txt10 opacity-7" thin flat
332
- @click="deleteItem(i)"
333
- />
334
- </div>
335
- </div>
336
-
337
- <!-- Add Button -->
338
- <Btn v-if="add" thin icon="add" color="gray" class="txt12 arrayAddButton" @click="addItem">
339
- <p>{{ label }}</p>
340
- </Btn>
341
- </template>
342
- </div>
343
- </div>
344
- </template>
345
-
346
- <style>
347
- .minimized {
348
- height: 2.3rem;
349
- overflow: hidden;
350
- border-color: transparent !important;
351
-
352
- }
353
-
354
- .minimizedText {
355
- /* display: none; */
356
- padding-top: 1rem !important;
357
- }
358
-
359
- .minimized .minimizedText {
360
- display: block;
361
- }
362
-
363
- .minimized .rotate-180 {
364
- transform: rotate(0deg);
365
- }
366
-
367
- .itemBox {
368
- background: var(--btn-bg);
369
- --bgl-label-font-size: 0.6rem;
370
- --bgl-input-height: 30px;
371
- --bgl-input-font-size: 7px;
372
- }
373
-
374
- .bginputbg {
375
- background: var(--bgl-input-bg);
376
- }
377
-
378
- .pt-065 {
379
- padding-top: 0.65rem;
380
- }
381
-
382
- .itemBox .bagel-input input,
383
- .itemBox .bagel-input textarea,
384
- .itemBox .bagel-input select,
385
- .itemBox .custom-select .input {
386
- background: var(--bgl-white) !important;
387
- }
388
-
389
- .itemBox .code-editor-wrap textarea {
390
- background: transparent !important;
391
- }
392
-
393
- .itemBox .bagel-input {
394
- margin-bottom: 0.15rem !important;
395
- }
396
-
397
- .itemBox .richtext-editor-content {
398
- --richtext-font-size: 12px !important;
399
- font-size: 12px !important;
400
- }
401
-
402
- .itemBox iframe.editableContent {
403
- --richtext-font-size: 12px !important;
404
- }
405
-
406
- .itemBox .bagel-input.richtext-input {
407
- --richtext-font-size: 12px !important;
408
- }
409
-
410
- /* More specific selectors to override richtext font size */
411
- .itemBox .richtext-input iframe {
412
- font-size: 12px !important;
413
- }
414
-
415
- .itemBox .richtext-input .editableContent body {
416
- font-size: 12px !important;
417
- }
418
-
419
- .itemBox .richtext-input {
420
- font-size: 12px !important;
421
- }
422
- </style>
@@ -1,76 +0,0 @@
1
- import type { Ref } from 'vue'
2
- import { inject, provide, ref } from 'vue'
3
- import { getNestedValue } from '../../utils'
4
-
5
- export const FORM_STATE_KEY = Symbol('bagelFormState')
6
-
7
- export interface BagelFormState<T> {
8
- data: Ref<T>
9
- getFieldData: (path?: string) => any
10
- updateField: (path: string, value: any) => void
11
- isDirty: Ref<boolean>
12
- }
13
-
14
- // Helper function to safely clone objects without circular references
15
- function safeClone(obj: any): any {
16
- if (obj === null || typeof obj !== 'object') { return obj }
17
-
18
- const seen = new WeakSet()
19
- return JSON.parse(JSON.stringify(obj, (key, value) => {
20
- if (typeof value === 'object' && value !== null) {
21
- if (seen.has(value)) {
22
- return undefined // Remove circular reference
23
- }
24
- seen.add(value)
25
- }
26
- return value
27
- }))
28
- }
29
-
30
- export function provideBagelFormState<T>(initialData: T) {
31
- const data = ref(initialData) as Ref<T>
32
- const isDirty = ref(false)
33
-
34
- const getFieldData = (path?: string) => getNestedValue(data.value, path, '')
35
-
36
- const updateField = (path: string, value: any) => {
37
- const keys = path.split(/[.[]/)
38
-
39
- // Initialize the root if it's not an object
40
- if (typeof data.value !== 'object' || data.value === null) {
41
- data.value = {} as T
42
- }
43
-
44
- let current = data.value as any
45
-
46
- // Build the path, ensuring each level is an object
47
- for (let i = 0; i < keys.length - 1; i++) {
48
- const key = keys[i]
49
- if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
50
- current[key] = {}
51
- }
52
- current = current[key]
53
- }
54
-
55
- // Safely clone the value to remove circular references
56
- const safeValue = safeClone(value)
57
- current[keys[keys.length - 1]] = safeValue
58
- isDirty.value = true
59
- }
60
-
61
- const state: BagelFormState<T> = {
62
- data,
63
- getFieldData,
64
- updateField,
65
- isDirty
66
- }
67
-
68
- provide(FORM_STATE_KEY, state)
69
- return state
70
- }
71
-
72
- export function useBagelFormState<T>(injectionKey: typeof FORM_STATE_KEY = FORM_STATE_KEY): BagelFormState<T> {
73
- const state = inject<BagelFormState<T>>(injectionKey)
74
- if (!state) { throw new Error('BagelFormState must be provided') }
75
- return state
76
- }
@@ -1,38 +0,0 @@
1
- import type { Field } from '@bagelink/vue'
2
- import type { BagelFormState } from '../components/form/useBagelFormState'
3
- import { inject, computed } from 'vue'
4
- import { FORM_STATE_KEY } from '../components/form/useBagelFormState'
5
-
6
- export function useFormField<T>(props: {
7
- field: Field<T>
8
- fieldID?: string
9
- modelValue?: any
10
- }) {
11
- const formState = inject<BagelFormState<T>>(FORM_STATE_KEY)
12
-
13
- const fieldData = computed({
14
- get: () => {
15
- if (!props.fieldID || !formState) { return props.modelValue ?? props.field.defaultValue ?? '' }
16
- const value = formState.getFieldData(props.fieldID)
17
- // For nested form containers, default to empty object
18
- if (props.field.$el === 'form' && !value) { return {} }
19
- // Don't auto-restore defaultValue - let user control their content
20
- return value ?? ''
21
- },
22
- set: (val: any) => {
23
- if (!props.fieldID || !formState) { return }
24
- const currentValue = formState.getFieldData(props.fieldID)
25
- if (JSON.stringify(val) === JSON.stringify(currentValue)) { return }
26
-
27
- if (props.field.onUpdate) {
28
- props.field.onUpdate(val, currentValue)
29
- }
30
- formState.updateField(props.fieldID, val)
31
- }
32
- })
33
-
34
- return {
35
- fieldData,
36
- formState
37
- }
38
- }