@bagelink/vue 1.0.33 → 1.0.41

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 (32) hide show
  1. package/dist/components/DataPreview.vue.d.ts.map +1 -1
  2. package/dist/components/DataTable/DataTable.vue.d.ts +2 -2
  3. package/dist/components/DataTable/DataTable.vue.d.ts.map +1 -1
  4. package/dist/components/DataTable/useTableData.d.ts +2 -1
  5. package/dist/components/DataTable/useTableData.d.ts.map +1 -1
  6. package/dist/components/DataTable/useTableSelection.d.ts +4 -1
  7. package/dist/components/DataTable/useTableSelection.d.ts.map +1 -1
  8. package/dist/components/DataTable/useTableVirtualization.d.ts.map +1 -1
  9. package/dist/components/form/BagelForm.vue.d.ts +17 -11
  10. package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
  11. package/dist/components/form/FieldArray.vue.d.ts.map +1 -1
  12. package/dist/composables/index.d.ts +1 -1
  13. package/dist/composables/index.d.ts.map +1 -1
  14. package/dist/index.cjs +441 -307
  15. package/dist/index.mjs +442 -308
  16. package/dist/style.css +502 -83
  17. package/dist/types/BagelForm.d.ts +1 -1
  18. package/dist/types/BagelForm.d.ts.map +1 -1
  19. package/package.json +1 -1
  20. package/src/components/DataPreview.vue +30 -4
  21. package/src/components/DataTable/DataTable.vue +9 -7
  22. package/src/components/DataTable/useTableData.ts +75 -9
  23. package/src/components/DataTable/useTableSelection.ts +17 -3
  24. package/src/components/DataTable/useTableVirtualization.ts +11 -2
  25. package/src/components/Loading.vue +2 -2
  26. package/src/components/form/BagelForm.vue +138 -78
  27. package/src/components/form/FieldArray.vue +129 -54
  28. package/src/components/layout/BottomMenu.vue +1 -1
  29. package/src/composables/index.ts +14 -4
  30. package/src/styles/layout.css +240 -0
  31. package/src/styles/mobilLayout.css +240 -0
  32. package/src/types/BagelForm.ts +1 -1
@@ -64,6 +64,6 @@ export type BglFormSchemaT<T = {
64
64
  }> = Field<T>[];
65
65
  export type BglFormSchemaFnT<T = {
66
66
  [key: string]: any;
67
- }> = (() => BglFormSchemaT<T>) | BglFormSchemaT<T>;
67
+ }> = (() => BglFormSchemaT<T>) | BglFormSchemaT<T> | (() => Promise<BglFormSchemaT<T>>) | Promise<BglFormSchemaT<T>>;
68
68
  export {};
69
69
  //# sourceMappingURL=BagelForm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BagelForm.d.ts","sourceRoot":"","sources":["../../src/types/BagelForm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,CAAA;AAEhC,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAA;AAEvG,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,cAAc,CAAA;AAE5F,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,GAAG;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;CAC9C;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI,CAC3D,MAAM,GACJ,CACD;IACC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACtB,GACC,MAAM,GACN,MAAM,GACN,OAAO,GACP;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CACxB,EAAE,GACD,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,CACnC,CAAA;AAED,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,OAAO,CAAA;AAEvD,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAC1C;KACA,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,MAAM,GAC7B,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAClB,CAAC,GAAG,GAAG,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAC/B,CAAC,GACF,KAAK;CACR,CAAC,MAAM,CAAC,CAAC,GACR,KAAK,CAAA;AAER,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE;IACzD,KAAK,CAAC,EAAE,GAAG,CAAA;IAEX,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,EAAE,CAAA;IAC1C,OAAO,CAAC,EAAE,cAAc,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IACzC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;IACvB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAA;IAChC,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAA;IAChC,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,GAAG,CAAA;IAC7C,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,CAAA;IAC5C,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;IAC9C,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAC5D,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,OAAO,SAAS,CAAC,CAAA;IAC5C,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAC7D,GAAG,EAAE,QAAQ,GAAG,YAAY,CAAC,OAAO,WAAW,CAAC,CAAA;IAChD,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;CAC3B;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAA;AAC5G,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAA;AAE5D,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AAEnE,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"BagelForm.d.ts","sourceRoot":"","sources":["../../src/types/BagelForm.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.0.33",
4
+ "version": "1.0.41",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -9,7 +9,14 @@ import { useTableData } from './DataTable/useTableData'
9
9
  const props = defineProps<Omit<TableSchemaProps<T>, 'data'> & { modelValue: T, includeUnset?: boolean }>()
10
10
  const slots = useSlots() as Slots & Record<string, (props: { row: T, field: BaseBagelField<T> }) => any>
11
11
 
12
- const data = computed(() => Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue])
12
+ const data = computed(() => {
13
+ // Handle undefined or null modelValue
14
+ if (!props.modelValue) {
15
+ return []
16
+ }
17
+
18
+ return Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue]
19
+ })
13
20
 
14
21
  const {
15
22
  computedSchema,
@@ -21,7 +28,14 @@ const {
21
28
  useServerSort: false
22
29
  })
23
30
 
24
- const firstItem = computed(() => computedData.value[0] || {} as T)
31
+ const firstItem = computed(() => {
32
+ // Handle empty computedData
33
+ if (!computedData.value || computedData.value.length === 0) {
34
+ return {} as T
35
+ }
36
+
37
+ return computedData.value[0] || {} as T
38
+ })
25
39
 
26
40
  const { renderField } = useSchemaField<T>({
27
41
  mode: 'preview',
@@ -32,9 +46,14 @@ const { renderField } = useSchemaField<T>({
32
46
 
33
47
  <template>
34
48
  <div class="data-preview">
35
- <template v-for="field in computedSchema" :key="field.id">
36
- <component :is="renderField(field, slots)" />
49
+ <template v-if="computedSchema && computedSchema.length > 0">
50
+ <template v-for="field in computedSchema" :key="field.id">
51
+ <component :is="renderField(field, slots)" />
52
+ </template>
37
53
  </template>
54
+ <div v-else class="empty-preview">
55
+ No data to display
56
+ </div>
38
57
  </div>
39
58
  </template>
40
59
 
@@ -61,4 +80,11 @@ const { renderField } = useSchemaField<T>({
61
80
  .field-value {
62
81
  font-size: 0.95rem;
63
82
  }
83
+
84
+ .empty-preview {
85
+ font-size: 0.9rem;
86
+ color: var(--bgl-black-tint);
87
+ padding: 1rem 0;
88
+ text-align: center;
89
+ }
64
90
  </style>
@@ -40,7 +40,8 @@ const {
40
40
  computedData,
41
41
  sortField,
42
42
  sortDirection,
43
- toggleSort
43
+ toggleSort,
44
+ cleanTransformedData
44
45
  } = useTableData<T>({ data, schema, columns, useServerSort, onSort: (field, direction) => {
45
46
  emit('orderBy', `${field} ${direction}`.trim() as EmitOrderT)
46
47
  } })
@@ -55,6 +56,7 @@ const {
55
56
  } = useTableSelection<T>({
56
57
  selectable: props.selectable,
57
58
  selectedItems,
59
+ cleanData: cleanTransformedData,
58
60
  onSelect: (item) => { emit('select', item) }
59
61
  })
60
62
 
@@ -142,10 +144,10 @@ watch(
142
144
  @click="toggleSort(field?.id || '')"
143
145
  >
144
146
  <div class="flex">
145
- {{ field.label || keyToLabel(field.id) }}
147
+ {{ field.label || keyToLabel(field?.id) }}
146
148
  <div
147
149
  class="list-arrows"
148
- :class="{ sorted: sortField === field.id }"
150
+ :class="{ sorted: sortField === field?.id }"
149
151
  >
150
152
  <Icon
151
153
  :class="{ desc: sortDirection === 'DESC' }"
@@ -158,9 +160,9 @@ watch(
158
160
  <tbody>
159
161
  <tr
160
162
  v-for="{ data: row } in list"
161
- :key="row.id"
163
+ :key="row?.id || `row-${Math.random()}`"
162
164
  class="row row-item position-relative"
163
- :class="{ selected: computedSelectedItems.includes(row.id ?? '') }"
165
+ :class="{ selected: row?.id && computedSelectedItems.includes(row.id) }"
164
166
  @click="toggleSelectItem(row)"
165
167
  >
166
168
  <td v-if="isSelectable">
@@ -168,13 +170,13 @@ watch(
168
170
  <input
169
171
  v-model="selectedItems"
170
172
  type="checkbox"
171
- :value="row.id"
173
+ :value="row?.id || ''"
172
174
  >
173
175
  </div>
174
176
  </td>
175
177
  <td
176
178
  v-for="field in computedSchema"
177
- :key="`${field.id}-${row.id}`"
179
+ :key="`${field.id}-${row?.id || Math.random()}`"
178
180
  class="col"
179
181
  >
180
182
  <slot
@@ -1,6 +1,6 @@
1
1
  import type { ComputedRef } from 'vue'
2
2
  import type { TableDataOptions, SortDirectionsT } from '../../types/TableSchema'
3
- import { useBglSchema, isDate } from '@bagelink/vue'
3
+ import { useBglSchema, isDate, keyToLabel } from '@bagelink/vue'
4
4
  import { computed, ref } from 'vue'
5
5
 
6
6
  const NON_DIGIT_REGEX = /[^\d.-]/g
@@ -34,19 +34,63 @@ export function useTableData<T extends Record<string, any>>(options: UseTableDat
34
34
  const sortField = ref('')
35
35
  const sortDirection = ref<SortDirectionsT>('ASC')
36
36
  // Helper function to get the value from a possibly computed ref
37
- function getValue<V>(value: ComputedRef<V> | V): V {
38
- return (value as ComputedRef<V>).value !== undefined
39
- ? (value as ComputedRef<V>).value
40
- : value as V
37
+ function getValue<V>(value: ComputedRef<V> | V | undefined): V | undefined {
38
+ if (value === undefined || value === null) {
39
+ return undefined
40
+ }
41
+
42
+ try {
43
+ // Check if it's a computed ref with a value property
44
+ if (value && typeof value === 'object' && 'value' in value) {
45
+ return (value as ComputedRef<V>).value
46
+ }
47
+ // Otherwise return the value directly
48
+ return value as V
49
+ } catch (error) {
50
+ console.error('Error in getValue:', error)
51
+ return undefined
52
+ }
41
53
  }
42
54
 
43
55
  // Create a computed property for the schema that will react to changes in options.columns
44
56
  const computedSchema = computed(() => {
45
- return useBglSchema<T>({
57
+ // Get the data safely
58
+ const dataValue = options.data.value || []
59
+
60
+ // Get the schema from useBglSchema
61
+ const schema = useBglSchema<T>({
46
62
  schema: getValue(options.schema),
47
63
  columns: getValue(options.columns),
48
- data: options.data.value,
64
+ data: dataValue,
49
65
  })
66
+
67
+ // If we have a valid schema with fields, filter out fields without an ID
68
+ if (Array.isArray(schema) && schema.length > 0) {
69
+ return schema.filter(field => field && field.id)
70
+ }
71
+
72
+ // If no schema is provided or it's empty, generate a default schema from the data
73
+ if (Array.isArray(dataValue) && dataValue.length > 0) {
74
+ const firstItem = dataValue[0]
75
+
76
+ // Create a schema based on the keys of the first item
77
+ return Object.keys(firstItem || {})
78
+ .filter(key => key !== 'id' && !key.startsWith('_')) // Exclude id and internal fields
79
+ .map(key => ({
80
+ id: key,
81
+ label: keyToLabel(key),
82
+ $el: 'div',
83
+ transform: (val?: any) => {
84
+ // Handle date fields
85
+ const dateFields = ['created_at', 'updated_at']
86
+ if (dateFields.includes(key)) return val ? new Date(val).toLocaleString() : val
87
+ return val
88
+ }
89
+ }))
90
+ }
91
+
92
+ // Return an empty array if no data or schema
93
+ return []
50
94
  })
51
95
 
52
96
  function transform(rowData: T): TransformedData<T> {
@@ -82,10 +126,31 @@ export function useTableData<T extends Record<string, any>>(options: UseTableDat
82
126
  return transformed
83
127
  }
84
128
 
129
+ // Helper function to clean up transformed data by removing all added properties
130
+ function cleanTransformedData<T extends Record<string, any>>(data: TransformedData<T>): T {
131
+ const cleanData = { ...data } as T
132
+
133
+ // Remove all keys that start with underscore (these are added by the transform function)
134
+ Object.keys(cleanData).forEach((key) => {
135
+ if (key.startsWith('_')) {
136
+ delete cleanData[key]
137
+ }
138
+ })
139
+
140
+ return cleanData
141
+ }
142
+
85
143
  const computedSortField = computed(() => sortField.value ? `_transformed_${sortField.value}` : '')
86
144
 
87
145
  const computedData = computed(() => {
88
- const currentData = options.data.value
146
+ // Get the data safely
147
+ const currentData = options.data.value || []
148
+
149
+ // If there's no data, return an empty array
150
+ if (!Array.isArray(currentData) || currentData.length === 0) {
151
+ return []
152
+ }
153
+
89
154
  const useServerSortValue = getValue(options.useServerSort)
90
155
 
91
156
  if (!sortField.value || useServerSortValue === true) {
@@ -139,6 +204,7 @@ export function useTableData<T extends Record<string, any>>(options: UseTableDat
139
204
  transform,
140
205
  sortField: computed(() => sortField.value),
141
206
  sortDirection: computed(() => sortDirection.value),
142
- toggleSort
207
+ toggleSort,
208
+ cleanTransformedData
143
209
  }
144
210
  }
@@ -1,7 +1,11 @@
1
1
  import type { TableSelectionOptions } from '../../types/TableSchema'
2
2
  import { computed, ref } from 'vue'
3
3
 
4
- export function useTableSelection<T extends Record<string, any>>(options: TableSelectionOptions<T>) {
4
+ export interface UseTableSelectionOptions<T> extends TableSelectionOptions<T> {
5
+ cleanData?: (item: T) => T
6
+ }
7
+
8
+ export function useTableSelection<T extends Record<string, any>>(options: UseTableSelectionOptions<T>) {
5
9
  const allSelectorEl = ref<HTMLInputElement>()
6
10
  const computedSelectedItems = computed(() => options.selectedItems.value)
7
11
  const isSelectable = computed(() => options.selectable === true && Array.isArray(options.selectedItems.value))
@@ -16,8 +20,18 @@ export function useTableSelection<T extends Record<string, any>>(options: TableS
16
20
 
17
21
  function toggleSelectItem(item: T) {
18
22
  if (computedSelectedItems.value.length === 0) {
19
- const cleanItem = { ...item }
20
- Object.keys(cleanItem).forEach(key => key.startsWith('_transformed_') && delete cleanItem[key])
23
+ // Clean the item if a cleanData function is provided, otherwise use the default cleaning
24
+ const cleanItem = options.cleanData ? options.cleanData(item) : { ...item }
25
+
26
+ // If no cleanData function is provided, use the default cleaning
27
+ if (!options.cleanData) {
28
+ Object.keys(cleanItem).forEach((key) => {
29
+ if (key.startsWith('_')) {
30
+ delete cleanItem[key]
31
+ }
32
+ })
33
+ }
34
+
21
35
  options.onSelect(cleanItem)
22
36
  return
23
37
  }
@@ -1,6 +1,6 @@
1
1
  import type { TableVirtualizationOptions } from '../../types/TableSchema'
2
2
  import { useVirtualList, useIntersectionObserver, until } from '@vueuse/core'
3
- import { ref } from 'vue'
3
+ import { ref, isRef } from 'vue'
4
4
 
5
5
  export function useTableVirtualization<T>(options: TableVirtualizationOptions<T>) {
6
6
  const lastItemEl = ref<HTMLTableRowElement | null>(null)
@@ -16,7 +16,16 @@ export function useTableVirtualization<T>(options: TableVirtualizationOptions<T>
16
16
  await until(() => lastItemEl.value).toBeTruthy()
17
17
 
18
18
  useIntersectionObserver(lastItemEl, ([entry]) => {
19
- if (entry.isIntersecting && options.data.value.length) {
19
+ // Check if options.data.value exists and has length
20
+ let dataLength = 0
21
+
22
+ if (isRef(options.data) && options.data.value) {
23
+ dataLength = Array.isArray(options.data.value) ? options.data.value.length : 0
24
+ } else if (Array.isArray(options.data)) {
25
+ dataLength = options.data.length
26
+ }
27
+
28
+ if (entry.isIntersecting && dataLength > 0) {
20
29
  options.onLastItemVisible?.()
21
30
  }
22
31
  })
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  type LoadingType = 'ring' | 'ellipsis' | 'bar'
3
3
 
4
- const { type: theme = 'ring', size = 64, thickness, duration = 1.2, color } = defineProps<{
4
+ const { type: theme = 'ring', size = 50, thickness, duration = 1.2, color } = defineProps<{
5
5
  size?: number | string
6
6
  thickness?: number | string
7
7
  duration?: number | string
@@ -21,7 +21,7 @@ const animationDuration = $computed(() => `${duration}s`)
21
21
 
22
22
  // Border size for ring theme (defaults to 1/8th of the size)
23
23
  const computedBorder = $computed(() => {
24
- const borderValue = thickness ?? Number.parseInt(computedSize) / 7
24
+ const borderValue = thickness ?? Math.max(Number.parseInt(computedSize) / 9, 2)
25
25
  return standardSize(borderValue)
26
26
  })
27
27
  </script>
@@ -1,19 +1,18 @@
1
1
  <script setup lang="ts" generic="T extends {[key:string]:any}">
2
- import type { BglFormSchemaFnT, Field } from '@bagelink/vue'
2
+ import type { BglFormSchemaFnT, Field, BglFormSchemaT } from '@bagelink/vue'
3
3
  import type { VNode } from 'vue'
4
- import { onMounted, watch, ref } from 'vue'
4
+ import { Loading } from '@bagelink/vue'
5
+ import { onMounted, watch, ref, toRef, computed } from 'vue'
5
6
  import { useSchemaField } from '../../composables/useSchemaField'
6
7
  import { getNestedValue } from '../../utils'
7
8
 
8
- export interface BagelFormProps<_T> {
9
- modelValue?: _T
10
- schema?: BglFormSchemaFnT<_T>
9
+ const props = withDefaults(defineProps<{
10
+ modelValue?: T
11
+ schema?: BglFormSchemaFnT<T>
11
12
  tag?: 'form' | 'template'
12
13
  class?: string
13
- onSubmit?: (data: _T) => Promise<void> | void
14
- }
15
-
16
- const props = withDefaults(defineProps<BagelFormProps<T>>(), {
14
+ onSubmit?: (data: T) => Promise<void> | void
15
+ }>(), {
17
16
  modelValue: undefined,
18
17
  schema: undefined,
19
18
  tag: 'form',
@@ -24,131 +23,192 @@ const emit = defineEmits<{
24
23
  (e: 'update:modelValue', value: T): void
25
24
  }>()
26
25
 
27
- function safeClone<T>(obj: T): T {
28
- if (!obj) return obj
29
- try {
30
- return JSON.parse(JSON.stringify(obj))
31
- } catch (e) {
32
- console.warn('Failed to clone object:', e)
33
- return obj
34
- }
35
- }
26
+ // Clone helper
27
+ const clone = <T>(obj: T): T => !obj ? obj : JSON.parse(JSON.stringify(obj))
36
28
 
29
+ // Form state
37
30
  const form = ref<HTMLFormElement>()
38
- const formData = ref<T>(safeClone(props.modelValue ?? {}) as T)
39
- const initialFormData = ref<T>(safeClone(props.modelValue ?? {}) as T)
40
-
41
- onMounted(() => {
42
- if (props.modelValue) {
43
- initialFormData.value = safeClone(props.modelValue)
31
+ const formData = ref<T>(clone(props.modelValue ?? {}) as T)
32
+ const initialFormData = ref<T>(clone(props.modelValue ?? {}) as T)
33
+ const formState = ref<'success' | 'error' | 'idle' | 'submitting'>('idle')
34
+ const schemaState = ref<'loading' | 'loaded' | 'error'>('loaded')
35
+ const resolvedSchemaData = ref<BglFormSchemaT<T>>()
36
+ const schemaRef = toRef(props, 'schema')
37
+ const resolvedSchema = computed(() => resolvedSchemaData.value)
38
+ const isDirty = computed(() => {
39
+ try {
40
+ return JSON.stringify(formData.value) !== JSON.stringify(initialFormData.value)
41
+ } catch {
42
+ return false
44
43
  }
45
44
  })
46
45
 
47
- const formState = ref<'success' | 'error' | 'idle' | 'submitting'>('idle')
46
+ // Initialize on mount
47
+ onMounted(() => {
48
+ if (props.modelValue) initialFormData.value = clone(props.modelValue)
49
+ refreshSchema()
50
+ })
48
51
 
49
- watch(() => props.modelValue, (newValue) => {
50
- if (newValue !== undefined) {
51
- formData.value = safeClone(newValue)
52
- }
52
+ // Watch for model changes
53
+ watch(() => props.modelValue, (val) => {
54
+ if (val !== undefined) formData.value = clone(val)
53
55
  }, { immediate: true, deep: true })
54
56
 
55
- const resolvedSchema = $computed<BglFormSchemaFnT<T> | undefined>(() => {
56
- if (!props.schema) return undefined
57
- return typeof props.schema === 'function' ? props.schema() : props.schema
58
- })
57
+ // Schema resolution
58
+ async function resolveSchema(schema?: BglFormSchemaFnT<T>) {
59
+ if (!schema) {
60
+ resolvedSchemaData.value = undefined
61
+ schemaState.value = 'loaded'
62
+ return
63
+ }
59
64
 
60
- const isDirty = $computed(() => {
61
65
  try {
62
- const current = JSON.stringify(formData.value)
63
- const initial = JSON.stringify(initialFormData.value)
64
- return current !== initial
65
- } catch (e) {
66
- console.warn('Failed to compare form data:', e)
67
- return false
66
+ schemaState.value = 'loading'
67
+ const isPromise = (obj: any) => obj && typeof obj.then === 'function'
68
+
69
+ let result: any
70
+ if (typeof schema === 'function') {
71
+ result = schema()
72
+ result = isPromise(result) ? await result : result
73
+ } else {
74
+ result = isPromise(schema) ? await schema : schema
75
+ }
76
+
77
+ resolvedSchemaData.value = result
78
+ schemaState.value = 'loaded'
79
+ } catch (error) {
80
+ console.error('Schema error:', error)
81
+ schemaState.value = 'error'
82
+ resolvedSchemaData.value = undefined
68
83
  }
69
- })
84
+ }
85
+
86
+ // Public refresh method
87
+ async function refreshSchema() {
88
+ await resolveSchema(props.schema)
89
+ return resolvedSchemaData.value
90
+ }
91
+
92
+ // Watch schema changes
93
+ watch(schemaRef, resolveSchema, { immediate: true, deep: true })
70
94
 
95
+ // Update form data
71
96
  function updateFormData(fieldId: string, value: any) {
72
97
  const keys = fieldId.split('.')
73
- const newData = safeClone(formData.value) as Record<string, any>
98
+ const newData = clone(formData.value) as Record<string, any>
74
99
  let current = newData
75
100
 
76
- // Traverse the object, creating nested objects as needed
77
101
  for (let i = 0; i < keys.length - 1; i++) {
78
102
  const key = keys[i]
79
- // Initialize as empty object if current[key] is null/undefined or not an object
80
- if (!current[key] || typeof current[key] !== 'object') {
81
- current[key] = {}
82
- }
103
+ if (!current[key] || typeof current[key] !== 'object') current[key] = {}
83
104
  current = current[key]
84
105
  }
85
106
 
86
- // Set the value at the final key
87
107
  current[keys[keys.length - 1]] = value
88
108
  formData.value = newData as T
89
109
  emit('update:modelValue', formData.value)
90
110
  }
91
111
 
112
+ // Form submission
92
113
  async function handleSubmit() {
93
114
  try {
94
115
  if (formState.value === 'submitting') return
95
116
  formState.value = 'submitting'
96
117
  await props.onSubmit?.(formData.value)
97
- initialFormData.value = safeClone(formData.value)
118
+ initialFormData.value = clone(formData.value)
98
119
  formState.value = 'success'
99
- // Notify parent window of successful submission
100
120
  window.parent.postMessage({ type: 'BAGEL_FORM_SUCCESS', data: JSON.stringify(formData.value) }, '*')
101
121
  } catch (error) {
102
- console.error('Form submission error:', error)
122
+ console.error('Submit error:', error)
103
123
  formState.value = 'error'
104
124
  }
105
125
  }
106
126
 
107
- function validateForm() {
108
- if (!form.value) return false
109
- return form.value.reportValidity()
110
- }
127
+ // Form validation
128
+ const validateForm = () => form.value?.reportValidity() ?? false
111
129
 
130
+ // Field rendering
112
131
  const { renderField } = useSchemaField<T>({
113
132
  mode: 'form',
114
- getRowData: () => {
115
- return {
116
- ...formData.value,
117
- // Add a special getter to handle nested paths
118
- get: (path: string) => getNestedValue(formData.value, path, '')
119
- }
120
- },
133
+ getRowData: () => ({
134
+ ...formData.value,
135
+ get: (path: string) => getNestedValue(formData.value, path, '')
136
+ }),
121
137
  onUpdate: (field, value) => {
122
- if (!field.id) return
123
- updateFormData(field.id, value)
124
- field.onUpdate?.(value, formData.value)
138
+ if (field.id) {
139
+ updateFormData(field.id, value)
140
+ field.onUpdate?.(value, formData.value)
141
+ }
125
142
  }
126
143
  })
127
144
 
128
- function renderSchemaField(field: Field): VNode | null {
129
- return renderField(field)
130
- }
145
+ const renderSchemaField = (field: Field<T>): VNode | null => renderField(field)
131
146
 
132
- defineExpose({ form, isDirty, validateForm })
147
+ defineExpose({ form, isDirty, validateForm, resolveSchema, refreshSchema })
133
148
  </script>
134
149
 
135
150
  <template>
136
151
  <template v-if="formState !== 'success' || !$slots.success">
137
152
  <form v-if="props.tag === 'form'" ref="form" :class="props.class" @submit.prevent="handleSubmit">
138
- <template v-if="resolvedSchema">
139
- <template v-for="field in (resolvedSchema as Field[])" :key="field.id">
140
- <component :is="renderSchemaField(field)" />
141
- </template>
142
- </template>
153
+ <!-- Loading state -->
154
+ <slot v-if="schemaState === 'loading'" name="loading">
155
+ <div class="flex-center h-300px">
156
+ <Loading />
157
+ </div>
158
+ </slot>
159
+
160
+ <!-- Error state -->
161
+ <slot v-else-if="schemaState === 'error'" name="schema-error">
162
+ <div class="flex-center h-300px txt-red">
163
+ Error loading form
164
+ </div>
165
+ </slot>
166
+
167
+ <!-- Render fields -->
168
+ <component
169
+ :is="renderSchemaField(field)"
170
+ v-for="field in resolvedSchema"
171
+ v-else-if="resolvedSchema"
172
+ :key="field.id"
173
+ />
174
+
175
+ <!-- Default slot -->
143
176
  <slot v-else />
144
- <slot name="submit" :submit="handleSubmit" :isDirty="isDirty" :validateForm="validateForm" :formState="formState" />
177
+
178
+ <!-- Submit slot -->
179
+ <slot
180
+ name="submit"
181
+ :submit="handleSubmit"
182
+ :isDirty="isDirty"
183
+ :validateForm="validateForm"
184
+ :formState="formState"
185
+ :schemaState="schemaState"
186
+ />
145
187
  </form>
188
+
189
+ <!-- Template mode -->
146
190
  <template v-else>
147
- <template v-for="field in (resolvedSchema as Field[])" :key="field.id">
148
- <component :is="renderSchemaField(field)" :class="props.class" />
149
- </template>
191
+ <slot v-if="schemaState === 'loading'" name="loading">
192
+ <Loading />
193
+ </slot>
194
+
195
+ <slot v-else-if="schemaState === 'error'" name="schema-error">
196
+ <div class="flex-center h-300px txt-red">
197
+ Error loading form
198
+ </div>
199
+ </slot>
200
+
201
+ <component
202
+ :is="renderSchemaField(field)"
203
+ v-for="field in resolvedSchema"
204
+ v-else-if="resolvedSchema"
205
+ :key="field.id"
206
+ :class="props.class"
207
+ />
150
208
  </template>
151
209
  </template>
210
+
211
+ <!-- Success/error slots -->
152
212
  <slot v-if="formState === 'success'" name="success" />
153
213
  <slot v-if="formState === 'error'" name="error" />
154
214
  </template>