poe-svelte-ui-lib 1.2.30 → 1.2.32

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.
@@ -1,18 +1,7 @@
1
1
  <script lang="ts">
2
+ import type { IFileAttachProps } from '../types'
2
3
  import { twMerge } from 'tailwind-merge'
3
4
 
4
- export interface IFileInputProps {
5
- id?: string
6
- wrapperClass?: string
7
- label?: { name?: string; class?: string }
8
- type?: 'file' | 'image'
9
- accept?: string
10
- imageSize?: { height?: string; width?: string; fitMode?: 'cover' | 'contain'; form?: 'square' | 'circle' }
11
- disabled?: boolean
12
- currentImage?: string | null
13
- onChange?: (event: Event, file: File | null) => void
14
- }
15
-
16
5
  let {
17
6
  id = crypto.randomUUID(),
18
7
  wrapperClass = '',
@@ -23,7 +12,7 @@
23
12
  disabled = false,
24
13
  currentImage = $bindable(''),
25
14
  onChange = () => {},
26
- }: IFileInputProps = $props()
15
+ }: IFileAttachProps = $props()
27
16
 
28
17
  let ID = `${id}-${crypto.randomUUID().slice(0, 6)}`
29
18
  let selectedFile = $state<File | null>(null)
@@ -1,22 +1,4 @@
1
- export interface IFileInputProps {
2
- id?: string;
3
- wrapperClass?: string;
4
- label?: {
5
- name?: string;
6
- class?: string;
7
- };
8
- type?: 'file' | 'image';
9
- accept?: string;
10
- imageSize?: {
11
- height?: string;
12
- width?: string;
13
- fitMode?: 'cover' | 'contain';
14
- form?: 'square' | 'circle';
15
- };
16
- disabled?: boolean;
17
- currentImage?: string | null;
18
- onChange?: (event: Event, file: File | null) => void;
19
- }
20
- declare const FileAttach: import("svelte").Component<IFileInputProps, {}, "currentImage">;
1
+ import type { IFileAttachProps } from '../types';
2
+ declare const FileAttach: import("svelte").Component<IFileAttachProps, {}, "currentImage">;
21
3
  type FileAttach = ReturnType<typeof FileAttach>;
22
4
  export default FileAttach;
@@ -1,23 +1,76 @@
1
1
  <!-- $lib/ElementsUI/ButtonProps.svelte -->
2
2
  <script lang="ts">
3
3
  import { t } from '../locales/i18n'
4
- import { type IUIComponentHandler, type UIComponent, updateProperty } from '../types'
4
+ import { type IFileAttachProps, type IUIComponentHandler, type UIComponent, updateProperty } from '../types'
5
5
  import * as UI from '..'
6
6
  import { optionsStore } from '../options'
7
- import type { IFileInputProps } from './FileAttach.svelte'
7
+ import { getContext } from 'svelte'
8
+ import { twMerge } from 'tailwind-merge'
8
9
 
9
10
  const {
10
11
  component,
11
12
  onPropertyChange,
12
13
  forConstructor = true,
13
14
  } = $props<{
14
- component: UIComponent & { properties: Partial<IFileInputProps> }
15
+ component: UIComponent & { properties: Partial<IFileAttachProps> }
15
16
  onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void
16
17
  forConstructor?: boolean
17
18
  }>()
19
+
20
+ const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>('DeviceVariables')
21
+ let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
22
+
23
+ const initialAlign = $derived(
24
+ $optionsStore.TEXT_ALIGN_OPTIONS.find((a) =>
25
+ (a.value as string).includes(component.properties.label?.class?.split(' ').find((cls: string) => cls.startsWith('text-'))),
26
+ ),
27
+ )
18
28
  </script>
19
29
 
20
- {#if !forConstructor}
30
+ {#if forConstructor}
31
+ <div class="relative flex flex-row items-start justify-center">
32
+ <!-- Сообщение для отправки в ws по нажатию кнопки -->
33
+ <div class="flex w-1/3 flex-col items-center px-2">
34
+ <UI.Select
35
+ label={{ name: $t('constructor.props.variable') }}
36
+ options={VARIABLE_OPTIONS}
37
+ value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id)}
38
+ onUpdate={(value) => {
39
+ updateProperty('id', value.value as string, component, onPropertyChange)
40
+ onPropertyChange({ name: value.name?.split('—')[1].trim(), eventHandler: { Variables: value.value as string } })
41
+ }}
42
+ />
43
+ <UI.Select
44
+ label={{ name: $t('constructor.props.access') }}
45
+ type="buttons"
46
+ options={$optionsStore.ACCESS_OPTION}
47
+ value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
48
+ onUpdate={(option) => onPropertyChange({ access: option.value })}
49
+ />
50
+ </div>
51
+ <div class="flex w-1/3 flex-col px-2">
52
+ <UI.Input
53
+ label={{ name: $t('constructor.props.label') }}
54
+ value={component.properties.label.name}
55
+ onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
56
+ />
57
+ <UI.Select
58
+ label={{ name: $t('constructor.props.align') }}
59
+ type="buttons"
60
+ value={initialAlign}
61
+ options={$optionsStore.TEXT_ALIGN_OPTIONS}
62
+ onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
63
+ />
64
+ </div>
65
+ <div class="flex w-1/3 flex-col px-2">
66
+ <UI.Input
67
+ label={{ name: $t('constructor.props.file.accept') }}
68
+ value={component.properties.accept}
69
+ onUpdate={(value) => updateProperty('accept', value as string, component, onPropertyChange)}
70
+ />
71
+ </div>
72
+ </div>
73
+ {:else}
21
74
  <div class="relative flex flex-row items-start justify-center">
22
75
  <!-- Сообщение для отправки в ws по нажатию кнопки -->
23
76
  <div class="flex w-1/3 flex-col items-center px-2">
@@ -26,6 +79,13 @@
26
79
  value={component.properties.id}
27
80
  onUpdate={(value) => updateProperty('id', value as string, component, onPropertyChange)}
28
81
  />
82
+ <UI.Select
83
+ label={{ name: $t('constructor.props.access') }}
84
+ type="buttons"
85
+ options={$optionsStore.ACCESS_OPTION}
86
+ value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
87
+ onUpdate={(option) => onPropertyChange({ access: option.value })}
88
+ />
29
89
  <UI.Input
30
90
  label={{ name: $t('constructor.props.wrapperclass') }}
31
91
  value={component.properties.wrapperClass}
@@ -43,6 +103,13 @@
43
103
  value={component.properties.label.class}
44
104
  onUpdate={(value) => updateProperty('label.class', value as string, component, onPropertyChange)}
45
105
  />
106
+ <UI.Switch
107
+ wrapperClass="bg-blue"
108
+ label={{ name: $t('constructor.props.disabled') }}
109
+ value={component.properties.disabled}
110
+ options={[{ id: crypto.randomUUID(), value: 0, class: '' }]}
111
+ onChange={(value) => updateProperty('disabled', value, component, onPropertyChange)}
112
+ />
46
113
  </div>
47
114
  <div class="flex w-1/3 flex-col px-2">
48
115
  <UI.Input
@@ -1,8 +1,7 @@
1
- import { type IUIComponentHandler, type UIComponent } from '../types';
2
- import type { IFileInputProps } from './FileAttach.svelte';
1
+ import { type IFileAttachProps, type IUIComponentHandler, type UIComponent } from '../types';
3
2
  type $$ComponentProps = {
4
3
  component: UIComponent & {
5
- properties: Partial<IFileInputProps>;
4
+ properties: Partial<IFileAttachProps>;
6
5
  };
7
6
  onPropertyChange: (updates: Partial<{
8
7
  properties?: string | object;
@@ -33,7 +33,7 @@
33
33
  )
34
34
 
35
35
  /* Обновление свойства */
36
- const updateProperty = (path: string, value: string | object | boolean | number | RegExp, name?: string) => {
36
+ const updateProperty = (path: string, value: string | object | boolean | number | RegExp) => {
37
37
  const newProperties = JSON.parse(JSON.stringify(component.properties))
38
38
  if (path === 'regExp') {
39
39
  try {
@@ -68,7 +68,7 @@
68
68
  }
69
69
 
70
70
  obj[parts[parts.length - 1]] = value
71
- onPropertyChange({ properties: newProperties, name })
71
+ onPropertyChange({ properties: newProperties })
72
72
  }
73
73
 
74
74
  const handleOptionColorChange = (color: string) => {
@@ -96,8 +96,8 @@
96
96
  options={VARIABLE_OPTIONS}
97
97
  value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id)}
98
98
  onUpdate={(value) => {
99
- updateProperty('id', value.value as string, value.name?.split('—')[1].trim())
100
- onPropertyChange({ eventHandler: { Variables: value.value as string } })
99
+ updateProperty('id', value.value as string)
100
+ onPropertyChange({ name: value.name?.split('—')[1].trim(), eventHandler: { Variables: value.value as string } })
101
101
  }}
102
102
  />
103
103
  <UI.Select
@@ -6,15 +6,14 @@
6
6
  id = crypto.randomUUID(),
7
7
  wrapperClass = '',
8
8
  label = { name: '', class: '' },
9
- axesName = ['Roll', 'Pitch', 'Yaw'],
10
- value = $bindable([0, 0, 0]),
11
- limits = [
12
- { minNum: -100, maxNum: 100 },
13
- { minNum: -100, maxNum: 100 },
14
- { minNum: -100, maxNum: 100 },
9
+ value = $bindable([0, 0, 0, 0]),
10
+ axes = [
11
+ { name: 'Roll', minNum: -100, maxNum: 100 },
12
+ { name: 'Pitch', minNum: -100, maxNum: 100 },
13
+ { name: 'Yaw', minNum: -100, maxNum: 100 },
15
14
  ],
15
+ buttonIcon,
16
16
  onUpdate = () => {},
17
- onClick = () => {},
18
17
  }: IJoystickProps = $props()
19
18
 
20
19
  const directions = [
@@ -23,8 +22,8 @@
23
22
  angle: 30.5,
24
23
  content: true,
25
24
  onClick: () => {
26
- if (value[2] + sensitivity >= limits[2].maxNum) {
27
- value[2] = limits[2].maxNum
25
+ if (value[2] + sensitivity >= axes[2].maxNum) {
26
+ value[2] = axes[2].maxNum
28
27
  onUpdate(value)
29
28
  return
30
29
  }
@@ -38,15 +37,15 @@
38
37
  angle: 58,
39
38
  content: false,
40
39
  onClick: () => {
41
- if (value[2] + sensitivity >= limits[2].maxNum) {
42
- value[2] = limits[2].maxNum
40
+ if (value[2] + sensitivity >= axes[2].maxNum) {
41
+ value[2] = axes[2].maxNum
43
42
  onUpdate(value)
44
43
  } else {
45
44
  value[2] = roundToClean(value[2] + sensitivity)
46
45
  onUpdate(value)
47
46
  }
48
- if (value[1] - sensitivity <= limits[1].minNum) {
49
- value[1] = limits[1].minNum
47
+ if (value[1] - sensitivity <= axes[1].minNum) {
48
+ value[1] = axes[1].minNum
50
49
  onUpdate(value)
51
50
  } else {
52
51
  value[1] = roundToClean(value[1] - sensitivity)
@@ -59,8 +58,8 @@
59
58
  angle: 122,
60
59
  content: true,
61
60
  onClick: () => {
62
- if (value[1] - sensitivity <= limits[1].minNum) {
63
- value[1] = limits[1].minNum
61
+ if (value[1] - sensitivity <= axes[1].minNum) {
62
+ value[1] = axes[1].minNum
64
63
  onUpdate(value)
65
64
  return
66
65
  }
@@ -73,15 +72,15 @@
73
72
  angle: 149.5,
74
73
  content: false,
75
74
  onClick: () => {
76
- if (value[2] - sensitivity <= limits[2].minNum) {
77
- value[2] = limits[2].minNum
75
+ if (value[2] - sensitivity <= axes[2].minNum) {
76
+ value[2] = axes[2].minNum
78
77
  onUpdate(value)
79
78
  } else {
80
79
  value[2] = roundToClean(value[2] - sensitivity)
81
80
  onUpdate(value)
82
81
  }
83
- if (value[1] - sensitivity <= limits[1].minNum) {
84
- value[1] = limits[1].minNum
82
+ if (value[1] - sensitivity <= axes[1].minNum) {
83
+ value[1] = axes[1].minNum
85
84
  onUpdate(value)
86
85
  } else {
87
86
  value[1] = roundToClean(value[1] - sensitivity)
@@ -94,8 +93,8 @@
94
93
  angle: 212,
95
94
  content: true,
96
95
  onClick: () => {
97
- if (value[2] - sensitivity <= limits[2].minNum) {
98
- value[2] = limits[2].minNum
96
+ if (value[2] - sensitivity <= axes[2].minNum) {
97
+ value[2] = axes[2].minNum
99
98
  onUpdate(value)
100
99
  return
101
100
  }
@@ -108,15 +107,15 @@
108
107
  angle: 239,
109
108
  content: false,
110
109
  onClick: () => {
111
- if (value[1] + sensitivity >= limits[1].maxNum) {
112
- value[1] = limits[1].maxNum
110
+ if (value[1] + sensitivity >= axes[1].maxNum) {
111
+ value[1] = axes[1].maxNum
113
112
  onUpdate(value)
114
113
  } else {
115
114
  value[1] = roundToClean(value[1] + sensitivity)
116
115
  onUpdate(value)
117
116
  }
118
- if (value[2] - sensitivity <= limits[2].minNum) {
119
- value[2] = limits[2].minNum
117
+ if (value[2] - sensitivity <= axes[2].minNum) {
118
+ value[2] = axes[2].minNum
120
119
  onUpdate(value)
121
120
  } else {
122
121
  value[2] = roundToClean(value[2] - sensitivity)
@@ -129,8 +128,8 @@
129
128
  angle: 301,
130
129
  content: true,
131
130
  onClick: () => {
132
- if (value[1] + sensitivity >= limits[1].maxNum) {
133
- value[1] = limits[1].maxNum
131
+ if (value[1] + sensitivity >= axes[1].maxNum) {
132
+ value[1] = axes[1].maxNum
134
133
  onUpdate(value)
135
134
  return
136
135
  }
@@ -143,15 +142,15 @@
143
142
  angle: 328,
144
143
  content: false,
145
144
  onClick: () => {
146
- if (value[1] + sensitivity >= limits[1].maxNum) {
147
- value[1] = limits[1].maxNum
145
+ if (value[1] + sensitivity >= axes[1].maxNum) {
146
+ value[1] = axes[1].maxNum
148
147
  onUpdate(value)
149
148
  } else {
150
149
  value[1] = roundToClean(value[1] + sensitivity)
151
150
  onUpdate(value)
152
151
  }
153
- if (value[2] + sensitivity >= limits[2].maxNum) {
154
- value[2] = limits[2].maxNum
152
+ if (value[2] + sensitivity >= axes[2].maxNum) {
153
+ value[2] = axes[2].maxNum
155
154
  onUpdate(value)
156
155
  } else {
157
156
  value[2] = roundToClean(value[2] + sensitivity)
@@ -206,16 +205,12 @@
206
205
  onmouseenter={(e) => (e.currentTarget.style.backgroundColor = 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 20%)')}
207
206
  onmouseleave={(e) => (e.currentTarget.style.backgroundColor = 'var(--bg-color)')}
208
207
  >
209
- {#if direction.content}
210
- <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"
211
- ><path
212
- fill="currentColor"
213
- d="M12.6 12L8.7 8.1q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7z"
214
- /></svg
215
- >
216
- {:else}
217
-
218
- {/if}
208
+ <svg xmlns="http://www.w3.org/2000/svg" width={direction.content ? 32 : 16} height={direction.content ? 32 : 16} viewBox="0 0 24 24"
209
+ ><path
210
+ fill="currentColor"
211
+ d="M12.6 12L8.7 8.1q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7z"
212
+ /></svg
213
+ >
219
214
  </span>
220
215
  </button>
221
216
  {/each}
@@ -237,22 +232,20 @@
237
232
  class="z-20 flex size-20 items-center justify-center rounded-full bg-(--bg-color) shadow-[0_0_15px_rgb(0_0_0_/0.25)] transition hover:scale-103"
238
233
  >
239
234
  <button
240
- class="flex size-18 cursor-pointer items-center justify-center rounded-full bg-(--bg-color)"
241
- title=""
235
+ class="flex size-18 cursor-pointer items-center justify-center rounded-full"
236
+ style="background: {value[3] == 1 ? 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)' : 'var(--bg-color)'}"
242
237
  onclick={() => {
243
- onClick(value)
238
+ value[3] = value[3] == 0 ? 1 : 0
244
239
  }}
245
- ><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"
246
- ><path
247
- fill="currentColor"
248
- d="M6 19h3v-5q0-.425.288-.712T10 13h4q.425 0 .713.288T15 14v5h3v-9l-6-4.5L6 10zm-2 0v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-4q-.425 0-.712-.288T13 20v-5h-2v5q0 .425-.288.713T10 21H6q-.825 0-1.412-.587T4 19m8-6.75"
249
- /></svg
250
- ></button
251
240
  >
241
+ {@html buttonIcon
242
+ ? buttonIcon
243
+ : '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor"d="M6 19h3v-5q0-.425.288-.712T10 13h4q.425 0 .713.288T15 14v5h3v-9l-6-4.5L6 10zm-2 0v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-4q-.425 0-.712-.288T13 20v-5h-2v5q0 .425-.288.713T10 21H6q-.825 0-1.412-.587T4 19m8-6.75"/></svg>'}
244
+ </button>
252
245
  </div>
253
246
  </div>
254
247
  <!-- Боковые кнопки (ось roll) -->
255
- {#if axesName.length == 3}
248
+ {#if axes.length == 3}
256
249
  <div
257
250
  class="absolute flex h-15 w-65 items-center justify-between rounded-full shadow-[0_0_15px_rgb(0_0_0_/0.25)]"
258
251
  style="background: color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)"
@@ -261,12 +254,12 @@
261
254
  class="h-full cursor-pointer rounded-l-full px-3.5"
262
255
  title=""
263
256
  onclick={() => {
264
- if (value[0] - sensitivity <= limits[0].minNum) {
265
- value[0] = limits[0].minNum
257
+ if (value[0] - sensitivity <= axes[0].minNum) {
258
+ value[0] = axes[0].minNum
266
259
  onUpdate(value)
267
260
  return
268
261
  }
269
- value[0] -= sensitivity
262
+ value[0] = roundToClean(value[0] - sensitivity)
270
263
  onUpdate(value)
271
264
  }}
272
265
  onmouseenter={(e) => (e.currentTarget.style.backgroundColor = 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 30%)')}
@@ -284,12 +277,12 @@
284
277
  class="h-full cursor-pointer rounded-r-full px-3.5"
285
278
  title=""
286
279
  onclick={() => {
287
- if (value[0] + sensitivity >= limits[0].maxNum) {
288
- value[0] = limits[0].maxNum
280
+ if (value[0] + sensitivity >= axes[0].maxNum) {
281
+ value[0] = axes[0].maxNum
289
282
  onUpdate(value)
290
283
  return
291
284
  }
292
- value[0] += sensitivity
285
+ value[0] = roundToClean(value[0] + sensitivity)
293
286
  onUpdate(value)
294
287
  }}
295
288
  onmouseenter={(e) => (e.currentTarget.style.backgroundColor = 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 30%)')}
@@ -307,7 +300,7 @@
307
300
  {/if}
308
301
  </div>
309
302
 
310
- <div class="right-10 flex items-center md:absolute">
303
+ <div class="flex items-center md:absolute md:left-[calc(50%+120px)]">
311
304
  <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class="flex h-full flex-col justify-center rounded-full p-10">
312
305
  {#each sensitivityOptions as option, index}
313
306
  <button
@@ -338,15 +331,15 @@
338
331
  </div>
339
332
 
340
333
  <div>
341
- {#each axesName as axe, index}
342
- <h5 class={twMerge(` w-full px-4 text-center`, label.class)}>{axe}</h5>
334
+ {#each axes as axe, index}
335
+ <h5 class="w-full px-4 text-center">{axe.name}</h5>
343
336
  <input
344
337
  class={`w-20 rounded-2xl border border-(--border-color) px-4 py-1 text-center transition-all duration-300 outline-none
345
338
  hover:shadow-md
346
339
  [&::-webkit-inner-spin-button]:hidden
347
340
  [&::-webkit-outer-spin-button]:hidden`}
348
341
  style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
349
- value={value[index]}
342
+ value={value[axes.length == 3 ? index : index + 1]}
350
343
  id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
351
344
  readonly
352
345
  />
@@ -354,4 +347,3 @@
354
347
  </div>
355
348
  </div>
356
349
  </div>
357
- <!-- sensitivity == 0.01 ? value[num].toFixed(2) : sensitivity == 0.1 ? value[num].toFixed(1) : value[num].toFixed(0) -->
@@ -31,185 +31,317 @@
31
31
  },
32
32
  )
33
33
 
34
- const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>('DeviceVariables')
35
- let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
36
-
37
34
  const initialAlign = $derived(
38
35
  $optionsStore.TEXT_ALIGN_OPTIONS.find((a) =>
39
36
  (a.value as string).includes(component.properties.label?.class?.split(' ').find((cls: string) => cls.startsWith('text-'))),
40
37
  ),
41
38
  )
39
+
40
+ const initialColor = $derived(
41
+ $optionsStore.COLOR_OPTIONS.find((c) =>
42
+ (c.value as string).includes(component.properties.wrapperClass?.split(' ').find((cls: string) => cls.startsWith('bg-'))),
43
+ ),
44
+ )
42
45
  </script>
43
46
 
44
47
  {#if forConstructor}
45
- <div class="relative flex flex-row items-start justify-center">
46
- <!-- Сообщение для отправки в ws по нажатию кнопки -->
47
- <div class="flex w-1/3 flex-col items-center px-2">
48
- <UI.Select
49
- label={{ name: $t('constructor.props.header') }}
50
- type="buttons"
51
- value={Header}
52
- options={$optionsStore.HEADER_OPTIONS}
53
- onUpdate={(option) => {
54
- Header = { ...option }
55
- onPropertyChange({ eventHandler: { Header: Header.value as string } })
56
- }}
57
- />
58
- {#if Header.value === 'SET'}
48
+ <div>
49
+ <div class="relative flex flex-row items-start justify-center">
50
+ <!-- Сообщение для отправки в ws по нажатию кнопки -->
51
+ <div class="flex w-1/3 flex-col items-center px-2">
59
52
  <UI.Select
60
- label={{ name: $t('constructor.props.argument') }}
53
+ label={{ name: $t('constructor.props.header') }}
61
54
  type="buttons"
62
- value={$optionsStore.FULL_ARGUMENT_OPTION.find((h) => h.value === component.eventHandler.Argument) ??
63
- $optionsStore.FULL_ARGUMENT_OPTION.find((h) => h.value === '')}
64
- options={$optionsStore.FULL_ARGUMENT_OPTION}
55
+ value={Header}
56
+ options={$optionsStore.HEADER_OPTIONS}
65
57
  onUpdate={(option) => {
66
- onPropertyChange({ eventHandler: { Argument: option.value as string } })
58
+ Header = { ...option }
59
+ onPropertyChange({ eventHandler: { Header: Header.value as string } })
67
60
  }}
68
61
  />
69
- {/if}
70
- <UI.Input
71
- label={{ name: Header.value !== 'SET' ? $t('constructor.props.argument') : '' }}
72
- wrapperClass="{Header.value === 'SET' ? 'mt-1' : ''} "
73
- value={component.eventHandler.Argument}
74
- maxlength={32}
75
- disabled={Header.value === 'SET' && (component.eventHandler.Argument == 'Save' || component.eventHandler.Argument == 'NoSave')}
76
- help={{ info: $t('constructor.props.argument.info'), autocomplete: 'on', regExp: /^[a-zA-Z0-9\-_]{0,32}$/ }}
77
- onUpdate={(value) => onPropertyChange({ eventHandler: { Argument: value as string } })}
78
- />
79
- {#if (component.eventHandler.Argument !== 'Save' && component.eventHandler.Argument !== 'NoSave') || Header.value === 'SET'}
62
+ {#if Header.value === 'SET'}
63
+ <UI.Select
64
+ label={{ name: $t('constructor.props.argument') }}
65
+ type="buttons"
66
+ value={$optionsStore.FULL_ARGUMENT_OPTION.find((h) => h.value === component.eventHandler.Argument) ??
67
+ $optionsStore.FULL_ARGUMENT_OPTION.find((h) => h.value === '')}
68
+ options={$optionsStore.FULL_ARGUMENT_OPTION}
69
+ onUpdate={(option) => {
70
+ onPropertyChange({ eventHandler: { Argument: option.value as string } })
71
+ }}
72
+ />
73
+ {/if}
80
74
  <UI.Input
81
- label={{ name: $t('constructor.props.value') }}
82
- value={component.eventHandler.Value}
83
- help={{ info: $t('constructor.props.value.info') }}
84
- maxlength={500}
85
- onUpdate={(value) => onPropertyChange({ eventHandler: { Value: value as string } })}
75
+ label={{ name: Header.value !== 'SET' ? $t('constructor.props.argument') : '' }}
76
+ wrapperClass="{Header.value === 'SET' ? 'mt-1' : ''} "
77
+ value={component.eventHandler.Argument}
78
+ maxlength={32}
79
+ disabled={Header.value === 'SET' && (component.eventHandler.Argument == 'Save' || component.eventHandler.Argument == 'NoSave')}
80
+ help={{ info: $t('constructor.props.argument.info'), autocomplete: 'on', regExp: /^[a-zA-Z0-9\-_]{0,32}$/ }}
81
+ onUpdate={(value) => onPropertyChange({ eventHandler: { Argument: value as string } })}
86
82
  />
87
- {/if}
88
- </div>
89
- <div class="flex w-1/3 flex-col px-2">
90
- <UI.Select
91
- label={{ name: $t('constructor.props.access') }}
92
- type="buttons"
93
- options={$optionsStore.ACCESS_OPTION}
94
- value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
95
- onUpdate={(option) => onPropertyChange({ access: option.value })}
96
- />
97
- <UI.Input
98
- label={{ name: $t('constructor.props.label') }}
99
- value={component.properties.label.name}
100
- onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
101
- />
102
- <UI.Select
103
- label={{ name: $t('constructor.props.align') }}
104
- type="buttons"
105
- value={initialAlign}
106
- options={$optionsStore.TEXT_ALIGN_OPTIONS}
107
- onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
108
- />
109
- </div>
110
- <div class="flex w-1/3 flex-col px-2">
111
- <div class="mt-6 flex gap-2">
112
- <UI.Button content={{ name: $t('constructor.props.markerIcon') }} onClick={() => (showIconLib = true)} />
113
- {#if showIconLib}
114
- <Modal bind:isOpen={showIconLib} wrapperClass="w-130">
115
- {#snippet main()}
116
- <div class="grid grid-cols-3">
117
- {#each ICONS as category}
118
- <div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
119
- <div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
120
- <div class="grid grid-cols-3 place-items-center gap-2">
121
- {#each category[1] as icon}
122
- <button
123
- class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
124
- onclick={() => {
125
- updateProperty('markerIcon', icon as string, component, onPropertyChange)
126
- }}
127
- >
128
- {@html icon}
129
- </button>{/each}
130
- </div>
131
- </div>
132
- {/each}
133
- </div>
134
- {/snippet}
135
- </Modal>
136
- {/if}
137
- {#if component.properties.markerIcon}
138
- <Button
139
- wrapperClass="w-8.5 "
140
- componentClass="p-0.5 bg-red"
141
- content={{ icon: CrossIcon }}
142
- onClick={() => {
143
- updateProperty('markerIcon', '', component, onPropertyChange)
144
- }}
83
+ {#if (component.eventHandler.Argument !== 'Save' && component.eventHandler.Argument !== 'NoSave') || Header.value === 'SET'}
84
+ <UI.Input
85
+ label={{ name: $t('constructor.props.value') }}
86
+ value={component.eventHandler.Value}
87
+ help={{ info: $t('constructor.props.value.info') }}
88
+ maxlength={500}
89
+ onUpdate={(value) => onPropertyChange({ eventHandler: { Value: value as string } })}
145
90
  />
146
91
  {/if}
147
92
  </div>
93
+ <div class="flex w-1/3 flex-col px-2">
94
+ <UI.Select
95
+ label={{ name: $t('constructor.props.access') }}
96
+ type="buttons"
97
+ options={$optionsStore.ACCESS_OPTION}
98
+ value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
99
+ onUpdate={(option) => onPropertyChange({ access: option.value })}
100
+ />
101
+ <UI.Input
102
+ label={{ name: $t('constructor.props.label') }}
103
+ value={component.properties.label.name}
104
+ onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
105
+ />
106
+ <UI.Input
107
+ label={{ name: $t('constructor.props.label.class') }}
108
+ value={component.properties.label.class}
109
+ onUpdate={(value) => updateProperty('label.class', value as string, component, onPropertyChange)}
110
+ />
111
+ </div>
112
+ <div class="flex w-1/3 flex-col px-2">
113
+ <div class="mt-6 flex gap-2">
114
+ <UI.Button content={{ name: $t('constructor.props.buttonIcon') }} onClick={() => (showIconLib = true)} />
115
+ {#if showIconLib}
116
+ <Modal bind:isOpen={showIconLib} wrapperClass="w-130">
117
+ {#snippet main()}
118
+ <div class="grid grid-cols-3">
119
+ {#each ICONS as category}
120
+ <div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
121
+ <div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
122
+ <div class="grid grid-cols-3 place-items-center gap-2">
123
+ {#each category[1] as icon}
124
+ <button
125
+ class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
126
+ onclick={() => {
127
+ updateProperty('buttonIcon', icon as string, component, onPropertyChange)
128
+ }}
129
+ >
130
+ {@html icon}
131
+ </button>{/each}
132
+ </div>
133
+ </div>
134
+ {/each}
135
+ </div>
136
+ {/snippet}
137
+ </Modal>
138
+ {/if}
139
+ {#if component.properties.buttonIcon}
140
+ <Button
141
+ wrapperClass="w-8.5 "
142
+ componentClass="p-0.5 bg-red"
143
+ content={{ icon: CrossIcon }}
144
+ onClick={() => {
145
+ updateProperty('buttonIcon', '', component, onPropertyChange)
146
+ }}
147
+ />
148
+ {/if}
149
+ </div>
150
+ <UI.Input
151
+ label={{ name: $t('constructor.props.joystick.axes') }}
152
+ value={component.properties.axes.map((axe: any) => axe.name).join(' ')}
153
+ help={{ info: $t('constructor.props.joystick.axes.info'), regExp: /^[\p{L}0-9\-_":{}]+ +[\p{L}0-9\-_":{}]+(?: +[\p{L}0-9\-_":{}]+)?$/u }}
154
+ maxlength={100}
155
+ onUpdate={(value) => {
156
+ const stringValue = value as string
157
+ const spaceCount = (stringValue.match(/\s/g) || []).length
158
+ if (spaceCount > 2) {
159
+ return
160
+ }
161
+ const parts = stringValue.trim().split(/\s+/)
162
+ updateProperty(
163
+ 'axes',
164
+ parts.map((a: any, index: number) => {
165
+ let axeIndex = parts.length == 2 && component.properties.axes.length === 3 ? index + 1 : index
166
+ return {
167
+ name: a,
168
+ minNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].minNum : -100,
169
+ maxNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].maxNum : 100,
170
+ }
171
+ }),
172
+ component,
173
+ onPropertyChange,
174
+ )
175
+ }}
176
+ />
177
+ <UI.Select
178
+ wrapperClass="!h-14"
179
+ label={{ name: $t('constructor.props.colors') }}
180
+ type="buttons"
181
+ options={$optionsStore.COLOR_OPTIONS}
182
+ value={initialColor}
183
+ onUpdate={(option) => updateProperty('wrapperClass', twMerge(component.properties.wrapperClass, option.value), component, onPropertyChange)}
184
+ />
185
+ </div>
186
+ </div>
187
+ <div class="mt-2 flex w-full justify-around gap-2">
188
+ {#each component.properties.axes as axe, index}
189
+ <div class="flex items-start gap-2">
190
+ <h5 class="mt-1">{axe.name}</h5>
191
+ <UI.Slider
192
+ type="range"
193
+ number={{ minNum: -360, maxNum: 360, step: 10 }}
194
+ value={[component.properties.axes[index].minNum, component.properties.axes[index].maxNum]}
195
+ onUpdate={(value) => {
196
+ if (Array.isArray(value)) {
197
+ const axes = component.properties.axes
198
+
199
+ updateProperty(
200
+ 'axes',
201
+ axes.map((a: any, i: number) => (i === index ? { ...a, minNum: value[0], maxNum: value[1] } : a)),
202
+ component,
203
+ onPropertyChange,
204
+ )
205
+ console.log(component.properties.axes)
206
+ }
207
+ }}
208
+ />
209
+ </div>
210
+ {/each}
148
211
  </div>
149
212
  </div>
150
213
  {:else}
151
- <div class="relative mb-2 flex flex-row items-start justify-center">
152
- <!-- Сообщение для отправки в ws по нажатию кнопки -->
153
- <div class="flex w-1/3 flex-col items-center px-2">
154
- <UI.Input
155
- label={{ name: $t('constructor.props.id') }}
156
- value={component.properties.id}
157
- onUpdate={(value) => updateProperty('id', value as string, component, onPropertyChange)}
158
- />
159
- </div>
160
- <div class="flex w-1/3 flex-col px-2">
161
- <UI.Input
162
- label={{ name: $t('constructor.props.label') }}
163
- value={component.properties.label.name}
164
- onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
165
- />
166
- <UI.Select
167
- label={{ name: $t('constructor.props.align') }}
168
- type="buttons"
169
- value={initialAlign}
170
- options={$optionsStore.TEXT_ALIGN_OPTIONS}
171
- onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
172
- />
214
+ <div>
215
+ <div class="relative mb-2 flex flex-row items-start justify-center">
216
+ <!-- Сообщение для отправки в ws по нажатию кнопки -->
217
+ <div class="flex w-1/3 flex-col items-center px-2">
218
+ <UI.Input
219
+ label={{ name: $t('constructor.props.id') }}
220
+ value={component.properties.id}
221
+ onUpdate={(value) => updateProperty('id', value as string, component, onPropertyChange)}
222
+ />
223
+ <UI.Select
224
+ label={{ name: $t('constructor.props.access') }}
225
+ type="buttons"
226
+ options={$optionsStore.ACCESS_OPTION}
227
+ value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
228
+ onUpdate={(option) => onPropertyChange({ access: option.value })}
229
+ />
230
+ </div>
231
+ <div class="flex w-1/3 flex-col px-2">
232
+ <UI.Input
233
+ label={{ name: $t('constructor.props.label') }}
234
+ value={component.properties.label.name}
235
+ onUpdate={(value) => updateProperty('label.name', value as string, component, onPropertyChange)}
236
+ />
237
+ <UI.Select
238
+ label={{ name: $t('constructor.props.align') }}
239
+ type="buttons"
240
+ value={initialAlign}
241
+ options={$optionsStore.TEXT_ALIGN_OPTIONS}
242
+ onUpdate={(option) => updateProperty('label.class', twMerge(component.properties.label.class, option.value), component, onPropertyChange)}
243
+ />
244
+ </div>
245
+ <div class="flex w-1/3 flex-col px-2">
246
+ <div class="mt-6 flex gap-2">
247
+ <UI.Button content={{ name: $t('constructor.props.buttonIcon') }} onClick={() => (showIconLib = true)} />
248
+ {#if showIconLib}
249
+ <Modal bind:isOpen={showIconLib} wrapperClass="w-130">
250
+ {#snippet main()}
251
+ <div class="grid grid-cols-3">
252
+ {#each ICONS as category}
253
+ <div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
254
+ <div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
255
+ <div class="grid grid-cols-3 place-items-center gap-2">
256
+ {#each category[1] as icon}
257
+ <button
258
+ class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
259
+ onclick={() => {
260
+ updateProperty('buttonIcon', icon as string, component, onPropertyChange)
261
+ }}
262
+ >
263
+ {@html icon}
264
+ </button>{/each}
265
+ </div>
266
+ </div>
267
+ {/each}
268
+ </div>
269
+ {/snippet}
270
+ </Modal>
271
+ {/if}
272
+ {#if component.properties.buttonIcon}
273
+ <Button
274
+ wrapperClass="w-8.5 "
275
+ componentClass="p-0.5 bg-red"
276
+ content={{ icon: CrossIcon }}
277
+ onClick={() => {
278
+ updateProperty('buttonIcon', '', component, onPropertyChange)
279
+ }}
280
+ />
281
+ {/if}
282
+ </div>
283
+ <UI.Input
284
+ label={{ name: $t('constructor.props.joystick.axes') }}
285
+ value={component.properties.axes.map((axe: any) => axe.name).join(' ')}
286
+ help={{ info: $t('constructor.props.joystick.axes.info'), regExp: /^[\p{L}0-9\-_":{}]+ +[\p{L}0-9\-_":{}]+(?: +[\p{L}0-9\-_":{}]+)?$/u }}
287
+ maxlength={100}
288
+ onUpdate={(value) => {
289
+ const stringValue = value as string
290
+ const spaceCount = (stringValue.match(/\s/g) || []).length
291
+ if (spaceCount > 2) {
292
+ return
293
+ }
294
+ const parts = stringValue.trim().split(/\s+/)
295
+ updateProperty(
296
+ 'axes',
297
+ parts.map((a: any, index: number) => {
298
+ let axeIndex = parts.length == 2 && component.properties.axes.length === 3 ? index + 1 : index
299
+ return {
300
+ name: a,
301
+ minNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].minNum : -100,
302
+ maxNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].maxNum : 100,
303
+ }
304
+ }),
305
+ component,
306
+ onPropertyChange,
307
+ )
308
+ }}
309
+ />
310
+
311
+ <UI.Select
312
+ wrapperClass="!h-14"
313
+ label={{ name: $t('constructor.props.colors') }}
314
+ type="buttons"
315
+ options={$optionsStore.COLOR_OPTIONS}
316
+ value={initialColor}
317
+ onUpdate={(option) => updateProperty('wrapperClass', twMerge(component.properties.wrapperClass, option.value), component, onPropertyChange)}
318
+ />
319
+ </div>
173
320
  </div>
321
+ <div class="mt-2 flex w-full justify-around gap-2">
322
+ {#each component.properties.axes as axe, index}
323
+ <div class="flex items-start gap-2">
324
+ <h5 class="mt-1">{axe.name}</h5>
325
+ <UI.Slider
326
+ type="range"
327
+ number={{ minNum: -360, maxNum: 360, step: 10 }}
328
+ value={[component.properties.axes[index].minNum, component.properties.axes[index].maxNum]}
329
+ onUpdate={(value) => {
330
+ if (Array.isArray(value)) {
331
+ const axes = component.properties.axes
174
332
 
175
- <div class="flex w-1/3 flex-col px-2">
176
- <div class="mt-6 flex gap-2">
177
- <UI.Button content={{ name: $t('constructor.props.markerIcon') }} onClick={() => (showIconLib = true)} />
178
- {#if showIconLib}
179
- <Modal bind:isOpen={showIconLib} wrapperClass="w-130">
180
- {#snippet main()}
181
- <div class="grid grid-cols-3">
182
- {#each ICONS as category}
183
- <div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
184
- <div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
185
- <div class="grid grid-cols-3 place-items-center gap-2">
186
- {#each category[1] as icon}
187
- <button
188
- class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
189
- onclick={() => {
190
- updateProperty('markerIcon', icon as string, component, onPropertyChange)
191
- }}
192
- >
193
- {@html icon}
194
- </button>{/each}
195
- </div>
196
- </div>
197
- {/each}
198
- </div>
199
- {/snippet}
200
- </Modal>
201
- {/if}
202
- {#if component.properties.markerIcon}
203
- <Button
204
- wrapperClass="w-8.5 "
205
- componentClass="p-0.5 bg-red"
206
- content={{ icon: CrossIcon }}
207
- onClick={() => {
208
- updateProperty('markerIcon', '', component, onPropertyChange)
333
+ updateProperty(
334
+ 'axes',
335
+ axes.map((a: any, i: number) => (i === index ? { ...a, minNum: value[0], maxNum: value[1] } : a)),
336
+ component,
337
+ onPropertyChange,
338
+ )
339
+ console.log(component.properties.axes)
340
+ }
209
341
  }}
210
342
  />
211
- {/if}
212
- </div>
343
+ </div>
344
+ {/each}
213
345
  </div>
214
346
  </div>
215
347
  {/if}
@@ -8,6 +8,7 @@
8
8
  wrapperClass = '',
9
9
  label = { name: '', class: '' },
10
10
  value = $bindable(0),
11
+ type = 'vertical',
11
12
  number = {
12
13
  minNum: 0,
13
14
  maxNum: 100,
@@ -38,17 +39,41 @@
38
39
  return (((Math.min(Math.max(value, min), max) - min) / (max - min)) * 100) as number
39
40
  }
40
41
  })
42
+
43
+ const roundToClean = (num: number): number => {
44
+ if (Number.isInteger(num)) return num
45
+
46
+ const rounded1 = Number(num.toFixed(1))
47
+ if (Math.abs(rounded1 - num) < 1e-10) return rounded1
48
+
49
+ const rounded2 = Number(num.toFixed(2))
50
+ if (Math.abs(rounded2 - num) < 1e-10) return rounded2
51
+
52
+ return rounded2
53
+ }
41
54
  </script>
42
55
 
43
- <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge(`relative flex w-full flex-col items-center`, wrapperClass)}>
56
+ <div
57
+ id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
58
+ class={twMerge(`relative flex ${type == 'vertical' ? 'h-full' : ''} w-full flex-col items-center`, wrapperClass)}
59
+ >
44
60
  {#if label.name}
45
61
  <h5 class={twMerge(` w-full px-4 text-center`, label.class)}>{label.name}</h5>
46
62
  {/if}
47
63
 
48
- <div class="flex h-7 w-full items-center gap-2 rounded-full bg-(--bg-color) px-2">
49
- <span class="m-auto font-semibold">{numericValue?.toFixed(2)}{number.units}</span>
50
- <div class="relative my-auto h-3.5 w-[85%] rounded-full bg-(--back-color)/40">
51
- <div class="absolute top-0 left-0 flex h-full rounded-full bg-(--field-color)" style="width: {progressPercent()}%;"></div>
64
+ {#if type == 'vertical'}
65
+ <div class="flex h-full w-fit min-w-16 flex-col items-center gap-2 rounded-full bg-(--bg-color) px-2">
66
+ <div class="relative my-auto h-[80%] w-[70%] rounded-full bg-(--back-color)/40">
67
+ <div class="absolute bottom-0 left-0 flex w-full rounded-full bg-(--field-color)" style="height: {progressPercent()}%;"></div>
68
+ </div>
69
+ <span class="m-auto font-semibold">{roundToClean(Number(numericValue))}{number.units}</span>
70
+ </div>
71
+ {:else}
72
+ <div class="flex h-7 w-full items-center gap-2 rounded-full bg-(--bg-color) px-2">
73
+ <span class="m-auto font-semibold">{roundToClean(Number(numericValue))}{number.units}</span>
74
+ <div class="relative my-auto h-3.5 w-[85%] rounded-full bg-(--back-color)/40">
75
+ <div class="absolute top-0 left-0 flex h-full rounded-full bg-(--field-color)" style="width: {progressPercent()}%;"></div>
76
+ </div>
52
77
  </div>
53
- </div>
78
+ {/if}
54
79
  </div>
@@ -27,7 +27,7 @@
27
27
 
28
28
  let activeRound: 'floor' | 'ceil' = $state('floor')
29
29
 
30
- let centerNum = $derived(lowerValue + Math[activeRound]((upperValue - lowerValue) / 2 / number.step) * number.step)
30
+ let centerNum = $derived(lowerValue + Math[activeRound](Math.abs(upperValue - lowerValue) / 2 / number.step) * number.step)
31
31
 
32
32
  $effect(() => {
33
33
  if (value === undefined || value === null) {
@@ -105,13 +105,12 @@
105
105
  onmousedown={() => (activeRound = 'ceil')}
106
106
  {disabled}
107
107
  class={twMerge(
108
- `basis-[calc(${(centerNum / number.maxNum) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
108
+ `basis-[calc(${(Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
109
109
  accent-(--back-color)
110
110
  [&::-webkit-slider-runnable-track]:rounded-l-full
111
111
  [&::-webkit-slider-runnable-track]:bg-(--gray-color)
112
112
  [&::-webkit-slider-runnable-track]:px-2
113
113
  [&::-webkit-slider-thumb]:relative
114
- [&::-webkit-slider-thumb]:z-100
115
114
  [&::-webkit-slider-thumb]:size-4
116
115
  [&::-webkit-slider-thumb]:cursor-pointer
117
116
  [&::-webkit-slider-thumb]:rounded-full
@@ -134,7 +133,7 @@
134
133
  `[&::-moz-range-thumb]:shadow-[calc(100rem+0.5rem)_0_0_100rem]
135
134
  [&::-webkit-slider-thumb]:shadow-[calc(100rem+0.5rem)_0_0_100rem]`,
136
135
  )}
137
- style="color: var(--bg-color); flex-basis: {`calc(${(centerNum / number.maxNum) * 100}% + 2rem + 5px)`};"
136
+ style="color: var(--bg-color); flex-basis: {`calc(${(Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}% + 2rem + 5px)`};"
138
137
  />
139
138
  <input
140
139
  type="range"
@@ -152,13 +151,12 @@
152
151
  onmousedown={() => (activeRound = 'floor')}
153
152
  {disabled}
154
153
  class={twMerge(
155
- `basis-[calc(${100 - (centerNum / number.maxNum) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
154
+ `basis-[calc(${100 - (Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}%+2rem+5px)] h-8 w-full appearance-none overflow-hidden
156
155
  accent-(--back-color)
157
156
  [&::-webkit-slider-runnable-track]:rounded-r-full
158
157
  [&::-webkit-slider-runnable-track]:bg-(--gray-color)
159
158
  [&::-webkit-slider-runnable-track]:px-2
160
159
  [&::-webkit-slider-thumb]:relative
161
- [&::-webkit-slider-thumb]:z-100
162
160
  [&::-webkit-slider-thumb]:size-4
163
161
  [&::-webkit-slider-thumb]:cursor-pointer
164
162
  [&::-webkit-slider-thumb]:rounded-full
@@ -181,7 +179,7 @@
181
179
  `[&::-moz-range-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]
182
180
  [&::-webkit-slider-thumb]:shadow-[calc(100rem*-1-0.5rem)_0_0_100rem]`,
183
181
  )}
184
- style="color: var(--bg-color); flex-basis: {`calc(${(1 - centerNum / number.maxNum) * 100}% + 2rem + 5px)`};"
182
+ style="color: var(--bg-color); flex-basis: {`calc(${(1 - Math.abs(centerNum - number.minNum) / Math.abs(number.maxNum - number.minNum)) * 100}% + 2rem + 5px)`};"
185
183
  />
186
184
  </div>
187
185
  {:else}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { default as ButtonProps } from './Button/ButtonProps.svelte';
5
5
  export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
6
6
  export { default as ColorPickerProps } from './ColorPicker/ColorPickerProps.svelte';
7
7
  export { default as FileAttach } from './FileAttach/FileAttach.svelte';
8
+ export { default as FileAttachProps } from './FileAttach/FileAttachProps.svelte';
8
9
  export { default as Graph } from './Graph/Graph.svelte';
9
10
  export { default as GraphProps } from './Graph/GraphProps.svelte';
10
11
  export { default as Input } from './Input/Input.svelte';
@@ -30,4 +31,4 @@ export { default as TextField } from './TextField/TextField.svelte';
30
31
  export { default as TextFieldProps } from './TextField/TextFieldProps.svelte';
31
32
  export * from './locales/i18n';
32
33
  export * from './locales/translations';
33
- export { type UIComponent, type Position, type IUIComponentHandler, type IButtonProps, type IAccordionProps, type IInputProps, type ISelectProps, type ISelectOption, type ISwitchProps, type IColorPickerProps, type ISliderProps, type ITextFieldProps, type IMapProps, type IProgressBarProps, type IGraphProps, type IGraphDataObject, type ITableHeader, type ITableProps, type ITabsProps, type IJoystickProps, } from './types';
34
+ export { type UIComponent, type Position, type IUIComponentHandler, type IButtonProps, type IAccordionProps, type IInputProps, type ISelectProps, type ISelectOption, type ISwitchProps, type IColorPickerProps, type ISliderProps, type ITextFieldProps, type IMapProps, type IProgressBarProps, type IGraphProps, type IGraphDataObject, type ITableHeader, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, } from './types';
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export { default as ButtonProps } from './Button/ButtonProps.svelte';
6
6
  export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
7
7
  export { default as ColorPickerProps } from './ColorPicker/ColorPickerProps.svelte';
8
8
  export { default as FileAttach } from './FileAttach/FileAttach.svelte';
9
+ export { default as FileAttachProps } from './FileAttach/FileAttachProps.svelte';
9
10
  export { default as Graph } from './Graph/Graph.svelte';
10
11
  export { default as GraphProps } from './Graph/GraphProps.svelte';
11
12
  export { default as Input } from './Input/Input.svelte';
@@ -73,6 +73,7 @@ const translations = {
73
73
  'constructor.props.image': 'Фоновое изображение',
74
74
  'constructor.props.labelicon': 'Иконка заголовка',
75
75
  'constructor.props.markerIcon': 'Иконка маркера',
76
+ 'constructor.props.buttonIcon': 'Иконка кнопки',
76
77
  'constructor.props.removeimage': 'Удалить изображение',
77
78
  'constructor.props.name': 'Текст',
78
79
  'constructor.props.height': 'Высота',
@@ -127,6 +128,8 @@ const translations = {
127
128
  'constructor.props.bitmode': 'Битовый режим',
128
129
  'constructor.props.access': 'Доступ (не для владельца)',
129
130
  'constructor.props.map.timeout': 'Таймаут маркеров:',
131
+ 'constructor.props.joystick.axes': 'Названия осей',
132
+ 'constructor.props.joystick.axes.info': 'Поле для ввода названий осей, разделенных пробелами (2 или 3 названия)',
130
133
  'constructor.props.table.columns': 'Колонки таблицы',
131
134
  'constructor.props.table.columns.key': 'Ключ',
132
135
  'constructor.props.table.columns.label': 'Название колонки',
package/dist/types.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { Writable } from 'svelte/store';
3
- import type { IFileInputProps } from './FileAttach/FileAttach.svelte';
4
3
  export declare const updateProperty: (path: string, value: string | number | boolean | object | string[], component: UIComponent & {
5
4
  properties: Partial<UIComponent["properties"]>;
6
5
  }, onPropertyChange: (updates: Partial<{
@@ -17,7 +16,7 @@ export declare const updateComponent: (component: UIComponent, updates: Partial<
17
16
  }>) => {
18
17
  access: "full" | "viewOnly" | "hidden" | undefined;
19
18
  name: string | undefined;
20
- properties: IFileInputProps | IAccordionProps | IButtonProps | IInputProps | ISelectProps<unknown> | ISwitchProps | IColorPickerProps | ISliderProps | ITextFieldProps | IProgressBarProps | IGraphProps | ITableProps<object> | ITabsProps | IJoystickProps | IMapProps;
19
+ properties: IAccordionProps | IButtonProps | IInputProps | ISelectProps<unknown> | ISwitchProps | IColorPickerProps | ISliderProps | ITextFieldProps | IProgressBarProps | IGraphProps | ITableProps<object> | ITabsProps | IFileAttachProps | IJoystickProps | IMapProps;
21
20
  eventHandler: IUIComponentHandler | undefined;
22
21
  id: string;
23
22
  type: "Button" | "Accordion" | "Input" | "Select" | "Switch" | "ColorPicker" | "Slider" | "TextField" | "Joystick" | "ProgressBar" | "Graph" | "Table" | "Tabs" | "FileAttach" | "Map";
@@ -29,7 +28,7 @@ export interface UIComponent {
29
28
  name?: string;
30
29
  access?: 'full' | 'viewOnly' | 'hidden';
31
30
  type: 'Button' | 'Accordion' | 'Input' | 'Select' | 'Switch' | 'ColorPicker' | 'Slider' | 'TextField' | 'Joystick' | 'ProgressBar' | 'Graph' | 'Table' | 'Tabs' | 'FileAttach' | 'Map';
32
- properties: IAccordionProps | IButtonProps | IInputProps | ISelectProps | ISwitchProps | IColorPickerProps | ISliderProps | ITextFieldProps | IProgressBarProps | IGraphProps | ITableProps<object> | ITabsProps | IFileInputProps | IJoystickProps | IMapProps;
31
+ properties: IAccordionProps | IButtonProps | IInputProps | ISelectProps | ISwitchProps | IColorPickerProps | ISliderProps | ITextFieldProps | IProgressBarProps | IGraphProps | ITableProps<object> | ITabsProps | IFileAttachProps | IJoystickProps | IMapProps;
33
32
  position: Position;
34
33
  parentId: string;
35
34
  eventHandler?: IUIComponentHandler;
@@ -206,6 +205,7 @@ export interface IProgressBarProps {
206
205
  maxNum?: number;
207
206
  units?: string;
208
207
  };
208
+ type?: 'horizontal' | 'vertical';
209
209
  wrapperClass?: string;
210
210
  }
211
211
  export interface IGraphDataObject {
@@ -305,14 +305,14 @@ export interface IJoystickProps {
305
305
  name?: string;
306
306
  class?: string;
307
307
  };
308
- axesName?: [string, string, string?];
309
308
  value?: number[];
310
- limits?: {
309
+ axes?: {
310
+ name: string;
311
311
  minNum: number;
312
312
  maxNum: number;
313
313
  }[];
314
+ buttonIcon?: string;
314
315
  onUpdate?: (value: number[]) => void;
315
- onClick?: (value: number[]) => void;
316
316
  }
317
317
  export interface IDeviceGNSS {
318
318
  NavLat: number;
@@ -332,3 +332,22 @@ export interface IMapProps {
332
332
  data: IDeviceGNSS | null;
333
333
  markerIcon?: string;
334
334
  }
335
+ export interface IFileAttachProps {
336
+ id?: string;
337
+ wrapperClass?: string;
338
+ label?: {
339
+ name?: string;
340
+ class?: string;
341
+ };
342
+ type?: 'file' | 'image';
343
+ accept?: string;
344
+ imageSize?: {
345
+ height?: string;
346
+ width?: string;
347
+ fitMode?: 'cover' | 'contain';
348
+ form?: 'square' | 'circle';
349
+ };
350
+ disabled?: boolean;
351
+ currentImage?: string | null;
352
+ onChange?: (event: Event, file: File | null) => void;
353
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.2.30",
3
+ "version": "1.2.32",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {