poe-svelte-ui-lib 1.0.0 → 1.0.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.
Files changed (36) hide show
  1. package/LICENSE +3 -3
  2. package/dist/Accordion/Accordion.svelte +53 -53
  3. package/dist/Accordion/AccordionProps.svelte +70 -70
  4. package/dist/Button/Button.svelte +144 -144
  5. package/dist/Button/ButtonProps.svelte +200 -200
  6. package/dist/ColorPicker/ColorPicker.svelte +207 -207
  7. package/dist/ColorPicker/ColorPickerProps.svelte +100 -100
  8. package/dist/FileAttach/FileAttach.svelte +103 -103
  9. package/dist/Graph/Graph.svelte +270 -270
  10. package/dist/Graph/GraphProps.svelte +56 -56
  11. package/dist/Input/Input.svelte +239 -239
  12. package/dist/Input/InputProps.svelte +221 -221
  13. package/dist/Loader.svelte +12 -12
  14. package/dist/MessageModal.svelte +54 -54
  15. package/dist/ProgressBar/ProgressBar.svelte +48 -48
  16. package/dist/ProgressBar/ProgressBarProps.svelte +145 -145
  17. package/dist/Select/Select.svelte +191 -187
  18. package/dist/Select/SelectProps.svelte +260 -260
  19. package/dist/Slider/Slider.svelte +260 -260
  20. package/dist/Slider/SliderProps.svelte +161 -161
  21. package/dist/Switch/Switch.svelte +83 -83
  22. package/dist/Switch/SwitchProps.svelte +144 -144
  23. package/dist/Table/Table.svelte +276 -276
  24. package/dist/Table/TableProps.svelte +286 -286
  25. package/dist/TextField/TextField.svelte +22 -22
  26. package/dist/TextField/TextFieldProps.svelte +92 -92
  27. package/dist/{appIcons → libIcons}/ButtonAdd.svelte +10 -10
  28. package/dist/{appIcons → libIcons}/ButtonDelete.svelte +13 -13
  29. package/dist/{appIcons → libIcons}/LoaderRotate.svelte +9 -9
  30. package/dist/locales/CircleFlagsEn.svelte +14 -14
  31. package/dist/locales/CircleFlagsRu.svelte +8 -8
  32. package/dist/locales/CircleFlagsZh.svelte +8 -8
  33. package/package.json +49 -47
  34. /package/dist/{appIcons → libIcons}/ButtonAdd.svelte.d.ts +0 -0
  35. /package/dist/{appIcons → libIcons}/ButtonDelete.svelte.d.ts +0 -0
  36. /package/dist/{appIcons → libIcons}/LoaderRotate.svelte.d.ts +0 -0
@@ -1,239 +1,239 @@
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
+ 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>