poe-svelte-ui-lib 0.2.0 → 1.0.0

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 (73) hide show
  1. package/dist/Accordion/Accordion.svelte +53 -0
  2. package/dist/Accordion/Accordion.svelte.d.ts +4 -0
  3. package/dist/Accordion/AccordionProps.svelte +70 -0
  4. package/dist/Accordion/AccordionProps.svelte.d.ts +10 -0
  5. package/dist/{Button.svelte → Button/Button.svelte} +43 -24
  6. package/dist/{Button.svelte.d.ts → Button/Button.svelte.d.ts} +5 -5
  7. package/dist/Button/ButtonProps.svelte +200 -0
  8. package/dist/Button/ButtonProps.svelte.d.ts +10 -0
  9. package/dist/ColorPicker/ColorPicker.svelte +207 -0
  10. package/dist/ColorPicker/ColorPicker.svelte.d.ts +4 -0
  11. package/dist/ColorPicker/ColorPickerProps.svelte +100 -0
  12. package/dist/ColorPicker/ColorPickerProps.svelte.d.ts +10 -0
  13. package/dist/FileAttach/FileAttach.svelte +103 -0
  14. package/dist/FileAttach/FileAttach.svelte.d.ts +22 -0
  15. package/dist/Graph/Graph.svelte +270 -0
  16. package/dist/Graph/Graph.svelte.d.ts +4 -0
  17. package/dist/Graph/GraphProps.svelte +56 -0
  18. package/dist/Graph/GraphProps.svelte.d.ts +10 -0
  19. package/dist/Input/Input.svelte +239 -0
  20. package/dist/Input/Input.svelte.d.ts +4 -0
  21. package/dist/Input/InputProps.svelte +221 -0
  22. package/dist/Input/InputProps.svelte.d.ts +10 -0
  23. package/dist/Loader.svelte +12 -0
  24. package/dist/Loader.svelte.d.ts +5 -0
  25. package/dist/MessageModal.svelte +54 -0
  26. package/dist/MessageModal.svelte.d.ts +10 -0
  27. package/dist/ProgressBar/ProgressBar.svelte +48 -0
  28. package/dist/ProgressBar/ProgressBar.svelte.d.ts +4 -0
  29. package/dist/ProgressBar/ProgressBarProps.svelte +145 -0
  30. package/dist/ProgressBar/ProgressBarProps.svelte.d.ts +10 -0
  31. package/dist/Select/Select.svelte +187 -0
  32. package/dist/Select/Select.svelte.d.ts +18 -0
  33. package/dist/Select/SelectProps.svelte +260 -0
  34. package/dist/Select/SelectProps.svelte.d.ts +10 -0
  35. package/dist/Slider/Slider.svelte +260 -0
  36. package/dist/Slider/Slider.svelte.d.ts +4 -0
  37. package/dist/Slider/SliderProps.svelte +161 -0
  38. package/dist/Slider/SliderProps.svelte.d.ts +10 -0
  39. package/dist/Switch/Switch.svelte +83 -0
  40. package/dist/Switch/Switch.svelte.d.ts +4 -0
  41. package/dist/Switch/SwitchProps.svelte +144 -0
  42. package/dist/Switch/SwitchProps.svelte.d.ts +10 -0
  43. package/dist/Table/Table.svelte +276 -0
  44. package/dist/Table/Table.svelte.d.ts +4 -0
  45. package/dist/Table/TableProps.svelte +286 -0
  46. package/dist/Table/TableProps.svelte.d.ts +10 -0
  47. package/dist/TextField/TextField.svelte +22 -0
  48. package/dist/TextField/TextField.svelte.d.ts +4 -0
  49. package/dist/TextField/TextFieldProps.svelte +92 -0
  50. package/dist/TextField/TextFieldProps.svelte.d.ts +10 -0
  51. package/dist/appIcons/ButtonAdd.svelte +10 -0
  52. package/dist/appIcons/ButtonAdd.svelte.d.ts +18 -0
  53. package/dist/appIcons/ButtonDelete.svelte +13 -0
  54. package/dist/appIcons/ButtonDelete.svelte.d.ts +18 -0
  55. package/dist/appIcons/LoaderRotate.svelte +9 -0
  56. package/dist/appIcons/LoaderRotate.svelte.d.ts +18 -0
  57. package/dist/index.d.ts +26 -1
  58. package/dist/index.js +27 -2
  59. package/dist/locales/CircleFlagsEn.svelte +14 -0
  60. package/dist/locales/CircleFlagsEn.svelte.d.ts +26 -0
  61. package/dist/locales/CircleFlagsRu.svelte +8 -0
  62. package/dist/locales/CircleFlagsRu.svelte.d.ts +26 -0
  63. package/dist/locales/CircleFlagsZh.svelte +8 -0
  64. package/dist/locales/CircleFlagsZh.svelte.d.ts +26 -0
  65. package/dist/locales/i18n.d.ts +10 -0
  66. package/dist/locales/i18n.js +36 -0
  67. package/dist/locales/translations.d.ts +7 -0
  68. package/dist/locales/translations.js +450 -0
  69. package/dist/options.d.ts +78 -0
  70. package/dist/options.js +71 -0
  71. package/dist/types.d.ts +284 -0
  72. package/dist/types.js +1 -0
  73. package/package.json +28 -21
@@ -0,0 +1,270 @@
1
+ <!-- $lib/ElementsUI/Graph.svelte -->
2
+ <script lang="ts">
3
+ import { onMount } from 'svelte'
4
+ import Select from '../Select/Select.svelte'
5
+ import type { IGraphDataObject, IGraphProps, ISelectOption } from '../types'
6
+
7
+ /* Инициализация пропсов с дефолтными значениями */
8
+ let {
9
+ id = { name: '', value: crypto.randomUUID() },
10
+ wrapperClass = '',
11
+ label = { name: '', class: '' },
12
+ streamingData = { data: [], timestamp: Date.now() },
13
+ isTest = false,
14
+ }: IGraphProps = $props()
15
+
16
+ /* Состояние компонента */
17
+ let graphData = $state<{ id: string; points: { x: number; y: number }[]; color: string; name: string }[]>([])
18
+ let currentValues = $state<number[]>([])
19
+ let container: HTMLDivElement
20
+ let canvas: HTMLCanvasElement
21
+ let ctx: CanvasRenderingContext2D
22
+ let width = $state(600)
23
+ let height = $state(125)
24
+
25
+ /* Константы и настройки */
26
+ const REFRESH_OPTIONS: ISelectOption[] = [
27
+ { id: 'RefreshOption-AUTO', name: 'AUTO', value: 0, class: '' },
28
+ { id: 'RefreshOption-10', name: '10', value: 10, class: '' },
29
+ { id: 'RefreshOption-25', name: '25', value: 25, class: '' },
30
+ { id: 'RefreshOption-50', name: '50', value: 50, class: '' },
31
+ { id: 'RefreshOption-100', name: '100', value: 100, class: '' },
32
+ { id: 'RefreshOption-250', name: '250', value: 250, class: '' },
33
+ { id: 'RefreshOption-500', name: '500', value: 500, class: '' },
34
+ { id: 'RefreshOption-1000', name: '1000', value: 1000, class: '' },
35
+ { id: 'RefreshOption-5000', name: '5000', value: 5000, class: '' },
36
+ ]
37
+ // const REFRESH_OPTIONS = [10, 25, 50, 100, 250, 500, 1000, 5000]
38
+ const SCALE_OPTIONS: ISelectOption[] = [
39
+ { id: 'ScaleOption-50', name: '50', value: 50, class: '' },
40
+ { id: 'ScaleOption-100', name: '100', value: 100, class: '' },
41
+ { id: 'ScaleOption-500', name: '500', value: 500, class: '' },
42
+ { id: 'ScaleOption-1000', name: '1000', value: 1000, class: '' },
43
+ { id: 'ScaleOption-2000', name: '2000', value: 2000, class: '' },
44
+ ]
45
+ // const SCALE_OPTIONS = [50, 100, 500, 1000, 2000]
46
+ let selectedRefreshRate = $state(0)
47
+ let selectedScale = $state(100)
48
+ const maxDataPoints = $derived(selectedRefreshRate == 0 ? 20 : 100)
49
+ const defaultColors = ['#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899']
50
+
51
+ let previousDataTimestamp: number = $state(0)
52
+
53
+ /* Инициализация данных графиков */
54
+ const initializeGraphData = () => {
55
+ if (typeof streamingData === 'string') {
56
+ streamingData = JSON.parse(streamingData)
57
+ }
58
+ if (!streamingData.data || streamingData.data.length === 0) {
59
+ graphData = []
60
+ currentValues = []
61
+ return
62
+ }
63
+ const newGraphData = (streamingData.data as IGraphDataObject[]).slice(0, 6).map((d, i) => {
64
+ const existingData = graphData.find((g) => g.name === d.name) || graphData[i]
65
+ return {
66
+ id: existingData?.id || crypto.randomUUID(),
67
+ points: existingData?.points || [],
68
+ color: d.color || defaultColors[i % defaultColors.length],
69
+ name: d.name || `Value ${i}`,
70
+ }
71
+ })
72
+ if (JSON.stringify(graphData) !== JSON.stringify(newGraphData)) {
73
+ graphData = newGraphData
74
+ currentValues = (streamingData.data as IGraphDataObject[]).map((d) => d.value || 0)
75
+ }
76
+ }
77
+
78
+ /* Вызываем инициализацию при монтировании и при изменении streamingData */
79
+ onMount(initializeGraphData)
80
+ $effect(() => {
81
+ initializeGraphData()
82
+ })
83
+
84
+ /* Обработка входящих данных */
85
+ let intervalId: ReturnType<typeof setInterval>
86
+ $effect(() => {
87
+ clearInterval(intervalId)
88
+ if (selectedRefreshRate > 0 && streamingData.data && streamingData.data.length > 0) {
89
+ intervalId = setInterval(() => {
90
+ let newValues
91
+ if (isTest) newValues = graphData.map(() => Math.random() * 100 - 50)
92
+ else newValues = (streamingData.data as IGraphDataObject[]).map((dataset) => dataset.value)
93
+ const now = Date.now()
94
+ newValues.forEach((value, i) => {
95
+ if (!graphData[i]) return
96
+ graphData[i].points.push({ x: now, y: value })
97
+ if (graphData[i].points.length > maxDataPoints) {
98
+ graphData[i].points.shift()
99
+ }
100
+ currentValues[i] = value
101
+ })
102
+ drawAllGraphs()
103
+ }, selectedRefreshRate)
104
+ } else if (selectedRefreshRate == 0 && streamingData.data && streamingData.data.length > 0 && !isTest) {
105
+ intervalId = setInterval(() => {
106
+ if (previousDataTimestamp < (streamingData.timestamp ?? Date.now())) {
107
+ let newValues = (streamingData.data as IGraphDataObject[]).map((dataset) => dataset.value)
108
+
109
+ newValues.forEach((value, i) => {
110
+ if (!graphData[i]) return
111
+ graphData[i].points.push({ x: streamingData.timestamp ?? Date.now(), y: value })
112
+ if (graphData[i].points.length > maxDataPoints) {
113
+ graphData[i].points.shift()
114
+ }
115
+ currentValues[i] = value
116
+ })
117
+ drawAllGraphs()
118
+ previousDataTimestamp = streamingData.timestamp ?? Date.now()
119
+ }
120
+ }, 10)
121
+ }
122
+ console.log(streamingData.data)
123
+ return () => clearInterval(intervalId)
124
+ })
125
+
126
+ let resizeObserver: ResizeObserver
127
+ $effect(() => {
128
+ if (!container || !canvas) return
129
+ const dpr = window.devicePixelRatio || 1
130
+ resizeObserver = new ResizeObserver(() => {
131
+ const rect = container.getBoundingClientRect()
132
+ width = rect.width
133
+ height = rect.height
134
+ canvas.width = width * dpr
135
+ canvas.height = height * dpr
136
+ canvas.style.width = `${width}px`
137
+ canvas.style.height = `${height}px`
138
+ ctx = canvas.getContext('2d')!
139
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
140
+ drawAllGraphs()
141
+ })
142
+ resizeObserver.observe(container)
143
+ return () => resizeObserver.disconnect()
144
+ })
145
+
146
+ const drawAllGraphs = () => {
147
+ if (!ctx) return
148
+ ctx.clearRect(0, 0, width, height)
149
+
150
+ const padding = {
151
+ top: 10,
152
+ right: 10,
153
+ bottom: 20,
154
+ left: 35,
155
+ }
156
+
157
+ const graphWidth = width - padding.left - padding.right
158
+ const graphHeight = height - padding.top - padding.bottom
159
+
160
+ const allPoints = graphData.flatMap((g) => g.points)
161
+ const minX = Math.min(...allPoints.map((p) => p.x))
162
+ const maxX = Math.max(...allPoints.map((p) => p.x))
163
+ const timeSpan = maxX - minX || 1
164
+
165
+ // Функции преобразования координат с учетом отступов
166
+ const getX = (x: number) => padding.left + ((x - minX) / timeSpan) * graphWidth
167
+ const getY = (y: number) => padding.top + graphHeight - (((y / selectedScale) * graphHeight) / 2 + graphHeight / 2)
168
+
169
+ /* Сетка X */
170
+ ctx.strokeStyle = '#777'
171
+ ctx.fillStyle = '#777'
172
+ ctx.lineWidth = 0.5
173
+ ctx.font = '10px monospace'
174
+ ctx.textAlign = 'center'
175
+
176
+ const now = Date.now()
177
+ for (let i = 0; i <= 10; i++) {
178
+ const t = minX + (i / 10) * timeSpan
179
+ const x = getX(t)
180
+ const secondsAgo = ((t - now) / 1000).toFixed(0)
181
+ ctx.beginPath()
182
+ ctx.moveTo(x, padding.top)
183
+ ctx.lineTo(x, height - padding.bottom)
184
+ ctx.stroke()
185
+ ctx.textBaseline = 'top'
186
+ ctx.fillText(`${secondsAgo}s`, x, height - padding.bottom + 2)
187
+ }
188
+
189
+ /* Сетка Y */
190
+ ctx.textAlign = 'right'
191
+ ctx.textBaseline = 'middle'
192
+ const ySteps = 8
193
+ for (let i = 0; i <= ySteps; i++) {
194
+ const yVal = selectedScale - (i * 2 * selectedScale) / ySteps
195
+ const y = getY(yVal)
196
+ ctx.beginPath()
197
+ ctx.moveTo(padding.left, y)
198
+ ctx.lineTo(width - padding.right, y)
199
+ ctx.stroke()
200
+ ctx.fillText(`${yVal.toFixed(0)}`, padding.left - 5, y)
201
+ }
202
+
203
+ /* Отрисовка графиков */
204
+ ctx.lineWidth = 2
205
+ graphData.forEach(({ points, color }) => {
206
+ if (points.length < 2) return
207
+ ctx.strokeStyle = color
208
+ ctx.beginPath()
209
+ ctx.moveTo(getX(points[0].x), getY(points[0].y))
210
+ for (let i = 1; i < points.length - 1; i++) {
211
+ const p1 = points[i]
212
+ const p2 = points[i + 1]
213
+ const xc2 = (p1.x + p2.x) / 2
214
+ const yc2 = (p1.y + p2.y) / 2
215
+ ctx.quadraticCurveTo(getX(p1.x), getY(p1.y), getX(xc2), getY(yc2))
216
+ }
217
+ const last = points[points.length - 1]
218
+ ctx.lineTo(getX(last.x), getY(last.y))
219
+ ctx.stroke()
220
+ })
221
+ }
222
+ </script>
223
+
224
+ <div id={id.value} class={`relative flex w-full flex-col items-center justify-center ${wrapperClass}`}>
225
+ {#if label.name}
226
+ <h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
227
+ {/if}
228
+
229
+ <div class="flex w-full flex-row gap-4">
230
+ <!-- График -->
231
+ <div bind:this={container} class="h-64 flex-grow overflow-hidden rounded-md border border-gray-200">
232
+ <canvas class="h-full w-full bg-[var(--back-color)]" bind:this={canvas}></canvas>
233
+ </div>
234
+
235
+ <!-- Панель настроек -->
236
+ <div class="flex w-48 flex-col gap-2">
237
+ <!-- Развертка по горизонтали -->
238
+ <Select
239
+ label={{ name: 'Refresh rate', class: '' }}
240
+ options={REFRESH_OPTIONS}
241
+ value={REFRESH_OPTIONS.find((o) => o.value == selectedRefreshRate)}
242
+ onUpdate={(value) => (selectedRefreshRate = value.value as number)}
243
+ />
244
+
245
+ <!-- Масштаб по вертикали -->
246
+ <Select
247
+ label={{ name: 'Scale' }}
248
+ options={SCALE_OPTIONS}
249
+ value={REFRESH_OPTIONS.find((o) => o.value == selectedScale)}
250
+ onUpdate={(value) => (selectedScale = value.value as number)}
251
+ />
252
+
253
+ <!-- Переменные и их значение -->
254
+ <div>
255
+ <h5 class="px-4">Values</h5>
256
+ <table class="w-full font-mono text-sm">
257
+ <tbody>
258
+ {#each graphData as data, i (i)}
259
+ <tr>
260
+ <td><div class="mr-2 h-4 w-4 rounded-full" style="background-color: {data.color}"></div></td>
261
+ <td class="w-24 truncate text-left font-semibold">{(streamingData.data as IGraphDataObject[])?.[i]?.name}</td>
262
+ <td class="w-16 text-right">{currentValues[i].toFixed(2)}</td>
263
+ </tr>
264
+ {/each}
265
+ </tbody>
266
+ </table>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { IGraphProps } from '../types';
2
+ declare const Graph: import("svelte").Component<IGraphProps, {}, "">;
3
+ type Graph = ReturnType<typeof Graph>;
4
+ export default Graph;
@@ -0,0 +1,56 @@
1
+ <!-- $lib/ElementsUI/SwitchProps.svelte -->
2
+ <script lang="ts">
3
+ import { getContext } from 'svelte'
4
+ import { t } from '../locales/i18n'
5
+ import type { UIComponent, IGraphProps } from '../types'
6
+ import * as UI from '../index'
7
+
8
+ const { component, onPropertyChange } = $props<{
9
+ component: UIComponent & { properties: Partial<IGraphProps> }
10
+ onPropertyChange: (value: string | object) => void
11
+ }>()
12
+
13
+ const DeviceVariables = getContext<{ value: string; name: string }[]>('DeviceVariables')
14
+ let VARIABLE_OPTIONS = $derived(
15
+ DeviceVariables.map((variable: { value: string; name: string }) => ({
16
+ id: variable.name,
17
+ value: variable.value,
18
+ name: `${variable.value} | ${variable.name}`,
19
+ })),
20
+ )
21
+
22
+ /* Обновление свойства */
23
+ const updateProperty = (path: string, value: string | object) => {
24
+ const newProperties = JSON.parse(JSON.stringify(component.properties))
25
+ const parts = path.split('.')
26
+ let obj = newProperties
27
+
28
+ for (let i = 0; i < parts.length - 1; i++) {
29
+ const part = parts[i]
30
+ if (!obj[part]) obj[part] = {}
31
+ obj = obj[part]
32
+ }
33
+
34
+ obj[parts[parts.length - 1]] = value
35
+ onPropertyChange(newProperties)
36
+ }
37
+ </script>
38
+
39
+ {#if component && component.properties}
40
+ <div class="relative flex flex-row items-start justify-center">
41
+ <!-- Сообщение для отправки в ws по нажатию кнопки -->
42
+ <div class="flex w-1/3 flex-col items-center px-2">
43
+ <UI.Select
44
+ label={{ name: $t('service.constructor.props.variable') }}
45
+ options={VARIABLE_OPTIONS}
46
+ value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id.value)}
47
+ onUpdate={(value) => {
48
+ updateProperty('id.name', (value.name as string).split('|')[1].trim())
49
+ updateProperty('id.value', value.value as string)
50
+ }}
51
+ />
52
+ </div>
53
+ <div class="flex w-1/3 flex-col px-2"></div>
54
+ <div class="flex w-1/3 flex-col px-2"></div>
55
+ </div>
56
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { UIComponent, IGraphProps } from '../types';
2
+ type $$ComponentProps = {
3
+ component: UIComponent & {
4
+ properties: Partial<IGraphProps>;
5
+ };
6
+ onPropertyChange: (value: string | object) => void;
7
+ };
8
+ declare const GraphProps: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type GraphProps = ReturnType<typeof GraphProps>;
10
+ export default GraphProps;
@@ -0,0 +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>
@@ -0,0 +1,4 @@
1
+ import type { IInputProps } from '../types';
2
+ declare const Input: import("svelte").Component<IInputProps, {}, "value">;
3
+ type Input = ReturnType<typeof Input>;
4
+ export default Input;