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,239 +1,240 @@
1
- <!-- $lib/ElementsUI/Input.svelte -->
2
- <script lang="ts">
3
- import { onMount } from 'svelte'
4
- import { fly } from 'svelte/transition'
5
- import type { IInputProps } from '../types'
6
- import { t } from '../locales/i18n'
7
-
8
- let {
9
- id = { name: '', value: crypto.randomUUID() },
10
- wrapperClass = '',
11
- label = { name: '', class: '' },
12
- disabled = false,
13
- readonly = false,
14
- value = $bindable(),
15
- type = 'text',
16
- autocomplete = 'off',
17
- componentClass = '',
18
- maxlength = 100,
19
- number = { minNum: -1000000, maxNum: 1000000, step: 1 },
20
- textareaRows = 3,
21
- copyButton = false,
22
- regExp = '^[\\s\\S]*$',
23
- help = { placeholder: '', info: '' },
24
- onUpdate = () => {},
25
- }: IInputProps = $props()
26
-
27
- let showPassword = $state(false)
28
- let showInfo = $state(false)
29
- let isCopied = $state(false)
30
-
31
- /* Закрытие INFO при клике вне компонента */
32
- onMount(() => {
33
- const handleClickOutside = (event: MouseEvent) => {
34
- const target = event.target as HTMLElement
35
- if (!target.closest('.info-container') && !target.closest('.button-info')) {
36
- showInfo = false
37
- }
38
- }
39
- window.addEventListener('click', handleClickOutside)
40
- return () => {
41
- window.removeEventListener('click', handleClickOutside)
42
- }
43
- })
44
-
45
- $effect(() => {
46
- if (type === 'number') {
47
- if (value === undefined || value === null || value === '') value = number.minNum
48
- }
49
- })
50
-
51
- /* Обработка регулярного выражения */
52
- const parseRegExp = (pattern: string | RegExp): RegExp => {
53
- if (pattern instanceof RegExp) return pattern
54
- const match = pattern.match(/^\/(.*)\/([gimsuy]*)$/)
55
- return match ? new RegExp(match[1], match[2]) : new RegExp(pattern)
56
- }
57
- let RegExpObj = $derived(() => parseRegExp(regExp))
58
- let isValid = $derived(RegExpObj().test(typeof value === 'string' ? value : String(value)))
59
-
60
- const handleInputChange = (value: string | number) => {
61
- if (type === 'number') {
62
- const numValue = typeof value === 'string' ? parseFloat(value.replace(',', '.')) : Number(value)
63
- if (!isNaN(numValue)) onUpdate?.(numValue)
64
- else onUpdate?.(value as string)
65
- } else {
66
- onUpdate?.(value as string)
67
- }
68
- }
69
- </script>
70
-
71
- <div class="bg-max relative flex w-full flex-col items-center {type === 'text-area' ? 'h-full' : ''} {wrapperClass}">
72
- {#if label.name}
73
- <h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
74
- {/if}
75
-
76
- <div class="relative flex w-full items-center {type === 'text-area' ? 'h-full' : ''}">
77
- {#if type === 'text' || type === 'password' || type === 'number'}
78
- <input
79
- bind:value
80
- class="w-full rounded-2xl border px-4 py-1 text-center transition-all duration-300 outline-none focus:border-blue-400
81
- [&::-webkit-inner-spin-button]:hidden [&::-webkit-outer-spin-button]:hidden
82
- {isValid ? 'border-[var(--border-color)]' : '!border-red-400 shadow-[0_0_6px_var(--red-color)]'}
83
- {disabled ? 'opacity-50' : 'hover:shadow-md'}
84
- {readonly ? '' : 'hover:shadow-md'}
85
- {help?.info ? 'pl-8' : ''}
86
- {copyButton || type === 'password' || type === 'number' ? 'pr-8' : ''}
87
- {componentClass}"
88
- style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
89
- id={id.value}
90
- placeholder={help?.placeholder}
91
- {disabled}
92
- {autocomplete}
93
- oninput={(e) => handleInputChange((e.currentTarget as HTMLInputElement).value)}
94
- type={type === 'password' ? (showPassword ? 'text' : 'password') : type === 'number' ? 'number' : 'text'}
95
- {maxlength}
96
- min={number?.minNum}
97
- max={number?.maxNum}
98
- step={number?.step}
99
- {readonly}
100
- />
101
- {:else if type === 'text-area'}
102
- <textarea
103
- bind:value
104
- class="h-full w-full resize-y rounded-2xl border border-[var(--border-color)] px-2 py-1 text-center font-mono transition-all duration-300 outline-none focus:border-blue-400
105
- {isValid ? 'border-[var(--border-color)]' : '!border-red-400 shadow-[0_0_6px_var(--red-color)]'}
106
- {disabled ? 'opacity-50' : 'hover:shadow-md'}
107
- {readonly ? '' : 'hover:shadow-md'}
108
- {help?.info ? 'pl-8' : ''}
109
- {copyButton ? 'pr-8' : ''}
110
- {componentClass}"
111
- style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
112
- id={id.value}
113
- {disabled}
114
- {maxlength}
115
- rows={textareaRows}
116
- placeholder={help?.placeholder}
117
- {readonly}
118
- oninput={(e) => handleInputChange((e.currentTarget as HTMLTextAreaElement).value)}
119
- ></textarea>
120
- {/if}
121
-
122
- {#if type === 'password' && !disabled}
123
- <button
124
- type="button"
125
- class="absolute right-2 flex cursor-pointer border-none bg-transparent"
126
- onclick={() => (showPassword = !showPassword)}
127
- aria-label={showPassword ? 'Скрыть пароль' : 'Показать пароль'}
128
- >
129
- {#if showPassword}
130
- <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24">
131
- <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
132
- <path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0-4 0" />
133
- <path d="M21 12q-3.6 6-9 6t-9-6q3.6-6 9-6t9 6" />
134
- </g>
135
- </svg>
136
- {:else}
137
- <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24">
138
- <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
139
- <path d="M10.585 10.587a2 2 0 0 0 2.829 2.828" />
140
- <path d="M16.681 16.673A8.7 8.7 0 0 1 12 18q-5.4 0-9-6q1.908-3.18 4.32-4.674m2.86-1.146A9 9 0 0 1 12 6q5.4 0 9 6q-1 1.665-2.138 2.87M3 3l18 18" />
141
- </g>
142
- </svg>
143
- {/if}
144
- </button>
145
- {/if}
146
-
147
- {#if copyButton && (type === 'text' || type === 'text-area')}
148
- <button
149
- type="button"
150
- class="absolute right-2 flex cursor-pointer border-none bg-transparent {type === 'text-area' ? 'top-2' : ''}"
151
- onclick={(e) => {
152
- e.preventDefault()
153
- navigator.clipboard.writeText(value as string)
154
- isCopied = true
155
- setTimeout(() => (isCopied = false), 1000)
156
- }}
157
- aria-label="Копировать текст"
158
- >
159
- <svg xmlns="http://www.w3.org/2000/svg" width="1.3em" height="1.3em" viewBox="0 0 24 24">
160
- <g fill="none" stroke="currentColor" stroke-width="1.5">
161
- <path
162
- d="M6 11c0-2.828 0-4.243.879-5.121C7.757 5 9.172 5 12 5h3c2.828 0 4.243 0 5.121.879C21 6.757 21 8.172 21 11v5c0 2.828 0 4.243-.879 5.121C19.243 22 17.828 22 15 22h-3c-2.828 0-4.243 0-5.121-.879C6 20.243 6 18.828 6 16z"
163
- />
164
- <path d="M6 19a3 3 0 0 1-3-3v-6c0-3.771 0-5.657 1.172-6.828S7.229 2 11 2h4a3 3 0 0 1 3 3" />
165
- </g>
166
- </svg>
167
- </button>
168
-
169
- {#if isCopied}
170
- <div
171
- class="absolute top-1/2 right-10 -translate-y-1/2 transform rounded-md bg-[var(--green-color)] px-2 py-1 text-sm shadow-lg"
172
- transition:fly={{ x: 10, duration: 200 }}
173
- >
174
- {$t('component.input.copy')}
175
- </div>
176
- {/if}
177
- {/if}
178
-
179
- {#if type === 'number' && !readonly && !disabled}
180
- <div class="absolute right-0 flex h-full w-8 flex-col items-center justify-center rounded-r-2xl border-l border-[var(--border-color)]">
181
- <button
182
- class="flex h-1/2 w-full items-center rounded-tr-2xl border-b border-[var(--border-color)] pl-2 transition-colors duration-150 hover:bg-[var(--gray-color)]/30 active:bg-[var(--gray-color)]/10"
183
- onclick={() => {
184
- if (!number.maxNum || !number.step) return
185
- if (Number(value) + number.step >= number.maxNum) {
186
- value = number.maxNum
187
- onUpdate?.(value as number)
188
- return
189
- }
190
- value = Number(value) + (number.step ?? 1)
191
- onUpdate?.(value as number)
192
- }}
193
- aria-label="Увеличить">+</button
194
- >
195
-
196
- <button
197
- class="flex h-1/2 w-full items-center rounded-br-2xl pl-2 transition-colors duration-150 hover:bg-[var(--gray-color)]/30 active:bg-[var(--gray-color)]/10"
198
- onclick={() => {
199
- if (number.minNum === null || number.minNum === undefined || !number.step) return
200
- if (Number(value) - number.step <= number.minNum) {
201
- value = number.minNum
202
- onUpdate?.(value as number)
203
- return
204
- }
205
- value = Number(value) - (number.step ?? 1)
206
- onUpdate?.(value as number)
207
- }}
208
- aria-label="Уменьшить">−</button
209
- >
210
- </div>
211
- {/if}
212
-
213
- {#if help.info}
214
- <button
215
- type="button"
216
- class="button-info absolute left-2 flex border-none bg-transparent {type === 'text-area' ? 'top-2' : ''} {disabled ? 'opacity-50' : 'cursor-pointer'}"
217
- onclick={() => (showInfo = !showInfo)}
218
- aria-label={showInfo ? 'Скрыть инфо' : 'Показать инфо'}
219
- >
220
- <svg xmlns="http://www.w3.org/2000/svg" height="1.5rem" width="1.5rem" viewBox="0 0 24 24">
221
- <path
222
- fill="currentColor"
223
- d="M12 16.5q.214 0 .357-.144T12.5 16v-4.5q0-.213-.144-.356T11.999 11t-.356.144t-.143.356V16q0 .213.144.356t.357.144M12 9.577q.262 0 .439-.177t.176-.438t-.177-.439T12 8.346t-.438.177t-.177.439t.177.438t.438.177M12.003 21q-1.867 0-3.51-.708q-1.643-.709-2.859-1.924t-1.925-2.856T3 12.003t.709-3.51Q4.417 6.85 5.63 5.634t2.857-1.925T11.997 3t3.51.709q1.643.708 2.859 1.922t1.925 2.857t.709 3.509t-.708 3.51t-1.924 2.859t-2.856 1.925t-3.509.709M12 20q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"
224
- />
225
- </svg>
226
- </button>
227
-
228
- {#if showInfo}
229
- <div
230
- transition:fly={{ x: -15, duration: 250 }}
231
- class="info-container absolute z-50 w-auto rounded px-2 py-1 shadow-lg"
232
- style="left: 2.5rem; top: 50%; transform: translateY(-50%); background: color-mix(in srgb, var(--yellow-color) 20%, var(--back-color));"
233
- >
234
- {help?.info}
235
- </div>
236
- {/if}
237
- {/if}
238
- </div>
239
- </div>
1
+ <!-- $lib/ElementsUI/Input.svelte -->
2
+ <script lang="ts">
3
+ import { onMount } from 'svelte'
4
+ import { fly } from 'svelte/transition'
5
+ import type { IInputProps } from '../types'
6
+
7
+ let {
8
+ id = { name: '', value: crypto.randomUUID() },
9
+ wrapperClass = '',
10
+ label = { name: '', class: '' },
11
+ disabled = false,
12
+ readonly = false,
13
+ value = $bindable(),
14
+ type = 'text',
15
+ autocomplete = 'off',
16
+ componentClass = '',
17
+ maxlength = 100,
18
+ number = { minNum: -1000000, maxNum: 1000000, step: 1 },
19
+ textareaRows = 3,
20
+ copyButton = false,
21
+ regExp = '^[\\s\\S]*$',
22
+ help = { placeholder: '', info: '' },
23
+ onUpdate = () => {},
24
+ }: IInputProps = $props()
25
+
26
+ let showPassword = $state(false)
27
+ let showInfo = $state(false)
28
+ let isCopied = $state(false)
29
+
30
+ /* Закрытие INFO при клике вне компонента */
31
+ onMount(() => {
32
+ const handleClickOutside = (event: MouseEvent) => {
33
+ const target = event.target as HTMLElement
34
+ if (!target.closest('.info-container') && !target.closest('.button-info')) {
35
+ showInfo = false
36
+ }
37
+ }
38
+ window.addEventListener('click', handleClickOutside)
39
+ return () => {
40
+ window.removeEventListener('click', handleClickOutside)
41
+ }
42
+ })
43
+
44
+ $effect(() => {
45
+ if (type === 'number') {
46
+ if (value === undefined || value === null || value === '') value = number.minNum
47
+ }
48
+ })
49
+
50
+ /* Обработка регулярного выражения */
51
+ const parseRegExp = (pattern: string | RegExp): RegExp => {
52
+ if (pattern instanceof RegExp) return pattern
53
+ const match = pattern.match(/^\/(.*)\/([gimsuy]*)$/)
54
+ return match ? new RegExp(match[1], match[2]) : new RegExp(pattern)
55
+ }
56
+ let RegExpObj = $derived(() => parseRegExp(regExp))
57
+ let isValid = $derived(RegExpObj().test(typeof value === 'string' ? value : String(value)))
58
+
59
+ const handleInputChange = (value: string | number) => {
60
+ if (type === 'number') {
61
+ const numValue = typeof value === 'string' ? parseFloat(value.replace(',', '.')) : Number(value)
62
+ if (!isNaN(numValue)) onUpdate?.(numValue)
63
+ else onUpdate?.(value as string)
64
+ } else {
65
+ onUpdate?.(value as string)
66
+ }
67
+ }
68
+ </script>
69
+
70
+ <div class="bg-max relative flex w-full flex-col items-center {type === 'text-area' ? 'h-full' : ''} {wrapperClass}">
71
+ {#if label.name}
72
+ <h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
73
+ {/if}
74
+
75
+ <div class="relative flex w-full items-center {type === 'text-area' ? 'h-full' : ''}">
76
+ {#if type === 'text' || type === 'password' || type === 'number'}
77
+ <input
78
+ bind:value
79
+ class="w-full rounded-2xl border px-4 py-1 text-center transition-all duration-300 outline-none focus:border-blue-400
80
+ [&::-webkit-inner-spin-button]:hidden [&::-webkit-outer-spin-button]:hidden
81
+ {isValid ? 'border-[var(--border-color)]' : '!border-red-400 shadow-[0_0_6px_var(--red-color)]'}
82
+ {disabled ? 'opacity-50' : 'hover:shadow-md'}
83
+ {readonly ? '' : 'hover:shadow-md'}
84
+ {help?.info ? 'pl-8' : ''}
85
+ {copyButton || type === 'password' || type === 'number' ? 'pr-8' : ''}
86
+ {componentClass}"
87
+ style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
88
+ id={id.value}
89
+ placeholder={help?.placeholder}
90
+ {disabled}
91
+ {autocomplete}
92
+ oninput={(e) => handleInputChange((e.currentTarget as HTMLInputElement).value)}
93
+ type={type === 'password' ? (showPassword ? 'text' : 'password') : type === 'number' ? 'number' : 'text'}
94
+ {maxlength}
95
+ min={number?.minNum}
96
+ max={number?.maxNum}
97
+ step={number?.step}
98
+ {readonly}
99
+ />
100
+ {:else if type === 'text-area'}
101
+ <textarea
102
+ bind:value
103
+ class="h-full w-full resize-y rounded-2xl border border-[var(--border-color)] px-2 py-1 text-center font-mono transition-all duration-300 outline-none focus:border-blue-400
104
+ {isValid ? 'border-[var(--border-color)]' : '!border-red-400 shadow-[0_0_6px_var(--red-color)]'}
105
+ {disabled ? 'opacity-50' : 'hover:shadow-md'}
106
+ {readonly ? '' : 'hover:shadow-md'}
107
+ {help?.info ? 'pl-8' : ''}
108
+ {copyButton ? 'pr-8' : ''}
109
+ {componentClass}"
110
+ style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
111
+ id={id.value}
112
+ {disabled}
113
+ {maxlength}
114
+ rows={textareaRows}
115
+ placeholder={help?.placeholder}
116
+ {readonly}
117
+ oninput={(e) => handleInputChange((e.currentTarget as HTMLTextAreaElement).value)}
118
+ ></textarea>
119
+ {/if}
120
+
121
+ {#if type === 'password' && !disabled}
122
+ <button
123
+ type="button"
124
+ class="absolute right-2 flex cursor-pointer border-none bg-transparent"
125
+ onclick={() => (showPassword = !showPassword)}
126
+ aria-label={showPassword ? 'Скрыть пароль' : 'Показать пароль'}
127
+ >
128
+ {#if showPassword}
129
+ <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"
130
+ ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
131
+ ><path d="M15 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0" /><path
132
+ d="M2 12c1.6-4.097 5.336-7 10-7s8.4 2.903 10 7c-1.6 4.097-5.336 7-10 7s-8.4-2.903-10-7"
133
+ /></g
134
+ ></svg
135
+ >
136
+ {:else}
137
+ <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"
138
+ ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
139
+ ><path
140
+ stroke-linejoin="round"
141
+ d="M10.73 5.073A11 11 0 0 1 12 5c4.664 0 8.4 2.903 10 7a11.6 11.6 0 0 1-1.555 2.788M6.52 6.519C4.48 7.764 2.9 9.693 2 12c1.6 4.097 5.336 7 10 7a10.44 10.44 0 0 0 5.48-1.52m-7.6-7.6a3 3 0 1 0 4.243 4.243"
142
+ /><path d="m4 4l16 16" /></g
143
+ ></svg
144
+ >
145
+ {/if}
146
+ </button>
147
+ {/if}
148
+
149
+ {#if copyButton && (type === 'text' || type === 'text-area')}
150
+ <button
151
+ type="button"
152
+ class="absolute right-2 flex cursor-pointer border-none bg-transparent {type === 'text-area' ? 'top-2' : ''}"
153
+ onclick={(e) => {
154
+ e.preventDefault()
155
+ navigator.clipboard.writeText(value as string)
156
+ isCopied = true
157
+ setTimeout(() => (isCopied = false), 1000)
158
+ }}
159
+ aria-label="Копировать текст"
160
+ >
161
+ <svg xmlns="http://www.w3.org/2000/svg" width="1.3em" height="1.3em" viewBox="0 0 24 24">
162
+ <g fill="none" stroke="currentColor" stroke-width="1.5">
163
+ <path
164
+ d="M6 11c0-2.828 0-4.243.879-5.121C7.757 5 9.172 5 12 5h3c2.828 0 4.243 0 5.121.879C21 6.757 21 8.172 21 11v5c0 2.828 0 4.243-.879 5.121C19.243 22 17.828 22 15 22h-3c-2.828 0-4.243 0-5.121-.879C6 20.243 6 18.828 6 16z"
165
+ />
166
+ <path d="M6 19a3 3 0 0 1-3-3v-6c0-3.771 0-5.657 1.172-6.828S7.229 2 11 2h4a3 3 0 0 1 3 3" />
167
+ </g>
168
+ </svg>
169
+ </button>
170
+
171
+ {#if isCopied}
172
+ <div
173
+ class="absolute top-1/2 right-10 -translate-y-1/2 transform rounded-md bg-[var(--green-color)] px-2 py-1 text-sm shadow-lg"
174
+ transition:fly={{ x: 10, duration: 200 }}
175
+ >
176
+
177
+ </div>
178
+ {/if}
179
+ {/if}
180
+
181
+ {#if type === 'number' && !readonly && !disabled}
182
+ <div class="absolute right-0 flex h-full w-8 flex-col items-center justify-center rounded-r-2xl border-l border-[var(--border-color)]">
183
+ <button
184
+ class="flex h-1/2 w-full items-center rounded-tr-2xl border-b border-[var(--border-color)] pl-2 transition-colors duration-150 hover:bg-[var(--gray-color)]/30 active:bg-[var(--gray-color)]/10"
185
+ onclick={() => {
186
+ if (!number.maxNum || !number.step) return
187
+ if (Number(value) + number.step >= number.maxNum) {
188
+ value = number.maxNum
189
+ onUpdate?.(value as number)
190
+ return
191
+ }
192
+ value = Number(value) + (number.step ?? 1)
193
+ onUpdate?.(value as number)
194
+ }}
195
+ aria-label="Увеличить">+</button
196
+ >
197
+
198
+ <button
199
+ class="flex h-1/2 w-full items-center rounded-br-2xl pl-2 transition-colors duration-150 hover:bg-[var(--gray-color)]/30 active:bg-[var(--gray-color)]/10"
200
+ onclick={() => {
201
+ if (number.minNum === null || number.minNum === undefined || !number.step) return
202
+ if (Number(value) - number.step <= number.minNum) {
203
+ value = number.minNum
204
+ onUpdate?.(value as number)
205
+ return
206
+ }
207
+ value = Number(value) - (number.step ?? 1)
208
+ onUpdate?.(value as number)
209
+ }}
210
+ aria-label="Уменьшить">−</button
211
+ >
212
+ </div>
213
+ {/if}
214
+
215
+ {#if help.info}
216
+ <button
217
+ type="button"
218
+ class="button-info absolute left-2 flex border-none bg-transparent {type === 'text-area' ? 'top-2' : ''} {disabled ? 'opacity-50' : 'cursor-pointer'}"
219
+ onclick={() => (showInfo = !showInfo)}
220
+ aria-label={showInfo ? 'Скрыть инфо' : 'Показать инфо'}
221
+ >
222
+ <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"
223
+ ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
224
+ ><circle cx="12" cy="12" r="10" stroke-width="1.3" /><path stroke-width="1.5" d="M12 16v-4.5" /><path stroke-width="1.8" d="M12 8.012v-.01" /></g
225
+ ></svg
226
+ >
227
+ </button>
228
+
229
+ {#if showInfo}
230
+ <div
231
+ transition:fly={{ x: -15, duration: 250 }}
232
+ class="info-container absolute z-50 w-auto rounded px-2 py-1 shadow-lg"
233
+ style="left: 2.5rem; top: 50%; transform: translateY(-50%); background: color-mix(in srgb, var(--yellow-color) 20%, var(--back-color));"
234
+ >
235
+ {help?.info}
236
+ </div>
237
+ {/if}
238
+ {/if}
239
+ </div>
240
+ </div>
@@ -1,12 +1,12 @@
1
- <!-- $lib/ElementsUI/Loader.svelte -->
2
- <script lang="ts">
3
- import LoaderRotate from './appIcons/LoaderRotate.svelte'
4
-
5
- let { show = false } = $props()
6
- </script>
7
-
8
- {#if show}
9
- <div class="absolute inset-0 z-50 flex items-center justify-center">
10
- <LoaderRotate />
11
- </div>
12
- {/if}
1
+ <!-- $lib/ElementsUI/Loader.svelte -->
2
+ <script lang="ts">
3
+ import LoaderRotate from './libIcons/LoaderRotate.svelte'
4
+
5
+ let { show = false } = $props()
6
+ </script>
7
+
8
+ {#if show}
9
+ <div class="absolute inset-0 z-50 flex items-center justify-center">
10
+ <LoaderRotate />
11
+ </div>
12
+ {/if}
@@ -1,54 +1,54 @@
1
- <!-- $lib/ElementsUI/MessageModal.svelte -->
2
- <script lang="ts">
3
- import { onMount } from 'svelte'
4
- import { fly } from 'svelte/transition'
5
-
6
- interface Props {
7
- message: { id: number; text: string }
8
- onCLick: (messageId: number) => {}
9
- }
10
-
11
- let { message, onCLick }: Props = $props()
12
-
13
- const getMessageStyle = (text: string) => {
14
- if (text.startsWith('ERR: ')) return 'text-red-500'
15
- if (text.startsWith('OK: ')) return 'text-lime-500'
16
- if (text.startsWith('WR: ')) return 'text-yellow-500'
17
- return 'text-gray-400'
18
- }
19
-
20
- const getMessageText = (text: string) => {
21
- if (text.startsWith('ERR: ')) return text.replace('ERR: ', '')
22
- if (text.startsWith('OK: ')) return text.replace('OK: ', '')
23
- if (text.startsWith('WR: ')) return text.replace('WR: ', '')
24
- return text
25
- }
26
-
27
- let progress = $state(100)
28
- onMount(() => {
29
- const duration = 5000
30
- const interval = 50
31
- const step = (interval / duration) * 100
32
- const timer = setInterval(() => {
33
- progress -= step
34
- if (progress <= 0) {
35
- clearInterval(timer)
36
- onCLick(message.id)
37
- }
38
- }, interval)
39
- })
40
- </script>
41
-
42
- <div
43
- transition:fly={{ y: 5, duration: 250 }}
44
- class="my-1 flex flex-col rounded-2xl border border-[var(--border-color)] bg-[var(--back-color)] px-4 py-2 shadow-lg"
45
- >
46
- <div class="flex items-center justify-between">
47
- <p class={`font-semibold ${getMessageStyle(message.text)}`}>{getMessageText(message.text)}</p>
48
- <button class="ml-2 cursor-pointer text-2xl" onclick={() => onCLick(message.id)}>&times;</button>
49
- </div>
50
-
51
- <div class="mt-2 h-2 w-full overflow-hidden rounded bg-gray-200">
52
- <div class="h-full bg-[var(--green-color)]" style={`width: ${progress}%`}></div>
53
- </div>
54
- </div>
1
+ <!-- $lib/ElementsUI/MessageModal.svelte -->
2
+ <script lang="ts">
3
+ import { onMount } from 'svelte'
4
+ import { fly } from 'svelte/transition'
5
+
6
+ interface Props {
7
+ message: { id: number; text: string }
8
+ onCLick: (messageId: number) => {}
9
+ }
10
+
11
+ let { message, onCLick }: Props = $props()
12
+
13
+ const getMessageStyle = (text: string) => {
14
+ if (text.startsWith('ERR: ')) return 'text-red-500'
15
+ if (text.startsWith('OK: ')) return 'text-lime-500'
16
+ if (text.startsWith('WR: ')) return 'text-yellow-500'
17
+ return 'text-gray-400'
18
+ }
19
+
20
+ const getMessageText = (text: string) => {
21
+ if (text.startsWith('ERR: ')) return text.replace('ERR: ', '')
22
+ if (text.startsWith('OK: ')) return text.replace('OK: ', '')
23
+ if (text.startsWith('WR: ')) return text.replace('WR: ', '')
24
+ return text
25
+ }
26
+
27
+ let progress = $state(100)
28
+ onMount(() => {
29
+ const duration = 5000
30
+ const interval = 50
31
+ const step = (interval / duration) * 100
32
+ const timer = setInterval(() => {
33
+ progress -= step
34
+ if (progress <= 0) {
35
+ clearInterval(timer)
36
+ onCLick(message.id)
37
+ }
38
+ }, interval)
39
+ })
40
+ </script>
41
+
42
+ <div
43
+ transition:fly={{ y: 5, duration: 250 }}
44
+ class="my-1 flex flex-col rounded-2xl border border-[var(--border-color)] bg-[var(--back-color)] px-4 py-2 shadow-lg"
45
+ >
46
+ <div class="flex items-center justify-between">
47
+ <p class={`font-semibold ${getMessageStyle(message.text)}`}>{getMessageText(message.text)}</p>
48
+ <button class="ml-2 cursor-pointer text-2xl" onclick={() => onCLick(message.id)}>&times;</button>
49
+ </div>
50
+
51
+ <div class="mt-2 h-2 w-full overflow-hidden rounded bg-gray-200">
52
+ <div class="h-full bg-[var(--green-color)]" style={`width: ${progress}%`}></div>
53
+ </div>
54
+ </div>