@bagelink/vue 0.0.1139 → 0.0.1141
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/Btn.vue.d.ts.map +1 -1
- package/dist/components/DataTable/DataTable.vue.d.ts +31 -0
- package/dist/components/DataTable/DataTable.vue.d.ts.map +1 -0
- package/dist/components/DataTable/tableTypes.d.ts +35 -0
- package/dist/components/DataTable/tableTypes.d.ts.map +1 -0
- package/dist/components/DataTable/useSorting.d.ts +7 -0
- package/dist/components/DataTable/useSorting.d.ts.map +1 -0
- package/dist/components/DataTable/useTableData.d.ts +13 -0
- package/dist/components/DataTable/useTableData.d.ts.map +1 -0
- package/dist/components/DataTable/useTableSelection.d.ts +10 -0
- package/dist/components/DataTable/useTableSelection.d.ts.map +1 -0
- package/dist/components/DataTable/useTableVirtualization.d.ts +25 -0
- package/dist/components/DataTable/useTableVirtualization.d.ts.map +1 -0
- package/dist/components/TableSchema.vue.d.ts +2 -2
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.cjs +3414 -3328
- package/dist/index.mjs +3415 -3329
- package/dist/style.css +272 -272
- package/dist/types/TableSchema.d.ts +36 -0
- package/dist/types/TableSchema.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Btn.vue +2 -2
- package/src/components/{TableSchema.vue → DataTable/DataTable.vue} +62 -154
- package/src/components/DataTable/tableTypes.d.ts +0 -0
- package/src/components/DataTable/tableTypes.ts +38 -0
- package/src/components/DataTable/useSorting.ts +30 -0
- package/src/components/DataTable/useTableData.ts +95 -0
- package/src/components/DataTable/useTableSelection.ts +45 -0
- package/src/components/DataTable/useTableVirtualization.ts +33 -0
- package/src/components/Icon/Icon.vue +1 -1
- package/src/components/form/BagelForm.vue +5 -12
- package/src/components/form/inputs/RichText/index.vue +6 -4
- package/src/components/index.ts +3 -2
- package/src/types/TableSchema.ts +39 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { BglFormSchemaT } from '..';
|
|
2
|
+
import { Ref, ComputedRef } from 'vue';
|
|
3
|
+
export type SortDirectionsT = 'ASC' | 'DESC';
|
|
4
|
+
export type EmitOrderT = `${string} ${SortDirectionsT}`;
|
|
5
|
+
export interface TableSchemaProps<T extends Record<string, any> = Record<string, any>> {
|
|
6
|
+
data: T[];
|
|
7
|
+
schema?: BglFormSchemaT<T> | (() => BglFormSchemaT<T>);
|
|
8
|
+
showFields?: string[];
|
|
9
|
+
useServerSort?: boolean;
|
|
10
|
+
selectable?: boolean;
|
|
11
|
+
onLastItemVisible?: () => void;
|
|
12
|
+
}
|
|
13
|
+
export interface SortingOptions<T> {
|
|
14
|
+
onSort: (field: string, direction: SortDirectionsT) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface TableSelectionOptions<T> {
|
|
17
|
+
selectable: boolean | undefined;
|
|
18
|
+
selectedItems: {
|
|
19
|
+
value: string[];
|
|
20
|
+
};
|
|
21
|
+
onSelect: (item: T) => void;
|
|
22
|
+
}
|
|
23
|
+
export interface TableVirtualizationOptions<T> {
|
|
24
|
+
data: Ref<T[]> | ComputedRef<T[]>;
|
|
25
|
+
itemHeight: number;
|
|
26
|
+
onLastItemVisible?: () => void;
|
|
27
|
+
}
|
|
28
|
+
export interface TableDataOptions<T> {
|
|
29
|
+
data: Ref<T[]> | ComputedRef<T[]>;
|
|
30
|
+
schema?: BglFormSchemaT<T> | (() => BglFormSchemaT<T>);
|
|
31
|
+
showFields?: string[];
|
|
32
|
+
sortField: string;
|
|
33
|
+
sortDirection: SortDirectionsT;
|
|
34
|
+
useServerSort?: boolean;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=TableSchema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableSchema.d.ts","sourceRoot":"","sources":["../../src/types/TableSchema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AAE3C,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,MAAM,CAAA;AAC5C,MAAM,MAAM,UAAU,GAAG,GAAG,MAAM,IAAI,eAAe,EAAE,CAAA;AAEvD,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IACpF,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAChC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,KAAK,IAAI,CAAA;CAC3D;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACvC,UAAU,EAAE,OAAO,GAAG,SAAS,CAAA;IAC/B,aAAa,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,0BAA0B,CAAC,CAAC;IAC5C,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC9B;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAClC,IAAI,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAA;IACjC,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,eAAe,CAAA;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAA;CACvB"}
|
package/package.json
CHANGED
package/src/components/Btn.vue
CHANGED
|
@@ -81,12 +81,12 @@ const slots: SetupContext['slots'] = useSlots()
|
|
|
81
81
|
>
|
|
82
82
|
<Loading v-if="loading" class="h-100p" size="15" />
|
|
83
83
|
<div v-else class="bgl_btn-flex">
|
|
84
|
-
<Icon v-if="icon" :icon="icon" />
|
|
84
|
+
<Icon v-if="icon" :icon="icon" class="transition-400" />
|
|
85
85
|
<slot />
|
|
86
86
|
<template v-if="!slots.default && value">
|
|
87
87
|
{{ value }}
|
|
88
88
|
</template>
|
|
89
|
-
<Icon v-if="iconEnd" :icon="iconEnd" />
|
|
89
|
+
<Icon v-if="iconEnd" :icon="iconEnd" class="transition-400" />
|
|
90
90
|
</div>
|
|
91
91
|
</component>
|
|
92
92
|
</template>
|
|
@@ -1,37 +1,20 @@
|
|
|
1
1
|
<script setup lang="ts" generic="T extends Record<string, any>">
|
|
2
|
-
import type {
|
|
2
|
+
import type { TableSchemaProps } from '../../types/TableSchema'
|
|
3
3
|
import {
|
|
4
4
|
BglComponent,
|
|
5
5
|
Icon,
|
|
6
|
-
isDate,
|
|
7
6
|
keyToLabel,
|
|
8
|
-
useBglSchema
|
|
9
7
|
} from '@bagelink/vue'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from '
|
|
15
|
-
import { computed, useSlots, watch } from 'vue'
|
|
8
|
+
|
|
9
|
+
import { useSlots, watch, computed } from 'vue'
|
|
10
|
+
import { useTableData } from './useTableData'
|
|
11
|
+
import { useTableSelection } from './useTableSelection'
|
|
12
|
+
import { useTableVirtualization } from './useTableVirtualization'
|
|
16
13
|
|
|
17
14
|
export type SortDirectionsT = 'ASC' | 'DESC'
|
|
18
15
|
export type EmitOrderT = `${string} ${SortDirectionsT}`
|
|
19
16
|
|
|
20
|
-
const
|
|
21
|
-
data,
|
|
22
|
-
schema,
|
|
23
|
-
showFields,
|
|
24
|
-
onLastItemVisible,
|
|
25
|
-
useServerSort = false,
|
|
26
|
-
selectable = false,
|
|
27
|
-
} = defineProps<{
|
|
28
|
-
data: T[]
|
|
29
|
-
schema?: BglFormSchemaT<T> | (() => BglFormSchemaT<T>)
|
|
30
|
-
showFields?: string[]
|
|
31
|
-
useServerSort?: boolean
|
|
32
|
-
selectable?: boolean
|
|
33
|
-
onLastItemVisible?: () => void
|
|
34
|
-
}>()
|
|
17
|
+
const props = defineProps<TableSchemaProps<T>>()
|
|
35
18
|
|
|
36
19
|
const emit = defineEmits<{
|
|
37
20
|
'update:selectedItems': [string[]]
|
|
@@ -40,8 +23,6 @@ const emit = defineEmits<{
|
|
|
40
23
|
'lastItemVisible': []
|
|
41
24
|
}>()
|
|
42
25
|
|
|
43
|
-
const NON_DIGIT_REGEX = /[^\d.-]/g
|
|
44
|
-
|
|
45
26
|
const slots: any = useSlots()
|
|
46
27
|
|
|
47
28
|
const loading = defineModel('loading', { default: false })
|
|
@@ -54,148 +35,75 @@ const selectedItems = defineModel<string[]>(
|
|
|
54
35
|
}
|
|
55
36
|
)
|
|
56
37
|
|
|
57
|
-
|
|
58
|
-
let sortDirection = $ref<SortDirectionsT>('ASC')
|
|
59
|
-
|
|
60
|
-
const allSelectorEl = $ref<HTMLInputElement | undefined>()
|
|
61
|
-
const lastItemEl = $ref<HTMLTableRowElement | null>()
|
|
62
|
-
|
|
63
|
-
const computedSelectedItems = $computed(() => selectedItems.value)
|
|
64
|
-
const computedItemHeight = $computed(() => `${itemHeight.value}px`)
|
|
65
|
-
const isSelectable = $computed(() => selectable === true && Array.isArray(selectedItems.value))
|
|
66
|
-
const computedSortField = $computed(() => `_transformed_${sortField}`)
|
|
67
|
-
const computedSchema = $computed(() => useBglSchema<T>({
|
|
68
|
-
schema,
|
|
69
|
-
showFields,
|
|
70
|
-
data,
|
|
71
|
-
}))
|
|
72
|
-
|
|
73
|
-
const computedData = computed(() => {
|
|
74
|
-
if (!sortField || useServerSort === true) return data.map(transform)
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
data
|
|
78
|
-
.map(transform)
|
|
79
|
-
.sort(
|
|
80
|
-
(a, z) => {
|
|
81
|
-
let aValue = a[computedSortField] ?? a[sortField] ?? ''
|
|
82
|
-
let bValue = z[computedSortField] ?? z[sortField] ?? ''
|
|
83
|
-
|
|
84
|
-
if (isDate(aValue) && isDate(bValue)) {
|
|
85
|
-
// ? now we can sort by as number
|
|
86
|
-
aValue = new Date(aValue).getTime()
|
|
87
|
-
bValue = new Date(bValue).getTime()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const numAValue = Number.parseInt(`${aValue}`.replaceAll(NON_DIGIT_REGEX, ''), 10)
|
|
91
|
-
const numBValue = Number.parseInt(`${bValue}`.replaceAll(NON_DIGIT_REGEX, ''), 10)
|
|
92
|
-
|
|
93
|
-
if (!Number.isNaN(numAValue) && !Number.isNaN(numBValue)) {
|
|
94
|
-
if (sortDirection === 'ASC') return numAValue - numBValue
|
|
95
|
-
return numBValue - numAValue
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (typeof aValue === 'string') {
|
|
99
|
-
if (sortDirection === 'ASC') return aValue.localeCompare(bValue)
|
|
100
|
-
return bValue.localeCompare(aValue)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (sortDirection === 'ASC') return aValue < bValue ? -1 : 1
|
|
104
|
-
return aValue < bValue ? 1 : -1
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
function transform(rowData: any) {
|
|
111
|
-
const obj: { [key: string]: any } = { ...rowData }
|
|
112
|
-
const schemaFields = computedSchema.filter((f: any) => f.id)
|
|
113
|
-
|
|
114
|
-
for (const field of schemaFields) {
|
|
115
|
-
const fieldData = rowData[`${field.id}`]
|
|
116
|
-
const newFieldVal = field.transform?.(fieldData, rowData)
|
|
38
|
+
const data = computed(() => props.data)
|
|
117
39
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
[`_transformed_${field.id}`]: newFieldVal,
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
return obj
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function sort(fieldname: string) {
|
|
127
|
-
if (sortField === fieldname) {
|
|
128
|
-
if (sortDirection === 'ASC') sortDirection = 'DESC'
|
|
129
|
-
else sortField = ''
|
|
130
|
-
} else {
|
|
131
|
-
sortField = fieldname
|
|
132
|
-
sortDirection = 'ASC'
|
|
133
|
-
}
|
|
134
|
-
emit('orderBy', `${fieldname} ${sortDirection}`.trim() as EmitOrderT)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
|
40
|
+
const {
|
|
41
|
+
computedSchema,
|
|
138
42
|
computedData,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
43
|
+
sortField,
|
|
44
|
+
sortDirection,
|
|
45
|
+
toggleSort
|
|
46
|
+
} = useTableData<T>({
|
|
47
|
+
data,
|
|
48
|
+
schema: props.schema,
|
|
49
|
+
showFields: props.showFields,
|
|
50
|
+
useServerSort: props.useServerSort,
|
|
51
|
+
onSort: (field, direction) => {
|
|
52
|
+
emit('orderBy', `${field} ${direction}`.trim() as EmitOrderT)
|
|
142
53
|
}
|
|
143
|
-
)
|
|
54
|
+
})
|
|
144
55
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
56
|
+
const {
|
|
57
|
+
computedSelectedItems,
|
|
58
|
+
isSelectable,
|
|
59
|
+
allSelectorEl,
|
|
60
|
+
updateAllSelectorState,
|
|
61
|
+
toggleSelectItem,
|
|
62
|
+
toggleSelectAll
|
|
63
|
+
} = useTableSelection<T>({
|
|
64
|
+
selectable: props.selectable,
|
|
65
|
+
selectedItems,
|
|
66
|
+
onSelect: (item) => { emit('select', item) }
|
|
67
|
+
})
|
|
152
68
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
69
|
+
const {
|
|
70
|
+
list,
|
|
71
|
+
containerProps,
|
|
72
|
+
wrapperProps,
|
|
73
|
+
scrollTo,
|
|
74
|
+
lastItemEl,
|
|
75
|
+
registerLastItemObserver
|
|
76
|
+
} = useTableVirtualization<T>({
|
|
77
|
+
data: computedData,
|
|
78
|
+
itemHeight: itemHeight.value,
|
|
79
|
+
onLastItemVisible: () => {
|
|
80
|
+
emit('lastItemVisible')
|
|
81
|
+
props.onLastItemVisible?.()
|
|
159
82
|
}
|
|
160
|
-
|
|
161
|
-
index > -1 ? computedSelectedItems.splice(index, 1) : computedSelectedItems.push(item.id)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function toggleSelectAll(event: Event) {
|
|
165
|
-
const value = (event.target as HTMLInputElement).checked
|
|
166
|
-
selectedItems.value = value ? computedData.value.map((d: any) => d.id) : []
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function registerLastItemObserver() {
|
|
170
|
-
await until(() => lastItemEl).toBeTruthy()
|
|
83
|
+
})
|
|
171
84
|
|
|
172
|
-
|
|
173
|
-
if (entry.isIntersecting && computedData.value.length) {
|
|
174
|
-
emit('lastItemVisible')
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
}
|
|
85
|
+
const computedItemHeight = $computed(() => `${itemHeight.value}px`)
|
|
178
86
|
|
|
179
87
|
watch(
|
|
180
|
-
() =>
|
|
181
|
-
(
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
}
|
|
88
|
+
() => loading.value,
|
|
89
|
+
(newLoadingVal, oldLoadingVal) => {
|
|
90
|
+
if (newLoadingVal === oldLoadingVal) return
|
|
91
|
+
if (!newLoadingVal) registerLastItemObserver()
|
|
92
|
+
},
|
|
93
|
+
{ immediate: true }
|
|
185
94
|
)
|
|
186
95
|
|
|
187
96
|
watch(
|
|
188
|
-
|
|
97
|
+
computedSelectedItems,
|
|
189
98
|
() => { updateAllSelectorState() }
|
|
190
99
|
)
|
|
191
100
|
|
|
192
101
|
watch(
|
|
193
|
-
|
|
194
|
-
(
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
{ immediate: true }
|
|
102
|
+
computedData,
|
|
103
|
+
(newData, oldData) => {
|
|
104
|
+
if (newData.length === oldData.length || props.onLastItemVisible !== undefined) return
|
|
105
|
+
scrollTo(0)
|
|
106
|
+
}
|
|
199
107
|
)
|
|
200
108
|
</script>
|
|
201
109
|
|
|
@@ -226,7 +134,7 @@ watch(
|
|
|
226
134
|
v-for="field in computedSchema"
|
|
227
135
|
:key="field.id"
|
|
228
136
|
class="col"
|
|
229
|
-
@click="
|
|
137
|
+
@click="toggleSort(field?.id || '')"
|
|
230
138
|
>
|
|
231
139
|
<div class="flex">
|
|
232
140
|
{{ field.label || keyToLabel(field.id) }}
|
|
@@ -281,7 +189,7 @@ watch(
|
|
|
281
189
|
</div>
|
|
282
190
|
</td>
|
|
283
191
|
</tr>
|
|
284
|
-
<tr v-if="onLastItemVisible !== undefined" ref="lastItemEl" style="height: 1px;" />
|
|
192
|
+
<tr v-if="props.onLastItemVisible !== undefined" ref="lastItemEl" style="height: 1px;" />
|
|
285
193
|
</tbody>
|
|
286
194
|
</table>
|
|
287
195
|
</div>
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BglFormSchemaT } from '@bagelink/vue'
|
|
2
|
+
|
|
3
|
+
export type SortDirectionsT = 'ASC' | 'DESC'
|
|
4
|
+
export type EmitOrderT = `${string} ${SortDirectionsT}`
|
|
5
|
+
|
|
6
|
+
export interface TableSchemaProps<T extends Record<string, any> = Record<string, any>> {
|
|
7
|
+
data: T[]
|
|
8
|
+
schema?: BglFormSchemaT<T> | (() => BglFormSchemaT<T>)
|
|
9
|
+
showFields?: string[]
|
|
10
|
+
useServerSort?: boolean
|
|
11
|
+
selectable?: boolean
|
|
12
|
+
onLastItemVisible?: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SortingOptions<T> {
|
|
16
|
+
onSort: (field: string, direction: SortDirectionsT) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TableSelectionOptions<T> {
|
|
20
|
+
selectable: boolean | undefined
|
|
21
|
+
selectedItems: { value: string[] }
|
|
22
|
+
onSelect: (item: T) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TableVirtualizationOptions<T> {
|
|
26
|
+
data: T[]
|
|
27
|
+
itemHeight: number
|
|
28
|
+
onLastItemVisible?: () => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TableDataOptions<T> {
|
|
32
|
+
data: T[]
|
|
33
|
+
schema?: BglFormSchemaT<T> | (() => BglFormSchemaT<T>)
|
|
34
|
+
showFields?: string[]
|
|
35
|
+
sortField: string
|
|
36
|
+
sortDirection: SortDirectionsT
|
|
37
|
+
useServerSort?: boolean
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SortDirectionsT, SortingOptions } from '../../types/TableSchema'
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function useSorting<T>(options: SortingOptions<T>) {
|
|
5
|
+
const sortFieldRef = ref('')
|
|
6
|
+
const sortDirectionRef = ref<SortDirectionsT>('ASC')
|
|
7
|
+
|
|
8
|
+
const sortField = computed(() => sortFieldRef.value)
|
|
9
|
+
const sortDirection = computed(() => sortDirectionRef.value)
|
|
10
|
+
|
|
11
|
+
function sort(fieldname: string) {
|
|
12
|
+
if (sortFieldRef.value === fieldname) {
|
|
13
|
+
if (sortDirectionRef.value === 'ASC') {
|
|
14
|
+
sortDirectionRef.value = 'DESC'
|
|
15
|
+
} else {
|
|
16
|
+
sortFieldRef.value = ''
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
sortFieldRef.value = fieldname
|
|
20
|
+
sortDirectionRef.value = 'ASC'
|
|
21
|
+
}
|
|
22
|
+
options.onSort(sortFieldRef.value, sortDirectionRef.value)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
sortField,
|
|
27
|
+
sortDirection,
|
|
28
|
+
sort
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { TableDataOptions, SortDirectionsT } from '../../types/TableSchema'
|
|
2
|
+
import { useBglSchema, isDate } from '@bagelink/vue'
|
|
3
|
+
import { computed, ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
const NON_DIGIT_REGEX = /[^\d.-]/g
|
|
6
|
+
|
|
7
|
+
export interface UseTableDataOptions<T> extends Omit<TableDataOptions<T>, 'sortField' | 'sortDirection'> {
|
|
8
|
+
onSort?: (field: string, direction: SortDirectionsT) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useTableData<T extends Record<string, any>>(options: UseTableDataOptions<T>) {
|
|
12
|
+
// Create reactive reference to data prop
|
|
13
|
+
|
|
14
|
+
// Sorting state
|
|
15
|
+
const sortField = ref('')
|
|
16
|
+
const sortDirection = ref<SortDirectionsT>('ASC')
|
|
17
|
+
|
|
18
|
+
const computedSchema = computed(() => useBglSchema<T>({
|
|
19
|
+
schema: options.schema,
|
|
20
|
+
showFields: options.showFields,
|
|
21
|
+
data: options.data.value,
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
function transform(rowData: T): T {
|
|
25
|
+
const transformed = { ...rowData }
|
|
26
|
+
const schemaFields = computedSchema.value.filter((f: any) => f.id)
|
|
27
|
+
|
|
28
|
+
for (const field of schemaFields) {
|
|
29
|
+
const fieldData = rowData[`${field.id}`]
|
|
30
|
+
const newFieldVal = field.transform?.(fieldData, rowData)
|
|
31
|
+
;(transformed as any)[`_transformed_${field.id}`] = newFieldVal
|
|
32
|
+
}
|
|
33
|
+
return transformed
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const computedSortField = computed(() => `_transformed_${sortField.value}`)
|
|
37
|
+
|
|
38
|
+
const computedData = computed(() => {
|
|
39
|
+
const currentData = options.data.value
|
|
40
|
+
if (!sortField.value || options.useServerSort === true) {
|
|
41
|
+
return currentData.map(transform)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return currentData
|
|
45
|
+
.map(transform)
|
|
46
|
+
.sort((a, z) => {
|
|
47
|
+
let aValue = (a as any)[computedSortField.value] ?? a[sortField.value] ?? ''
|
|
48
|
+
let bValue = (z as any)[computedSortField.value] ?? z[sortField.value] ?? ''
|
|
49
|
+
|
|
50
|
+
if (isDate(aValue) && isDate(bValue)) {
|
|
51
|
+
aValue = new Date(aValue).getTime()
|
|
52
|
+
bValue = new Date(bValue).getTime()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const numAValue = Number.parseInt(`${aValue}`.replaceAll(NON_DIGIT_REGEX, ''), 10)
|
|
56
|
+
const numBValue = Number.parseInt(`${bValue}`.replaceAll(NON_DIGIT_REGEX, ''), 10)
|
|
57
|
+
|
|
58
|
+
if (!Number.isNaN(numAValue) && !Number.isNaN(numBValue)) {
|
|
59
|
+
if (sortDirection.value === 'ASC') return numAValue - numBValue
|
|
60
|
+
return numBValue - numAValue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof aValue === 'string') {
|
|
64
|
+
if (sortDirection.value === 'ASC') return aValue.localeCompare(bValue)
|
|
65
|
+
return bValue.localeCompare(aValue)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (sortDirection.value === 'ASC') return aValue < bValue ? -1 : 1
|
|
69
|
+
return aValue < bValue ? 1 : -1
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
function toggleSort(fieldname: string) {
|
|
74
|
+
if (sortField.value === fieldname) {
|
|
75
|
+
if (sortDirection.value === 'ASC') {
|
|
76
|
+
sortDirection.value = 'DESC'
|
|
77
|
+
} else {
|
|
78
|
+
sortField.value = ''
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
sortField.value = fieldname
|
|
82
|
+
sortDirection.value = 'ASC'
|
|
83
|
+
}
|
|
84
|
+
options.onSort?.(sortField.value, sortDirection.value)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
computedSchema,
|
|
89
|
+
computedData,
|
|
90
|
+
transform,
|
|
91
|
+
sortField: computed(() => sortField.value),
|
|
92
|
+
sortDirection: computed(() => sortDirection.value),
|
|
93
|
+
toggleSort
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TableSelectionOptions } from '../../types/TableSchema'
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function useTableSelection<T extends Record<string, any>>(options: TableSelectionOptions<T>) {
|
|
5
|
+
const allSelectorEl = ref<HTMLInputElement>()
|
|
6
|
+
const computedSelectedItems = computed(() => options.selectedItems.value)
|
|
7
|
+
const isSelectable = computed(() => options.selectable === true && Array.isArray(options.selectedItems.value))
|
|
8
|
+
|
|
9
|
+
function updateAllSelectorState() {
|
|
10
|
+
if (!allSelectorEl.value) return
|
|
11
|
+
const allSelected = computedSelectedItems.value.length === options.selectedItems.value.length
|
|
12
|
+
&& computedSelectedItems.value.every(id => options.selectedItems.value.includes(id))
|
|
13
|
+
allSelectorEl.value.checked = allSelected
|
|
14
|
+
allSelectorEl.value.indeterminate = !allSelected && computedSelectedItems.value.length > 0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toggleSelectItem(item: T) {
|
|
18
|
+
if (computedSelectedItems.value.length === 0) {
|
|
19
|
+
const cleanItem = { ...item }
|
|
20
|
+
Object.keys(cleanItem).forEach(key => key.startsWith('_transformed_') && delete cleanItem[key])
|
|
21
|
+
options.onSelect(cleanItem)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
const index = computedSelectedItems.value.indexOf(item.id)
|
|
25
|
+
if (index > -1) {
|
|
26
|
+
options.selectedItems.value.splice(index, 1)
|
|
27
|
+
} else {
|
|
28
|
+
options.selectedItems.value.push(item.id)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toggleSelectAll(event: Event) {
|
|
33
|
+
const value = (event.target as HTMLInputElement).checked
|
|
34
|
+
options.selectedItems.value = value ? computedSelectedItems.value.map((d: any) => d.id) : []
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
computedSelectedItems,
|
|
39
|
+
isSelectable,
|
|
40
|
+
allSelectorEl,
|
|
41
|
+
updateAllSelectorState,
|
|
42
|
+
toggleSelectItem,
|
|
43
|
+
toggleSelectAll
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TableVirtualizationOptions } from '../../types/TableSchema'
|
|
2
|
+
import { useVirtualList, useIntersectionObserver, until } from '@vueuse/core'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
export function useTableVirtualization<T>(options: TableVirtualizationOptions<T>) {
|
|
6
|
+
const lastItemEl = ref<HTMLTableRowElement | null>(null)
|
|
7
|
+
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(
|
|
8
|
+
options.data,
|
|
9
|
+
{
|
|
10
|
+
itemHeight: options.itemHeight,
|
|
11
|
+
overscan: 10,
|
|
12
|
+
}
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
async function registerLastItemObserver() {
|
|
16
|
+
await until(() => lastItemEl.value).toBeTruthy()
|
|
17
|
+
|
|
18
|
+
useIntersectionObserver(lastItemEl, ([entry]) => {
|
|
19
|
+
if (entry.isIntersecting && options.data.value.length) {
|
|
20
|
+
options.onLastItemVisible?.()
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
list,
|
|
27
|
+
containerProps,
|
|
28
|
+
wrapperProps,
|
|
29
|
+
scrollTo,
|
|
30
|
+
lastItemEl,
|
|
31
|
+
registerLastItemObserver
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -10,7 +10,7 @@ const props = defineProps<{
|
|
|
10
10
|
weight?: number | string
|
|
11
11
|
}>()
|
|
12
12
|
|
|
13
|
-
const iconRender = (props.icon || props.name) as IconType
|
|
13
|
+
const iconRender = $computed(() => props.icon || props.name) as IconType
|
|
14
14
|
|
|
15
15
|
const iconRenderType = $computed(() => {
|
|
16
16
|
if (MATERIAL_ICONS.includes(iconRender)) return 'material'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { BglFormSchemaT, BglFormSchemaFnT, Field } from '@bagelink/vue'
|
|
3
3
|
import type { VNode } from 'vue'
|
|
4
|
-
import { BglForm,
|
|
4
|
+
import { BglForm, CheckInput, DateInput, FieldArray, FileUpload, NumberInput, RichText, SelectInput, TabsNav, TextInput, ToggleInput, UploadInput } from '@bagelink/vue'
|
|
5
5
|
import { h, watch } from 'vue'
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
@@ -63,17 +63,10 @@ function getComponent(field: Field) {
|
|
|
63
63
|
return componentMap[field.$el as keyof typeof componentMap] ?? field.$el ?? 'div'
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
function
|
|
67
|
-
if (value == null) return value
|
|
68
|
-
if (fieldType === 'select' && typeof value === 'object') return value.value
|
|
69
|
-
if (typeof value === 'object' && 'value' in value) return value.value
|
|
70
|
-
return value
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function updateFormData(fieldId: string, value: any, fieldType?: any) {
|
|
66
|
+
function updateFormData(fieldId: string, value: any) {
|
|
74
67
|
formData = {
|
|
75
68
|
...formData,
|
|
76
|
-
[fieldId]:
|
|
69
|
+
[fieldId]: value
|
|
77
70
|
}
|
|
78
71
|
emit('update:modelValue', formData)
|
|
79
72
|
}
|
|
@@ -116,10 +109,10 @@ function renderSchemaField(field: Field): VNode | null {
|
|
|
116
109
|
label,
|
|
117
110
|
placeholder,
|
|
118
111
|
disabled,
|
|
119
|
-
'modelValue':
|
|
112
|
+
'modelValue': currentValue,
|
|
120
113
|
'onUpdate:modelValue': (value: any) => {
|
|
121
114
|
if (!field.id) return
|
|
122
|
-
updateFormData(field.id, value
|
|
115
|
+
updateFormData(field.id, value)
|
|
123
116
|
field.onUpdate?.(value, formData)
|
|
124
117
|
}
|
|
125
118
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { ToolbarConfig } from './richTextTypes'
|
|
3
3
|
import { CodeEditor, copyText, Btn } from '@bagelink/vue'
|
|
4
|
-
import { watch
|
|
4
|
+
import { watch } from 'vue'
|
|
5
5
|
import EditorToolbar from './components/EditorToolbar.vue'
|
|
6
6
|
import { useCommands } from './composables/useCommands'
|
|
7
7
|
import { useEditor } from './composables/useEditor'
|
|
@@ -15,8 +15,8 @@ const editor = useEditor()
|
|
|
15
15
|
const commands = useCommands(editor.state, props.debug ? editor.debug : undefined)
|
|
16
16
|
|
|
17
17
|
// Expose debug methods if debug mode is enabled
|
|
18
|
-
const debugMethods = computed(() => props.debug ? editor.debug : undefined)
|
|
19
|
-
|
|
18
|
+
const debugMethods = $computed(() => props.debug ? editor.debug : undefined)
|
|
19
|
+
const hasRTL = $computed(() => /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(props.modelValue))
|
|
20
20
|
async function initEditor() {
|
|
21
21
|
if (!iframe) {
|
|
22
22
|
setTimeout(initEditor, 100)
|
|
@@ -30,7 +30,6 @@ async function initEditor() {
|
|
|
30
30
|
doc.body.contentEditable = 'true'
|
|
31
31
|
|
|
32
32
|
// Set default direction based on content
|
|
33
|
-
const hasRTL = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(props.modelValue)
|
|
34
33
|
doc.body.dir = hasRTL ? 'rtl' : 'ltr'
|
|
35
34
|
|
|
36
35
|
const style = doc.createElement('style')
|
|
@@ -79,6 +78,9 @@ watch(() => props.modelValue, (newValue) => {
|
|
|
79
78
|
})
|
|
80
79
|
|
|
81
80
|
watch(() => editor.state.content, (newValue) => {
|
|
81
|
+
if (editor.state.doc?.body.innerHTML) {
|
|
82
|
+
editor.state.doc.body.dir = hasRTL ? 'rtl' : 'ltr'
|
|
83
|
+
}
|
|
82
84
|
emit('update:modelValue', newValue)
|
|
83
85
|
})
|
|
84
86
|
</script>
|