@bagelink/vue 1.2.99 → 1.2.103

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 (50) hide show
  1. package/dist/components/AddressSearch.vue.d.ts.map +1 -1
  2. package/dist/components/DragOver.vue.d.ts +27 -0
  3. package/dist/components/DragOver.vue.d.ts.map +1 -0
  4. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  5. package/dist/components/ImportData.vue.d.ts +20 -0
  6. package/dist/components/ImportData.vue.d.ts.map +1 -0
  7. package/dist/components/calendar/views/CalendarPopover.vue.d.ts +2 -2
  8. package/dist/components/calendar/views/CalendarPopover.vue.d.ts.map +1 -1
  9. package/dist/components/form/FieldArray.vue.d.ts +3 -1
  10. package/dist/components/form/FieldArray.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/Upload/useFileUpload.d.ts +1 -1
  13. package/dist/components/form/inputs/Upload/useFileUpload.d.ts.map +1 -1
  14. package/dist/components/index.d.ts +2 -0
  15. package/dist/components/index.d.ts.map +1 -1
  16. package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  17. package/dist/composables/index.d.ts +2 -1
  18. package/dist/composables/index.d.ts.map +1 -1
  19. package/dist/composables/useExcel.d.ts +25 -0
  20. package/dist/composables/useExcel.d.ts.map +1 -0
  21. package/dist/index.cjs +2562 -675
  22. package/dist/index.mjs +2563 -676
  23. package/dist/plugins/modalTypes.d.ts +1 -3
  24. package/dist/plugins/modalTypes.d.ts.map +1 -1
  25. package/dist/style.css +222 -186
  26. package/dist/types/BagelForm.d.ts +5 -4
  27. package/dist/types/BagelForm.d.ts.map +1 -1
  28. package/dist/types/timeago.d.ts +23 -0
  29. package/dist/types/timeago.d.ts.map +1 -0
  30. package/dist/utils/BagelFormUtils.d.ts +9 -4
  31. package/dist/utils/BagelFormUtils.d.ts.map +1 -1
  32. package/dist/utils/index.d.ts +1 -0
  33. package/dist/utils/index.d.ts.map +1 -1
  34. package/package.json +2 -2
  35. package/src/components/DragOver.vue +112 -0
  36. package/src/components/ImportData.vue +1964 -0
  37. package/src/components/form/FieldArray.vue +4 -2
  38. package/src/components/form/inputs/RichText/utils/media.ts +2 -2
  39. package/src/components/form/inputs/Upload/useFileUpload.ts +1 -1
  40. package/src/components/index.ts +2 -0
  41. package/src/components/lightbox/Lightbox.vue +2 -14
  42. package/src/composables/index.ts +2 -1
  43. package/src/composables/useExcel.ts +220 -0
  44. package/src/plugins/modalTypes.ts +1 -1
  45. package/src/styles/buttons.css +79 -75
  46. package/src/types/BagelForm.ts +10 -10
  47. package/src/utils/BagelFormUtils.ts +18 -6
  48. package/src/utils/index.ts +21 -0
  49. package/dist/components/Carousel2.vue.d.ts +0 -89
  50. package/dist/components/Carousel2.vue.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- <script lang="ts" setup generic="T, P extends Path<T>">
1
+ <script lang="ts" setup generic="T extends {[key: string]: any}, P extends Path<T>">
2
2
  import type {
3
3
  AttributeFn,
4
4
  AttributeValue,
@@ -50,6 +50,8 @@ const props = withDefaults(
50
50
 
51
51
  const emit = defineEmits(['update:modelValue'])
52
52
 
53
+ const BagelFormFA = BagelForm<T, P>
54
+
53
55
  // State
54
56
  const minimizedItems = ref<boolean[]>([])
55
57
  const internalData = ref<any[]>(props.modelValue || [])
@@ -201,7 +203,7 @@ const showMinimizeButton = computed(() => {
201
203
  </p>
202
204
 
203
205
  <!-- Form View -->
204
- <BagelForm
206
+ <BagelFormFA
205
207
  v-else
206
208
  :model-value="isPrimitiveType ? { value: item } : item"
207
209
  :schema="resolvedSchemaData"
@@ -1,4 +1,4 @@
1
- import type { Field } from '@bagelink/vue'
1
+ import type { BaseBagelField } from '@bagelink/vue'
2
2
  import type { ModalApi } from '../../../../../plugins/modal'
3
3
 
4
4
  import type { EditorState } from '../richTextTypes'
@@ -90,7 +90,7 @@ export function insertEmbed(modal: ModalApi, state: EditorState) {
90
90
  bglFrmUtil.numField('width', 'Width', { min: 200, placeholder: '560' }),
91
91
  bglFrmUtil.numField('height', 'Height', { min: 200, placeholder: '315' })
92
92
  ),
93
- { id: 'allowFullscreen', $el: 'check', label: 'Allow Fullscreen', value: true } as Field<InsertImbedModalData>,
93
+ { id: 'allowFullscreen', $el: 'check', label: 'Allow Fullscreen', value: true } as BaseBagelField<InsertImbedModalData, 'allowFullscreen'>,
94
94
  ],
95
95
  onSubmit: (data: { url: string, width?: number, height?: number, allowFullscreen?: boolean }) => {
96
96
  if (!data.url) return
@@ -48,7 +48,7 @@ export function useFileUpload(props: UseFileUploadProps = {}) {
48
48
  fileQueue.value.push(...newQueueFiles)
49
49
  }
50
50
 
51
- const removeFile = async (pathKeyOrFile: string | File) => {
51
+ const removeFile = async (pathKeyOrFile: string | File | QueueFile) => {
52
52
  if (typeof pathKeyOrFile === 'string') {
53
53
  // Remove from both lists
54
54
  storageFiles.value = storageFiles.value.filter(file => file.path_key !== pathKeyOrFile)
@@ -15,6 +15,7 @@ export { default as DataTable } from './dataTable/DataTable.vue'
15
15
  /** @deprecated Use DataTable instead. TableSchema is an alias that will be removed in a future version. */
16
16
  export { default as TableSchema } from './dataTable/DataTable.vue'
17
17
  export { Draggable, useDraggable, vDraggable } from './draggable'
18
+ export { default as DragOver } from './DragOver.vue'
18
19
  export { default as Dropdown } from './Dropdown.vue'
19
20
  export { default as FieldSetVue } from './FieldSetVue.vue'
20
21
  export { default as Flag } from './Flag.vue'
@@ -22,6 +23,7 @@ export * from './form'
22
23
  export { default as Icon } from './Icon/Icon.vue'
23
24
  export { default as IframeVue } from './IframeVue.vue'
24
25
  export { default as Image } from './Image.vue'
26
+ export { default as ImportData } from './ImportData.vue'
25
27
  export * from './layout'
26
28
  export { default as ListItem } from './ListItem.vue'
27
29
  export { default as ListView } from './ListView.vue'
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { LightboxItem } from './lightbox.types'
3
3
 
4
- import { BglVideo, Btn, Icon, Zoomer, Image, normalizeURL, Carousel } from '@bagelink/vue'
4
+ import { BglVideo, Btn, Icon, Zoomer, Image, normalizeURL, Carousel, downloadFile } from '@bagelink/vue'
5
5
  import { watch } from 'vue'
6
6
 
7
7
  let isOpen = $ref(false)
@@ -61,18 +61,6 @@ function clickOutside() {
61
61
  if (zoom === 1) close()
62
62
  }
63
63
 
64
- const upgradeHeaders = (url: string) => url.replace(/http:\/\//, '//')
65
-
66
- function downloadFile() {
67
- const link = document.createElement('a')
68
- const src = currentItem.src || ''
69
- link.target = '_blank'
70
- link.href = upgradeHeaders(src)
71
- link.download = src ? src.split('/').pop() || 'download' : 'download'
72
- document.body.appendChild(link)
73
- link.click()
74
- document.body.removeChild(link)
75
- }
76
64
  defineExpose({ open, close })
77
65
  </script>
78
66
 
@@ -118,7 +106,7 @@ defineExpose({ open, close })
118
106
  <Btn
119
107
  v-if="currentItem?.download && currentItem?.src" class="color-white" round thin flat icon="download"
120
108
  value="Download File"
121
- @click="downloadFile"
109
+ @click="downloadFile(currentItem?.src)"
122
110
  />
123
111
  <div v-if="!currentItem?.openFile && !currentItem?.download" />
124
112
  </div>
@@ -5,8 +5,8 @@ import { getFallbackSchema } from '@bagelink/vue'
5
5
  import { ref, toValue, watch } from 'vue'
6
6
 
7
7
  export { useDevice } from './useDevice'
8
+ export { useExcel } from './useExcel'
8
9
  export { usePolling } from './usePolling'
9
- export { useValidateFieldValue } from './useValidateFieldValue'
10
10
  interface UseBglSchemaParamsT<T> {
11
11
  schema?: MaybeRefOrGetter<BglFormSchemaT<T>>
12
12
  columns?: MaybeRefOrGetter<string[]>
@@ -47,3 +47,4 @@ export function localRef<T>(
47
47
  }
48
48
 
49
49
  export const useLocalStorage = localRef
50
+ export { useValidateFieldValue } from './useValidateFieldValue'
@@ -0,0 +1,220 @@
1
+ import type { MaybeRefOrGetter } from 'vue'
2
+ import type { BglFormSchemaT } from '../types'
3
+ import { ref, toValue } from 'vue'
4
+ import { appendScript, downloadFile, getNestedValue } from '../utils'
5
+
6
+ declare global {
7
+ interface Window {
8
+ XLSX: any
9
+ }
10
+ }
11
+
12
+ interface SheetData {
13
+ headers: string[]
14
+ data: Record<string, any>[]
15
+ }
16
+
17
+ const objKeys = (object: object) => Object.keys(object)
18
+ const strToTitle = (str?: string) => str?.split(' ').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
19
+
20
+ function formatData<T>(data: T[], schema?: BglFormSchemaT<T>) {
21
+ return schema
22
+ ? data.map((row) => {
23
+ const newRow: any = {}
24
+ schema.forEach(({ id, transform, label }) => {
25
+ if (id) {
26
+ // Support for dot notation to access nested properties
27
+ const value = getNestedValue(row, id)
28
+ const key = label || id
29
+ newRow[key] = transform?.(value, row) || value || ''
30
+ }
31
+ })
32
+ return newRow
33
+ })
34
+ : data
35
+ }
36
+
37
+ export function useExcel() {
38
+ const xlsxLoaded = ref(false)
39
+
40
+ async function ensureXLSXLoaded() {
41
+ if (!xlsxLoaded.value) {
42
+ await appendScript('https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js')
43
+ xlsxLoaded.value = true
44
+ }
45
+ return window.XLSX
46
+ }
47
+
48
+ async function downloadExcel<T>(data: T[], fileName = 'data', schemaFn?: MaybeRefOrGetter<BglFormSchemaT<T>>) {
49
+ const schema = toValue(schemaFn) || []
50
+ const XLSX = await ensureXLSXLoaded()
51
+
52
+ const formattedData = formatData(data, schema)
53
+ const headers = schema.length > 0 ? schema.map(({ id, label }) => ({ v: label || id, t: 's' })) : []
54
+ const ws = XLSX.utils.json_to_sheet(formattedData)
55
+
56
+ if (headers.length > 0) {
57
+ XLSX.utils.sheet_add_aoa(ws, [headers.map(h => h.v)], { origin: 'A1' })
58
+ }
59
+
60
+ const wb = XLSX.utils.book_new()
61
+ XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
62
+ fileName = fileName.endsWith('.xlsx') ? fileName : `${fileName}.xlsx`
63
+ XLSX.writeFile(wb, fileName)
64
+ }
65
+
66
+ async function downloadCSV(data: Record<string, any>[], fileName = 'data', schema?: BglFormSchemaT<any>) {
67
+ const keys = schema ? schema.map(({ id }) => id).filter(Boolean) : [...new Set(data.flatMap(objKeys))]
68
+ const columns = schema ? schema.map(({ label, id }) => label || strToTitle(id)) : keys.map(strToTitle)
69
+
70
+ const formattedData = formatData(data, schema)
71
+ const csv = [
72
+ columns.join(','),
73
+ ...formattedData.map(row => keys.map((key) => {
74
+ const value = row[key as string] !== undefined ? row[key as string] : ''
75
+ return `"${String(value).replace(/"/g, '""')}"`
76
+ }).join(','))
77
+ ].join('\n')
78
+ fileName = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`
79
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
80
+ downloadFile(blob, fileName)
81
+ }
82
+
83
+ async function getWorkbook(file: File) {
84
+ const XLSX = await ensureXLSXLoaded()
85
+ const data = await file.arrayBuffer()
86
+ return XLSX.read(data)
87
+ }
88
+
89
+ async function getSheetNames(file: File): Promise<string[]> {
90
+ const workbook = await getWorkbook(file)
91
+ return workbook.SheetNames
92
+ }
93
+
94
+ async function readSheetData(file: File, sheetName: string, hasHeaders = true): Promise<SheetData> {
95
+ const XLSX = await ensureXLSXLoaded()
96
+ const workbook = await getWorkbook(file)
97
+ const worksheet = workbook.Sheets[sheetName]
98
+
99
+ // Read the raw sheet data as arrays
100
+ const rawSheetData = XLSX.utils.sheet_to_json(worksheet, { header: 1 }) as any[][]
101
+
102
+ if (rawSheetData.length === 0) {
103
+ return { headers: [], data: [] }
104
+ }
105
+
106
+ // Extract headers based on hasHeaders setting
107
+ let headers: string[] = []
108
+ let startRow = 0
109
+
110
+ if (hasHeaders) {
111
+ // Use first row as headers
112
+ headers = rawSheetData[0].map(h => String(h || '').trim())
113
+ startRow = 1
114
+ } else {
115
+ // Generate column letter headers (A, B, C...)
116
+ const firstRow = rawSheetData[0]
117
+ headers = firstRow.map((_, i) => String.fromCharCode(65 + i))
118
+ }
119
+
120
+ // Convert the raw data into objects
121
+ const parsedData = []
122
+ for (let i = startRow; i < rawSheetData.length; i++) {
123
+ const row = rawSheetData[i]
124
+ const rowData: Record<string, any> = {}
125
+ for (let j = 0; j < headers.length; j++) {
126
+ if (j < row.length) {
127
+ rowData[headers[j]] = row[j]
128
+ } else {
129
+ rowData[headers[j]] = ''
130
+ }
131
+ }
132
+
133
+ parsedData.push(rowData)
134
+ }
135
+
136
+ return {
137
+ headers,
138
+ data: parsedData
139
+ }
140
+ }
141
+
142
+ async function readExcelFile(file: File): Promise<any[]> {
143
+ const XLSX = await ensureXLSXLoaded()
144
+
145
+ return new Promise((resolve, reject) => {
146
+ const reader = new FileReader()
147
+
148
+ reader.onload = (e) => {
149
+ try {
150
+ const data = new Uint8Array(e.target?.result as ArrayBuffer)
151
+ const workbook = XLSX.read(data, { type: 'array' })
152
+
153
+ const firstSheetName = workbook.SheetNames[0]
154
+ const worksheet = workbook.Sheets[firstSheetName]
155
+ const jsonData = XLSX.utils.sheet_to_json(worksheet)
156
+
157
+ resolve(jsonData)
158
+ } catch (error) {
159
+ reject(error)
160
+ }
161
+ }
162
+
163
+ reader.onerror = () => {
164
+ reject(new Error('Failed to read file'))
165
+ }
166
+
167
+ reader.readAsArrayBuffer(file)
168
+ })
169
+ }
170
+
171
+ // Helper function for date detection and conversion
172
+ function isExcelSerialDate(value: any): boolean {
173
+ return (
174
+ typeof value === 'number'
175
+ && Number.isInteger(value)
176
+ && value >= 20000
177
+ && value <= 50000
178
+ )
179
+ }
180
+
181
+ function excelSerialDateToJSDate(serial: number): Date {
182
+ const excelEpoch = new Date(Date.UTC(1899, 11, 30)) // Excel counts from Dec 30, 1899
183
+ const msPerDay = 24 * 60 * 60 * 1000
184
+ return new Date(excelEpoch.getTime() + serial * msPerDay)
185
+ }
186
+
187
+ function formatDate(date: Date, includeTime = false): string {
188
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
189
+ return ''
190
+ }
191
+
192
+ const year = date.getFullYear()
193
+ const month = String(date.getMonth() + 1).padStart(2, '0')
194
+ const day = String(date.getDate()).padStart(2, '0')
195
+
196
+ if (!includeTime) {
197
+ return `${year}-${month}-${day}`
198
+ }
199
+
200
+ const hours = String(date.getHours()).padStart(2, '0')
201
+ const minutes = String(date.getMinutes()).padStart(2, '0')
202
+ const seconds = String(date.getSeconds()).padStart(2, '0')
203
+
204
+ return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`
205
+ }
206
+
207
+ return {
208
+ downloadExcel,
209
+ downloadCSV,
210
+ readExcelFile,
211
+ ensureXLSXLoaded,
212
+ getSheetNames,
213
+ readSheetData,
214
+ getWorkbook,
215
+ // Date helpers
216
+ isExcelSerialDate,
217
+ excelSerialDateToJSDate,
218
+ formatDate
219
+ }
220
+ }
@@ -37,7 +37,7 @@ export interface ModalFormComponentProps<T extends { [key: string]: any }> exten
37
37
  modalOptions: ModalFormOptions<T>
38
38
  }
39
39
 
40
- export interface ModalFormOptions<T extends { [key: string]: any }> {
40
+ export interface ModalFormOptions<T> {
41
41
  'schema': MaybeRefOrGetter<BglFormSchemaT<T>>
42
42
  'modelValue'?: T
43
43
  'onUpdate:modelValue'?: (val: T) => void
@@ -1,140 +1,144 @@
1
1
  .bgl_btn,
2
2
  .bgl_flatBtn,
3
3
  .bgl_btn-icon {
4
- font-family: inherit;
5
- white-space: nowrap;
6
- cursor: pointer;
7
- box-sizing: border-box;
8
- user-select: none;
9
- border: none;
10
- transition: var(--bgl-transition);
11
- border-radius: var(--btn-border-radius);
12
- line-height: var(--btn-height);
13
- font-size: var(--input-font-size);
14
- display: inline-block;
15
- height: var(--btn-height);
16
- padding: 0;
4
+ font-family: inherit;
5
+ white-space: nowrap;
6
+ cursor: pointer;
7
+ box-sizing: border-box;
8
+ user-select: none;
9
+ border: none;
10
+ transition: var(--bgl-transition);
11
+ border-radius: var(--btn-border-radius);
12
+ line-height: var(--btn-height);
13
+ font-size: var(--input-font-size);
14
+ display: inline-block;
15
+ height: var(--btn-height);
16
+ padding: 0;
17
17
  }
18
18
 
19
19
  .btn-close {
20
- margin-top: -20px;
21
- margin-inline-end: -20px;
22
- margin-inline-start: auto;
23
- margin-bottom: 15px;
24
- transition: var(--bgl-transition);
25
- height: 30px;
26
- width: 30px;
27
- opacity: 0.6;
28
- cursor: pointer;
29
- border-radius: 100%;
30
- outline: 2px solid transparent;
31
- display: flex;
32
- align-items: center;
33
- justify-content: center;
20
+ margin-top: -20px;
21
+ margin-inline-end: -20px;
22
+ margin-inline-start: auto;
23
+ margin-bottom: 15px;
24
+ transition: var(--bgl-transition);
25
+ height: 30px;
26
+ width: 30px;
27
+ opacity: 0.6;
28
+ cursor: pointer;
29
+ border-radius: 100%;
30
+ outline: 2px solid transparent;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
34
  }
35
35
 
36
36
  .btn-close:hover {
37
- background: var(--bgl-gray-light);
38
- opacity: 1;
37
+ background: var(--bgl-gray-light);
38
+ opacity: 1;
39
39
  }
40
40
 
41
41
  .btn-close:active {
42
- background: var(--bgl-gray);
42
+ background: var(--bgl-gray);
43
43
  }
44
44
 
45
45
  .btn-close::before {
46
- content: 'close';
47
- font-family: 'Material Symbols Outlined', serif;
46
+ content: 'close';
47
+ font-family: 'Material Symbols Outlined', serif;
48
48
  }
49
49
 
50
50
  .bgl_btn.thin {
51
- height: calc(var(--btn-height) * 0.7);
52
- line-height: calc(var(--btn-height) * 0.7);
51
+ height: calc(var(--btn-height) * 0.7);
52
+ line-height: calc(var(--btn-height) * 0.7);
53
53
  }
54
54
 
55
55
  .hover {
56
- cursor: pointer;
57
- transition: all 400ms ease;
56
+ cursor: pointer;
57
+ transition: all 400ms ease;
58
58
  }
59
59
 
60
60
  .hover:hover {
61
- filter: brightness(90%);
61
+ filter: brightness(90%);
62
62
  }
63
63
 
64
64
  .hover:active {
65
- filter: brightness(80%);
65
+ filter: brightness(80%);
66
66
  }
67
67
 
68
68
  .border {
69
- border: 1px solid var(--border-color);
69
+ border: 1px solid var(--border-color);
70
70
  }
71
71
 
72
72
  .border-primary {
73
- border: 1px solid var(--bgl-primary);
73
+ border: 1px solid var(--bgl-primary);
74
74
  }
75
75
 
76
76
  .outline {
77
- outline: 1px solid var(--border-color);
77
+ outline: 1px solid var(--border-color);
78
+ }
79
+
80
+ .outline-dashed {
81
+ outline: 2px dashed var(--border-color);
78
82
  }
79
83
 
80
84
  .outline-primary {
81
- outline: 1px solid var(--bgl-primary);
85
+ outline: 1px solid var(--bgl-primary);
82
86
  }
83
87
 
84
88
  .rotate-270 {
85
- transform: rotate(270deg);
89
+ transform: rotate(270deg);
86
90
  }
87
91
 
88
92
  .rotate-180 {
89
- transform: rotate(180deg);
93
+ transform: rotate(180deg);
90
94
  }
91
95
 
92
96
  .rotate-90 {
93
- transform: rotate(90deg);
97
+ transform: rotate(90deg);
94
98
  }
95
99
 
96
100
  .rotate-0 {
97
- transform: rotate(0deg) !important;
101
+ transform: rotate(0deg) !important;
98
102
  }
99
103
 
100
104
  @media screen and (max-width: 910px) {
101
- .bgl_btn {
102
- padding: 0 20px;
103
- }
105
+ .bgl_btn {
106
+ padding: 0 20px;
107
+ }
104
108
 
105
- .m_border {
106
- border: 1px solid var(--border-color);
107
- }
109
+ .m_border {
110
+ border: 1px solid var(--border-color);
111
+ }
108
112
 
109
- .m_rotate-270 {
110
- transform: rotate(270deg);
111
- }
113
+ .m_rotate-270 {
114
+ transform: rotate(270deg);
115
+ }
112
116
 
113
- .m_rotate-180 {
114
- transform: rotate(180deg);
115
- }
117
+ .m_rotate-180 {
118
+ transform: rotate(180deg);
119
+ }
116
120
 
117
- .m_rotate-90 {
118
- transform: rotate(90deg);
119
- }
121
+ .m_rotate-90 {
122
+ transform: rotate(90deg);
123
+ }
120
124
 
121
- .m_rotate-0 {
122
- transform: rotate(0deg) !important;
123
- }
125
+ .m_rotate-0 {
126
+ transform: rotate(0deg) !important;
127
+ }
124
128
  }
125
129
 
126
130
  .ripple {
127
- position: absolute;
128
- border-radius: 50%;
129
- transform: scale(0);
130
- background: rgba(0, 0, 0, 0.3);
131
- pointer-events: none;
132
- animation: rippleEffect 0.6s ease-out;
131
+ position: absolute;
132
+ border-radius: 50%;
133
+ transform: scale(0);
134
+ background: rgba(0, 0, 0, 0.3);
135
+ pointer-events: none;
136
+ animation: rippleEffect 0.6s ease-out;
133
137
  }
134
138
 
135
139
  @keyframes rippleEffect {
136
- to {
137
- transform: scale(4);
138
- opacity: 0;
139
- }
140
- }
140
+ to {
141
+ transform: scale(4);
142
+ opacity: 0;
143
+ }
144
+ }
@@ -100,22 +100,22 @@ export type BglFieldT<T> = Field<T>
100
100
 
101
101
  export type BglFormSchemaT<T> = Field<T>[]
102
102
 
103
- export type InputBagelField<T> = Field<T> & {
104
- $el: 'text' | ComponentExposed<typeof TextInput>
105
- type?: string
106
- }
107
- // export interface InputBagelField<T> extends BaseBagelField<T, Path<T>> {
103
+ // export type InputBagelField<T> = Field<T> & {
108
104
  // $el: 'text' | ComponentExposed<typeof TextInput>
109
105
  // type?: string
110
106
  // }
111
-
112
- export type SelectBagelField<T> = Field<T> & {
113
- $el: 'select' | ComponentExposed<typeof SelectInput>
107
+ export interface InputBagelField<T, P extends Path<T>> extends BaseBagelField<T, P> {
108
+ $el: 'text' | ComponentExposed<typeof TextInput>
109
+ type?: string
114
110
  }
115
- // export interface SelectBagelField<T> extends BaseBagelField<T, Path<T>> {
111
+
112
+ // export type SelectBagelField<T> = Field<T> & {
116
113
  // $el: 'select' | ComponentExposed<typeof SelectInput>
117
- // type?: string
118
114
  // }
115
+ export interface SelectBagelField<T, P extends Path<T>> extends BaseBagelField<T, P> {
116
+ $el: 'select' | ComponentExposed<typeof SelectInput>
117
+ type?: string
118
+ }
119
119
 
120
120
  export interface ValidateInputBaseT {
121
121
  validate?: ValidationFn<{ [key: string]: unknown }, string>
@@ -75,7 +75,7 @@ export function txtField<T, P extends Path<T>>(
75
75
  id: P,
76
76
  label?: string,
77
77
  options?: TextInputOptions<T, P>,
78
- ): BaseBagelField<T, P> | InputBagelField<T> {
78
+ ): BaseBagelField<T, P> | InputBagelField<T, P> {
79
79
  return {
80
80
  $el: 'text',
81
81
  id,
@@ -107,7 +107,7 @@ export function selectField<T, P extends Path<T>>(
107
107
  label?: string,
108
108
  options?: Option[] | (() => Option[]),
109
109
  config?: SlctInputOptions<T, P>,
110
- ): BaseBagelField<T, P> | SelectBagelField<T> {
110
+ ): BaseBagelField<T, P> | SelectBagelField<T, P> {
111
111
  return {
112
112
  $el: 'select',
113
113
  id,
@@ -230,17 +230,29 @@ export function numField<T, P extends Path<T>>(
230
230
  }
231
231
  }
232
232
 
233
- export function frmRow<T>(...children: SchemaChild<T, Path<T>>[]): Field<T> {
233
+ export function frmRow<
234
+ T,
235
+ P extends Path<T>
236
+ >(
237
+ ...children: SchemaChild<T, P>[]
238
+ ) {
234
239
  return {
235
240
  $el: 'div',
236
241
  class: 'flex gap-1 m_block align-items-end',
237
242
  children,
238
- }
243
+ } satisfies BaseBagelField<T, P>
239
244
  }
240
245
 
241
- export type UploadOptions<T, K extends Path<T>> = InputOptions<T, K> & UploadInputProps
246
+ export interface UploadOptions<T, K extends Path<T>> extends InputOptions<T, K>, UploadInputProps {}
242
247
 
243
- export function uploadField<T, P extends Path<T>>(id: P, label?: string, options?: UploadOptions<T, P>): BaseBagelField<T, P> {
248
+ export function uploadField<
249
+ T,
250
+ P extends Path<T>
251
+ >(
252
+ id: P,
253
+ label?: string,
254
+ options?: UploadOptions<T, P>
255
+ ): BaseBagelField<T, P> {
244
256
  return {
245
257
  $el: 'upload',
246
258
  id,
@@ -232,3 +232,24 @@ export function getNestedValue(obj: any, path?: string, defaultValue: any = unde
232
232
 
233
233
  return current ?? defaultValue
234
234
  }
235
+
236
+ const upgradeHeaders = (url: string) => url.replace(/http:\/\//, '//')
237
+
238
+ export function downloadFile(source: string | Blob, fileName?: string) {
239
+ const link = document.createElement('a')
240
+ link.target = '_blank'
241
+
242
+ if (typeof source === 'string') {
243
+ link.href = upgradeHeaders(source)
244
+ link.download = fileName || source.split('/').pop() || 'download'
245
+ } else {
246
+ const url = URL.createObjectURL(source)
247
+ link.href = url
248
+ link.download = fileName || 'download'
249
+ URL.revokeObjectURL(url)
250
+ }
251
+
252
+ document.body.appendChild(link)
253
+ link.click()
254
+ document.body.removeChild(link)
255
+ }