poe-svelte-ui-lib 1.35.0 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +24 -0
  2. package/dist/Carousel/Carousel.svelte +4 -3
  3. package/dist/ColorPicker/ColorPicker.svelte +12 -14
  4. package/dist/Divider/README.md +8 -8
  5. package/dist/Dragging.README.md +9 -9
  6. package/dist/FileAttach/FileAttach.svelte +12 -11
  7. package/dist/FileAttach/README.md +17 -17
  8. package/dist/Graph/Graph.svelte +3 -2
  9. package/dist/Input/README.md +29 -29
  10. package/dist/Joystick/Joystick.svelte +8 -8
  11. package/dist/Map/Map.svelte +12 -9
  12. package/dist/Map/MapProps.svelte +1 -3
  13. package/dist/Modal.README.md +10 -10
  14. package/dist/Select/README.md +25 -22
  15. package/dist/Table/README.md +5 -0
  16. package/dist/Table/TableProps.svelte +211 -213
  17. package/dist/VideoViewer/VideoViewer.svelte +8 -7
  18. package/dist/Widget/README.md +4 -0
  19. package/dist/WidgetDeviceInfo/README.md +22 -20
  20. package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +1 -4
  21. package/dist/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte +1 -12
  22. package/dist/WidgetFileSystem/README.md +2 -0
  23. package/dist/WidgetRGBStrip/LightIcon.svelte +12 -0
  24. package/dist/WidgetRGBStrip/LightIcon.svelte.d.ts +18 -0
  25. package/dist/WidgetRGBStrip/README.md +60 -0
  26. package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte +185 -0
  27. package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte.d.ts +4 -0
  28. package/dist/WidgetRGBStrip/WidgetRGBStripProps.svelte +33 -0
  29. package/dist/WidgetRGBStrip/WidgetRGBStripProps.svelte.d.ts +14 -0
  30. package/dist/WidgetStackInfo/README.md +2 -0
  31. package/dist/WidgetWiFi/README.md +3 -1
  32. package/dist/index.d.ts +3 -1
  33. package/dist/index.js +2 -0
  34. package/dist/locales/translations.js +5 -0
  35. package/dist/types.d.ts +27 -4
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,24 @@ npm install poe-svelte-ui-lib
22
22
  @source "../node_modules/poe-svelte-ui-lib/dist";
23
23
  ```
24
24
 
25
+ **Глобальные стили тегов — только в `@layer base`.** Компоненты задают размеры текста и отступы Tailwind-утилитами (например `text-[12px]` у подписи `<h5>`). Правило вне слоя (`h5 { font-size: 1rem }`, `label { … }`, `p { … }`) сильнее любой утилиты, и в приложении подписи станут крупнее задуманного и перестанут помещаться. Поэтому типографику тегов оборачивайте:
26
+
27
+ ```css
28
+ @layer base {
29
+ h5 {
30
+ font-size: 1rem;
31
+ font-weight: 600;
32
+ }
33
+ label {
34
+ font-size: 0.8rem;
35
+ }
36
+ }
37
+ ```
38
+
39
+ Классы ролей (`.bg-red`, `.bg-max`, …) и темы (`.light`/`.dark`) остаются вне слоя — они как раз должны перебивать утилиты.
40
+
41
+ Необязательный токен `--table-header-color` — фон шапки `Table` (по умолчанию `--container-color`); смарт-виджеты задают его сами из мастер-цвета.
42
+
25
43
  Справочник по регулярным выражениям (`docs/info/RegExp.md`) и протокол обмена данными с устройством (`docs/info/ExchangeRules.md`) — вспомогательные материалы для тех, кто настраивает валидацию полей и работу с `DeviceStore`.
26
44
 
27
45
  ## Использование
@@ -39,6 +57,12 @@ npm install poe-svelte-ui-lib
39
57
 
40
58
  Полный список компонентов и типов — в `src/lib/index.ts`. У каждого компонента есть README рядом с исходником (`src/lib/<Component>/README.md`, для `Modal`/`Dragging` — `src/lib/<Component>.README.md`) с описанием пропсов, примерами и внутренним устройством; живая витрина всех компонентов — `npm run dev` → `/components/all`.
41
59
 
60
+ **Витрина повторяет конструктор DevCloud:** слева компонент и код (прижат к низу), справа панель свойств шириной `24rem` с тем же оформлением, что в облаке, — переполнение подписей видно сразу. Кнопка (i) в шапке панели открывает описание компонента.
61
+
62
+ **Описания компонентов (i)** — ключи `component.info.<Тип>` в `src/lib/locales/translations.ts` (ru/en/zh, Markdown). Один источник для витрины и конструктора DevCloud: облако показывает кнопку (i) у любого компонента, для которого есть описание. Добавляя компонент, допишите и описание: для чего он, какую переменную устройства показывает, что и когда отправляет, главные настройки и ограничения.
63
+
64
+ **Иконки вариантов панелей свойств** — `src/lib/libIcons/propsIcons.ts` (контурные SVG 24×24 строками), подставляются в `IOption.icon` в `options.ts`.
65
+
42
66
  ## Разработка
43
67
 
44
68
  ```bash
@@ -42,12 +42,13 @@
42
42
  <h5 class={`${label.class}`}>{label.name}</h5>
43
43
  {/if}
44
44
  <!-- Карусель -->
45
- <div class="relative w-full overflow-hidden rounded-2xl bg-(--back-color)/50">
45
+ <!-- По design-handoff (раздел 9): сплошной фон, стрелки 32 px на поверхности с линией и тенью уровня 2 -->
46
+ <div class="relative w-full overflow-hidden rounded-2xl bg-(--container-color)">
46
47
  <!-- Кнопки навигации -->
47
48
  {#if !isAtStart}
48
49
  <button
49
50
  transition:slide={{ axis: "x" }}
50
- class="absolute z-10 size-10 -translate-y-1/2 cursor-pointer top-1/2 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
+ class="absolute z-10 size-[32px] -translate-y-1/2 cursor-pointer top-1/2 left-1 p-1.5 rounded-full border border-(--hairline-color) bg-(--back-color) text-(--muted-color) transition-colors duration-150 hover:text-(--font-color) [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full shadow-(--elevation-2) rotate-180"
51
52
  onclick={() => scrollCarousel(-scrollValue)}
52
53
  >
53
54
  <ArrowIcon />
@@ -56,7 +57,7 @@
56
57
  {#if !isAtEnd}
57
58
  <button
58
59
  transition:slide={{ axis: "x" }}
59
- class="absolute z-10 size-10 -translate-y-1/2 cursor-pointer top-1/2 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)]"
60
+ class="absolute z-10 size-[32px] -translate-y-1/2 cursor-pointer top-1/2 right-1 p-1.5 rounded-full border border-(--hairline-color) bg-(--back-color) text-(--muted-color) transition-colors duration-150 hover:text-(--font-color) [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full shadow-(--elevation-2)"
60
61
  onclick={() => scrollCarousel(scrollValue)}
61
62
  >
62
63
  <ArrowIcon />
@@ -137,7 +137,7 @@
137
137
  <div class="flex w-full flex-col gap-2">
138
138
  <!-- Выбор цвета -->
139
139
  <div
140
- class="hue-slider relative h-7 w-full cursor-pointer overflow-hidden rounded-2xl shadow-sm transition duration-200 hover:shadow-md"
140
+ class="hue-slider relative h-7 w-full cursor-pointer overflow-hidden rounded-[10px] border border-(--hairline-color)"
141
141
  role="slider"
142
142
  aria-valuenow={null}
143
143
  tabindex={null}
@@ -152,17 +152,15 @@
152
152
  ></div>
153
153
  {#if mode === "hsv"}
154
154
  <div
155
- class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-2xl border-2 border-white"
156
- style={`left: ${huePointer}%; background: rgb(${hsvToRgb(hsv.h, 100, 100).join(",")})`}
155
+ class="pointer-events-none absolute inset-y-0 w-[2px] -translate-x-1/2 bg-white shadow-[0_0_0_1px_rgb(0_0_0/0.25),0_1px_3px_rgb(0_0_0/0.4)]"
156
+ style={`left: ${huePointer}%`}
157
157
  ></div>
158
158
  {/if}
159
159
  </div>
160
160
 
161
161
  <!-- Яркость цвета -->
162
162
  <div
163
- class="brightness-slider relative h-4 w-full cursor-pointer overflow-hidden rounded-2xl {mode === 'hsv'
164
- ? 'shadow-sm transition duration-200 hover:shadow-md'
165
- : ''}"
163
+ class="brightness-slider relative h-4 w-full cursor-pointer overflow-hidden rounded-[10px] border border-(--hairline-color)"
166
164
  role="slider"
167
165
  aria-valuenow={null}
168
166
  tabindex={null}
@@ -172,15 +170,15 @@
172
170
  <div class="absolute inset-0" style={`background: linear-gradient(to right, rgb(0,0,0), rgb(${hsvToRgb(hsv.h, hsv.s, 100).join(",")}))`}></div>
173
171
 
174
172
  <div
175
- class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-2xl border-2 border-white"
176
- style={`left: ${brightnessPointer}%; background: rgb(${hsvToRgb(hsv.h, hsv.s, hsv.v).join(",")})`}
173
+ class="pointer-events-none absolute inset-y-0 w-[2px] -translate-x-1/2 bg-white shadow-[0_0_0_1px_rgb(0_0_0/0.25),0_1px_3px_rgb(0_0_0/0.4)]"
174
+ style={`left: ${brightnessPointer}%`}
177
175
  ></div>
178
176
  {/if}
179
177
  </div>
180
178
 
181
179
  <!-- Яркость белого цвета -->
182
180
  <div
183
- class="white-slider relative mt-4 h-4 w-full cursor-pointer overflow-hidden rounded-2xl shadow-sm transition duration-200 hover:shadow-md"
181
+ class="white-slider relative mt-4 h-4 w-full cursor-pointer overflow-hidden rounded-[10px] border border-(--hairline-color)"
184
182
  role="slider"
185
183
  aria-valuenow={null}
186
184
  tabindex={null}
@@ -189,21 +187,21 @@
189
187
  <div class="absolute inset-0 bg-linear-to-r from-black to-white"></div>
190
188
  {#if mode === "white"}
191
189
  <div
192
- class="pointer-events-none absolute top-1/2 h-7 w-1 -translate-x-1/2 -translate-y-1/2 rounded-2xl border-2 border-white"
193
- style={`left: ${whiteValue}%; background: rgb(${[255, 255, 255].map((c) => Math.round((whiteValue / 100) * c)).join(",")})`}
190
+ class="pointer-events-none absolute inset-y-0 w-[2px] -translate-x-1/2 bg-white shadow-[0_0_0_1px_rgb(0_0_0/0.25),0_1px_3px_rgb(0_0_0/0.4)]"
191
+ style={`left: ${whiteValue}%`}
194
192
  ></div>
195
193
  {/if}
196
194
  </div>
197
195
  </div>
198
196
  {/if}
199
197
 
200
- <!-- Превью цвета -->
198
+ <!-- Превью цвета: квадрат 48 с радиусом 10 (design-handoff, раздел 9) -->
201
199
  <div class="flex flex-col items-center m-auto" style="width: 7rem;">
202
200
  <div
203
- class={`flex size-15 flex-col justify-center gap-1 rounded-full px-2 font-mono text-sm shadow-sm transition duration-200 select-none ${textColor()}`}
201
+ class={`size-[48px] rounded-[10px] border border-(--hairline-color) transition-colors duration-200 select-none ${textColor()}`}
204
202
  style={`background: rgb(${previewBaseColor().join(",")})`}
205
203
  ></div>
206
- <div class="w-full text-center font-semibold">{hex()}</div>
204
+ <div class="mt-1 w-full text-center font-mono text-[12px] font-semibold text-(--muted-color)">{hex()}</div>
207
205
  </div>
208
206
  </div>
209
207
  </div>
@@ -6,14 +6,14 @@
6
6
 
7
7
  ## Пропсы
8
8
 
9
- | Название | Тип | По умолчанию | Описание |
10
- | -------------- | ------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
- | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
- | `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента |
13
- | `type` | `"line" \| "empty"` | `"line"` | `line` — видимая полоса, `empty` — чистый отступ без полосы |
14
- | `width` | `number` | `1` | Толщина полосы в пикселях (применяется только при `type="line"`) |
15
- | `color` | `string` | `""` | Класс цвета полосы из общей палитры (`bg-max`, `bg-red`, `bg-yellow`, `bg-green`, `bg-blue`, `bg-purple`, `bg-gray`); при пустом значении полоса красится в нейтральный `--hairline-color`, при заданном `bg-*` — в `--bg-color` (как в `Button.svelte`) |
16
- | `anchor` | `"top" \| "center" \| "bottom"` | `"center"` | Положение полосы внутри выделенной компоненту высоты сетки |
9
+ | Название | Тип | По умолчанию | Описание |
10
+ | -------------- | ------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
+ | `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента |
13
+ | `type` | `"line" \| "empty"` | `"line"` | `line` — видимая полоса, `empty` — чистый отступ без полосы |
14
+ | `width` | `number` | `1` | Толщина полосы в пикселях (применяется только при `type="line"`) |
15
+ | `color` | `string` | `""` | Класс цвета полосы из общей палитры (`bg-max`, `bg-red`, `bg-yellow`, `bg-green`, `bg-blue`, `bg-purple`, `bg-gray`); при пустом значении полоса красится в нейтральный `--hairline-color`, при заданном `bg-*` — в `--bg-color` (как в `Button.svelte`) |
16
+ | `anchor` | `"top" \| "center" \| "bottom"` | `"center"` | Положение полосы внутри выделенной компоненту высоты сетки |
17
17
 
18
18
  ## События
19
19
 
@@ -8,15 +8,15 @@
8
8
 
9
9
  ## Пропсы
10
10
 
11
- | Название | Тип | По умолчанию | Описание |
12
- | ---------------- | --------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
13
- | `wrapperClass` | `string` | `undefined` | CSS-классы для обёртки ручки перетаскивания |
14
- | `container` | `HTMLDivElement` | `-` (обязателен) | Контейнер, внутри которого ищутся элементы списка (по атрибуту `id`, см. ниже) для определения точки вставки |
15
- | `array` | `any[]` | `-` (обязателен) | Массив элементов списка — мутируется на месте (`splice`) в процессе перетаскивания |
16
- | `elementsId` | `string` | `"item"` | Префикс `id` DOM-элементов списка: ожидается `#{elementsId}-{index}` или `#{elementsId}-{index}-{containerIndex}` |
17
- | `elementIndex` | `number` | `-` (обязателен) | Индекс элемента, которому принадлежит эта ручка, в `array` |
18
- | `containerIndex` | `number` | `undefined` | Индекс контейнера — нужен, когда на странице несколько независимых списков и id элементов включают ещё и номер контейнера |
19
- | `onUpdate` | `(arr: any[], index?: number \| null) => void` | `undefined` | Вызывается дважды за одно перетаскивание: сразу после извлечения элемента (`array` без него) и по завершении (`array` с элементом на новом месте, вторым аргументом — `containerIndex`) |
11
+ | Название | Тип | По умолчанию | Описание |
12
+ | ---------------- | ---------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13
+ | `wrapperClass` | `string` | `undefined` | CSS-классы для обёртки ручки перетаскивания |
14
+ | `container` | `HTMLDivElement` | `-` (обязателен) | Контейнер, внутри которого ищутся элементы списка (по атрибуту `id`, см. ниже) для определения точки вставки |
15
+ | `array` | `any[]` | `-` (обязателен) | Массив элементов списка — мутируется на месте (`splice`) в процессе перетаскивания |
16
+ | `elementsId` | `string` | `"item"` | Префикс `id` DOM-элементов списка: ожидается `#{elementsId}-{index}` или `#{elementsId}-{index}-{containerIndex}` |
17
+ | `elementIndex` | `number` | `-` (обязателен) | Индекс элемента, которому принадлежит эта ручка, в `array` |
18
+ | `containerIndex` | `number` | `undefined` | Индекс контейнера — нужен, когда на странице несколько независимых списков и id элементов включают ещё и номер контейнера |
19
+ | `onUpdate` | `(arr: any[], index?: number \| null) => void` | `undefined` | Вызывается дважды за одно перетаскивание: сразу после извлечения элемента (`array` без него) и по завершении (`array` с элементом на новом месте, вторым аргументом — `containerIndex`) |
20
20
 
21
21
  ## События
22
22
 
@@ -53,10 +53,8 @@
53
53
  }
54
54
  </script>
55
55
 
56
- <!-- Не входил в исходный design-handoff (пропущен вместе с остальными точками,
57
- которые авторы документа не осматривали) — сейчас приведён к общей палитре:
58
- радиус 10/высота 36 как у Input/Select, сегмент "Выбрать файл" залит акцентом
59
- с текстом --on-accent (было --font-color по тёмно-синему — нечитаемо). -->
56
+ <!-- По design-handoff (раздел 9): зона сброса — пунктир 1 px с радиусом 12 вместо залитой кнопки,
57
+ высота как у Input; «Выбрать файл» — акцентный текст, имя файла — обычным цветом, пока не выбрано — бледным. -->
60
58
  <div class={twMerge(`flex flex-col items-center gap-1`, wrapperClass)}>
61
59
  {#if label.name}
62
60
  <span class={twMerge(`w-full text-[12px] font-semibold text-(--muted-color)`, label.class)}>{label.name}</span>
@@ -65,8 +63,8 @@
65
63
  {#if type === "image"}
66
64
  <div class="relative">
67
65
  <button
68
- class="flex items-center justify-center overflow-hidden {imageSize.form === 'circle' ? 'rounded-full' : 'rounded-2xl'}
69
- border border-(--hairline-color) bg-(--container-color) transition-colors duration-150
66
+ class="flex items-center justify-center overflow-hidden {imageSize.form === 'circle' ? 'rounded-full' : 'rounded-[12px]'}
67
+ border border-dashed border-(--border-color) bg-(--container-color) transition-colors duration-150
70
68
  {disabled ? 'cursor-not-allowed opacity-45' : 'cursor-pointer hover:bg-(--border-color)'}"
71
69
  style={`height: ${imageSize.height}; width: ${imageSize.width}`}
72
70
  onclick={triggerFileInput}
@@ -98,13 +96,16 @@
98
96
  onchange={handleFileChange}
99
97
  />
100
98
  <div
101
- class="flex h-8 w-full overflow-hidden rounded-[10px] border border-(--border-color) text-[14px] transition-colors duration-150
102
- {disabled ? 'opacity-45' : 'hover:border-(--faint-color)'}"
99
+ class={twMerge(
100
+ `flex h-8 w-full items-center gap-3 overflow-hidden rounded-[12px] border border-dashed border-(--border-color) bg-(--field-color) px-3 text-[14px]
101
+ transition-colors duration-150 ${disabled ? "opacity-45" : "hover:border-(--accent-color)"}`,
102
+ componentClass,
103
+ )}
103
104
  >
104
- <div class="flex shrink-0 items-center justify-center bg-(--accent-color) px-3 font-semibold whitespace-nowrap text-(--on-accent)">
105
+ <span class="shrink-0 font-semibold whitespace-nowrap text-(--accent-color)">
105
106
  {$T("constructor.props.file.select")}
106
- </div>
107
- <div class={twMerge("flex min-w-0 flex-1 items-center justify-start truncate bg-(--field-color) px-3 text-(--font-color)", componentClass)}>
107
+ </span>
108
+ <div class="flex min-w-0 flex-1 items-center justify-start truncate {fileName ? 'text-(--font-color)' : 'text-(--faint-color)'}">
108
109
  <p class="w-full truncate">{fileName || $T("constructor.props.file.notselected")}</p>
109
110
  </div>
110
111
  </div>
@@ -6,24 +6,24 @@
6
6
 
7
7
  ## Пропсы
8
8
 
9
- | Название | Тип | По умолчанию | Описание |
10
- | ------------------- | -------------------------------------------- | --------------------- | ------------------------------------------------ |
11
- | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
- | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
9
+ | Название | Тип | По умолчанию | Описание |
10
+ | ------------------- | -------------------------------------------- | --------------------- | ---------------------------------------------------------------- |
11
+ | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
+ | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
13
13
  | `componentClass` | `string` | `""` | Дополнительные CSS-классы для элемента с именем выбранного файла |
14
- | `label` | `object` | `-` | Настройки заголовка компонента |
15
- | `label.name` | `string` | `""` | Текст заголовка компонента |
16
- | `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
17
- | `type` | `"file" \| "image"` | `"file"` | Тип компонента (файл или изображение) |
18
- | `accept` | `string` | `"*/*"` | MIME-типы файлов, которые можно выбрать |
19
- | `imageSize` | `object` | `-` | Настройки отображения изображения |
20
- | `imageSize.height` | `string` | `"10rem"` | Высота области отображения изображения |
21
- | `imageSize.width` | `string` | `"10rem"` | Ширина области отображения изображения |
22
- | `imageSize.fitMode` | `"cover" \| "contain"` | `"cover"` | Режим масштабирования изображения |
23
- | `imageSize.form` | `"square" \| "circle"` | `"square"` | Форма области отображения изображения |
24
- | `disabled` | `boolean` | `false` | Состояние отключения компонента |
25
- | `currentImage` | `string \| null` | `""` | Текущее изображение (для связывания) |
26
- | `onChange` | `(event: Event, file: File \| null) => void` | `() => {}` | Обработчик изменения файла |
14
+ | `label` | `object` | `-` | Настройки заголовка компонента |
15
+ | `label.name` | `string` | `""` | Текст заголовка компонента |
16
+ | `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
17
+ | `type` | `"file" \| "image"` | `"file"` | Тип компонента (файл или изображение) |
18
+ | `accept` | `string` | `"*/*"` | MIME-типы файлов, которые можно выбрать |
19
+ | `imageSize` | `object` | `-` | Настройки отображения изображения |
20
+ | `imageSize.height` | `string` | `"10rem"` | Высота области отображения изображения |
21
+ | `imageSize.width` | `string` | `"10rem"` | Ширина области отображения изображения |
22
+ | `imageSize.fitMode` | `"cover" \| "contain"` | `"cover"` | Режим масштабирования изображения |
23
+ | `imageSize.form` | `"square" \| "circle"` | `"square"` | Форма области отображения изображения |
24
+ | `disabled` | `boolean` | `false` | Состояние отключения компонента |
25
+ | `currentImage` | `string \| null` | `""` | Текущее изображение (для связывания) |
26
+ | `onChange` | `(event: Event, file: File \| null) => void` | `() => {}` | Обработчик изменения файла |
27
27
 
28
28
  ## События
29
29
 
@@ -390,12 +390,13 @@
390
390
 
391
391
  <div class="flex w-full flex-row gap-4">
392
392
  <!-- График -->
393
- <div bind:this={container} class="relative h-64 grow overflow-hidden rounded-xl border border-gray-200 shadow-sm">
393
+ <!-- design-handoff, раздел 9: рамка --hairline-color без тени, тултип в оформлении общего Tooltip.svelte -->
394
+ <div bind:this={container} class="relative h-64 grow overflow-hidden rounded-[14px] border border-(--hairline-color)">
394
395
  <canvas class="h-full w-full bg-(--back-color)" bind:this={canvas} onmousemove={handleMouseMove} onmouseleave={handleMouseLeave}></canvas>
395
396
 
396
397
  {#if tooltip.visible}
397
398
  <div
398
- class="pointer-events-none absolute z-10 rounded-lg border border-(--border-color) bg-(--back-color) px-2 py-1 text-xs whitespace-nowrap shadow-lg"
399
+ class="pointer-events-none absolute z-10 rounded-lg border border-(--hairline-color) bg-(--back-color) px-2.5 py-1.5 text-[12px] whitespace-nowrap shadow-(--elevation-2)"
399
400
  style="left: {Math.min(tooltip.x + 12, width - 150)}px; top: {Math.min(tooltip.y + 12, height - 16 - tooltip.items.length * 16)}px;"
400
401
  >
401
402
  <div class="mb-1 font-semibold">{formatTooltipTime(tooltip.time)}</div>
@@ -6,35 +6,35 @@
6
6
 
7
7
  ## Пропсы
8
8
 
9
- | Название | Тип | По умолчанию | Описание |
10
- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
- | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
- | `type` | `"text" \| "password" \| "number" \| "text-area" \| "bitMode"` | `"text"` | Тип поля ввода: `text` — обычный текст, `password` — скрытый ввод с кнопкой показа, `number` — числовое поле со стрелками, `text-area` — многострочное поле, `bitMode` — числовое поле, диапазон которого рассчитывается из `range` |
13
- | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для внешней обёртки компонента |
14
- | `label` | `object` | `-` | Настройки подписи компонента |
15
- | `label.name` | `string` | `""` | Текст заголовка |
16
- | `label.class` | `string` | `""` | CSS-классы для стилизации заголовка |
17
- | `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого элемента ввода (`input`/`textarea`) |
18
- | `disabled` | `boolean` | `false` | Отключает ввод: блокирует взаимодействие с элементом и визуально затемняет компонент |
19
- | `readonly` | `boolean` | `false` | Режим только для чтения: позволяет выделять и копировать текст, но запрещает редактирование |
20
- | `value` | `string \| number` | `undefined` | Текущее значение поля; поддерживает двустороннее связывание (`$bindable`) |
21
- | `range` | `object` | `undefined` | ⚠️ Читается только конструкторской панелью `InputProps.svelte` (для расчёта `number.maxNum`, `2^(end - start + 1) - 1`) — сам `Input.svelte` проп `range` не использует. На голом `<UI.Input type="bitMode">` диапазон задаётся напрямую через `number.maxNum` |
22
- | `range.start` | `number` | `undefined` | Начальный индекс бита (только для конструкторской панели) |
23
- | `range.end` | `number` | `undefined` | Конечный индекс бита (только для конструкторской панели) |
24
- | `maxlength` | `number` | `100` | Максимальное количество символов, разрешённых для ввода |
25
- | `textareaRows` | `number` | `3` | Количество видимых строк для типа `text-area` |
26
- | `placeholder` | `string` | `""` | Текст-подсказка, отображаемый в пустом поле ввода |
27
- | `isValid` | `boolean` | `true` | Состояние валидации: при `false` поле подсвечивается красным; поддерживает двустороннее связывание (`$bindable`) |
28
- | `number` | `object` | `-` | Настройки для числового ввода |
29
- | `number.minNum` | `number` | `-1000000` | Минимальное значение диапазона |
30
- | `number.maxNum` | `number` | `1000000` | Максимальное значение диапазона |
31
- | `number.step` | `number` | `1` | Шаг изменения значения стрелками |
32
- | `help` | `object` | `-` | Дополнительные настройки поля |
33
- | `help.info` | `string` | `""` | Текст всплывающей подсказки |
34
- | `help.copyButton` | `boolean` | `false` | Показать кнопку копирования значения |
35
- | `help.regExp` | `string \| RegExp` | `undefined` | Регулярное выражение для валидации ввода |
36
- | `help.autocomplete` | `"on" \| "off" \| "given-name" \| "family-name" \| "nickname" \| "username" \| "new-password" \| "current-password" \| "organization-title" \| "country-name" \| "address-level1" \| "address-level2" \| "street-address" \| "postal-code" \| "email" \| "tel" \| null` | `"off"` | Атрибут автозаполнения браузера |
37
- | `onUpdate` | `(value: string \| number) => void` | `undefined` | Callback-функция, вызываемая при изменении значения поля; передаёт новое значение |
9
+ | Название | Тип | По умолчанию | Описание |
10
+ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
+ | `type` | `"text" \| "password" \| "number" \| "text-area" \| "bitMode"` | `"text"` | Тип поля ввода: `text` — обычный текст, `password` — скрытый ввод с кнопкой показа, `number` — числовое поле со стрелками, `text-area` — многострочное поле, `bitMode` — числовое поле, диапазон которого рассчитывается из `range` |
13
+ | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для внешней обёртки компонента |
14
+ | `label` | `object` | `-` | Настройки подписи компонента |
15
+ | `label.name` | `string` | `""` | Текст заголовка |
16
+ | `label.class` | `string` | `""` | CSS-классы для стилизации заголовка |
17
+ | `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого элемента ввода (`input`/`textarea`) |
18
+ | `disabled` | `boolean` | `false` | Отключает ввод: блокирует взаимодействие с элементом и визуально затемняет компонент |
19
+ | `readonly` | `boolean` | `false` | Режим только для чтения: позволяет выделять и копировать текст, но запрещает редактирование |
20
+ | `value` | `string \| number` | `undefined` | Текущее значение поля; поддерживает двустороннее связывание (`$bindable`) |
21
+ | `range` | `object` | `undefined` | ⚠️ Читается только конструкторской панелью `InputProps.svelte` (для расчёта `number.maxNum`, `2^(end - start + 1) - 1`) — сам `Input.svelte` проп `range` не использует. На голом `<UI.Input type="bitMode">` диапазон задаётся напрямую через `number.maxNum` |
22
+ | `range.start` | `number` | `undefined` | Начальный индекс бита (только для конструкторской панели) |
23
+ | `range.end` | `number` | `undefined` | Конечный индекс бита (только для конструкторской панели) |
24
+ | `maxlength` | `number` | `100` | Максимальное количество символов, разрешённых для ввода |
25
+ | `textareaRows` | `number` | `3` | Количество видимых строк для типа `text-area` |
26
+ | `placeholder` | `string` | `""` | Текст-подсказка, отображаемый в пустом поле ввода |
27
+ | `isValid` | `boolean` | `true` | Состояние валидации: при `false` поле подсвечивается красным; поддерживает двустороннее связывание (`$bindable`) |
28
+ | `number` | `object` | `-` | Настройки для числового ввода |
29
+ | `number.minNum` | `number` | `-1000000` | Минимальное значение диапазона |
30
+ | `number.maxNum` | `number` | `1000000` | Максимальное значение диапазона |
31
+ | `number.step` | `number` | `1` | Шаг изменения значения стрелками |
32
+ | `help` | `object` | `-` | Дополнительные настройки поля |
33
+ | `help.info` | `string` | `""` | Текст всплывающей подсказки |
34
+ | `help.copyButton` | `boolean` | `false` | Показать кнопку копирования значения |
35
+ | `help.regExp` | `string \| RegExp` | `undefined` | Регулярное выражение для валидации ввода |
36
+ | `help.autocomplete` | `"on" \| "off" \| "given-name" \| "family-name" \| "nickname" \| "username" \| "new-password" \| "current-password" \| "organization-title" \| "country-name" \| "address-level1" \| "address-level2" \| "street-address" \| "postal-code" \| "email" \| "tel" \| null` | `"off"` | Атрибут автозаполнения браузера |
37
+ | `onUpdate` | `(value: string \| number) => void` | `undefined` | Callback-функция, вызываемая при изменении значения поля; передаёт новое значение |
38
38
 
39
39
  ## События
40
40
 
@@ -130,7 +130,7 @@
130
130
 
131
131
  {#if !readonly}
132
132
  <div class="flex w-1/2 items-center justify-center">
133
- <div class="relative z-10 flex size-40 min-h-40 min-w-40 items-center justify-center rounded-full bg-(--bg-color) shadow-[0_0_20px_rgb(0_0_0_/0.25)]">
133
+ <div class="relative z-10 flex size-40 min-h-40 min-w-40 items-center justify-center rounded-full bg-(--bg-color) shadow-(--elevation-2)">
134
134
  <!-- Основные кнопки (оси pitch и yaw) -->
135
135
  <div class="absolute h-full w-full overflow-hidden rounded-full">
136
136
  {#each directions as direction, index}
@@ -176,7 +176,7 @@
176
176
  {/each}
177
177
  </div>
178
178
  <!-- Кнопка по центру -->
179
- <div 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)]">
179
+ <div class="z-20 flex size-20 items-center justify-center rounded-full bg-(--bg-color) shadow-(--elevation-2)">
180
180
  <button
181
181
  class="flex size-18 cursor-pointer rounded-full p-2 origin-center active:scale-95
182
182
  [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
@@ -202,11 +202,11 @@
202
202
  </div>
203
203
  </div>
204
204
 
205
- <!-- Копка 0 -->
205
+ <!-- Кнопка 0 — 32 px по общему правилу кнопок-иконок (design-handoff, раздел 9) -->
206
206
  <div class="absolute {label.name ? 'top-4' : 'top-0'} w-55 flex items-center justify-end">
207
207
  <button
208
- class="flex shadow-sm hover:shadow-md transition duration-200 justify-center cursor-pointer items-center bg-(--back-color) rounded-full size-10 p-0.5
209
- font-semibold text-2xl [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
208
+ class="flex size-[32px] cursor-pointer items-center justify-center rounded-full border border-(--hairline-color) bg-(--back-color) p-0.5 text-[15px] font-semibold
209
+ transition-colors duration-150 hover:border-(--border-color) [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full"
210
210
  style="background: color-mix(in srgb, var(--bg-color), var(--back-color) 70%);"
211
211
  title=""
212
212
  onclick={() => (value = [0, 0, 0, 0])}
@@ -218,7 +218,7 @@
218
218
  <!-- Боковые кнопки (ось roll) -->
219
219
  {#if axes[2].name !== ""}
220
220
  <div
221
- class="absolute flex h-15 w-65 items-center justify-between rounded-full shadow-[0_0_15px_rgb(0_0_0_/0.25)]"
221
+ class="absolute flex h-15 w-65 items-center justify-between rounded-full shadow-(--elevation-2)"
222
222
  style="background: color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)"
223
223
  >
224
224
  <button
@@ -234,7 +234,7 @@
234
234
  onUpdate(value)
235
235
  }}
236
236
  onmouseenter={(e) => (e.currentTarget.style.backgroundColor = "color-mix(in srgb, var(--bg-color), var(--shadow-color) 30%)")}
237
- onmouseleave={(e) => (e.currentTarget.style.backgroundColor = "background: color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)")}
237
+ onmouseleave={(e) => (e.currentTarget.style.backgroundColor = "")}
238
238
  >
239
239
  <svg xmlns="http://www.w3.org/2000/svg" class="segment-icon rotate-270" width="24" height="24" viewBox="0 0 24 24"
240
240
  ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5"
@@ -258,7 +258,7 @@
258
258
  onUpdate(value)
259
259
  }}
260
260
  onmouseenter={(e) => (e.currentTarget.style.backgroundColor = "color-mix(in srgb, var(--bg-color), var(--shadow-color) 30%)")}
261
- onmouseleave={(e) => (e.currentTarget.style.backgroundColor = "vabackground: color-mix(in srgb, var(--bg-color), var(--shadow-color) 10%)")}
261
+ onmouseleave={(e) => (e.currentTarget.style.backgroundColor = "")}
262
262
  >
263
263
  <svg xmlns="http://www.w3.org/2000/svg" class="segment-icon rotate-90" width="24" height="24" viewBox="0 0 24 24"
264
264
  ><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="1.5"
@@ -164,7 +164,7 @@
164
164
  <h5 class={twMerge(` w-full px-4 text-center`, label.class)}>{label.name}</h5>
165
165
  {/if}
166
166
  <MapLibre
167
- class={twMerge("h-[calc(100%-2rem)] min-h-50 overflow-hidden rounded-xl shadow-sm transition duration-200 hover:shadow-md", roleRingClass)}
167
+ class={twMerge("h-[calc(100%-2rem)] min-h-50 overflow-hidden rounded-[14px] border border-(--hairline-color)", roleRingClass)}
168
168
  style={isDarkMode ? "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" : "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"}
169
169
  zoom={1.5}
170
170
  center={{ lat: 30, lng: 0 }}
@@ -222,7 +222,7 @@
222
222
  <div class="flex flex-col items-center justify-center leading-none">
223
223
  <div
224
224
  class="flex size-8 shrink-0 items-center justify-center [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full
225
- {device.isFresh ? 'text-green-500' : 'text-red-500'}"
225
+ {device.isFresh ? 'text-(--green-color)' : 'text-(--red-color)'}"
226
226
  style="rotate: {device.NavHeading}deg;"
227
227
  >
228
228
  {#if markerIcon}
@@ -251,16 +251,14 @@
251
251
  </div>
252
252
  {/snippet}
253
253
 
254
- <Popup closeButton={false} class="map-popup rounded-2xl text-left">
254
+ <Popup closeButton={false} class="map-popup text-left">
255
255
  <div class="flex flex-col gap-1.5">
256
256
  <div class="flex items-center gap-1.5">
257
- <span class="size-2 shrink-0 rounded-full {device.isFresh ? 'bg-green-500' : 'bg-red-500'}"></span>
257
+ <span class="size-2 shrink-0 rounded-full {device.isFresh ? 'bg-(--green-color)' : 'bg-(--red-color)'}"></span>
258
258
  <h6 class="text-sm leading-none font-bold">{device.DevName}</h6>
259
259
  </div>
260
260
 
261
- <div
262
- class={`${twMerge("grid grid-cols-2 gap-x-3 gap-y-1 rounded-lg bg-(--container-color) p-2 text-xs", roleBgClass)} ${roleBgPaint}`}
263
- >
261
+ <div class={`${twMerge("grid grid-cols-2 gap-x-3 gap-y-1 rounded-lg bg-(--container-color) p-2 text-xs", roleBgClass)} ${roleBgPaint}`}>
264
262
  <div>
265
263
  <p class="text-[0.6rem] font-semibold tracking-wide uppercase opacity-60">Lat / Lon</p>
266
264
  <p class="font-medium">{device.NavLat.toFixed(5)}, {device.NavLon.toFixed(5)}</p>
@@ -281,7 +279,7 @@
281
279
 
282
280
  <p class="text-[0.6rem] font-mono text-center tracking-wide uppercase">{device.DevSN}</p>
283
281
 
284
- <div class="flex items-center justify-between border-t border-(--border-color) pt-1">
282
+ <div class="flex items-center justify-between border-t border-(--hairline-color) pt-1">
285
283
  <div class="relative flex items-center">
286
284
  <button
287
285
  class="flex cursor-pointer items-center gap-1 rounded-lg border-none bg-transparent px-1 py-0.5 opacity-70 transition hover:opacity-100"
@@ -309,7 +307,7 @@
309
307
  </div>
310
308
 
311
309
  <button
312
- class="cursor-pointer rounded-lg border-none bg-transparent p-0.5 text-red-500 transition hover:text-red-600"
310
+ class="cursor-pointer rounded-lg border-none bg-transparent p-0.5 text-(--red-color) transition hover:opacity-80"
313
311
  aria-label="Delete"
314
312
  title="Delete"
315
313
  onclick={() => (devices = devices.filter((dev) => dev.DevSN !== device.DevSN))}
@@ -335,9 +333,14 @@
335
333
  </div>
336
334
 
337
335
  <style>
336
+ /* Попап по правилам карточки (design-handoff, раздел 9): радиус 14, линия, тень уровня 2 */
338
337
  :global(.map-popup .maplibregl-popup-content) {
339
338
  background-color: var(--back-color);
340
339
  color: inherit;
340
+ border: 1px solid var(--hairline-color);
341
+ border-radius: 14px;
342
+ box-shadow: var(--elevation-2);
343
+ padding: 12px;
341
344
  }
342
345
 
343
346
  :global(.map-popup.maplibregl-popup-anchor-bottom .maplibregl-popup-tip) {
@@ -29,9 +29,7 @@
29
29
  ),
30
30
  )
31
31
 
32
- const initialColor = $derived(
33
- findColorOption($optionsStore.COLOR_OPTIONS, component.properties.wrapperClass),
34
- )
32
+ const initialColor = $derived(findColorOption($optionsStore.COLOR_OPTIONS, component.properties.wrapperClass))
35
33
  </script>
36
34
 
37
35
  {#if forConstructor}