poe-svelte-ui-lib 1.9.6 → 1.9.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.
@@ -5,6 +5,7 @@
5
5
  import * as UI from ".."
6
6
  import { optionsStore } from "../options"
7
7
  import { twMerge } from "tailwind-merge"
8
+ import Library from "../libIcons/Library.svelte"
8
9
 
9
10
  const {
10
11
  component,
@@ -17,6 +18,7 @@
17
18
  }>()
18
19
 
19
20
  let isValidRegExp = $state(true)
21
+ let showRegExpLibrary = $state(true)
20
22
  const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>("DeviceVariables")
21
23
  let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
22
24
 
@@ -146,14 +148,42 @@
146
148
  value={component.properties.maxlength}
147
149
  onUpdate={(value) => updateProperty("maxlength", value as string)}
148
150
  />
149
- <UI.Input
150
- label={{ name: $t("constructor.props.regexp") }}
151
- value={component.properties.help.regExp}
152
- maxlength={150}
153
- help={{ info: $t("constructor.props.regexp.info") }}
154
- componentClass={isValidRegExp === false ? "!border-2 !border-red-400" : ""}
155
- onUpdate={(value) => updateProperty("help.regExp", value as string)}
156
- />
151
+ <div class="flex items-end">
152
+ <UI.Input
153
+ label={{ name: $t("constructor.props.regexp") }}
154
+ value={component.properties.help.regExp}
155
+ maxlength={150}
156
+ help={{ info: $t("constructor.props.regexp.info") }}
157
+ componentClass={isValidRegExp === false ? "!border-2 !border-red-400" : ""}
158
+ onUpdate={(value) => updateProperty("help.regExp", value as string)}
159
+ />
160
+ <UI.Button
161
+ wrapperClass="w-8"
162
+ content={{ icon: Library, info: { text: $t("constructor.props.regexp.library.info"), side: "top" } }}
163
+ onClick={() => {
164
+ showRegExpLibrary = !showRegExpLibrary
165
+ }}
166
+ />
167
+ {#if showRegExpLibrary}
168
+ <UI.Modal bind:isOpen={showRegExpLibrary} wrapperClass="w-200 h-[80%]">
169
+ {#snippet main()}
170
+ {#each $optionsStore.INPUT_REGEXP_OPTIONS as regexp}
171
+ <div
172
+ class="flex flex-col items-start justify-start m-1.5 rounded-xl border-2 border-(--border-color) p-3 cursor-pointer hover:bg-(--gray-color)/30 transition duration-150"
173
+ role="button"
174
+ tabindex={null}
175
+ onkeydown={null}
176
+ onclick={() => (component.properties.help.regExp = regexp.value)}
177
+ >
178
+ <h5>{regexp.name}</h5>
179
+ <span>{regexp.value}</span>
180
+ </div>
181
+ {/each}
182
+ {/snippet}
183
+ </UI.Modal>
184
+ {/if}
185
+ </div>
186
+
157
187
  {#if component.properties.type === "text-area"}
158
188
  <UI.Input
159
189
  label={{ name: $t("constructor.props.textarea.rows") }}
@@ -126,7 +126,7 @@
126
126
 
127
127
  <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge(`bg-blue relative flex w-full flex-col items-center`, wrapperClass)}>
128
128
  {#if label.name}
129
- <h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name} {value}</h5>
129
+ <h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name}</h5>
130
130
  {/if}
131
131
 
132
132
  {#if !readonly}
@@ -209,7 +209,7 @@
209
209
  </div>
210
210
  </div>
211
211
  <!-- Боковые кнопки (ось roll) -->
212
- {#if axes.length == 3}
212
+ {#if axes[1].name !== ""}
213
213
  <div
214
214
  class="absolute flex h-15 w-65 items-center justify-between rounded-full shadow-[0_0_15px_rgb(0_0_0_/0.25)]"
215
215
  style="background: color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)"
@@ -300,21 +300,23 @@
300
300
  </div>
301
301
  {/if}
302
302
 
303
- <div class="flex justify-around">
303
+ <div class="flex justify-around items-end gap-2">
304
304
  {#each axes as axe, index}
305
- <div>
306
- <h5 class=" px-4 text-center">{axe.name}</h5>
307
- <input
308
- class={`w-20 rounded-2xl border border-(--border-color) px-4 py-1 text-center transition-all duration-300 outline-none
305
+ {#if axe.name !== "" || index !== 1}
306
+ <div>
307
+ <h5 class="px-2 text-center">{axe.name}</h5>
308
+ <input
309
+ class={`w-20 rounded-2xl border border-(--border-color) px-4 py-1 text-center transition-all duration-300 outline-none
309
310
  hover:shadow-md
310
311
  [&::-webkit-inner-spin-button]:hidden
311
312
  [&::-webkit-outer-spin-button]:hidden`}
312
- style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
313
- value={value[axes.length == 2 && index == 1 ? index + 1 : index]}
314
- id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
315
- readonly
316
- />
317
- </div>
313
+ style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
314
+ value={value[index]}
315
+ id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
316
+ readonly
317
+ />
318
+ </div>
319
+ {/if}
318
320
  {/each}
319
321
  </div>
320
322
  </div>
@@ -33,56 +33,43 @@
33
33
  )
34
34
  </script>
35
35
 
36
- {#snippet JoystickAxesNames()}
37
- <UI.Input
38
- label={{ name: $t("constructor.props.joystick.axes") }}
39
- value={component.properties.axes.map((axe: any) => axe.name).join(" ")}
40
- help={{ info: $t("constructor.props.joystick.axes.info"), regExp: /^[\p{L}0-9\-_":{}]+ +[\p{L}0-9\-_":{}]+(?: +[\p{L}0-9\-_":{}]+)?$/u }}
41
- maxlength={100}
42
- onUpdate={(value) => {
43
- const stringValue = value as string
44
- const spaceCount = (stringValue.match(/\s/g) || []).length
45
- if (spaceCount > 2) {
46
- return
47
- }
48
- const parts = stringValue.trim().split(/\s+/)
49
- updateProperty(
50
- "axes",
51
- parts.map((a: any, index: number) => {
52
- let axeIndex = parts.length == 2 && component.properties.axes.length === 3 ? index + 1 : index
53
- return {
54
- name: a,
55
- minNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].minNum : -100,
56
- maxNum: component.properties.axes[axeIndex] ? component.properties.axes[axeIndex].maxNum : 100,
57
- }
58
- }),
59
- component,
60
- onPropertyChange,
61
- )
62
- }}
63
- />
64
- {/snippet}
65
-
66
36
  {#snippet JoystickAxesMinMax()}
67
37
  <div class="mt-2 flex w-full justify-around gap-2">
68
38
  {#each component.properties.axes as axe, index}
69
- <div class="flex items-start gap-2">
70
- <h5 class="mt-1">{axe.name}</h5>
39
+ {@const axesOptions = [
40
+ { name: $t("constructor.props.joystick.pitch.axe"), info: "", regExp: /^[\p{L}0-9\-_"':{}]+$/u },
41
+ { name: $t("constructor.props.joystick.roll.axe"), info: $t("constructor.props.joystick.axes.info"), regExp: /^[\p{L}0-9\-_"':{}]*$/u },
42
+ { name: $t("constructor.props.joystick.yaw.axe"), info: "", regExp: /^[\p{L}0-9\-_"':{}]+$/u },
43
+ ]}
44
+ <div class="flex flex-col gap-1">
45
+ <UI.Input
46
+ label={{ name: axesOptions[index].name }}
47
+ value={component.properties.axes[index].name}
48
+ help={{ info: axesOptions[index].info, regExp: axesOptions[index].regExp }}
49
+ maxlength={20}
50
+ onUpdate={(value) => {
51
+ updateProperty(
52
+ "axes",
53
+ component.properties.axes.map((a: any, i: number) => (i === index ? { ...a, name: value } : a)),
54
+ component,
55
+ onPropertyChange,
56
+ )
57
+ }}
58
+ />
59
+
71
60
  <UI.Slider
72
61
  type="range"
73
62
  number={{ minNum: -360, maxNum: 360, step: 10 }}
63
+ disabled={index == 1 && axe.name == ""}
74
64
  value={[component.properties.axes[index].minNum, component.properties.axes[index].maxNum]}
75
65
  onUpdate={(value) => {
76
- if (Array.isArray(value)) {
77
- const axes = component.properties.axes
78
-
66
+ if (Array.isArray(value))
79
67
  updateProperty(
80
68
  "axes",
81
- axes.map((a: any, i: number) => (i === index ? { ...a, minNum: value[0], maxNum: value[1] } : a)),
69
+ component.properties.axes.map((a: any, i: number) => (i === index ? { ...a, minNum: value[0], maxNum: value[1] } : a)),
82
70
  component,
83
71
  onPropertyChange,
84
72
  )
85
- }
86
73
  }}
87
74
  />
88
75
  </div>
@@ -125,7 +112,6 @@
125
112
  {component}
126
113
  {onPropertyChange}
127
114
  />
128
- {@render JoystickAxesNames()}
129
115
  <CommonSnippets snippet="Colors" initialValue={{ color: initialColor }} {component} {onPropertyChange} />
130
116
  </div>
131
117
  </div>
@@ -156,7 +142,6 @@
156
142
  {component}
157
143
  {onPropertyChange}
158
144
  />
159
- {@render JoystickAxesNames()}
160
145
  <CommonSnippets snippet="Colors" initialValue={{ color: initialColor }} {component} {onPropertyChange} />
161
146
  </div>
162
147
  </div>
package/dist/Modal.svelte CHANGED
@@ -24,6 +24,8 @@
24
24
  onCancel?: () => void
25
25
  } = $props()
26
26
 
27
+ let modalWrapper: HTMLDivElement | null = $state(null)
28
+
27
29
  const handleKeyDown = (event: KeyboardEvent) => {
28
30
  if (event.key === "Escape") {
29
31
  isOpen = false
@@ -31,15 +33,27 @@
31
33
  }
32
34
  }
33
35
 
36
+ const handleClickOutside = (event: MouseEvent) => {
37
+ if (modalWrapper && !modalWrapper.contains(event.target as Node)) {
38
+ isOpen = false
39
+ onCancel()
40
+ }
41
+ }
42
+
34
43
  onMount(() => {
35
44
  document.addEventListener("keydown", handleKeyDown)
36
- return () => document.removeEventListener("keydown", handleKeyDown)
45
+ document.addEventListener("mousedown", handleClickOutside)
46
+ return () => {
47
+ document.removeEventListener("keydown", handleKeyDown)
48
+ document.removeEventListener("mousedown", handleClickOutside)
49
+ }
37
50
  })
38
51
  </script>
39
52
 
40
53
  {#if isOpen}
41
54
  <div class="fixed inset-0 z-100 flex items-center justify-center bg-black/50" transition:fade={{ duration: 200, delay: 1 }}>
42
55
  <div
56
+ bind:this={modalWrapper}
43
57
  class={twMerge(`flex w-300 flex-col overflow-hidden rounded-2xl bg-(--back-color) text-center`, wrapperClass)}
44
58
  style="width: {width};"
45
59
  transition:scale={{ duration: 250, start: 0.8 }}
@@ -4,6 +4,7 @@
4
4
 
5
5
  let {
6
6
  id = crypto.randomUUID(),
7
+ wrapperClass,
7
8
  label = { name: "", class: "" },
8
9
  readonly = false,
9
10
  settings = { label: "", number: { minNum: 0, maxNum: 1000, step: 1 } },
@@ -101,14 +102,14 @@
101
102
  }
102
103
  </script>
103
104
 
104
- <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class="w-full h-full p-1">
105
+ <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge("w-full h-full p-1", wrapperClass)}>
105
106
  <div
106
107
  class={`h-full grid grid-rows-[4fr_9fr_5fr] rounded-xl bg-(--container-color)
107
108
  transition-shadow duration-250 p-1
108
109
  shadow-[0_0_3px_rgb(0_0_0_/0.25)] hover:shadow-[0_0_6px_rgb(0_0_0_/0.25)]`}
109
110
  >
110
- <div class="grid gap-2 overflow-hidden items-center" style="grid-template-columns:{icons.array ? '3.5rem' : ''} 1fr;">
111
- {#if icons.array}
111
+ <div class="grid gap-2 overflow-hidden items-center" style="grid-template-columns:{icons.array && icons.array.length !== 0 ? '3.5rem' : ''} 1fr;">
112
+ {#if icons.array && icons.array.length !== 0}
112
113
  <div class="size-14 p-0.5 [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full {icons.class}">
113
114
  {@html currentImage}
114
115
  </div>
@@ -127,7 +128,7 @@
127
128
  <span class="text-5xl">{currentValue === 0 ? (settings.switch?.captionLeft ?? "Off") : (settings.switch?.captionRight ?? "On")}</span>
128
129
  {/if}
129
130
  </div>
130
- <div class="px-2">
131
+ <div class="flex flex-col items-center justify-center px-2">
131
132
  {#if settings.label}
132
133
  <h5>{settings.label}</h5>
133
134
  {/if}
@@ -0,0 +1,8 @@
1
+ <script lang="ts"></script>
2
+
3
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16"
4
+ ><path
5
+ fill="currentColor"
6
+ d="M1 3.25C1 2.56 1.56 2 2.249 2h.5c.69 0 1.248.56 1.248 1.25v9.495c0 .69-.559 1.25-1.248 1.25h-.5A1.25 1.25 0 0 1 1 12.744zM2.249 3a.25.25 0 0 0-.25.25v9.495c0 .138.112.25.25.25h.5a.25.25 0 0 0 .25-.25V3.249a.25.25 0 0 0-.25-.25zm2.748.25c0-.69.559-1.25 1.249-1.25h.5c.689 0 1.248.56 1.248 1.25v9.495c0 .69-.56 1.25-1.249 1.25h-.5a1.25 1.25 0 0 1-1.248-1.25zM6.246 3a.25.25 0 0 0-.25.25v9.495c0 .138.112.25.25.25h.5a.25.25 0 0 0 .249-.25V3.249a.25.25 0 0 0-.25-.25zm5.726 1.777a1.25 1.25 0 0 0-1.57-.713l-.583.204a1.25 1.25 0 0 0-.746 1.645l2.937 7.304c.249.62.94.933 1.571.713l.582-.204a1.25 1.25 0 0 0 .746-1.646zm-1.24.23a.25.25 0 0 1 .313.143l2.937 7.303a.25.25 0 0 1-.149.33l-.582.203a.25.25 0 0 1-.314-.142L10 5.54a.25.25 0 0 1 .149-.329z"
7
+ /></svg
8
+ >
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Library: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Library = InstanceType<typeof Library>;
18
+ export default Library;
@@ -98,7 +98,19 @@ const translations = {
98
98
  "constructor.props.autocomplete": "Автозаполнение",
99
99
  "constructor.props.maxlength": "Максимальная длина",
100
100
  "constructor.props.regexp": "Выражение валидации",
101
- "constructor.props.regexp.info": "Введите RegExp без /.../ (например: ^\\d+$)",
101
+ "constructor.props.regexp.library.info": "Библиотека выражений",
102
+ "constructor.props.regexp.latin10": "Строка латинских символов с максимальной длиной 10",
103
+ "constructor.props.regexp.kiril10": "Строка букв русского алфавита с максимальной длиной 10",
104
+ "constructor.props.regexp.ipv4": "IPv4",
105
+ "constructor.props.regexp.ipv6": "IPv6",
106
+ "constructor.props.regexp.date": "Дата в формате ДД.ММ.ГГГГ",
107
+ "constructor.props.regexp.time": "Время (24-часовой формат)",
108
+ "constructor.props.regexp.email": "Электронная почта",
109
+ "constructor.props.regexp.url": "URL",
110
+ "constructor.props.regexp.numbers": "Целые числа и числа с плавающей точкой (разделитель точка)",
111
+ "constructor.props.regexp.colorhex": "Цвет HEX",
112
+ "constructor.props.regexp.devid": "ID устройства",
113
+ "constructor.props.regexp.serialnum": "Серийный номер устройства",
102
114
  "constructor.props.min": "Мин.",
103
115
  "constructor.props.max": "Макс.",
104
116
  "constructor.props.units": "Единица измерения",
@@ -142,8 +154,10 @@ const translations = {
142
154
  "constructor.props.multiselect": "Мультивыбор",
143
155
  "constructor.props.access": "Доступ (не для владельца)",
144
156
  "constructor.props.map.timeout": "Таймаут маркеров:",
145
- "constructor.props.joystick.axes": "Названия осей",
146
- "constructor.props.joystick.axes.info": "Поле для ввода названий осей, разделенных пробелами (2 или 3 названия)",
157
+ "constructor.props.joystick.pitch.axe": "Ocь ",
158
+ "constructor.props.joystick.roll.axe": "Ось ",
159
+ "constructor.props.joystick.yaw.axe": "Ось ⮂",
160
+ "constructor.props.joystick.axes.info": "Если поле пустое - ось не видна",
147
161
  "constructor.props.joystick.homebutton": "Центральная кнопка сброса",
148
162
  "constructor.props.file.select": "Выберите файл",
149
163
  "constructor.props.file.notselected": "Файл не выбран",
package/dist/options.d.ts CHANGED
@@ -155,6 +155,11 @@ export declare const optionsStore: import("svelte/store").Readable<{
155
155
  value: string;
156
156
  name: string;
157
157
  }[];
158
+ INPUT_REGEXP_OPTIONS: {
159
+ id: string;
160
+ value: string;
161
+ name: string;
162
+ }[];
158
163
  AUTOCOMPLETE_CONSTRUCTOR_OPTIONS: {
159
164
  id: string;
160
165
  value: string;
package/dist/options.js CHANGED
@@ -199,6 +199,24 @@ export const optionsStore = derived(t, ($t) => {
199
199
  { id: id(), value: "slider", name: $t("constructor.props.slider") },
200
200
  { id: id(), value: "switch", name: $t("constructor.props.switch") },
201
201
  ],
202
+ INPUT_REGEXP_OPTIONS: [
203
+ { id: id(), value: "/^[0-9a-z]{0,10}$/", name: $t("constructor.props.regexp.latin10") },
204
+ { id: id(), value: "/^[а-яА-яЁё]{0,10}$/", name: $t("constructor.props.regexp.kiril10") },
205
+ {
206
+ id: id(),
207
+ value: "/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/",
208
+ name: $t("constructor.props.regexp.ipv4"),
209
+ },
210
+ { id: id(), value: "/((^|:)([0-9a-fA-F]{0,4})){1,8}$/", name: $t("constructor.props.regexp.ipv6") },
211
+ { id: id(), value: "/(0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\\d\\d/", name: $t("constructor.props.regexp.date") },
212
+ { id: id(), value: "/^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/", name: $t("constructor.props.regexp.time") },
213
+ { id: id(), value: "/^[a-zA-Z0-9.!#$%&'*+/=?^_{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)\\*$/", name: $t("constructor.props.regexp.email") },
214
+ { id: id(), value: "/(https?):((//)|(\\\\))+[\\w\\d:#@%/$()~_?\\+-=\\\\\\.&]*/", name: $t("constructor.props.regexp.url") },
215
+ { id: id(), value: "/^-?\\d+(\\.\\d+)?$/", name: $t("constructor.props.regexp.numbers") },
216
+ { id: id(), value: "/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/", name: $t("constructor.props.regexp.colorhex") },
217
+ { id: id(), value: "/^[0-9A-F]{0,4}$/", name: $t("constructor.props.regexp.devid") },
218
+ { id: id(), value: "/^[0-9a-fA-F]{4}-[0-9a-fA-F]{24}[:][0-9a-fA-F]{2}$|^$/", name: $t("constructor.props.regexp.serialnum") },
219
+ ],
202
220
  AUTOCOMPLETE_CONSTRUCTOR_OPTIONS: [
203
221
  { id: id(), value: "on", name: $t("constructor.props.autocomplete.on") },
204
222
  { id: id(), value: "off", name: $t("constructor.props.autocomplete.off") },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.9.6",
3
+ "version": "1.9.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@sveltejs/adapter-static": "^3.0.10",
40
- "@sveltejs/kit": "^2.56.1",
40
+ "@sveltejs/kit": "^2.57.0",
41
41
  "@sveltejs/package": "^2.5.7",
42
42
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
43
43
  "@tailwindcss/vite": "^4.2.2",
@@ -47,11 +47,11 @@
47
47
  "prettier-plugin-svelte": "^3.5.1",
48
48
  "prettier-plugin-tailwindcss": "^0.7.2",
49
49
  "publint": "^0.3.18",
50
- "svelte": "^5.55.1",
50
+ "svelte": "^5.55.2",
51
51
  "tailwindcss": "^4.2.2",
52
52
  "tsx": "^4.21.0",
53
53
  "typescript": "^6.0.2",
54
- "vite": "^8.0.5",
54
+ "vite": "^8.0.8",
55
55
  "vite-plugin-compression": "^0.5.1"
56
56
  }
57
57
  }