simplesvelte 2.5.1 → 2.5.2
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/README.md +227 -214
- package/dist/AG_GRID_SERVER_API.md +373 -373
- package/dist/Grid.svelte +168 -168
- package/dist/Input.svelte +145 -145
- package/dist/Label.svelte +43 -43
- package/dist/Modal.svelte +39 -39
- package/dist/Select.svelte +697 -696
- package/dist/TextArea.svelte +45 -45
- package/dist/styles.css +15 -15
- package/package.json +73 -69
package/dist/Grid.svelte
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { onMount } from 'svelte'
|
|
3
|
-
import { env } from '$env/dynamic/public'
|
|
4
|
-
import {
|
|
5
|
-
AllEnterpriseModule,
|
|
6
|
-
ClientSideRowModelModule,
|
|
7
|
-
createGrid,
|
|
8
|
-
LicenseManager,
|
|
9
|
-
ModuleRegistry,
|
|
10
|
-
themeQuartz,
|
|
11
|
-
type GridApi,
|
|
12
|
-
type GridOptions,
|
|
13
|
-
} from 'ag-grid-enterprise'
|
|
14
|
-
|
|
15
|
-
type Props = {
|
|
16
|
-
gridEl?: HTMLDivElement
|
|
17
|
-
/** Bindable reference to the AG Grid API. Use `bind:gridApi` on the parent. */
|
|
18
|
-
gridApi?: GridApi
|
|
19
|
-
gridData?: any[] // Replace with your actual data type
|
|
20
|
-
gridOptions: GridOptions
|
|
21
|
-
/**
|
|
22
|
-
* localStorage key for automatic grid state persistence (filters, sorts, column widths).
|
|
23
|
-
* When set, state is saved on every change and restored on mount.
|
|
24
|
-
*/
|
|
25
|
-
stateKey?: string
|
|
26
|
-
class?: string
|
|
27
|
-
}
|
|
28
|
-
let {
|
|
29
|
-
gridEl = $bindable(),
|
|
30
|
-
gridApi = $bindable(),
|
|
31
|
-
gridData,
|
|
32
|
-
gridOptions,
|
|
33
|
-
stateKey,
|
|
34
|
-
class: gridClass = 'grow',
|
|
35
|
-
}: Props = $props()
|
|
36
|
-
let initCheckInterval: ReturnType<typeof setInterval> | undefined
|
|
37
|
-
let attemptCount = 0
|
|
38
|
-
|
|
39
|
-
function initializeGrid() {
|
|
40
|
-
if (!gridEl) {
|
|
41
|
-
console.log('⏳ Grid: Element not available yet')
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (gridApi) {
|
|
46
|
-
console.log('ℹ️ Grid: Already initialized, skipping')
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
attemptCount++
|
|
51
|
-
console.log(`📊 Grid: Initializing AG Grid (attempt ${attemptCount})...`)
|
|
52
|
-
console.log('📋 Grid: Registering modules...')
|
|
53
|
-
ModuleRegistry.registerModules([AllEnterpriseModule, ClientSideRowModelModule])
|
|
54
|
-
|
|
55
|
-
if (env.PUBLIC_AGGRID_KEY) {
|
|
56
|
-
LicenseManager.setLicenseKey(env.PUBLIC_AGGRID_KEY)
|
|
57
|
-
console.log('✅ Grid: License key applied successfully')
|
|
58
|
-
} else {
|
|
59
|
-
console.warn('⚠️ Grid: No license key found. Running in trial mode.')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Wrap onGridReady to expose the gridApi bindable and handle stateKey persistence,
|
|
63
|
-
// while still calling the user's own onGridReady callback if provided.
|
|
64
|
-
const userOnGridReady = gridOptions.onGridReady
|
|
65
|
-
|
|
66
|
-
// Load saved state upfront — must be in gridConfig.initialState, not applied post-init.
|
|
67
|
-
let savedState: object | undefined
|
|
68
|
-
if (stateKey) {
|
|
69
|
-
try {
|
|
70
|
-
const raw = localStorage.getItem(stateKey)
|
|
71
|
-
if (raw) savedState = JSON.parse(raw)
|
|
72
|
-
} catch {
|
|
73
|
-
/* ignore parse errors */
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const gridConfig = {
|
|
78
|
-
...gridOptions,
|
|
79
|
-
theme: themeQuartz,
|
|
80
|
-
...(gridData !== undefined && { rowData: gridData }),
|
|
81
|
-
...(savedState !== undefined && { initialState: savedState }),
|
|
82
|
-
onGridReady: (params: Parameters<NonNullable<GridOptions['onGridReady']>>[0]) => {
|
|
83
|
-
userOnGridReady?.(params)
|
|
84
|
-
gridApi = params.api
|
|
85
|
-
if (stateKey) {
|
|
86
|
-
params.api.addEventListener('stateUpdated', (e: any) => {
|
|
87
|
-
try {
|
|
88
|
-
localStorage.setItem(stateKey, JSON.stringify(e.state))
|
|
89
|
-
} catch {
|
|
90
|
-
/* ignore storage errors */
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
console.log('🎨 Grid: Creating grid instance...')
|
|
98
|
-
gridApi = createGrid(gridEl, gridConfig)
|
|
99
|
-
|
|
100
|
-
if (gridData !== undefined) {
|
|
101
|
-
const rowCount = gridData.length
|
|
102
|
-
console.log(`✅ Grid: Initialized with ${rowCount} row(s) (client-side)`)
|
|
103
|
-
} else {
|
|
104
|
-
console.log('✅ Grid: Initialized with server-side data source')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Clear the interval once grid is created
|
|
108
|
-
if (initCheckInterval) {
|
|
109
|
-
console.log('⏹️ Grid: Stopping initialization checks')
|
|
110
|
-
clearInterval(initCheckInterval)
|
|
111
|
-
initCheckInterval = undefined
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
onMount(() => {
|
|
116
|
-
console.log('🚀 Grid: Component mounted')
|
|
117
|
-
|
|
118
|
-
// Try to initialize immediately
|
|
119
|
-
initializeGrid()
|
|
120
|
-
|
|
121
|
-
// If grid wasn't created, set up interval to keep checking
|
|
122
|
-
if (!gridApi) {
|
|
123
|
-
console.log('⏱️ Grid: Element not ready, checking every 100ms...')
|
|
124
|
-
initCheckInterval = setInterval(() => {
|
|
125
|
-
initializeGrid()
|
|
126
|
-
}, 100)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Cleanup function to destroy grid and clear interval when component unmounts
|
|
130
|
-
return () => {
|
|
131
|
-
console.log('💥 Grid: Component unmounting')
|
|
132
|
-
if (initCheckInterval) {
|
|
133
|
-
console.log('⏹️ Grid: Clearing initialization interval')
|
|
134
|
-
clearInterval(initCheckInterval)
|
|
135
|
-
initCheckInterval = undefined
|
|
136
|
-
}
|
|
137
|
-
if (gridApi) {
|
|
138
|
-
console.log('🧹 Grid: Destroying grid instance')
|
|
139
|
-
gridApi.destroy()
|
|
140
|
-
gridApi = undefined
|
|
141
|
-
console.log('✅ Grid: Cleanup complete')
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// Update grid when options or data change
|
|
147
|
-
$effect(() => {
|
|
148
|
-
if (gridApi && gridData !== undefined) {
|
|
149
|
-
const rowCount = gridData.length
|
|
150
|
-
console.log(`🔄 Grid: Data changed, updating grid with ${rowCount} row(s)`)
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
gridApi.updateGridOptions({
|
|
154
|
-
...gridOptions,
|
|
155
|
-
rowData: gridData,
|
|
156
|
-
})
|
|
157
|
-
gridApi.refreshCells()
|
|
158
|
-
console.log('✅ Grid: Data update complete')
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.error('❌ Grid: Error updating data:', error)
|
|
161
|
-
}
|
|
162
|
-
} else if (!gridApi && gridData !== undefined) {
|
|
163
|
-
console.log('⚠️ Grid: Data available but grid not initialized yet')
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
</script>
|
|
167
|
-
|
|
168
|
-
<div bind:this={gridEl} class={gridClass}></div>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte'
|
|
3
|
+
import { env } from '$env/dynamic/public'
|
|
4
|
+
import {
|
|
5
|
+
AllEnterpriseModule,
|
|
6
|
+
ClientSideRowModelModule,
|
|
7
|
+
createGrid,
|
|
8
|
+
LicenseManager,
|
|
9
|
+
ModuleRegistry,
|
|
10
|
+
themeQuartz,
|
|
11
|
+
type GridApi,
|
|
12
|
+
type GridOptions,
|
|
13
|
+
} from 'ag-grid-enterprise'
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
gridEl?: HTMLDivElement
|
|
17
|
+
/** Bindable reference to the AG Grid API. Use `bind:gridApi` on the parent. */
|
|
18
|
+
gridApi?: GridApi
|
|
19
|
+
gridData?: any[] // Replace with your actual data type
|
|
20
|
+
gridOptions: GridOptions
|
|
21
|
+
/**
|
|
22
|
+
* localStorage key for automatic grid state persistence (filters, sorts, column widths).
|
|
23
|
+
* When set, state is saved on every change and restored on mount.
|
|
24
|
+
*/
|
|
25
|
+
stateKey?: string
|
|
26
|
+
class?: string
|
|
27
|
+
}
|
|
28
|
+
let {
|
|
29
|
+
gridEl = $bindable(),
|
|
30
|
+
gridApi = $bindable(),
|
|
31
|
+
gridData,
|
|
32
|
+
gridOptions,
|
|
33
|
+
stateKey,
|
|
34
|
+
class: gridClass = 'grow',
|
|
35
|
+
}: Props = $props()
|
|
36
|
+
let initCheckInterval: ReturnType<typeof setInterval> | undefined
|
|
37
|
+
let attemptCount = 0
|
|
38
|
+
|
|
39
|
+
function initializeGrid() {
|
|
40
|
+
if (!gridEl) {
|
|
41
|
+
console.log('⏳ Grid: Element not available yet')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (gridApi) {
|
|
46
|
+
console.log('ℹ️ Grid: Already initialized, skipping')
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
attemptCount++
|
|
51
|
+
console.log(`📊 Grid: Initializing AG Grid (attempt ${attemptCount})...`)
|
|
52
|
+
console.log('📋 Grid: Registering modules...')
|
|
53
|
+
ModuleRegistry.registerModules([AllEnterpriseModule, ClientSideRowModelModule])
|
|
54
|
+
|
|
55
|
+
if (env.PUBLIC_AGGRID_KEY) {
|
|
56
|
+
LicenseManager.setLicenseKey(env.PUBLIC_AGGRID_KEY)
|
|
57
|
+
console.log('✅ Grid: License key applied successfully')
|
|
58
|
+
} else {
|
|
59
|
+
console.warn('⚠️ Grid: No license key found. Running in trial mode.')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Wrap onGridReady to expose the gridApi bindable and handle stateKey persistence,
|
|
63
|
+
// while still calling the user's own onGridReady callback if provided.
|
|
64
|
+
const userOnGridReady = gridOptions.onGridReady
|
|
65
|
+
|
|
66
|
+
// Load saved state upfront — must be in gridConfig.initialState, not applied post-init.
|
|
67
|
+
let savedState: object | undefined
|
|
68
|
+
if (stateKey) {
|
|
69
|
+
try {
|
|
70
|
+
const raw = localStorage.getItem(stateKey)
|
|
71
|
+
if (raw) savedState = JSON.parse(raw)
|
|
72
|
+
} catch {
|
|
73
|
+
/* ignore parse errors */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const gridConfig = {
|
|
78
|
+
...gridOptions,
|
|
79
|
+
theme: themeQuartz,
|
|
80
|
+
...(gridData !== undefined && { rowData: gridData }),
|
|
81
|
+
...(savedState !== undefined && { initialState: savedState }),
|
|
82
|
+
onGridReady: (params: Parameters<NonNullable<GridOptions['onGridReady']>>[0]) => {
|
|
83
|
+
userOnGridReady?.(params)
|
|
84
|
+
gridApi = params.api
|
|
85
|
+
if (stateKey) {
|
|
86
|
+
params.api.addEventListener('stateUpdated', (e: any) => {
|
|
87
|
+
try {
|
|
88
|
+
localStorage.setItem(stateKey, JSON.stringify(e.state))
|
|
89
|
+
} catch {
|
|
90
|
+
/* ignore storage errors */
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('🎨 Grid: Creating grid instance...')
|
|
98
|
+
gridApi = createGrid(gridEl, gridConfig)
|
|
99
|
+
|
|
100
|
+
if (gridData !== undefined) {
|
|
101
|
+
const rowCount = gridData.length
|
|
102
|
+
console.log(`✅ Grid: Initialized with ${rowCount} row(s) (client-side)`)
|
|
103
|
+
} else {
|
|
104
|
+
console.log('✅ Grid: Initialized with server-side data source')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Clear the interval once grid is created
|
|
108
|
+
if (initCheckInterval) {
|
|
109
|
+
console.log('⏹️ Grid: Stopping initialization checks')
|
|
110
|
+
clearInterval(initCheckInterval)
|
|
111
|
+
initCheckInterval = undefined
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
onMount(() => {
|
|
116
|
+
console.log('🚀 Grid: Component mounted')
|
|
117
|
+
|
|
118
|
+
// Try to initialize immediately
|
|
119
|
+
initializeGrid()
|
|
120
|
+
|
|
121
|
+
// If grid wasn't created, set up interval to keep checking
|
|
122
|
+
if (!gridApi) {
|
|
123
|
+
console.log('⏱️ Grid: Element not ready, checking every 100ms...')
|
|
124
|
+
initCheckInterval = setInterval(() => {
|
|
125
|
+
initializeGrid()
|
|
126
|
+
}, 100)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Cleanup function to destroy grid and clear interval when component unmounts
|
|
130
|
+
return () => {
|
|
131
|
+
console.log('💥 Grid: Component unmounting')
|
|
132
|
+
if (initCheckInterval) {
|
|
133
|
+
console.log('⏹️ Grid: Clearing initialization interval')
|
|
134
|
+
clearInterval(initCheckInterval)
|
|
135
|
+
initCheckInterval = undefined
|
|
136
|
+
}
|
|
137
|
+
if (gridApi) {
|
|
138
|
+
console.log('🧹 Grid: Destroying grid instance')
|
|
139
|
+
gridApi.destroy()
|
|
140
|
+
gridApi = undefined
|
|
141
|
+
console.log('✅ Grid: Cleanup complete')
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Update grid when options or data change
|
|
147
|
+
$effect(() => {
|
|
148
|
+
if (gridApi && gridData !== undefined) {
|
|
149
|
+
const rowCount = gridData.length
|
|
150
|
+
console.log(`🔄 Grid: Data changed, updating grid with ${rowCount} row(s)`)
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
gridApi.updateGridOptions({
|
|
154
|
+
...gridOptions,
|
|
155
|
+
rowData: gridData,
|
|
156
|
+
})
|
|
157
|
+
gridApi.refreshCells()
|
|
158
|
+
console.log('✅ Grid: Data update complete')
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('❌ Grid: Error updating data:', error)
|
|
161
|
+
}
|
|
162
|
+
} else if (!gridApi && gridData !== undefined) {
|
|
163
|
+
console.log('⚠️ Grid: Data available but grid not initialized yet')
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<div bind:this={gridEl} class={gridClass}></div>
|
package/dist/Input.svelte
CHANGED
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { SvelteDate } from 'svelte/reactivity'
|
|
3
|
-
import Label from './Label.svelte'
|
|
4
|
-
type Props = {
|
|
5
|
-
value?: any
|
|
6
|
-
name?: string
|
|
7
|
-
label?: string
|
|
8
|
-
class?: string
|
|
9
|
-
required?: boolean
|
|
10
|
-
disabled?: boolean
|
|
11
|
-
element?: HTMLElement
|
|
12
|
-
type?:
|
|
13
|
-
| 'text'
|
|
14
|
-
| 'number'
|
|
15
|
-
| 'password'
|
|
16
|
-
| 'email'
|
|
17
|
-
| 'number'
|
|
18
|
-
| 'tel'
|
|
19
|
-
| 'url'
|
|
20
|
-
| 'date'
|
|
21
|
-
| 'datetime-local'
|
|
22
|
-
| 'color'
|
|
23
|
-
| 'file'
|
|
24
|
-
| 'checkbox'
|
|
25
|
-
error?: string
|
|
26
|
-
hideOptional?: boolean
|
|
27
|
-
zodErrors?: {
|
|
28
|
-
expected: string
|
|
29
|
-
code: string
|
|
30
|
-
path: string[]
|
|
31
|
-
message: string
|
|
32
|
-
}[]
|
|
33
|
-
[x: string]: any
|
|
34
|
-
}
|
|
35
|
-
let {
|
|
36
|
-
value = $bindable(),
|
|
37
|
-
element = $bindable(),
|
|
38
|
-
label,
|
|
39
|
-
type = 'text',
|
|
40
|
-
name,
|
|
41
|
-
required,
|
|
42
|
-
disabled,
|
|
43
|
-
class: myClass,
|
|
44
|
-
error,
|
|
45
|
-
hideOptional,
|
|
46
|
-
zodErrors,
|
|
47
|
-
...rest
|
|
48
|
-
}: Props = $props()
|
|
49
|
-
function getValue() {
|
|
50
|
-
if (type == 'date') {
|
|
51
|
-
if (!value) return ''
|
|
52
|
-
const dateString = new Date(value).toISOString().split('T')[0]
|
|
53
|
-
return dateString
|
|
54
|
-
} else if (type == 'datetime-local') {
|
|
55
|
-
if (!value) return ''
|
|
56
|
-
const date = new Date(value)
|
|
57
|
-
const dateString = new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1)
|
|
58
|
-
return dateString
|
|
59
|
-
}
|
|
60
|
-
return value ?? ''
|
|
61
|
-
}
|
|
62
|
-
function setValue(newValue: any) {
|
|
63
|
-
if (type == 'number') {
|
|
64
|
-
if (isNaN(Number(newValue))) {
|
|
65
|
-
value = null
|
|
66
|
-
} else {
|
|
67
|
-
value = Number(newValue)
|
|
68
|
-
}
|
|
69
|
-
} else if (type == 'date') {
|
|
70
|
-
const date = new SvelteDate(newValue)
|
|
71
|
-
if (isNaN(date.getTime())) {
|
|
72
|
-
value = null
|
|
73
|
-
} else {
|
|
74
|
-
date.setUTCHours(0, 0, 0, 0)
|
|
75
|
-
value = date
|
|
76
|
-
}
|
|
77
|
-
} else if (type == 'datetime-local') {
|
|
78
|
-
const date = new SvelteDate(newValue)
|
|
79
|
-
if (isNaN(date.getTime())) {
|
|
80
|
-
value = null
|
|
81
|
-
} else {
|
|
82
|
-
date.setSeconds(0, 0)
|
|
83
|
-
value = date
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
value = newValue
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// File input handlers - FileList is readonly
|
|
91
|
-
function getFiles() {
|
|
92
|
-
return value
|
|
93
|
-
}
|
|
94
|
-
function setFiles(fileList: FileList | null) {
|
|
95
|
-
// FileList is readonly, so we just set it directly
|
|
96
|
-
value = fileList
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let inputClass = $derived.by(() => {
|
|
100
|
-
if (type == 'file') return 'file-input w-full'
|
|
101
|
-
if (type == 'checkbox') return 'checkbox checkbox-lg'
|
|
102
|
-
return 'input w-full'
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
let showOptional = $derived.by(() => {
|
|
106
|
-
if (hideOptional) return false
|
|
107
|
-
return !required && !disabled && type != 'checkbox'
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
const errorText = $derived.by(() => {
|
|
111
|
-
if (error) return error
|
|
112
|
-
if (!name) return undefined
|
|
113
|
-
if (zodErrors) return zodErrors.find((e) => e.path.includes(name))?.message
|
|
114
|
-
return undefined
|
|
115
|
-
})
|
|
116
|
-
</script>
|
|
117
|
-
|
|
118
|
-
{#if type != 'file'}
|
|
119
|
-
<input type="hidden" {name} value={value ?? ''} />
|
|
120
|
-
{/if}
|
|
121
|
-
|
|
122
|
-
<Label class={myClass} {label} {name} optional={showOptional} {disabled} error={errorText}>
|
|
123
|
-
{#if type == 'checkbox'}
|
|
124
|
-
<input bind:this={element} type="checkbox" {disabled} class={inputClass} {...rest} bind:checked={value} />
|
|
125
|
-
{:else if type == 'file'}
|
|
126
|
-
<input
|
|
127
|
-
bind:this={element}
|
|
128
|
-
{name}
|
|
129
|
-
type="file"
|
|
130
|
-
{disabled}
|
|
131
|
-
{required}
|
|
132
|
-
class={inputClass}
|
|
133
|
-
{...rest}
|
|
134
|
-
bind:files={getFiles, setFiles} />
|
|
135
|
-
{:else}
|
|
136
|
-
<input
|
|
137
|
-
bind:this={element}
|
|
138
|
-
{type}
|
|
139
|
-
{disabled}
|
|
140
|
-
{required}
|
|
141
|
-
class="disabled:text-base-content disabled:!border-base-content/20 {inputClass} "
|
|
142
|
-
{...rest}
|
|
143
|
-
bind:value={getValue, setValue} />
|
|
144
|
-
{/if}
|
|
145
|
-
</Label>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { SvelteDate } from 'svelte/reactivity'
|
|
3
|
+
import Label from './Label.svelte'
|
|
4
|
+
type Props = {
|
|
5
|
+
value?: any
|
|
6
|
+
name?: string
|
|
7
|
+
label?: string
|
|
8
|
+
class?: string
|
|
9
|
+
required?: boolean
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
element?: HTMLElement
|
|
12
|
+
type?:
|
|
13
|
+
| 'text'
|
|
14
|
+
| 'number'
|
|
15
|
+
| 'password'
|
|
16
|
+
| 'email'
|
|
17
|
+
| 'number'
|
|
18
|
+
| 'tel'
|
|
19
|
+
| 'url'
|
|
20
|
+
| 'date'
|
|
21
|
+
| 'datetime-local'
|
|
22
|
+
| 'color'
|
|
23
|
+
| 'file'
|
|
24
|
+
| 'checkbox'
|
|
25
|
+
error?: string
|
|
26
|
+
hideOptional?: boolean
|
|
27
|
+
zodErrors?: {
|
|
28
|
+
expected: string
|
|
29
|
+
code: string
|
|
30
|
+
path: string[]
|
|
31
|
+
message: string
|
|
32
|
+
}[]
|
|
33
|
+
[x: string]: any
|
|
34
|
+
}
|
|
35
|
+
let {
|
|
36
|
+
value = $bindable(),
|
|
37
|
+
element = $bindable(),
|
|
38
|
+
label,
|
|
39
|
+
type = 'text',
|
|
40
|
+
name,
|
|
41
|
+
required,
|
|
42
|
+
disabled,
|
|
43
|
+
class: myClass,
|
|
44
|
+
error,
|
|
45
|
+
hideOptional,
|
|
46
|
+
zodErrors,
|
|
47
|
+
...rest
|
|
48
|
+
}: Props = $props()
|
|
49
|
+
function getValue() {
|
|
50
|
+
if (type == 'date') {
|
|
51
|
+
if (!value) return ''
|
|
52
|
+
const dateString = new Date(value).toISOString().split('T')[0]
|
|
53
|
+
return dateString
|
|
54
|
+
} else if (type == 'datetime-local') {
|
|
55
|
+
if (!value) return ''
|
|
56
|
+
const date = new Date(value)
|
|
57
|
+
const dateString = new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1)
|
|
58
|
+
return dateString
|
|
59
|
+
}
|
|
60
|
+
return value ?? ''
|
|
61
|
+
}
|
|
62
|
+
function setValue(newValue: any) {
|
|
63
|
+
if (type == 'number') {
|
|
64
|
+
if (isNaN(Number(newValue))) {
|
|
65
|
+
value = null
|
|
66
|
+
} else {
|
|
67
|
+
value = Number(newValue)
|
|
68
|
+
}
|
|
69
|
+
} else if (type == 'date') {
|
|
70
|
+
const date = new SvelteDate(newValue)
|
|
71
|
+
if (isNaN(date.getTime())) {
|
|
72
|
+
value = null
|
|
73
|
+
} else {
|
|
74
|
+
date.setUTCHours(0, 0, 0, 0)
|
|
75
|
+
value = date
|
|
76
|
+
}
|
|
77
|
+
} else if (type == 'datetime-local') {
|
|
78
|
+
const date = new SvelteDate(newValue)
|
|
79
|
+
if (isNaN(date.getTime())) {
|
|
80
|
+
value = null
|
|
81
|
+
} else {
|
|
82
|
+
date.setSeconds(0, 0)
|
|
83
|
+
value = date
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
value = newValue
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// File input handlers - FileList is readonly
|
|
91
|
+
function getFiles() {
|
|
92
|
+
return value
|
|
93
|
+
}
|
|
94
|
+
function setFiles(fileList: FileList | null) {
|
|
95
|
+
// FileList is readonly, so we just set it directly
|
|
96
|
+
value = fileList
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let inputClass = $derived.by(() => {
|
|
100
|
+
if (type == 'file') return 'file-input w-full'
|
|
101
|
+
if (type == 'checkbox') return 'checkbox checkbox-lg'
|
|
102
|
+
return 'input w-full'
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
let showOptional = $derived.by(() => {
|
|
106
|
+
if (hideOptional) return false
|
|
107
|
+
return !required && !disabled && type != 'checkbox'
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const errorText = $derived.by(() => {
|
|
111
|
+
if (error) return error
|
|
112
|
+
if (!name) return undefined
|
|
113
|
+
if (zodErrors) return zodErrors.find((e) => e.path.includes(name))?.message
|
|
114
|
+
return undefined
|
|
115
|
+
})
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
{#if type != 'file'}
|
|
119
|
+
<input type="hidden" {name} value={value ?? ''} />
|
|
120
|
+
{/if}
|
|
121
|
+
|
|
122
|
+
<Label class={myClass} {label} {name} optional={showOptional} {disabled} error={errorText}>
|
|
123
|
+
{#if type == 'checkbox'}
|
|
124
|
+
<input bind:this={element} type="checkbox" {disabled} class={inputClass} {...rest} bind:checked={value} />
|
|
125
|
+
{:else if type == 'file'}
|
|
126
|
+
<input
|
|
127
|
+
bind:this={element}
|
|
128
|
+
{name}
|
|
129
|
+
type="file"
|
|
130
|
+
{disabled}
|
|
131
|
+
{required}
|
|
132
|
+
class={inputClass}
|
|
133
|
+
{...rest}
|
|
134
|
+
bind:files={getFiles, setFiles} />
|
|
135
|
+
{:else}
|
|
136
|
+
<input
|
|
137
|
+
bind:this={element}
|
|
138
|
+
{type}
|
|
139
|
+
{disabled}
|
|
140
|
+
{required}
|
|
141
|
+
class="disabled:text-base-content disabled:!border-base-content/20 {inputClass} "
|
|
142
|
+
{...rest}
|
|
143
|
+
bind:value={getValue, setValue} />
|
|
144
|
+
{/if}
|
|
145
|
+
</Label>
|