poe-svelte-ui-lib 1.0.1 → 1.0.4

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 (61) hide show
  1. package/LICENSE +3 -3
  2. package/README.md +1 -0
  3. package/dist/Accordion/Accordion.svelte +53 -53
  4. package/dist/Button/Button.svelte +111 -144
  5. package/dist/Button/Button.svelte.d.ts +1 -34
  6. package/dist/ColorPicker/ColorPicker.svelte +205 -207
  7. package/dist/FileAttach/FileAttach.svelte +103 -103
  8. package/dist/Graph/Graph.svelte +270 -270
  9. package/dist/Input/Input.svelte +240 -239
  10. package/dist/Loader.svelte +12 -12
  11. package/dist/MessageModal.svelte +54 -54
  12. package/dist/ProgressBar/ProgressBar.svelte +48 -48
  13. package/dist/Select/Select.svelte +189 -191
  14. package/dist/Slider/Slider.svelte +260 -260
  15. package/dist/Switch/Switch.svelte +84 -83
  16. package/dist/Table/Table.svelte +275 -276
  17. package/dist/TextField/TextField.svelte +22 -22
  18. package/dist/index.d.ts +0 -11
  19. package/dist/index.js +0 -11
  20. package/dist/{appIcons → libIcons}/ButtonAdd.svelte +10 -10
  21. package/dist/{appIcons → libIcons}/ButtonDelete.svelte +13 -13
  22. package/dist/{appIcons → libIcons}/LoaderRotate.svelte +9 -9
  23. package/dist/options.d.ts +11 -11
  24. package/dist/options.js +27 -27
  25. package/dist/types.d.ts +1 -1
  26. package/package.json +48 -47
  27. package/dist/Accordion/AccordionProps.svelte +0 -70
  28. package/dist/Accordion/AccordionProps.svelte.d.ts +0 -10
  29. package/dist/Button/ButtonProps.svelte +0 -200
  30. package/dist/Button/ButtonProps.svelte.d.ts +0 -10
  31. package/dist/ColorPicker/ColorPickerProps.svelte +0 -100
  32. package/dist/ColorPicker/ColorPickerProps.svelte.d.ts +0 -10
  33. package/dist/Graph/GraphProps.svelte +0 -56
  34. package/dist/Graph/GraphProps.svelte.d.ts +0 -10
  35. package/dist/Input/InputProps.svelte +0 -221
  36. package/dist/Input/InputProps.svelte.d.ts +0 -10
  37. package/dist/ProgressBar/ProgressBarProps.svelte +0 -145
  38. package/dist/ProgressBar/ProgressBarProps.svelte.d.ts +0 -10
  39. package/dist/Select/SelectProps.svelte +0 -260
  40. package/dist/Select/SelectProps.svelte.d.ts +0 -10
  41. package/dist/Slider/SliderProps.svelte +0 -161
  42. package/dist/Slider/SliderProps.svelte.d.ts +0 -10
  43. package/dist/Switch/SwitchProps.svelte +0 -144
  44. package/dist/Switch/SwitchProps.svelte.d.ts +0 -10
  45. package/dist/Table/TableProps.svelte +0 -286
  46. package/dist/Table/TableProps.svelte.d.ts +0 -10
  47. package/dist/TextField/TextFieldProps.svelte +0 -92
  48. package/dist/TextField/TextFieldProps.svelte.d.ts +0 -10
  49. package/dist/locales/CircleFlagsEn.svelte +0 -14
  50. package/dist/locales/CircleFlagsEn.svelte.d.ts +0 -26
  51. package/dist/locales/CircleFlagsRu.svelte +0 -8
  52. package/dist/locales/CircleFlagsRu.svelte.d.ts +0 -26
  53. package/dist/locales/CircleFlagsZh.svelte +0 -8
  54. package/dist/locales/CircleFlagsZh.svelte.d.ts +0 -26
  55. package/dist/locales/i18n.d.ts +0 -10
  56. package/dist/locales/i18n.js +0 -36
  57. package/dist/locales/translations.d.ts +0 -7
  58. package/dist/locales/translations.js +0 -450
  59. /package/dist/{appIcons → libIcons}/ButtonAdd.svelte.d.ts +0 -0
  60. /package/dist/{appIcons → libIcons}/ButtonDelete.svelte.d.ts +0 -0
  61. /package/dist/{appIcons → libIcons}/LoaderRotate.svelte.d.ts +0 -0
@@ -1,207 +1,205 @@
1
- <!-- $lib/ElementsUI/ColorPicker.svelte -->
2
- <script lang="ts">
3
- import { t } from '../locales/i18n'
4
- import type { IColorPickerProps } from '../types'
5
-
6
- let {
7
- id = { name: '', value: crypto.randomUUID() },
8
- wrapperClass = '',
9
- label = { name: '', class: '' },
10
- value = [0, 0, 0],
11
- onChange = () => {},
12
- }: IColorPickerProps = $props()
13
-
14
- const hsv = $state({ h: 0, s: 100, v: 100 })
15
- let huePointer = $state(0)
16
- let brightnessPointer = $state(100)
17
- let mode = $state<'hsv' | 'white'>('hsv')
18
- let whiteValue = $state(100)
19
-
20
- $effect(() => {
21
- if (value.length !== 3) return
22
-
23
- const [r, g, b] = value
24
- const isWhite = r === g && g === b
25
- mode = isWhite ? 'white' : 'hsv'
26
- if (isWhite) {
27
- whiteValue = Math.round((r / 255) * 100)
28
- return
29
- }
30
-
31
- const normalized = value.map((v) => v / 255)
32
- const max = Math.max(...normalized)
33
- const min = Math.min(...normalized)
34
- const delta = max - min
35
- let h = 0
36
- if (delta !== 0) {
37
- const [rN, gN, bN] = normalized
38
- if (max === rN) h = ((gN - bN) / delta) % 6
39
- else if (max === gN) h = (bN - rN) / delta + 2
40
- else h = (rN - gN) / delta + 4
41
- h *= 60
42
- if (h < 0) h += 360
43
- }
44
- const s = max ? (delta / max) * 100 : 0
45
- const v = max * 100
46
- hsv.h = Math.round(h)
47
- hsv.s = Math.round(s)
48
- hsv.v = Math.round(v)
49
- huePointer = (h / 360) * 100
50
- brightnessPointer = v
51
- })
52
-
53
- const hsvToRgb = (h: number, s: number, v: number): [number, number, number] => {
54
- s /= 100
55
- v /= 100
56
- const c = v * s
57
- const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
58
- const m = v - c
59
- let r = 0,
60
- g = 0,
61
- b = 0
62
- if (h < 60) [r, g, b] = [c, x, 0]
63
- else if (h < 120) [r, g, b] = [x, c, 0]
64
- else if (h < 180) [r, g, b] = [0, c, x]
65
- else if (h < 240) [r, g, b] = [0, x, c]
66
- else if (h < 300) [r, g, b] = [x, 0, c]
67
- else [r, g, b] = [c, 0, x]
68
- return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)]
69
- }
70
-
71
- const handleDrag = (e: MouseEvent, target: 'hue' | 'brightness' | 'white') => {
72
- const slider = document.querySelector(`.${target}-slider`) as HTMLElement
73
- const rect = slider.getBoundingClientRect()
74
- let currentHSV = { ...hsv }
75
- let currentWhite = whiteValue
76
- if (target === 'hue' || target === 'brightness') {
77
- if (mode === 'white') hsv.s = 100
78
- mode = 'hsv'
79
- } else mode = 'white'
80
- const onMove = (event: MouseEvent) => {
81
- const x = Math.max(0, Math.min(event.clientX - rect.left, rect.width))
82
- const percent = (x / rect.width) * 100
83
- if (target === 'hue') {
84
- huePointer = percent
85
- currentHSV.h = (x / rect.width) * 360
86
- hsv.h = currentHSV.h
87
- } else if (target === 'brightness') {
88
- brightnessPointer = percent
89
- currentHSV.v = percent
90
- hsv.v = currentHSV.v
91
- } else if (target === 'white') {
92
- currentWhite = percent
93
- whiteValue = currentWhite
94
- }
95
- }
96
- const onUp = () => {
97
- window.removeEventListener('mousemove', onMove)
98
- window.removeEventListener('mouseup', onUp)
99
-
100
- if (mode === 'hsv') {
101
- onChange(hsvToRgb(currentHSV.h, hsv.s, currentHSV.v))
102
- } else {
103
- const val = Math.round((currentWhite / 100) * 255)
104
- onChange([val, val, val])
105
- }
106
- }
107
- onMove(e)
108
- window.addEventListener('mousemove', onMove)
109
- window.addEventListener('mouseup', onUp)
110
- }
111
-
112
- const rgb = $derived(() => (mode === 'white' ? [whiteValue, whiteValue, whiteValue].map((v) => Math.round((v / 100) * 255)) : hsvToRgb(hsv.h, hsv.s, hsv.v)))
113
- const hex = $derived(() =>
114
- rgb()
115
- .map((v) => v.toString(16).padStart(2, '0'))
116
- .join(' ')
117
- .toUpperCase(),
118
- )
119
- const previewBaseColor = $derived(() => (mode === 'white' ? [255, 255, 255].map((c) => Math.round((whiteValue / 100) * c)) : hsvToRgb(hsv.h, hsv.s, 100)))
120
-
121
- const textColor = $derived(() => {
122
- const [r, g, b] = rgb()
123
- const luminance = (r * 299 + g * 587 + b * 114) / 1000
124
- return luminance > 128 ? 'text-black' : 'text-white'
125
- })
126
- </script>
127
-
128
- <div id={id.value} class="relative flex w-full flex-col items-center {wrapperClass}">
129
- {#if label.name}
130
- <h5 class={`mb-2 w-full px-4 text-center ${label.class}`}>{label.name}</h5>
131
- {/if}
132
-
133
- <div class="flex w-full flex-row items-center gap-2">
134
- <!-- Слайдеры цвета -->
135
- <div class="flex w-full flex-col gap-2">
136
- <!-- Выбор цвета -->
137
- <div
138
- class="hue-slider relative h-7 w-full cursor-pointer overflow-hidden rounded-full border border-gray-400"
139
- role="slider"
140
- aria-valuenow={null}
141
- tabindex={null}
142
- onmousedown={(e) => handleDrag(e, 'hue')}
143
- >
144
- <div
145
- class="absolute inset-0"
146
- style={`background: linear-gradient(to right, ${Array.from({ length: 7 }, (_, i) => {
147
- const [r, g, b] = hsvToRgb(i * 60, 100, 100)
148
- return `rgb(${r},${g},${b})`
149
- }).join(', ')})`}
150
- ></div>
151
- {#if mode === 'hsv'}
152
- <div
153
- class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white"
154
- style={`left: ${huePointer}%; background: rgb(${hsvToRgb(hsv.h, 100, 100).join(',')})`}
155
- ></div>
156
- {/if}
157
- </div>
158
-
159
- <!-- Яркость цвета -->
160
- <div
161
- class="brightness-slider relative h-4 w-full cursor-pointer overflow-hidden rounded-full border border-gray-400"
162
- role="slider"
163
- aria-valuenow={null}
164
- tabindex={null}
165
- onmousedown={(e) => handleDrag(e, 'brightness')}
166
- >
167
- {#if mode === 'hsv'}
168
- <div class="absolute inset-0" style={`background: linear-gradient(to right, rgb(0,0,0), rgb(${hsvToRgb(hsv.h, hsv.s, 100).join(',')}))`}></div>
169
-
170
- <div
171
- class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white"
172
- style={`left: ${brightnessPointer}%; background: rgb(${hsvToRgb(hsv.h, hsv.s, hsv.v).join(',')})`}
173
- ></div>
174
- {/if}
175
- </div>
176
-
177
- <!-- Яркость белого цвета -->
178
- <p class="h-4 px-4 text-start font-bold">{$t('component.colorpicker.whitehue')}</p>
179
- <div
180
- class="white-slider relative h-4 w-full cursor-pointer overflow-hidden rounded-full border border-gray-400"
181
- role="slider"
182
- aria-valuenow={null}
183
- tabindex={null}
184
- onmousedown={(e) => handleDrag(e, 'white')}
185
- >
186
- <div class="absolute inset-0 bg-gradient-to-r from-black to-white"></div>
187
- {#if mode === 'white'}
188
- <div
189
- class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white"
190
- style={`left: ${whiteValue}%; background: rgb(${[255, 255, 255].map((c) => Math.round((whiteValue / 100) * c)).join(',')})`}
191
- ></div>
192
- {/if}
193
- </div>
194
- </div>
195
-
196
- <!-- Превью цвета -->
197
- <div
198
- class={`flex h-26 w-28 flex-col justify-center gap-1 rounded-2xl border border-gray-400 px-2 font-mono text-sm select-none ${textColor()}`}
199
- style={`background: rgb(${previewBaseColor().join(',')})`}
200
- >
201
- <div class="flex flex-col items-center">
202
- <span class="w-full flex-shrink-0">{mode === 'white' ? 'White' : 'RGB'}</span>
203
- <div class="пфз-1 w-full text-center tracking-wide">{hex()}</div>
204
- </div>
205
- </div>
206
- </div>
207
- </div>
1
+ <!-- $lib/ElementsUI/ColorPicker.svelte -->
2
+ <script lang="ts">
3
+ import type { IColorPickerProps } from '../types'
4
+
5
+ let {
6
+ id = { name: '', value: crypto.randomUUID() },
7
+ wrapperClass = '',
8
+ label = { name: '', class: '' },
9
+ value = [0, 0, 0],
10
+ onChange = () => {},
11
+ }: IColorPickerProps = $props()
12
+
13
+ const hsv = $state({ h: 0, s: 100, v: 100 })
14
+ let huePointer = $state(0)
15
+ let brightnessPointer = $state(100)
16
+ let mode = $state<'hsv' | 'white'>('hsv')
17
+ let whiteValue = $state(100)
18
+
19
+ $effect(() => {
20
+ if (value.length !== 3) return
21
+
22
+ const [r, g, b] = value
23
+ const isWhite = r === g && g === b
24
+ mode = isWhite ? 'white' : 'hsv'
25
+ if (isWhite) {
26
+ whiteValue = Math.round((r / 255) * 100)
27
+ return
28
+ }
29
+
30
+ const normalized = value.map((v) => v / 255)
31
+ const max = Math.max(...normalized)
32
+ const min = Math.min(...normalized)
33
+ const delta = max - min
34
+ let h = 0
35
+ if (delta !== 0) {
36
+ const [rN, gN, bN] = normalized
37
+ if (max === rN) h = ((gN - bN) / delta) % 6
38
+ else if (max === gN) h = (bN - rN) / delta + 2
39
+ else h = (rN - gN) / delta + 4
40
+ h *= 60
41
+ if (h < 0) h += 360
42
+ }
43
+ const s = max ? (delta / max) * 100 : 0
44
+ const v = max * 100
45
+ hsv.h = Math.round(h)
46
+ hsv.s = Math.round(s)
47
+ hsv.v = Math.round(v)
48
+ huePointer = (h / 360) * 100
49
+ brightnessPointer = v
50
+ })
51
+
52
+ const hsvToRgb = (h: number, s: number, v: number): [number, number, number] => {
53
+ s /= 100
54
+ v /= 100
55
+ const c = v * s
56
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
57
+ const m = v - c
58
+ let r = 0,
59
+ g = 0,
60
+ b = 0
61
+ if (h < 60) [r, g, b] = [c, x, 0]
62
+ else if (h < 120) [r, g, b] = [x, c, 0]
63
+ else if (h < 180) [r, g, b] = [0, c, x]
64
+ else if (h < 240) [r, g, b] = [0, x, c]
65
+ else if (h < 300) [r, g, b] = [x, 0, c]
66
+ else [r, g, b] = [c, 0, x]
67
+ return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)]
68
+ }
69
+
70
+ const handleDrag = (e: MouseEvent, target: 'hue' | 'brightness' | 'white') => {
71
+ const slider = document.querySelector(`.${target}-slider`) as HTMLElement
72
+ const rect = slider.getBoundingClientRect()
73
+ let currentHSV = { ...hsv }
74
+ let currentWhite = whiteValue
75
+ if (target === 'hue' || target === 'brightness') {
76
+ if (mode === 'white') hsv.s = 100
77
+ mode = 'hsv'
78
+ } else mode = 'white'
79
+ const onMove = (event: MouseEvent) => {
80
+ const x = Math.max(0, Math.min(event.clientX - rect.left, rect.width))
81
+ const percent = (x / rect.width) * 100
82
+ if (target === 'hue') {
83
+ huePointer = percent
84
+ currentHSV.h = (x / rect.width) * 360
85
+ hsv.h = currentHSV.h
86
+ } else if (target === 'brightness') {
87
+ brightnessPointer = percent
88
+ currentHSV.v = percent
89
+ hsv.v = currentHSV.v
90
+ } else if (target === 'white') {
91
+ currentWhite = percent
92
+ whiteValue = currentWhite
93
+ }
94
+ }
95
+ const onUp = () => {
96
+ window.removeEventListener('mousemove', onMove)
97
+ window.removeEventListener('mouseup', onUp)
98
+
99
+ if (mode === 'hsv') {
100
+ onChange(hsvToRgb(currentHSV.h, hsv.s, currentHSV.v))
101
+ } else {
102
+ const val = Math.round((currentWhite / 100) * 255)
103
+ onChange([val, val, val])
104
+ }
105
+ }
106
+ onMove(e)
107
+ window.addEventListener('mousemove', onMove)
108
+ window.addEventListener('mouseup', onUp)
109
+ }
110
+
111
+ const rgb = $derived(() => (mode === 'white' ? [whiteValue, whiteValue, whiteValue].map((v) => Math.round((v / 100) * 255)) : hsvToRgb(hsv.h, hsv.s, hsv.v)))
112
+ const hex = $derived(() =>
113
+ rgb()
114
+ .map((v) => v.toString(16).padStart(2, '0'))
115
+ .join(' ')
116
+ .toUpperCase(),
117
+ )
118
+ const previewBaseColor = $derived(() => (mode === 'white' ? [255, 255, 255].map((c) => Math.round((whiteValue / 100) * c)) : hsvToRgb(hsv.h, hsv.s, 100)))
119
+
120
+ const textColor = $derived(() => {
121
+ const [r, g, b] = rgb()
122
+ const luminance = (r * 299 + g * 587 + b * 114) / 1000
123
+ return luminance > 128 ? 'text-black' : 'text-white'
124
+ })
125
+ </script>
126
+
127
+ <div id={id.value} class="relative flex w-full flex-col items-center {wrapperClass}">
128
+ {#if label.name}
129
+ <h5 class={`mb-2 w-full px-4 text-center ${label.class}`}>{label.name}</h5>
130
+ {/if}
131
+
132
+ <div class="flex w-full flex-row items-center gap-2">
133
+ <!-- Слайдеры цвета -->
134
+ <div class="flex w-full flex-col gap-2">
135
+ <!-- Выбор цвета -->
136
+ <div
137
+ class="hue-slider relative h-7 w-full cursor-pointer overflow-hidden rounded-full border border-gray-400"
138
+ role="slider"
139
+ aria-valuenow={null}
140
+ tabindex={null}
141
+ onmousedown={(e) => handleDrag(e, 'hue')}
142
+ >
143
+ <div
144
+ class="absolute inset-0"
145
+ style={`background: linear-gradient(to right, ${Array.from({ length: 7 }, (_, i) => {
146
+ const [r, g, b] = hsvToRgb(i * 60, 100, 100)
147
+ return `rgb(${r},${g},${b})`
148
+ }).join(', ')})`}
149
+ ></div>
150
+ {#if mode === 'hsv'}
151
+ <div
152
+ class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white"
153
+ style={`left: ${huePointer}%; background: rgb(${hsvToRgb(hsv.h, 100, 100).join(',')})`}
154
+ ></div>
155
+ {/if}
156
+ </div>
157
+
158
+ <!-- Яркость цвета -->
159
+ <div
160
+ class="brightness-slider relative h-4 w-full cursor-pointer overflow-hidden rounded-full border border-gray-400"
161
+ role="slider"
162
+ aria-valuenow={null}
163
+ tabindex={null}
164
+ onmousedown={(e) => handleDrag(e, 'brightness')}
165
+ >
166
+ {#if mode === 'hsv'}
167
+ <div class="absolute inset-0" style={`background: linear-gradient(to right, rgb(0,0,0), rgb(${hsvToRgb(hsv.h, hsv.s, 100).join(',')}))`}></div>
168
+
169
+ <div
170
+ class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white"
171
+ style={`left: ${brightnessPointer}%; background: rgb(${hsvToRgb(hsv.h, hsv.s, hsv.v).join(',')})`}
172
+ ></div>
173
+ {/if}
174
+ </div>
175
+
176
+ <!-- Яркость белого цвета -->
177
+ <div
178
+ class="white-slider mt-4 relative h-4 w-full cursor-pointer overflow-hidden rounded-full border border-gray-400"
179
+ role="slider"
180
+ aria-valuenow={null}
181
+ tabindex={null}
182
+ onmousedown={(e) => handleDrag(e, 'white')}
183
+ >
184
+ <div class="absolute inset-0 bg-gradient-to-r from-black to-white"></div>
185
+ {#if mode === 'white'}
186
+ <div
187
+ class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white"
188
+ style={`left: ${whiteValue}%; background: rgb(${[255, 255, 255].map((c) => Math.round((whiteValue / 100) * c)).join(',')})`}
189
+ ></div>
190
+ {/if}
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Превью цвета -->
195
+ <div
196
+ class={`flex h-26 w-28 flex-col justify-center gap-1 rounded-2xl border border-gray-400 px-2 font-mono text-sm select-none ${textColor()}`}
197
+ style={`background: rgb(${previewBaseColor().join(',')})`}
198
+ >
199
+ <div class="flex flex-col items-center">
200
+ <span class="w-full flex-shrink-0">{mode === 'white' ? 'White' : 'RGB'}</span>
201
+ <div class="пфз-1 w-full text-center tracking-wide">{hex()}</div>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
@@ -1,103 +1,103 @@
1
- <!-- $lib/ElementsUI/FileAttach.svelte -->
2
- <script lang="ts">
3
- interface FileInputProps {
4
- id?: string
5
- wrapperClass?: string
6
- label?: { name?: string; class?: string }
7
- type?: 'file' | 'image'
8
- accept?: string
9
- imageSize?: { height?: string; width?: string; fitMode?: 'cover' | 'contain'; form?: 'square' | 'circle' }
10
- disabled?: boolean
11
- currentImage?: string | null
12
- onChange?: (event: Event, file: File | null) => void
13
- }
14
-
15
- let {
16
- id = crypto.randomUUID(),
17
- wrapperClass = '',
18
- label = { name: '', class: '' },
19
- type = 'file',
20
- accept = '*/*',
21
- imageSize = { height: '10rem', width: '10rem', fitMode: 'cover', form: 'square' },
22
- disabled = false,
23
- currentImage = null,
24
- onChange = () => {},
25
- }: FileInputProps = $props()
26
-
27
- let selectedFile = $state<File | null>(null)
28
- let previewUrl = $state<string | null>(null)
29
-
30
- $effect(() => {
31
- if (currentImage && !selectedFile) {
32
- previewUrl = currentImage.startsWith('data:') ? currentImage : `data:image/png;base64,${currentImage}`
33
- }
34
- })
35
-
36
- const handleFileChange = (event: Event) => {
37
- const input = event.target as HTMLInputElement
38
- if (!input.files || input.files.length === 0) {
39
- onChange(event, null)
40
- return
41
- }
42
-
43
- const file = input.files[0]
44
- selectedFile = file
45
-
46
- if (file.type.startsWith('image/')) {
47
- previewUrl = URL.createObjectURL(file)
48
- }
49
-
50
- onChange(event, file)
51
- }
52
-
53
- const triggerFileInput = () => {
54
- const input = document.getElementById(id)
55
- input?.click()
56
- }
57
- </script>
58
-
59
- <div class={`flex flex-col items-center ${wrapperClass}`}>
60
- {#if label.name}
61
- <h5 class={`${label.class}`}>{label.name}</h5>
62
- {/if}
63
-
64
- {#if type === 'image'}
65
- <div class="relative">
66
- <button
67
- class="flex items-center justify-center overflow-hidden {imageSize.form === 'circle' ? 'rounded-full' : 'rounded-2xl'}
68
- border border-[var(--border-color)] bg-[var(--back-color)] transition duration-250 hover:shadow-lg
69
- {disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}"
70
- style={`height: ${imageSize.height}; width: ${imageSize.width}`}
71
- onclick={triggerFileInput}
72
- {disabled}
73
- >
74
- {#if previewUrl || currentImage}
75
- <img
76
- src={previewUrl ?? (currentImage?.startsWith('data:') ? currentImage : `data:image/png;base64,${currentImage}`)}
77
- alt="Preview"
78
- class={`
79
- h-full w-full
80
- ${imageSize.fitMode === 'cover' ? 'object-cover' : 'object-contain'}
81
- `}
82
- />
83
- {:else}
84
- <span class="text-sm text-gray-500">Image</span>
85
- {/if}
86
- </button>
87
- <input {id} type="file" class="absolute -z-10 h-0 w-0 overflow-hidden opacity-0" {accept} {disabled} onchange={handleFileChange} />
88
- </div>
89
- {:else}
90
- <label class="relative inline-block w-full font-normal">
91
- <input
92
- {id}
93
- type="file"
94
- class={`h-9 w-full rounded-2xl border border-[var(--border-color)] bg-[var(--back-color)] transition duration-250 hover:shadow-lg
95
- ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'} file:h-full file:w-1/3 file:cursor-pointer
96
- file:border-none file:bg-[var(--blue-color)] invalid:border-red-400 invalid:shadow-[0_0_6px_var(--red-color)]`}
97
- {accept}
98
- {disabled}
99
- onchange={handleFileChange}
100
- />
101
- </label>
102
- {/if}
103
- </div>
1
+ <!-- $lib/ElementsUI/FileAttach.svelte -->
2
+ <script lang="ts">
3
+ interface FileInputProps {
4
+ id?: string
5
+ wrapperClass?: string
6
+ label?: { name?: string; class?: string }
7
+ type?: 'file' | 'image'
8
+ accept?: string
9
+ imageSize?: { height?: string; width?: string; fitMode?: 'cover' | 'contain'; form?: 'square' | 'circle' }
10
+ disabled?: boolean
11
+ currentImage?: string | null
12
+ onChange?: (event: Event, file: File | null) => void
13
+ }
14
+
15
+ let {
16
+ id = crypto.randomUUID(),
17
+ wrapperClass = '',
18
+ label = { name: '', class: '' },
19
+ type = 'file',
20
+ accept = '*/*',
21
+ imageSize = { height: '10rem', width: '10rem', fitMode: 'cover', form: 'square' },
22
+ disabled = false,
23
+ currentImage = null,
24
+ onChange = () => {},
25
+ }: FileInputProps = $props()
26
+
27
+ let selectedFile = $state<File | null>(null)
28
+ let previewUrl = $state<string | null>(null)
29
+
30
+ $effect(() => {
31
+ if (currentImage && !selectedFile) {
32
+ previewUrl = currentImage.startsWith('data:') ? currentImage : `data:image/png;base64,${currentImage}`
33
+ }
34
+ })
35
+
36
+ const handleFileChange = (event: Event) => {
37
+ const input = event.target as HTMLInputElement
38
+ if (!input.files || input.files.length === 0) {
39
+ onChange(event, null)
40
+ return
41
+ }
42
+
43
+ const file = input.files[0]
44
+ selectedFile = file
45
+
46
+ if (file.type.startsWith('image/')) {
47
+ previewUrl = URL.createObjectURL(file)
48
+ }
49
+
50
+ onChange(event, file)
51
+ }
52
+
53
+ const triggerFileInput = () => {
54
+ const input = document.getElementById(id)
55
+ input?.click()
56
+ }
57
+ </script>
58
+
59
+ <div class={`flex flex-col items-center ${wrapperClass}`}>
60
+ {#if label.name}
61
+ <h5 class={`${label.class}`}>{label.name}</h5>
62
+ {/if}
63
+
64
+ {#if type === 'image'}
65
+ <div class="relative">
66
+ <button
67
+ class="flex items-center justify-center overflow-hidden {imageSize.form === 'circle' ? 'rounded-full' : 'rounded-2xl'}
68
+ bg-[var(--back-color)] shadow-sm transition duration-250 hover:shadow-md
69
+ {disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}"
70
+ style={`height: ${imageSize.height}; width: ${imageSize.width}`}
71
+ onclick={triggerFileInput}
72
+ {disabled}
73
+ >
74
+ {#if previewUrl || currentImage}
75
+ <img
76
+ src={previewUrl ?? (currentImage?.startsWith('data:') ? currentImage : `data:image/png;base64,${currentImage}`)}
77
+ alt="Preview"
78
+ class={`
79
+ h-full w-full
80
+ ${imageSize.fitMode === 'cover' ? 'object-cover' : 'object-contain'}
81
+ `}
82
+ />
83
+ {:else}
84
+ <span class="text-sm text-gray-500">Image</span>
85
+ {/if}
86
+ </button>
87
+ <input {id} type="file" class="absolute -z-10 h-0 w-0 overflow-hidden opacity-0" {accept} {disabled} onchange={handleFileChange} />
88
+ </div>
89
+ {:else}
90
+ <label class="relative inline-block w-full">
91
+ <input
92
+ {id}
93
+ type="file"
94
+ class={`h-8.5 w-full rounded-2xl bg-[var(--back-color)] font-semibold shadow-sm transition duration-250 hover:shadow-md
95
+ ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'} file:h-full file:w-1/3 file:cursor-pointer
96
+ file:border-none file:bg-[var(--blue-color)] invalid:border-red-400 invalid:shadow-[0_0_6px_var(--red-color)]`}
97
+ {accept}
98
+ {disabled}
99
+ onchange={handleFileChange}
100
+ />
101
+ </label>
102
+ {/if}
103
+ </div>