@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.
- package/dist/components/DataPreview.vue.d.ts.map +1 -1
- package/dist/components/DataTable/DataTable.vue.d.ts +2 -2
- package/dist/components/DataTable/DataTable.vue.d.ts.map +1 -1
- package/dist/components/DataTable/useTableData.d.ts +2 -1
- package/dist/components/DataTable/useTableData.d.ts.map +1 -1
- package/dist/components/DataTable/useTableSelection.d.ts +4 -1
- package/dist/components/DataTable/useTableSelection.d.ts.map +1 -1
- package/dist/components/DataTable/useTableVirtualization.d.ts.map +1 -1
- package/dist/components/form/BagelForm.vue.d.ts +17 -11
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/FieldArray.vue.d.ts.map +1 -1
- package/dist/composables/index.d.ts +1 -1
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/index.cjs +441 -307
- package/dist/index.mjs +442 -308
- package/dist/style.css +502 -83
- package/dist/types/BagelForm.d.ts +1 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/DataPreview.vue +30 -4
- package/src/components/DataTable/DataTable.vue +9 -7
- package/src/components/DataTable/useTableData.ts +75 -9
- package/src/components/DataTable/useTableSelection.ts +17 -3
- package/src/components/DataTable/useTableVirtualization.ts +11 -2
- package/src/components/Loading.vue +2 -2
- package/src/components/form/BagelForm.vue +138 -78
- package/src/components/form/FieldArray.vue +129 -54
- package/src/components/layout/BottomMenu.vue +1 -1
- package/src/composables/index.ts +14 -4
- package/src/styles/layout.css +240 -0
- package/src/styles/mobilLayout.css +240 -0
- 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
|
@@ -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(() =>
|
|
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(() =>
|
|
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-
|
|
36
|
-
<
|
|
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
|
|
147
|
+
{{ field.label || keyToLabel(field?.id) }}
|
|
146
148
|
<div
|
|
147
149
|
class="list-arrows"
|
|
148
|
-
:class="{ sorted: sortField === field
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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 =
|
|
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) /
|
|
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 {
|
|
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
|
-
|
|
9
|
-
modelValue?:
|
|
10
|
-
schema?: BglFormSchemaFnT<
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
modelValue?: T
|
|
11
|
+
schema?: BglFormSchemaFnT<T>
|
|
11
12
|
tag?: 'form' | 'template'
|
|
12
13
|
class?: string
|
|
13
|
-
onSubmit?: (data:
|
|
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
|
-
|
|
28
|
-
|
|
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>(
|
|
39
|
-
const initialFormData = ref<T>(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
46
|
+
// Initialize on mount
|
|
47
|
+
onMounted(() => {
|
|
48
|
+
if (props.modelValue) initialFormData.value = clone(props.modelValue)
|
|
49
|
+
refreshSchema()
|
|
50
|
+
})
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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('
|
|
122
|
+
console.error('Submit error:', error)
|
|
103
123
|
formState.value = 'error'
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
126
|
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 (
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
if (field.id) {
|
|
139
|
+
updateFormData(field.id, value)
|
|
140
|
+
field.onUpdate?.(value, formData.value)
|
|
141
|
+
}
|
|
125
142
|
}
|
|
126
143
|
})
|
|
127
144
|
|
|
128
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
148
|
-
<
|
|
149
|
-
</
|
|
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>
|