poe-svelte-ui-lib 1.0.6 → 1.0.7

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 (54) hide show
  1. package/dist/Accordion/Accordion.svelte +51 -40
  2. package/dist/Accordion/AccordionProps.svelte +76 -0
  3. package/dist/Accordion/AccordionProps.svelte.d.ts +10 -0
  4. package/dist/Button/Button.svelte +28 -34
  5. package/dist/Button/ButtonProps.svelte +113 -0
  6. package/dist/Button/ButtonProps.svelte.d.ts +10 -0
  7. package/dist/ColorPicker/ColorPicker.svelte +27 -14
  8. package/dist/ColorPicker/ColorPickerProps.svelte +71 -0
  9. package/dist/ColorPicker/ColorPickerProps.svelte.d.ts +10 -0
  10. package/dist/{FileAttach/FileAttach.svelte → FileAttach.svelte} +3 -11
  11. package/dist/{FileAttach/FileAttach.svelte.d.ts → FileAttach.svelte.d.ts} +1 -1
  12. package/dist/Graph/Graph.svelte +3 -3
  13. package/dist/Graph/GraphProps.svelte +41 -0
  14. package/dist/Graph/GraphProps.svelte.d.ts +10 -0
  15. package/dist/Input/Input.svelte +42 -48
  16. package/dist/Input/InputProps.svelte +205 -0
  17. package/dist/Input/InputProps.svelte.d.ts +10 -0
  18. package/dist/Modal.svelte +54 -0
  19. package/dist/Modal.svelte.d.ts +12 -0
  20. package/dist/ProgressBar/ProgressBar.svelte +23 -21
  21. package/dist/ProgressBar/ProgressBarProps.svelte +114 -0
  22. package/dist/ProgressBar/ProgressBarProps.svelte.d.ts +10 -0
  23. package/dist/Select/Select.svelte +38 -23
  24. package/dist/Select/SelectProps.svelte +216 -0
  25. package/dist/Select/SelectProps.svelte.d.ts +10 -0
  26. package/dist/Slider/Slider.svelte +17 -10
  27. package/dist/Slider/SliderProps.svelte +113 -0
  28. package/dist/Slider/SliderProps.svelte.d.ts +10 -0
  29. package/dist/Switch/Switch.svelte +15 -10
  30. package/dist/Switch/SwitchProps.svelte +99 -0
  31. package/dist/Switch/SwitchProps.svelte.d.ts +10 -0
  32. package/dist/Table/Table.svelte +62 -38
  33. package/dist/Table/Table.svelte.d.ts +1 -1
  34. package/dist/Table/TableProps.svelte +233 -0
  35. package/dist/Table/TableProps.svelte.d.ts +10 -0
  36. package/dist/TextField/TextField.svelte +15 -9
  37. package/dist/TextField/TextFieldProps.svelte +44 -44
  38. package/dist/TextField/TextFieldProps.svelte.d.ts +1 -1
  39. package/dist/index.d.ts +2 -3
  40. package/dist/index.js +2 -3
  41. package/dist/libIcons/ButtonAdd.svelte +5 -2
  42. package/dist/libIcons/ButtonDelete.svelte +1 -1
  43. package/dist/libIcons/CrossIcon.svelte +9 -0
  44. package/dist/libIcons/CrossIcon.svelte.d.ts +18 -0
  45. package/dist/locales/translations.js +81 -6
  46. package/dist/options.d.ts +7 -12
  47. package/dist/options.js +44 -33
  48. package/dist/types.d.ts +50 -89
  49. package/dist/types.js +13 -1
  50. package/package.json +7 -3
  51. package/dist/Loader.svelte +0 -12
  52. package/dist/Loader.svelte.d.ts +0 -5
  53. package/dist/MessageModal.svelte +0 -54
  54. package/dist/MessageModal.svelte.d.ts +0 -10
@@ -17,6 +17,6 @@ interface FileInputProps {
17
17
  currentImage?: string | null;
18
18
  onChange?: (event: Event, file: File | null) => void;
19
19
  }
20
- declare const FileAttach: import("svelte").Component<FileInputProps, {}, "">;
20
+ declare const FileAttach: import("svelte").Component<FileInputProps, {}, "currentImage">;
21
21
  type FileAttach = ReturnType<typeof FileAttach>;
22
22
  export default FileAttach;
@@ -6,7 +6,7 @@
6
6
 
7
7
  /* Инициализация пропсов с дефолтными значениями */
8
8
  let {
9
- id = { name: '', value: crypto.randomUUID() },
9
+ id = crypto.randomUUID(),
10
10
  wrapperClass = '',
11
11
  label = { name: '', class: '' },
12
12
  streamingData = { data: [], timestamp: Date.now() },
@@ -63,7 +63,7 @@
63
63
  return {
64
64
  id: existingData?.id || crypto.randomUUID(),
65
65
  points: existingData?.points || [],
66
- color: d.color || defaultColors[i % defaultColors.length],
66
+ color: defaultColors[i % defaultColors.length],
67
67
  name: d.name || `Value ${i}`,
68
68
  }
69
69
  })
@@ -219,7 +219,7 @@
219
219
  }
220
220
  </script>
221
221
 
222
- <div id={id.value} class={`relative flex w-full flex-col items-center justify-center ${wrapperClass}`}>
222
+ <div {id} class={`relative flex w-full flex-col items-center justify-center ${wrapperClass}`}>
223
223
  {#if label.name}
224
224
  <h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
225
225
  {/if}
@@ -0,0 +1,41 @@
1
+ <!-- $lib/ElementsUI/SwitchProps.svelte -->
2
+ <script lang="ts">
3
+ import { getContext } from 'svelte'
4
+ import { t } from '../locales/i18n'
5
+ import { type UIComponent, type IGraphProps, updateProperty } from '../types'
6
+ import * as UI from '..'
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 && Array.isArray(DeviceVariables)
16
+ ? DeviceVariables.map((variable) => ({
17
+ id: variable.name,
18
+ value: variable.value,
19
+ name: `${variable.value} | ${variable.name}`,
20
+ }))
21
+ : [],
22
+ )
23
+ </script>
24
+
25
+ {#if component && component.properties}
26
+ <div class="relative flex flex-row items-start justify-center">
27
+ <!-- Сообщение для отправки в ws по нажатию кнопки -->
28
+ <div class="flex w-1/3 flex-col items-center px-2">
29
+ <UI.Select
30
+ label={{ name: $t('constructor.props.variable') }}
31
+ options={VARIABLE_OPTIONS}
32
+ value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id.value)}
33
+ onUpdate={(value) => {
34
+ updateProperty('id', value.value as string, component, onPropertyChange)
35
+ }}
36
+ />
37
+ </div>
38
+ <div class="flex w-1/3 flex-col px-2"></div>
39
+ <div class="flex w-1/3 flex-col px-2"></div>
40
+ </div>
41
+ {/if}
@@ -0,0 +1,10 @@
1
+ import { type UIComponent, type 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;
@@ -3,23 +3,22 @@
3
3
  import { onMount } from 'svelte'
4
4
  import { fly } from 'svelte/transition'
5
5
  import type { IInputProps } from '../types'
6
+ import { twMerge } from 'tailwind-merge'
6
7
 
7
8
  let {
8
- id = { name: '', value: crypto.randomUUID() },
9
+ id = crypto.randomUUID(),
9
10
  wrapperClass = '',
10
11
  label = { name: '', class: '' },
11
12
  disabled = false,
12
13
  readonly = false,
13
14
  value = $bindable(),
14
15
  type = 'text',
15
- autocomplete = 'off',
16
+ placeholder = '',
16
17
  componentClass = '',
17
18
  maxlength = 100,
18
- number = { minNum: -1000000, maxNum: 1000000, step: 1 },
19
19
  textareaRows = 3,
20
- copyButton = false,
21
- regExp = '^[\\s\\S]*$',
22
- help = { placeholder: '', info: '' },
20
+ number = { minNum: -1000000, maxNum: Infinity, step: 1 },
21
+ help = { info: '', autocomplete: 'off', copyButton: false, regExp: '^[\\s\\S]*$' },
23
22
  onUpdate = () => {},
24
23
  }: IInputProps = $props()
25
24
 
@@ -27,20 +26,6 @@
27
26
  let showInfo = $state(false)
28
27
  let isCopied = $state(false)
29
28
 
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
29
  $effect(() => {
45
30
  if (type === 'number') {
46
31
  if (value === undefined || value === null || value === '') value = number.minNum
@@ -53,7 +38,7 @@
53
38
  const match = pattern.match(/^\/(.*)\/([gimsuy]*)$/)
54
39
  return match ? new RegExp(match[1], match[2]) : new RegExp(pattern)
55
40
  }
56
- let RegExpObj = $derived(() => parseRegExp(regExp))
41
+ let RegExpObj = $derived(() => parseRegExp(help.regExp ?? ''))
57
42
  let isValid = $derived(RegExpObj().test(typeof value === 'string' ? value : String(value)))
58
43
 
59
44
  const handleInputChange = (value: string | number) => {
@@ -67,28 +52,30 @@
67
52
  }
68
53
  </script>
69
54
 
70
- <div class="bg-max relative flex w-full flex-col items-center {type === 'text-area' ? 'h-full' : ''} {wrapperClass}">
55
+ <div class={twMerge(`bg-max ${type === 'text-area' ? 'h-full' : ''} relative flex w-full flex-col items-center`, wrapperClass)}>
71
56
  {#if label.name}
72
- <h5 class={`w-full px-4 text-center ${label.class}`}>{label.name}</h5>
57
+ <h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name}</h5>
73
58
  {/if}
74
59
 
75
60
  <div class="relative flex w-full items-center {type === 'text-area' ? 'h-full' : ''}">
76
61
  {#if type === 'text' || type === 'password' || type === 'number'}
77
62
  <input
78
63
  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
64
+ class={twMerge(
65
+ `w-full rounded-2xl border px-4 py-1 text-center transition-all duration-300 outline-none focus:border-blue-400
80
66
  [&::-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}"
67
+ ${isValid ? 'border-[var(--border-color)]' : '!border-red-400 shadow-[0_0_6px_var(--red-color)]'}
68
+ ${disabled ? 'opacity-50' : 'hover:shadow-md'}
69
+ ${readonly ? '' : 'hover:shadow-md'}
70
+ ${help?.info ? 'pl-8' : ''}
71
+ ${help.copyButton || type === 'password' || type === 'number' ? 'pr-8' : ''}`,
72
+ componentClass,
73
+ )}
87
74
  style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
88
- id={id.value}
89
- placeholder={help?.placeholder}
75
+ {id}
76
+ {placeholder}
90
77
  {disabled}
91
- {autocomplete}
78
+ autocomplete={help?.autocomplete}
92
79
  oninput={(e) => handleInputChange((e.currentTarget as HTMLInputElement).value)}
93
80
  type={type === 'password' ? (showPassword ? 'text' : 'password') : type === 'number' ? 'number' : 'text'}
94
81
  {maxlength}
@@ -100,19 +87,21 @@
100
87
  {:else if type === 'text-area'}
101
88
  <textarea
102
89
  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}"
90
+ class={twMerge(
91
+ `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
92
+ ${isValid ? 'border-[var(--border-color)]' : '!border-red-400 shadow-[0_0_6px_var(--red-color)]'}
93
+ ${disabled ? 'opacity-50' : 'hover:shadow-md'}
94
+ ${readonly ? '' : 'hover:shadow-md'}
95
+ ${help?.info ? 'pl-8' : ''}
96
+ ${help.copyButton ? 'pr-8' : ''}`,
97
+ componentClass,
98
+ )}
110
99
  style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
111
- id={id.value}
100
+ {id}
112
101
  {disabled}
113
102
  {maxlength}
114
103
  rows={textareaRows}
115
- placeholder={help?.placeholder}
104
+ {placeholder}
116
105
  {readonly}
117
106
  oninput={(e) => handleInputChange((e.currentTarget as HTMLTextAreaElement).value)}
118
107
  ></textarea>
@@ -146,7 +135,7 @@
146
135
  </button>
147
136
  {/if}
148
137
 
149
- {#if copyButton && (type === 'text' || type === 'text-area')}
138
+ {#if help.copyButton && (type === 'text' || type === 'text-area')}
150
139
  <button
151
140
  type="button"
152
141
  class="absolute right-2 flex cursor-pointer border-none bg-transparent {type === 'text-area' ? 'top-2' : ''}"
@@ -215,13 +204,19 @@
215
204
  {#if help.info}
216
205
  <button
217
206
  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)}
207
+ class="button-info absolute left-2 flex border-none bg-transparent {type === 'text-area' ? 'top-2' : ''} {disabled
208
+ ? 'opacity-50'
209
+ : 'cursor-pointer'}"
210
+ onmouseenter={() => (showInfo = true)}
211
+ onmouseleave={() => (showInfo = false)}
220
212
  aria-label={showInfo ? 'Скрыть инфо' : 'Показать инфо'}
221
213
  >
222
214
  <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"
223
215
  ><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
216
+ ><circle cx="12" cy="12" r="10" stroke-width="1.3" /><path stroke-width="1.5" d="M12 16v-4.5" /><path
217
+ stroke-width="1.8"
218
+ d="M12 8.012v-.01"
219
+ /></g
225
220
  ></svg
226
221
  >
227
222
  </button>
@@ -229,8 +224,7 @@
229
224
  {#if showInfo}
230
225
  <div
231
226
  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));"
227
+ class="absolute top-1/2 left-10 z-50 w-auto -translate-y-1/2 rounded bg-[var(--container-color)] px-2 py-1 shadow-lg"
234
228
  >
235
229
  {help?.info}
236
230
  </div>
@@ -0,0 +1,205 @@
1
+ <!-- $lib/ElementsUI/InputProps.svelte -->
2
+ <script lang="ts">
3
+ import { getContext } from 'svelte'
4
+ import { t } from '../locales/i18n'
5
+ import type { IInputProps, UIComponent, ISelectOption } from '../types'
6
+ import * as UI from '..'
7
+ import { optionsStore } from '../options'
8
+
9
+ const { component, onPropertyChange } = $props<{
10
+ component: UIComponent & { properties: Partial<IInputProps> }
11
+ onPropertyChange: (value: string | object) => void
12
+ }>()
13
+
14
+ let isValidRegExp = $state(true)
15
+ const DeviceVariables = getContext<{ value: string; name: string }[]>('DeviceVariables')
16
+ let VARIABLE_OPTIONS: ISelectOption<string>[] = $derived(
17
+ DeviceVariables && Array.isArray(DeviceVariables)
18
+ ? DeviceVariables.map((variable) => ({
19
+ id: variable.name,
20
+ value: variable.value,
21
+ name: `${variable.value} | ${variable.name}`,
22
+ }))
23
+ : [],
24
+ )
25
+
26
+ const initialColor = $derived(
27
+ $optionsStore.COLOR_OPTIONS.find((c) =>
28
+ (c.value as string).includes(component.properties.componentClass?.split(' ').find((cls: string) => cls.startsWith('bg-'))),
29
+ ),
30
+ )
31
+
32
+ const initialAlign = $derived(
33
+ $optionsStore.ALIGN_OPTIONS.find((a) =>
34
+ (a.value as string).includes(component.properties.label?.class?.split(' ').find((cls: string) => cls.startsWith('text-'))),
35
+ ),
36
+ )
37
+
38
+ /* Обновление свойства */
39
+ const updateProperty = (path: string, value: string | object | boolean | number | RegExp) => {
40
+ const newProperties = JSON.parse(JSON.stringify(component.properties))
41
+ if (path === 'regExp') {
42
+ try {
43
+ let regex: RegExp
44
+ if (typeof value === 'string') {
45
+ const pattern = value.match(/^\/(.*)\/([gimsuy]*)$/)
46
+
47
+ regex = pattern ? new RegExp(pattern[1], pattern[2]) : new RegExp(value)
48
+ if (pattern === null) return
49
+ regex.test('')
50
+ } else {
51
+ throw new Error('Invalid RegExp type')
52
+ }
53
+
54
+ newProperties.regExp = regex
55
+ isValidRegExp = true
56
+ } catch (error) {
57
+ console.warn('Invalid RegExp:', error)
58
+ newProperties.regExp = typeof value === 'string' ? value : String(value)
59
+ isValidRegExp = false
60
+ return
61
+ }
62
+ }
63
+
64
+ const parts = path.split('.')
65
+ let obj = newProperties
66
+
67
+ for (let i = 0; i < parts.length - 1; i++) {
68
+ const part = parts[i]
69
+ if (!obj[part]) obj[part] = {}
70
+ obj = obj[part]
71
+ }
72
+
73
+ obj[parts[parts.length - 1]] = value
74
+ onPropertyChange(newProperties)
75
+ }
76
+
77
+ const handleOptionColorChange = (color: string) => {
78
+ let componentClass = component.properties.componentClass || ''
79
+
80
+ componentClass = componentClass
81
+ .split(' ')
82
+ .filter((cls: string) => !cls.startsWith('bg-'))
83
+ .join(' ')
84
+
85
+ if (color) {
86
+ componentClass += ` ${color}`
87
+ }
88
+
89
+ updateProperty('componentClass', componentClass)
90
+ }
91
+ </script>
92
+
93
+ {#if component && component.properties}
94
+ <div class="relative flex flex-row items-start justify-center">
95
+ <!-- Сообщение для отправки в ws по нажатию кнопки -->
96
+ <div class="flex w-1/3 flex-col items-center px-2">
97
+ <UI.Select
98
+ label={{ name: $t('constructor.props.variable') }}
99
+ options={VARIABLE_OPTIONS}
100
+ value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id.value)}
101
+ onUpdate={(selectedOption) => {
102
+ if (selectedOption && selectedOption.name) {
103
+ updateProperty('id', selectedOption.value as string)
104
+ updateProperty('eventHandler.Variables', selectedOption.value as string)
105
+ }
106
+ }}
107
+ />
108
+ <UI.Select
109
+ label={{ name: $t('constructor.props.type') }}
110
+ options={$optionsStore.INPUT_TYPE_OPTIONS}
111
+ type="buttons"
112
+ value={$optionsStore.INPUT_TYPE_OPTIONS.find((opt) => opt.value === (component.properties.type || 'text'))}
113
+ onUpdate={(selectedOption) => updateProperty('type', selectedOption.value as string)}
114
+ />
115
+ {#if component.properties.type === 'text' || component.properties.type === 'password' || component.properties.type === 'text-area'}
116
+ <UI.Input
117
+ label={{ name: $t('constructor.props.maxlenght') }}
118
+ value={component.properties.maxlength}
119
+ onUpdate={(value) => updateProperty('maxlength', value as string)}
120
+ />
121
+ <UI.Input
122
+ label={{ name: $t('constructor.props.regexp') }}
123
+ value={component.properties.help.regExp}
124
+ maxlength={150}
125
+ help={{ info: $t('constructor.props.regexp.info') }}
126
+ componentClass={isValidRegExp === false ? '!border-2 !border-red-400' : ''}
127
+ onUpdate={(value) => updateProperty('help.regExp', value)}
128
+ />
129
+ {:else if component.properties.type === 'number' && !component.properties.readonly && !component.properties.disabled}
130
+ <UI.Input
131
+ label={{ name: $t('constructor.props.minnum') }}
132
+ value={component.properties.number.minNum as number}
133
+ type="number"
134
+ onUpdate={(value) => {
135
+ // if ((value as number) >= component.properties.number.maxNum) {
136
+ // value = component.properties.number.maxNum - component.properties.number.step
137
+ // }
138
+ updateProperty('number.minNum', Number(value))
139
+ }}
140
+ />
141
+ <UI.Input
142
+ label={{ name: $t('constructor.props.maxnum') }}
143
+ value={component.properties.number.maxNum as number}
144
+ type="number"
145
+ onUpdate={(value) => {
146
+ // if ((value as number) <= component.properties.number.minNum) {
147
+ // value = component.properties.number.minNum + component.properties.number.step
148
+ // }
149
+ updateProperty('number.maxNum', Number(value))
150
+ }}
151
+ />
152
+ <UI.Input
153
+ label={{ name: $t('constructor.props.step') }}
154
+ value={component.properties.number.step as number}
155
+ type="number"
156
+ onUpdate={(value) => updateProperty('number.step', Number(value))}
157
+ />
158
+ {/if}
159
+ </div>
160
+ <div class="flex w-1/3 flex-col px-2">
161
+ <UI.Input
162
+ label={{ name: $t('constructor.props.placeholder') }}
163
+ value={component.properties.placeholder as string}
164
+ onUpdate={(value) => updateProperty('placeholder', value)}
165
+ />
166
+ <UI.Input
167
+ label={{ name: $t('constructor.props.info') }}
168
+ value={component.properties.help.info as string}
169
+ onUpdate={(value) => updateProperty('help.info', value)}
170
+ />
171
+ <UI.Switch
172
+ label={{ name: $t('constructor.props.readonly') }}
173
+ value={component.properties.readonly ? 2 : 1}
174
+ onChange={(value) => updateProperty('readonly', value === 2)}
175
+ />
176
+ <UI.Switch
177
+ label={{ name: $t('constructor.props.copy') }}
178
+ value={component.properties.help.copyButton ? 2 : 1}
179
+ onChange={(value) => updateProperty('help.copyButton', value === 2)}
180
+ />
181
+ </div>
182
+ <div class="flex w-1/3 flex-col px-2">
183
+ <UI.Input
184
+ label={{ name: $t('constructor.props.label') }}
185
+ value={component.properties.label.name}
186
+ onUpdate={(value) => updateProperty('label.name', value as string)}
187
+ />
188
+ <UI.Select
189
+ label={{ name: $t('constructor.props.align') }}
190
+ type="buttons"
191
+ value={initialAlign}
192
+ options={$optionsStore.ALIGN_OPTIONS}
193
+ onUpdate={(option) => updateProperty('label.class', `${component.properties.label.class} ${option.value}`)}
194
+ />
195
+ <UI.Select
196
+ wrapperClass="h-14"
197
+ label={{ name: $t('constructor.props.colors') }}
198
+ type="buttons"
199
+ options={$optionsStore.COLOR_OPTIONS}
200
+ value={initialColor}
201
+ onUpdate={(option) => handleOptionColorChange(option.value as string)}
202
+ />
203
+ </div>
204
+ </div>
205
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { IInputProps, UIComponent } from '../types';
2
+ type $$ComponentProps = {
3
+ component: UIComponent & {
4
+ properties: Partial<IInputProps>;
5
+ };
6
+ onPropertyChange: (value: string | object) => void;
7
+ };
8
+ declare const InputProps: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type InputProps = ReturnType<typeof InputProps>;
10
+ export default InputProps;
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import { onMount, type Snippet } from 'svelte'
3
+ import { fade, scale } from 'svelte/transition'
4
+ import { twMerge } from 'tailwind-merge'
5
+ import CrossIcon from './libIcons/CrossIcon.svelte'
6
+
7
+ let {
8
+ isOpen = $bindable(false),
9
+ title,
10
+ wrapperClass = '',
11
+ main,
12
+ footer,
13
+ onCancel = () => (isOpen = false),
14
+ }: {
15
+ isOpen?: boolean
16
+ title?: string
17
+ wrapperClass?: string
18
+ main: Snippet
19
+ footer?: Snippet
20
+ onCancel?: () => void
21
+ } = $props()
22
+
23
+ const handleKeyDown = (event: KeyboardEvent) => {
24
+ if (event.key === 'Escape') isOpen = false
25
+ }
26
+
27
+ onMount(() => {
28
+ document.addEventListener('keydown', handleKeyDown)
29
+ return () => document.removeEventListener('keydown', handleKeyDown)
30
+ })
31
+ </script>
32
+
33
+ {#if isOpen}
34
+ <div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" transition:fade={{ duration: 200 }}>
35
+ <div
36
+ class={twMerge(`flex w-300 flex-col overflow-hidden rounded-2xl bg-[var(--back-color)] text-center`, wrapperClass)}
37
+ transition:scale={{ duration: 250, start: 0.8 }}
38
+ >
39
+ <div class="flex items-end justify-between bg-[var(--field-color)] px-6 py-3">
40
+ <h4>{title}</h4>
41
+ <button class="h-6 w-6 cursor-pointer" onclick={onCancel}> <CrossIcon /> </button>
42
+ </div>
43
+
44
+ <div class="flex h-full w-full flex-col overflow-auto p-2">
45
+ {@render main?.()}
46
+ </div>
47
+ {#if footer}
48
+ <div class="flex flex-row-reverse justify-between bg-[var(--field-color)] p-1.5">
49
+ {@render footer?.()}
50
+ </div>
51
+ {/if}
52
+ </div>
53
+ </div>
54
+ {/if}
@@ -0,0 +1,12 @@
1
+ import { type Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ isOpen?: boolean;
4
+ title?: string;
5
+ wrapperClass?: string;
6
+ main: Snippet;
7
+ footer?: Snippet;
8
+ onCancel?: () => void;
9
+ };
10
+ declare const Modal: import("svelte").Component<$$ComponentProps, {}, "isOpen">;
11
+ type Modal = ReturnType<typeof Modal>;
12
+ export default Modal;
@@ -1,48 +1,50 @@
1
1
  <!-- $lib/ElementsUI/ProgressBar.svelte -->
2
2
  <script lang="ts">
3
+ import { twMerge } from 'tailwind-merge'
3
4
  import type { IProgressBarProps } from '../types'
4
5
 
5
6
  let {
6
- id = { name: '', value: crypto.randomUUID() },
7
+ id = crypto.randomUUID(),
7
8
  label = { name: '', class: '' },
8
9
  value = $bindable(0),
9
- range = {
10
- min: 0,
11
- max: 100,
10
+ number = {
11
+ minNum: 0,
12
+ maxNum: 100,
12
13
  units: '%',
13
14
  },
14
15
  wrapperClass = '',
15
16
  }: IProgressBarProps = $props()
16
17
 
17
- let numericValue = $state(0)
18
- const min = $derived(range.min ?? 0)
19
- const max = $derived(range.max ?? 100)
18
+ const min = $derived(number.minNum ?? 0)
19
+ const max = $derived(number.maxNum ?? 100)
20
20
 
21
- $effect(() => {
22
- if (typeof value === 'number' && !isNaN(value)) {
23
- numericValue = Math.max(min, Math.min(max, value))
24
- } else if (typeof value === 'string') {
25
- const parsedValue = parseFloat(value)
26
- if (!isNaN(parsedValue)) {
27
- numericValue = Math.max(min, Math.min(max, parsedValue))
21
+ let numericValue = $derived(
22
+ (() => {
23
+ if (typeof value === 'number' && !isNaN(value)) {
24
+ return Math.max(min, Math.min(max, value))
25
+ } else if (typeof value === 'string') {
26
+ const parsedValue = parseFloat(value)
27
+ if (!isNaN(parsedValue)) {
28
+ return Math.max(min, Math.min(max, parsedValue))
29
+ }
30
+ } else {
31
+ return min
28
32
  }
29
- } else {
30
- numericValue = min
31
- }
32
- })
33
+ })(),
34
+ )
33
35
 
34
36
  let progressPercent = $derived((((value as number) - min) / (max - min)) * 100)
35
37
  </script>
36
38
 
37
- <div id={id.value} class={`relative flex w-full flex-col items-center ${wrapperClass}`}>
39
+ <div {id} class={twMerge(`relative flex w-full flex-col items-center`, wrapperClass)}>
38
40
  {#if label.name}
39
- <h5 class={`mb-1 w-full px-4 text-center ${label.class}`}>{label.name}</h5>
41
+ <h5 class={twMerge(`mb-1 w-full px-4 text-center`, label.class)}>{label.name}</h5>
40
42
  {/if}
41
43
 
42
44
  <div class="flex w-full flex-col items-center">
43
45
  <div class="relative h-2 w-full rounded bg-gray-400">
44
46
  <div class="absolute top-0 left-0 h-full rounded bg-[var(--bg-color)]" style="width: {progressPercent}%;"></div>
45
47
  </div>
46
- <span class="ml-2 font-semibold">{numericValue.toFixed(2)}{range.units}</span>
48
+ <span class="ml-2 font-semibold">{numericValue?.toFixed(2)}{number.units}</span>
47
49
  </div>
48
50
  </div>