poe-svelte-ui-lib 1.6.2 → 1.6.4

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.
@@ -0,0 +1,68 @@
1
+ <script lang="ts">
2
+ import { twMerge } from "tailwind-merge"
3
+ import ArrowIcon from "../libIcons/ArrowIcon.svelte"
4
+ import { slide } from "svelte/transition"
5
+ import type { ICarouselProps } from "../types"
6
+
7
+ let { id = crypto.randomUUID(), wrapperClass = "", label = { name: "", class: "text-left" }, scrollValue = 200, children }: ICarouselProps = $props()
8
+
9
+ let carouselRef: HTMLDivElement | null = $state(null)
10
+ let isAtStart = $state(true)
11
+ let isAtEnd = $state(true)
12
+
13
+ function scrollCarousel(delta: number) {
14
+ if (carouselRef) carouselRef.scrollBy({ left: delta, behavior: "smooth" })
15
+ }
16
+
17
+ function updateScrollState() {
18
+ if (!carouselRef) return
19
+ isAtStart = carouselRef.scrollLeft === 0
20
+ isAtEnd = carouselRef.scrollLeft + carouselRef.clientWidth >= carouselRef.scrollWidth - 1
21
+ }
22
+
23
+ $effect(() => {
24
+ if (!carouselRef) return
25
+ updateScrollState()
26
+
27
+ const onScroll = () => updateScrollState()
28
+ carouselRef.addEventListener("scroll", onScroll)
29
+
30
+ const resizeObserver = new ResizeObserver(() => updateScrollState())
31
+ resizeObserver.observe(carouselRef)
32
+
33
+ return () => {
34
+ carouselRef?.removeEventListener("scroll", onScroll)
35
+ resizeObserver.disconnect()
36
+ }
37
+ })
38
+ </script>
39
+
40
+ <div class={twMerge(`flex flex-col items-center`, wrapperClass)}>
41
+ {#if label.name}
42
+ <h5 class={`${label.class}`}>{label.name}</h5>
43
+ {/if}
44
+ <!-- Карусель -->
45
+ <div class="relative w-full overflow-hidden rounded-2xl bg-(--back-color)/50">
46
+ <!-- Кнопки навигации -->
47
+ {#if !isAtStart}
48
+ <button
49
+ transition:slide={{ axis: "x" }}
50
+ class="absolute z-10 size-10 translate-y-1/2 cursor-pointer left-1 p-2 rounded-full bg-(--field-color) [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full shadow-[0_0_6px_var(--shadow-color)] rotate-180"
51
+ onclick={() => scrollCarousel(-scrollValue)}>
52
+ <ArrowIcon />
53
+ </button>
54
+ {/if}
55
+ {#if !isAtEnd}
56
+ <button
57
+ transition:slide={{ axis: "x" }}
58
+ class="absolute z-10 size-10 translate-y-1/2 cursor-pointer right-1 p-2 rounded-full bg-(--field-color) [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full shadow-[0_0_6px_var(--shadow-color)]"
59
+ onclick={() => scrollCarousel(scrollValue)}>
60
+ <ArrowIcon />
61
+ </button>
62
+ {/if}
63
+
64
+ <div {id} bind:this={carouselRef} class="flex overflow-y-hidden gap-2 p-2 h-24">
65
+ {@render children?.()}
66
+ </div>
67
+ </div>
68
+ </div>
@@ -0,0 +1,4 @@
1
+ import type { ICarouselProps } from "../types";
2
+ declare const Carousel: import("svelte").Component<ICarouselProps, {}, "">;
3
+ type Carousel = ReturnType<typeof Carousel>;
4
+ export default Carousel;
@@ -1,24 +1,24 @@
1
1
  <!-- $lib/ElementsUI/Input.svelte -->
2
2
  <script lang="ts">
3
- import { fade, fly } from 'svelte/transition'
4
- import type { IInputProps } from '../types'
5
- import { twMerge } from 'tailwind-merge'
3
+ import { fade, fly } from "svelte/transition"
4
+ import type { IInputProps } from "../types"
5
+ import { twMerge } from "tailwind-merge"
6
6
 
7
7
  let {
8
8
  id = crypto.randomUUID(),
9
- wrapperClass = '',
10
- label = { name: '', class: '' },
9
+ wrapperClass = "",
10
+ label = { name: "", class: "" },
11
11
  disabled = false,
12
12
  readonly = false,
13
13
  value = $bindable(),
14
- type = 'text',
15
- placeholder = '',
16
- componentClass = '',
14
+ type = "text",
15
+ placeholder = "",
16
+ componentClass = "",
17
17
  maxlength = 100,
18
18
  textareaRows = 3,
19
19
  isValid = $bindable(true),
20
20
  number = { minNum: -1000000, maxNum: 1000000, step: 1 },
21
- help = { info: '', autocomplete: 'off', copyButton: false, regExp: '^[\\s\\S]*$' },
21
+ help = { info: "", autocomplete: "off", copyButton: false, regExp: "^[\\s\\S]*$" },
22
22
  onUpdate = () => {},
23
23
  }: IInputProps = $props()
24
24
 
@@ -26,11 +26,11 @@
26
26
  let showInfo = $state(false)
27
27
  let isCopied = $state(false)
28
28
 
29
- $effect(() => {
30
- if (type === 'number') {
31
- if (value === undefined || value === null || value === '') value = number.minNum
32
- }
33
- })
29
+ // $effect(() => {
30
+ // if (type === "number") {
31
+ // if (value === undefined || value === null || value === "") value = number.minNum
32
+ // }
33
+ // })
34
34
 
35
35
  /* Обработка регулярного выражения */
36
36
  const parseRegExp = (pattern: string | RegExp): RegExp => {
@@ -38,14 +38,15 @@
38
38
  const match = pattern.match(/^\/(.*)\/([gimsuy]*)$/)
39
39
  return match ? new RegExp(match[1], match[2]) : new RegExp(pattern)
40
40
  }
41
- let RegExpObj = $derived(() => parseRegExp(help.regExp ?? ''))
41
+ let RegExpObj = $derived(() => parseRegExp(help.regExp ?? ""))
42
+
42
43
  $effect(() => {
43
- isValid = RegExpObj().test(typeof value === 'string' ? value : String(value))
44
+ isValid = RegExpObj().test(typeof value === "string" ? value : String(value))
44
45
  })
45
46
 
46
47
  const handleInputChange = (value: string | number) => {
47
- if (type === 'number') {
48
- const numValue = typeof value === 'string' ? parseFloat(value.replace(',', '.')) : Number(value)
48
+ if (type === "number") {
49
+ const numValue = typeof value === "string" ? parseFloat(value.replace(",", ".")) : Number(value)
49
50
  if (!isNaN(numValue)) onUpdate?.(numValue)
50
51
  else onUpdate?.(value as string)
51
52
  } else {
@@ -53,8 +54,16 @@
53
54
  }
54
55
  }
55
56
 
57
+ const handleKeyDown = (e: KeyboardEvent) => {
58
+ if (type === "number") {
59
+ if (e.key === ".") {
60
+ e.preventDefault()
61
+ }
62
+ }
63
+ }
64
+
56
65
  $effect(() => {
57
- if (type === 'number' && typeof value == 'number') {
66
+ if (type === "number" && typeof value == "number") {
58
67
  value = roundToClean(value)
59
68
  }
60
69
  })
@@ -72,23 +81,23 @@
72
81
  }
73
82
  </script>
74
83
 
75
- <div class={twMerge(`bg-max ${type === 'text-area' ? 'h-full' : ''} relative flex w-full flex-col px-1 items-center`, wrapperClass)}>
84
+ <div class={twMerge(`bg-max ${type === "text-area" ? "h-full" : ""} relative flex w-full flex-col px-1 items-center`, wrapperClass)}>
76
85
  {#if label.name}
77
86
  <h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name}</h5>
78
87
  {/if}
79
88
 
80
89
  <div class="relative flex w-full items-center {type === 'text-area' ? 'h-full' : ''}">
81
- {#if type === 'text' || type === 'password' || type === 'number'}
90
+ {#if type === "text" || type === "password" || type === "number"}
82
91
  <input
83
92
  bind:value
84
93
  class={twMerge(
85
94
  `w-full rounded-2xl border px-4 py-1 text-center shadow-[0_0_3px_rgb(0_0_0_/0.25)] transition duration-200
86
95
  outline-none focus:shadow-[0_0_6px_var(--blue-color)] focus:border-(--blue-color) [&::-webkit-inner-spin-button]:hidden [&::-webkit-outer-spin-button]:hidden
87
- ${isValid ? 'border-(--bg-color)' : 'border-red-400 shadow-[0_0_6px_var(--red-color)] focus:shadow-[0_0_6px_var(--red-color)] focus:border-red-400'}
88
- ${disabled ? 'opacity-50' : 'hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]'}
89
- ${readonly ? '' : 'hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]'}
90
- ${help?.info ? 'pl-8' : ''}
91
- ${help.copyButton || type === 'password' || (type === 'number' && !readonly) ? 'pr-8' : ''}`,
96
+ ${isValid ? "border-(--bg-color)" : "border-red-400 shadow-[0_0_6px_var(--red-color)] focus:shadow-[0_0_6px_var(--red-color)] focus:border-red-400"}
97
+ ${disabled ? "opacity-50" : "hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]"}
98
+ ${readonly ? "" : "hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]"}
99
+ ${help?.info ? "pl-8" : ""}
100
+ ${help.copyButton || type === "password" || (type === "number" && !readonly) ? "pr-8" : ""}`,
92
101
  componentClass,
93
102
  )}
94
103
  style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
@@ -96,25 +105,25 @@
96
105
  {placeholder}
97
106
  {disabled}
98
107
  autocomplete={help?.autocomplete}
99
- oninput={(e) => handleInputChange((e.currentTarget as HTMLInputElement).value)}
100
- type={type === 'password' ? (showPassword ? 'text' : 'password') : type === 'number' ? 'number' : 'text'}
108
+ oninput={e => handleInputChange((e.currentTarget as HTMLInputElement).value)}
109
+ onkeydown={handleKeyDown}
110
+ type={type === "password" ? (showPassword ? "text" : "password") : type === "number" ? "number" : "text"}
101
111
  {maxlength}
102
112
  min={number?.minNum}
103
113
  max={number?.maxNum}
104
114
  step={number?.step}
105
- {readonly}
106
- />
107
- {:else if type === 'text-area'}
115
+ {readonly} />
116
+ {:else if type === "text-area"}
108
117
  <textarea
109
118
  bind:value
110
119
  class={twMerge(
111
120
  `h-full w-full resize-y rounded-2xl border border-(--border-color) px-2 py-1 text-center shadow-[0_0_3px_rgb(0_0_0_/0.25)] transition
112
121
  duration-200 outline-none focus:border-blue-400
113
- ${isValid ? 'border-(--bg-color)' : 'border-red-400 shadow-[0_0_6px_var(--red-color)] focus:shadow-[0_0_6px_var(--red-color)] focus:border-red-400'}
114
- ${disabled ? 'cursor-not-allowed opacity-50' : 'hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]'}
115
- ${readonly ? '' : 'hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]'}
116
- ${help?.info ? 'pl-8' : ''}
117
- ${help.copyButton ? 'pr-8' : ''}`,
122
+ ${isValid ? "border-(--bg-color)" : "border-red-400 shadow-[0_0_6px_var(--red-color)] focus:shadow-[0_0_6px_var(--red-color)] focus:border-red-400"}
123
+ ${disabled ? "cursor-not-allowed opacity-50" : "hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]"}
124
+ ${readonly ? "" : "hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]"}
125
+ ${help?.info ? "pl-8" : ""}
126
+ ${help.copyButton ? "pr-8" : ""}`,
118
127
  componentClass,
119
128
  )}
120
129
  style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
@@ -124,63 +133,55 @@
124
133
  rows={textareaRows}
125
134
  {placeholder}
126
135
  {readonly}
127
- oninput={(e) => handleInputChange((e.currentTarget as HTMLTextAreaElement).value)}
128
- ></textarea>
136
+ oninput={e => handleInputChange((e.currentTarget as HTMLTextAreaElement).value)}></textarea>
129
137
  {/if}
130
138
 
131
- {#if type === 'password' && !disabled}
139
+ {#if type === "password" && !disabled}
132
140
  <button
133
141
  type="button"
134
142
  class="absolute right-2 flex cursor-pointer border-none bg-transparent"
135
143
  onclick={() => (showPassword = !showPassword)}
136
- aria-label={showPassword ? 'Скрыть пароль' : 'Показать пароль'}
137
- >
144
+ aria-label={showPassword ? "Скрыть пароль" : "Показать пароль"}>
138
145
  {#if showPassword}
139
146
  <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"
140
147
  ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
141
148
  ><path d="M15 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0" /><path
142
- d="M2 12c1.6-4.097 5.336-7 10-7s8.4 2.903 10 7c-1.6 4.097-5.336 7-10 7s-8.4-2.903-10-7"
143
- /></g
144
- ></svg
145
- >
149
+ d="M2 12c1.6-4.097 5.336-7 10-7s8.4 2.903 10 7c-1.6 4.097-5.336 7-10 7s-8.4-2.903-10-7" /></g
150
+ ></svg>
146
151
  {:else}
147
152
  <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"
148
153
  ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
149
154
  ><path
150
155
  stroke-linejoin="round"
151
- d="M10.73 5.073A11 11 0 0 1 12 5c4.664 0 8.4 2.903 10 7a11.6 11.6 0 0 1-1.555 2.788M6.52 6.519C4.48 7.764 2.9 9.693 2 12c1.6 4.097 5.336 7 10 7a10.44 10.44 0 0 0 5.48-1.52m-7.6-7.6a3 3 0 1 0 4.243 4.243"
152
- /><path d="m4 4l16 16" /></g
153
- ></svg
154
- >
156
+ d="M10.73 5.073A11 11 0 0 1 12 5c4.664 0 8.4 2.903 10 7a11.6 11.6 0 0 1-1.555 2.788M6.52 6.519C4.48 7.764 2.9 9.693 2 12c1.6 4.097 5.336 7 10 7a10.44 10.44 0 0 0 5.48-1.52m-7.6-7.6a3 3 0 1 0 4.243 4.243" /><path
157
+ d="m4 4l16 16" /></g
158
+ ></svg>
155
159
  {/if}
156
160
  </button>
157
161
  {/if}
158
162
 
159
- {#if help.copyButton && (type === 'text' || type === 'text-area') && !disabled}
163
+ {#if help.copyButton && (type === "text" || type === "text-area") && !disabled}
160
164
  <button
161
165
  class="absolute right-3 flex border-none bg-transparent {type === 'text-area' ? 'top-2' : ''} cursor-pointer"
162
- onclick={(e) => {
166
+ onclick={e => {
163
167
  e.preventDefault()
164
168
  navigator.clipboard.writeText(value as string)
165
169
  isCopied = true
166
170
  setTimeout(() => (isCopied = false), 1000)
167
171
  }}
168
- aria-label="Копировать текст"
169
- >
172
+ aria-label="Копировать текст">
170
173
  <div class=" size-5 text-sm [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full">
171
174
  {#if isCopied}
172
175
  <div
173
176
  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"
174
- transition:fade={{ duration: 200 }}
175
- >
177
+ transition:fade={{ duration: 200 }}>
176
178
 
177
179
  </div>
178
180
  {:else}
179
181
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
180
182
  <g fill="none" stroke="currentColor" stroke-width="1.5">
181
183
  <path
182
- 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"
183
- />
184
+ 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" />
184
185
  <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" />
185
186
  </g>
186
187
  </svg>
@@ -189,12 +190,12 @@
189
190
  </button>
190
191
  {/if}
191
192
 
192
- {#if type === 'number' && !readonly && !disabled}
193
+ {#if type === "number" && !readonly && !disabled}
193
194
  <div class="absolute right-0 flex h-full w-8 flex-col items-center justify-center rounded-r-2xl border-l border-(--border-color)">
194
195
  <button
195
196
  class="flex h-1/2 w-full items-center rounded-tr-2xl border-b border-(--border-color) pl-2 transition-colors duration-150 hover:bg-(--gray-color)/30 active:bg-(--gray-color)/10"
196
197
  onclick={() => {
197
- if (!number.maxNum || !number.step) return
198
+ if (!number.maxNum || !number.step || !value) return
198
199
  if (Number(value) + number.step >= number.maxNum) {
199
200
  value = number.maxNum
200
201
  onUpdate(value as number)
@@ -203,13 +204,12 @@
203
204
  value = Number(value) + (number.step ?? 1)
204
205
  onUpdate(value as number)
205
206
  }}
206
- aria-label="Увеличить">+</button
207
- >
207
+ aria-label="Увеличить">+</button>
208
208
 
209
209
  <button
210
210
  class="flex h-1/2 w-full items-center rounded-br-2xl pl-2 transition-colors duration-150 hover:bg-(--gray-color)/30 active:bg-(--gray-color)/10"
211
211
  onclick={() => {
212
- if (number.minNum === null || number.minNum === undefined || !number.step) return
212
+ if (!number.minNum || !number.step || !value) return
213
213
  if (Number(value) - number.step <= number.minNum) {
214
214
  value = number.minNum
215
215
  onUpdate(value as number)
@@ -218,36 +218,27 @@
218
218
  value = Number(value) - (number.step ?? 1)
219
219
  onUpdate(value as number)
220
220
  }}
221
- aria-label="Уменьшить">−</button
222
- >
221
+ aria-label="Уменьшить">−</button>
223
222
  </div>
224
223
  {/if}
225
224
 
226
225
  {#if help.info}
227
226
  <button
228
227
  type="button"
229
- class="button-info absolute left-2 flex border-none bg-transparent {type === 'text-area' ? 'top-2' : ''} {disabled
230
- ? 'opacity-50'
231
- : 'cursor-pointer'}"
228
+ class="button-info absolute left-2 flex border-none bg-transparent {type === 'text-area' ? 'top-2' : ''} {disabled ? 'opacity-50' : 'cursor-pointer'}"
232
229
  onmouseenter={() => (showInfo = true)}
233
230
  onmouseleave={() => (showInfo = false)}
234
- aria-label={showInfo ? 'Скрыть инфо' : 'Показать инфо'}
235
- >
231
+ aria-label={showInfo ? "Скрыть инфо" : "Показать инфо"}>
236
232
  <svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"
237
233
  ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
238
- ><circle cx="12" cy="12" r="10" stroke-width="1.3" /><path stroke-width="1.5" d="M12 16v-4.5" /><path
239
- stroke-width="1.8"
240
- d="M12 8.012v-.01"
241
- /></g
242
- ></svg
243
- >
234
+ ><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
235
+ ></svg>
244
236
  </button>
245
237
 
246
238
  {#if showInfo}
247
239
  <div
248
240
  transition:fly={{ x: -15, duration: 250 }}
249
- class="absolute top-5 left-10 z-50 w-auto -translate-y-1/2 rounded bg-(--container-color) px-2 py-1 shadow-lg"
250
- >
241
+ class="absolute top-5 left-10 z-50 w-auto -translate-y-1/2 rounded bg-(--container-color) px-2 py-1 shadow-lg">
251
242
  {help?.info}
252
243
  </div>
253
244
  {/if}
@@ -1,4 +1,4 @@
1
- import type { IInputProps } from '../types';
1
+ import type { IInputProps } from "../types";
2
2
  declare const Input: import("svelte").Component<IInputProps, {}, "value" | "isValid">;
3
3
  type Input = ReturnType<typeof Input>;
4
4
  export default Input;