poe-svelte-ui-lib 1.3.1 → 1.3.3

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.
@@ -36,7 +36,12 @@
36
36
  class={`flex h-7 w-7 shrink-0 items-center justify-center overflow-visible [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full`}
37
37
  >
38
38
  {#if label?.icon}
39
- {@html label.icon}
39
+ {#if typeof label?.icon === 'string'}
40
+ {@html label.icon}
41
+ {:else}
42
+ {@const IconComponent = label?.icon}
43
+ <IconComponent />
44
+ {/if}
40
45
  {/if}
41
46
  </span>
42
47
 
@@ -88,7 +88,7 @@
88
88
  <div class=" flex flex-row items-center justify-center gap-2">
89
89
  {#if content?.icon}
90
90
  <span
91
- class={` ${content.name ? 'absolute left-3' : ''} flex items-center justify-center overflow-visible
91
+ class={` ${content.name ? 'absolute left-3' : ''} ${typeof content?.icon == 'string' ? 'p-0.5' : ''} flex items-center justify-center overflow-visible
92
92
  ${content.name ? 'h-8 w-8' : `${svgSize()}`} [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full`}
93
93
  >
94
94
  {#if typeof content?.icon === 'string'}
@@ -5,6 +5,10 @@
5
5
  import * as UI from '..'
6
6
  import { optionsStore } from '../options'
7
7
  import { twMerge } from 'tailwind-merge'
8
+ import CrossIcon from '../libIcons/CrossIcon.svelte'
9
+ import Modal from '../Modal.svelte'
10
+ import { ICONS } from '../icons'
11
+ import Button from './Button.svelte'
8
12
 
9
13
  const {
10
14
  component,
@@ -16,6 +20,8 @@
16
20
  forConstructor?: boolean
17
21
  }>()
18
22
 
23
+ let showIconLib = $state(false)
24
+
19
25
  let hasValue: boolean = $derived(component.eventHandler.Value)
20
26
 
21
27
  let Header: ISelectOption = $derived(
@@ -104,6 +110,43 @@
104
110
  value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
105
111
  onUpdate={(option) => onPropertyChange({ access: option.value })}
106
112
  />
113
+ <div class="relative mt-6 flex w-full 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('content.icon', 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.content.icon}
140
+ <Button
141
+ wrapperClass="w-8.5 "
142
+ componentClass="p-0.5 bg-red"
143
+ content={{ icon: CrossIcon }}
144
+ onClick={() => {
145
+ updateProperty('content.icon', '', component, onPropertyChange)
146
+ }}
147
+ />
148
+ {/if}
149
+ </div>
107
150
  </div>
108
151
  <div class="flex w-1/3 flex-col items-center px-2">
109
152
  <UI.Input
@@ -194,14 +237,43 @@
194
237
  onUpdate={(option) =>
195
238
  updateProperty('componentClass', twMerge(component.properties.componentClass, option.value), component, onPropertyChange)}
196
239
  />
197
-
198
- <UI.Input
199
- label={{ name: $t('constructor.props.svgicon') }}
200
- type="text-area"
201
- maxlength={100000}
202
- value={component.properties.content.icon}
203
- onUpdate={(value) => updateProperty('content.icon', value as string, component, onPropertyChange)}
204
- />
240
+ <div class="relative mt-6 flex w-full gap-2">
241
+ <UI.Button content={{ name: $t('constructor.props.buttonIcon') }} onClick={() => (showIconLib = true)} />
242
+ {#if showIconLib}
243
+ <Modal bind:isOpen={showIconLib} wrapperClass="w-130">
244
+ {#snippet main()}
245
+ <div class="grid grid-cols-3">
246
+ {#each ICONS as category}
247
+ <div class="relative m-1.5 rounded-xl border-2 border-(--border-color) p-3">
248
+ <div class="absolute -top-3.5 bg-(--back-color) px-1">{$t(`constructor.props.icon.${category[0]}`)}</div>
249
+ <div class="grid grid-cols-3 place-items-center gap-2">
250
+ {#each category[1] as icon}
251
+ <button
252
+ class="h-8 w-8 cursor-pointer [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
253
+ onclick={() => {
254
+ updateProperty('content.icon', icon as string, component, onPropertyChange)
255
+ }}
256
+ >
257
+ {@html icon}
258
+ </button>{/each}
259
+ </div>
260
+ </div>
261
+ {/each}
262
+ </div>
263
+ {/snippet}
264
+ </Modal>
265
+ {/if}
266
+ {#if component.properties.content.icon}
267
+ <Button
268
+ wrapperClass="w-8.5 "
269
+ componentClass="p-0.5 bg-red"
270
+ content={{ icon: CrossIcon }}
271
+ onClick={() => {
272
+ updateProperty('content.icon', '', component, onPropertyChange)
273
+ }}
274
+ />
275
+ {/if}
276
+ </div>
205
277
  </div>
206
278
  </div>
207
279
  {/if}
@@ -17,15 +17,36 @@
17
17
  <div class="max-h-[70%]" transition:fade={{ duration: 200 }}>
18
18
  {@render componentProps()}
19
19
  <div class="relative mt-3">
20
- <UI.Button
21
- wrapperClass="absolute top-3 right-5 w-6"
22
- content={{ icon: isCopied ? '<div class="rounded-md bg-(--green-color) shadow-lg px-1">✓</div>' : CopyButton }}
23
- onClick={() => {
20
+ <button
21
+ class="absolute top-2 right-3 flex cursor-pointer border-none bg-transparent"
22
+ onclick={(e) => {
23
+ e.preventDefault()
24
+ navigator.clipboard.writeText(codeText)
24
25
  isCopied = true
25
26
  setTimeout(() => (isCopied = false), 1000)
26
- navigator.clipboard.writeText(codeText)
27
27
  }}
28
- />
28
+ aria-label="Копировать текст"
29
+ >
30
+ <div class=" size-6 text-sm [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full">
31
+ {#if isCopied}
32
+ <div
33
+ class="right-1..5 absolute top-1/2 -translate-y-1/2 transform rounded-md bg-(--green-color) px-1.5 py-1 shadow-lg"
34
+ transition:fade={{ duration: 200 }}
35
+ >
36
+
37
+ </div>
38
+ {:else}
39
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
40
+ <g fill="none" stroke="currentColor" stroke-width="1.5">
41
+ <path
42
+ 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"
43
+ />
44
+ <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" />
45
+ </g>
46
+ </svg>
47
+ {/if}
48
+ </div>
49
+ </button>
29
50
  <pre class="overflow-x-auto">{codeText}
30
51
  </pre>
31
52
  </div>
@@ -177,15 +177,27 @@
177
177
  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"
178
178
  >
179
179
  <button
180
- class="flex size-18 cursor-pointer items-center justify-center rounded-full"
180
+ class="flex size-18 cursor-pointer items-center justify-center rounded-full p-3.5 [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
181
181
  style="background: {value[3] == 1 ? 'color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)' : 'var(--bg-color)'}"
182
182
  onclick={() => {
183
183
  value[3] = value[3] == 0 ? 1 : 0
184
184
  }}
185
185
  >
186
- {@html buttonIcon
187
- ? buttonIcon
188
- : '<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>'}
186
+ {#if buttonIcon}
187
+ {#if typeof buttonIcon === 'string'}
188
+ {@html buttonIcon}
189
+ {:else}
190
+ {@const IconComponent = buttonIcon}
191
+ <IconComponent />
192
+ {/if}
193
+ {:else}
194
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"
195
+ ><path
196
+ fill="currentColor"
197
+ 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"
198
+ /></svg
199
+ >
200
+ {/if}
189
201
  </button>
190
202
  </div>
191
203
  </div>
@@ -136,10 +136,29 @@
136
136
  <div
137
137
  class="flex size-8 shrink-0 items-center justify-center [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full
138
138
  {device.isFresh ? 'text-green-500' : 'text-red-500'}"
139
- style="rotate: {device.NavHeading - 90}deg;"
139
+ style="rotate: {device.NavHeading}deg;"
140
140
  >
141
- {@html markerIcon ||
142
- '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.76 12H6.832m0 0c0-.275-.057-.55-.17-.808L4.285 5.814c-.76-1.72 1.058-3.442 2.734-2.591L20.8 10.217c1.46.74 1.46 2.826 0 3.566L7.02 20.777c-1.677.851-3.495-.872-2.735-2.591l2.375-5.378A2 2 0 0 0 6.83 12"/></svg>'}
141
+ {#if markerIcon}
142
+ {#if typeof markerIcon === 'string'}
143
+ {@html markerIcon}
144
+ {:else}
145
+ {@const IconComponent = markerIcon}
146
+ <IconComponent />
147
+ {/if}
148
+ {:else}
149
+ <div class="rotate-270">
150
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24">
151
+ <path
152
+ fill="none"
153
+ stroke="currentColor"
154
+ stroke-linecap="round"
155
+ stroke-linejoin="round"
156
+ stroke-width="1.5"
157
+ d="M14.76 12H6.832m0 0c0-.275-.057-.55-.17-.808L4.285 5.814c-.76-1.72 1.058-3.442 2.734-2.591L20.8 10.217c1.46.74 1.46 2.826 0 3.566L7.02 20.777c-1.677.851-3.495-.872-2.735-2.591l2.375-5.378A2 2 0 0 0 6.83 12"
158
+ /></svg
159
+ >
160
+ </div>
161
+ {/if}
143
162
  </div>
144
163
  <p class="font-bold">{device.DevName}</p>
145
164
  </div>
@@ -7,7 +7,7 @@
7
7
  id = crypto.randomUUID(),
8
8
  wrapperClass = '',
9
9
  label = { name: '', class: '' },
10
- value = $bindable(0),
10
+ value = $bindable([0]),
11
11
  type = 'horizontal',
12
12
  number = {
13
13
  minNum: 0,
@@ -16,29 +16,35 @@
16
16
  },
17
17
  }: IProgressBarProps = $props()
18
18
 
19
+ let innerValue: number[] | null = $derived(
20
+ (() => {
21
+ if (typeof value == 'number') {
22
+ return [value]
23
+ } else return value
24
+ })(),
25
+ )
26
+
19
27
  const min = $derived(number.minNum ?? 0)
20
28
  const max = $derived(number.maxNum ?? 100)
21
29
 
22
- let numericValue = $derived(
23
- (() => {
24
- if (typeof value === 'number' && !isNaN(value)) {
25
- return Math.max(min, Math.min(max, value))
26
- } else if (typeof value === 'string') {
27
- const parsedValue = parseFloat(value)
28
- if (!isNaN(parsedValue)) {
29
- return Math.max(min, Math.min(max, parsedValue))
30
- }
31
- } else {
32
- return min
30
+ const numericValue = (value: number) => {
31
+ if (typeof value === 'number' && !isNaN(value)) {
32
+ return Math.max(min, Math.min(max, value))
33
+ } else if (typeof value === 'string') {
34
+ const parsedValue = parseFloat(value)
35
+ if (!isNaN(parsedValue)) {
36
+ return Math.max(min, Math.min(max, parsedValue))
33
37
  }
34
- })(),
35
- )
38
+ } else {
39
+ return min
40
+ }
41
+ }
36
42
 
37
- const progressPercent = $derived(() => {
43
+ const progressPercent = (value: number) => {
38
44
  if (value) {
39
45
  return (((Math.min(Math.max(value, min), max) - min) / (max - min)) * 100) as number
40
46
  }
41
- })
47
+ }
42
48
 
43
49
  const roundToClean = (num: number): number => {
44
50
  if (Number.isInteger(num)) return num
@@ -60,20 +66,27 @@
60
66
  {#if label.name}
61
67
  <h5 class={twMerge(` w-full px-4 text-center`, label.class)}>{label.name}</h5>
62
68
  {/if}
63
-
64
69
  {#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) p-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 class="flex h-full flex-wrap gap-3">
71
+ {#each innerValue as val}
72
+ <div class="flex h-full w-fit min-w-16 flex-col items-center gap-2 rounded-full bg-(--bg-color) p-2">
73
+ <div class="relative my-auto h-[80%] w-[70%] rounded-full bg-(--back-color)/40">
74
+ <div class="absolute bottom-0 left-0 flex w-full rounded-full bg-(--field-color)" style="height: {progressPercent(val)}%;"></div>
75
+ </div>
76
+ <span class="m-auto font-semibold">{roundToClean(Number(numericValue(val)))}{number.units}</span>
77
+ </div>
78
+ {/each}
70
79
  </div>
71
80
  {: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>
81
+ <div class="flex w-full flex-col gap-2">
82
+ {#each innerValue as val}
83
+ <div class="flex h-7 w-full items-center gap-2 rounded-full bg-(--bg-color) px-2">
84
+ <span class="m-auto font-semibold">{roundToClean(Number(numericValue(val)))}{number.units}</span>
85
+ <div class="relative my-auto h-3.5 w-[85%] rounded-full bg-(--back-color)/40">
86
+ <div class="absolute top-0 left-0 flex h-full rounded-full bg-(--field-color)" style="width: {progressPercent(val)}%;"></div>
87
+ </div>
88
+ </div>
89
+ {/each}
77
90
  </div>
78
91
  {/if}
79
92
  </div>
@@ -9,9 +9,6 @@
9
9
  let isDropdownOpen = $state(false)
10
10
  let dropdownElement: HTMLDivElement
11
11
 
12
- let searchValue = $state('')
13
- let filteredOptions = $state<ISelectOption<T>[]>([])
14
-
15
12
  let {
16
13
  id = crypto.randomUUID(),
17
14
  wrapperClass = '',
@@ -23,6 +20,9 @@
23
20
  onUpdate,
24
21
  }: ISelectProps<T> = $props()
25
22
 
23
+ let searchValue = $state('')
24
+ let filteredOptions = $state<ISelectOption<T>[]>([])
25
+
26
26
  /* Закрытие при клике вне компонента */
27
27
  const handleClickOutside = (event: MouseEvent) => {
28
28
  if (dropdownElement && !dropdownElement.contains(event.target as Node)) {
@@ -30,6 +30,10 @@
30
30
  }
31
31
  }
32
32
 
33
+ $effect(() => {
34
+ searchValue = value?.name ?? ''
35
+ })
36
+
33
37
  onMount(() => {
34
38
  if (type === 'select' || type === 'input') document.addEventListener('click', handleClickOutside)
35
39
  if (type === 'input') searchValue = value?.name ?? ''
@@ -48,7 +48,12 @@
48
48
  >
49
49
  {#if item?.icon}
50
50
  <span class="flex h-7 w-7 items-center justify-center overflow-visible [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full">
51
- {@html item.icon}
51
+ {#if typeof item.icon === 'string'}
52
+ {@html item.icon}
53
+ {:else}
54
+ {@const IconComponent = item.icon}
55
+ <IconComponent />
56
+ {/if}
52
57
  </span>
53
58
  {/if}
54
59
  {#if item?.name}
package/dist/types.d.ts CHANGED
@@ -69,7 +69,7 @@ export interface IButtonProps {
69
69
  }
70
70
  export interface IAccordionProps {
71
71
  id?: string;
72
- isOpen: boolean;
72
+ isOpen?: boolean;
73
73
  outline?: boolean;
74
74
  wrapperClass?: string;
75
75
  size?: {
@@ -79,7 +79,7 @@ export interface IAccordionProps {
79
79
  label?: {
80
80
  name?: string;
81
81
  class?: string;
82
- icon?: string | null;
82
+ icon?: ConstructorOfATypedSvelteComponent | string | null;
83
83
  };
84
84
  children?: Snippet;
85
85
  image?: string;
@@ -199,7 +199,7 @@ export interface IProgressBarProps {
199
199
  name?: string;
200
200
  class?: string;
201
201
  };
202
- value?: number | null;
202
+ value?: number | number[] | null;
203
203
  number?: {
204
204
  minNum?: number;
205
205
  maxNum?: number;
@@ -291,7 +291,7 @@ export interface ITabsProps {
291
291
  activeTab?: number;
292
292
  items: {
293
293
  name?: string;
294
- icon?: string;
294
+ icon?: ConstructorOfATypedSvelteComponent | string;
295
295
  class?: string;
296
296
  children?: Snippet;
297
297
  }[];
@@ -312,7 +312,7 @@ export interface IJoystickProps {
312
312
  minNum?: number;
313
313
  maxNum?: number;
314
314
  }[];
315
- buttonIcon?: string;
315
+ buttonIcon?: ConstructorOfATypedSvelteComponent | string;
316
316
  onUpdate?: (value: number[]) => void;
317
317
  }
318
318
  export interface IDeviceGNSS {
@@ -331,7 +331,7 @@ export interface IMapProps {
331
331
  class?: string;
332
332
  };
333
333
  data: IDeviceGNSS | null;
334
- markerIcon?: string;
334
+ markerIcon?: ConstructorOfATypedSvelteComponent | string;
335
335
  }
336
336
  export interface IFileAttachProps {
337
337
  id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -45,11 +45,11 @@
45
45
  "devDependencies": {
46
46
  "@sveltejs/adapter-static": "^3.0.10",
47
47
  "@sveltejs/kit": "^2.49.0",
48
- "@sveltejs/package": "^2.5.6",
48
+ "@sveltejs/package": "^2.5.7",
49
49
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
50
50
  "@types/node": "^24.10.1",
51
51
  "publint": "^0.3.15",
52
- "svelte": "^5.44.1",
52
+ "svelte": "^5.45.2",
53
53
  "svelte-preprocess": "^6.0.3",
54
54
  "vite": "^7.2.4",
55
55
  "vite-plugin-compression": "^0.5.1"