poe-svelte-ui-lib 1.35.0 → 1.35.1
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.
- package/README.md +24 -0
- package/dist/Carousel/Carousel.svelte +4 -3
- package/dist/ColorPicker/ColorPicker.svelte +12 -14
- package/dist/FileAttach/FileAttach.svelte +12 -11
- package/dist/FileAttach/README.md +17 -17
- package/dist/Graph/Graph.svelte +3 -2
- package/dist/Joystick/Joystick.svelte +8 -8
- package/dist/Map/Map.svelte +12 -9
- package/dist/Select/README.md +25 -22
- package/dist/Table/README.md +5 -0
- package/dist/VideoViewer/VideoViewer.svelte +8 -7
- package/dist/Widget/README.md +4 -0
- package/dist/WidgetDeviceInfo/README.md +22 -20
- package/dist/WidgetFileSystem/README.md +2 -0
- package/dist/WidgetStackInfo/README.md +2 -0
- package/dist/WidgetWiFi/README.md +3 -1
- 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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
156
|
-
style={`left: ${huePointer}
|
|
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-
|
|
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
|
|
176
|
-
style={`left: ${brightnessPointer}
|
|
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-
|
|
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
|
|
193
|
-
style={`left: ${whiteValue}
|
|
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={`
|
|
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>
|
|
@@ -53,10 +53,8 @@
|
|
|
53
53
|
}
|
|
54
54
|
</script>
|
|
55
55
|
|
|
56
|
-
<!--
|
|
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-
|
|
69
|
-
border border-(--
|
|
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=
|
|
102
|
-
|
|
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
|
-
<
|
|
105
|
+
<span class="shrink-0 font-semibold whitespace-nowrap text-(--accent-color)">
|
|
105
106
|
{$T("constructor.props.file.select")}
|
|
106
|
-
</
|
|
107
|
-
<div class=
|
|
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
|
|
package/dist/Graph/Graph.svelte
CHANGED
|
@@ -390,12 +390,13 @@
|
|
|
390
390
|
|
|
391
391
|
<div class="flex w-full flex-row gap-4">
|
|
392
392
|
<!-- График -->
|
|
393
|
-
|
|
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-(--
|
|
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>
|
|
@@ -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-
|
|
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-
|
|
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
|
-
<!--
|
|
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
|
|
209
|
-
|
|
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-
|
|
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 = "
|
|
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 = "
|
|
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"
|
package/dist/Map/Map.svelte
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
|
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-
|
|
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-(--
|
|
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-
|
|
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) {
|
package/dist/Select/README.md
CHANGED
|
@@ -6,21 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
## Пропсы
|
|
8
8
|
|
|
9
|
-
| Название
|
|
10
|
-
|
|
|
11
|
-
| `id`
|
|
12
|
-
| `wrapperClass`
|
|
13
|
-
| `componentClass` | `string`
|
|
14
|
-
| `disabled`
|
|
15
|
-
| `label`
|
|
16
|
-
| `label.name`
|
|
17
|
-
| `label.class`
|
|
18
|
-
| `type`
|
|
19
|
-
| `multiSelect`
|
|
20
|
-
| `listHeight`
|
|
21
|
-
| `value`
|
|
22
|
-
| `options`
|
|
23
|
-
| `onUpdate`
|
|
9
|
+
| Название | Тип | По умолчанию | Описание |
|
|
10
|
+
| ---------------- | --------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
11
|
+
| `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
|
|
12
|
+
| `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
|
|
13
|
+
| `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого поля/кнопок выбора |
|
|
14
|
+
| `disabled` | `boolean` | `false` | Состояние отключения компонента |
|
|
15
|
+
| `label` | `object` | `-` | Настройки заголовка компонента |
|
|
16
|
+
| `label.name` | `string` | `""` | Текст заголовка компонента |
|
|
17
|
+
| `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
|
|
18
|
+
| `type` | `"select" \| "buttons" \| "input"` | `"select"` | Тип отображения: `select` — выпадающий список с кнопкой, `buttons` — горизонтальный ряд кнопок, `input` — поле ввода с автодополнением и поиском |
|
|
19
|
+
| `multiSelect` | `boolean` | `false` | Режим множественного выбора: работает только с `type: "buttons"`; при `true` позволяет выбирать несколько опций, значение передаётся как массив |
|
|
20
|
+
| `listHeight` | `string` | `"16rem"` | Максимальная высота выпадающего списка опций (для типов `select`/`input`) |
|
|
21
|
+
| `value` | `IOption<T> \| IOption<T>[] \| undefined` | `undefined` | Текущее выбранное значение(я); поддерживает двустороннее связывание (`$bindable`) |
|
|
22
|
+
| `options` | `IOption<T>[]` | `[]` | Массив доступных опций |
|
|
23
|
+
| `onUpdate` | `(value: IOption<T> \| IOption<T>[]) => void` | `undefined` | Обработчик изменения значения |
|
|
24
24
|
|
|
25
25
|
> **`bitMode`/`range`**: эти поля объявлены в типе `ISelectProps` и настраиваются в конструкторской панели (`SelectProps.svelte`), но сам `Select.svelte` их не читает и не использует — они не оказывают никакого эффекта на голый `<UI.Select>`. Если нужен битовый выбор в реальном рантайме, интерпретируйте маску на стороне вызывающего кода и передавайте уже готовый `value`/`options`.
|
|
26
26
|
|
|
@@ -28,13 +28,16 @@
|
|
|
28
28
|
|
|
29
29
|
#### IOption
|
|
30
30
|
|
|
31
|
-
| Название | Тип | По умолчанию | Описание
|
|
32
|
-
| ---------- | --------- | ------------ |
|
|
33
|
-
| `id` | `string` | `-` | Уникальный идентификатор опции
|
|
34
|
-
| `value` | `T` | `undefined` | Значение опции
|
|
35
|
-
| `name` | `string` | `undefined` | Отображаемое имя опции
|
|
36
|
-
| `class` | `string` | `undefined` | CSS-классы для стилизации опции
|
|
37
|
-
| `disabled` | `boolean` | `undefined` | Состояние отключения опции
|
|
31
|
+
| Название | Тип | По умолчанию | Описание |
|
|
32
|
+
| ---------- | --------- | ------------ | ---------------------------------------------------------------- |
|
|
33
|
+
| `id` | `string` | `-` | Уникальный идентификатор опции |
|
|
34
|
+
| `value` | `T` | `undefined` | Значение опции |
|
|
35
|
+
| `name` | `string` | `undefined` | Отображаемое имя опции |
|
|
36
|
+
| `class` | `string` | `undefined` | CSS-классы для стилизации опции |
|
|
37
|
+
| `disabled` | `boolean` | `undefined` | Состояние отключения опции |
|
|
38
|
+
| `icon` | `string` | `undefined` | Иконка: SVG-разметка или короткая метка (`"S"`, `"XL"`, `"123"`) |
|
|
39
|
+
|
|
40
|
+
В `type="buttons"` вариант с `icon` показывает иконку вместо `name`, а `name` становится всплывающей подсказкой и `aria-label` — так узкие панели свойств не обрезают подписи. В остальных режимах `icon` не используется.
|
|
38
41
|
|
|
39
42
|
## События
|
|
40
43
|
|
package/dist/Table/README.md
CHANGED
|
@@ -180,6 +180,11 @@
|
|
|
180
180
|
<UI.Table label={{ name: "Devices" }} header={columns} body={rows} onClick={handleButtonClick} footer={`rows: ${rows.length}`} />
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
+
## Шапка
|
|
184
|
+
|
|
185
|
+
- Фон шапки — токен `--table-header-color`, по умолчанию `--container-color`. Внутри смарт-виджета он равен мягкому оттенку мастер-цвета (`widgetAccentStyle`), поэтому шапка перекрашивается вместе с виджетом.
|
|
186
|
+
- Когда у тела появляется полоса прокрутки, шапка сдвигается ровно на её фактическую ширину (`offsetWidth − clientWidth`, пересчёт при изменении строк и размеров) — столбцы шапки и строк совпадают. Для полос поверх содержимого (macOS) сдвиг 0.
|
|
187
|
+
|
|
183
188
|
## Внутренняя архитектура
|
|
184
189
|
|
|
185
190
|
### Реактивность
|
|
@@ -149,10 +149,11 @@
|
|
|
149
149
|
{#if label.name}
|
|
150
150
|
<h5 class={twMerge(`w-full px-4 text-center`, label.class)}>{label.name}</h5>
|
|
151
151
|
{/if}
|
|
152
|
-
|
|
152
|
+
<!-- design-handoff, раздел 9: фон --container-color, радиус 14, оверлей загрузки без фиксированных отступов -->
|
|
153
|
+
<div class={twMerge("relative flex w-full flex-1 items-center justify-center rounded-[14px]", roleBorderClass)}>
|
|
153
154
|
{#if isRemote}
|
|
154
155
|
{#if status !== "live" || !remoteImgSrc}
|
|
155
|
-
<div class="absolute h-full w-full
|
|
156
|
+
<div class="absolute z-10 flex h-full w-full flex-col items-center justify-center gap-4 rounded-[14px] bg-(--container-color) p-4 text-center">
|
|
156
157
|
{#if status === "error"}
|
|
157
158
|
<svg class="w-12 h-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
158
159
|
<path
|
|
@@ -170,14 +171,14 @@
|
|
|
170
171
|
{/if}
|
|
171
172
|
</div>
|
|
172
173
|
{/if}
|
|
173
|
-
<div class="absolute h-full w-full rounded-
|
|
174
|
+
<div class="absolute h-full w-full overflow-hidden rounded-[14px] bg-(--container-color)">
|
|
174
175
|
{#if remoteImgSrc}
|
|
175
|
-
<img src={remoteImgSrc} alt={label.name || ""} class="h-full w-full rounded-
|
|
176
|
+
<img src={remoteImgSrc} alt={label.name || ""} class="block h-full w-full rounded-[14px] object-contain" />
|
|
176
177
|
{/if}
|
|
177
178
|
</div>
|
|
178
179
|
{:else}
|
|
179
180
|
{#if loading || error}
|
|
180
|
-
<div class="absolute h-full w-full
|
|
181
|
+
<div class="absolute z-10 flex h-full w-full flex-col items-center justify-center gap-4 rounded-[14px] bg-(--container-color) p-4 text-center">
|
|
181
182
|
{#if loading}
|
|
182
183
|
<h2>{$T("constructor.props.camera.loading")}</h2>
|
|
183
184
|
{:else if error}
|
|
@@ -194,8 +195,8 @@
|
|
|
194
195
|
{/if}
|
|
195
196
|
</div>
|
|
196
197
|
{/if}
|
|
197
|
-
<div class="absolute h-full w-full rounded-
|
|
198
|
-
<video bind:this={videoElement} class="h-full w-full rounded-
|
|
198
|
+
<div class="absolute h-full w-full overflow-hidden rounded-[14px] bg-(--container-color)">
|
|
199
|
+
<video bind:this={videoElement} class="block h-full w-full rounded-[14px] object-contain {!isStreaming || loading ? 'invisible' : 'visible'}"></video>
|
|
199
200
|
</div>
|
|
200
201
|
{/if}
|
|
201
202
|
</div>
|
package/dist/Widget/README.md
CHANGED
|
@@ -157,6 +157,10 @@
|
|
|
157
157
|
| `onPropertyChange` | `(updates: Partial<{ properties?: string \| object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void` | `-` | Коллбэк для обновления свойств компонента |
|
|
158
158
|
| `forConstructor` | `boolean` | `true` | Режим отображения (для конструктора или редактирования) |
|
|
159
159
|
|
|
160
|
+
### Мастер-цвет
|
|
161
|
+
|
|
162
|
+
Пикер «Мастер-цвет» (`WidgetAccentPicker`, общий со смарт-виджетами) пишет роль `bg-*` в `settings.class` и подобранный цвет иконки в `icons.class` — формат сохранённых интерфейсов прежний. Корень виджета получает `widgetAccentStyle(settings.class)`: роль перекрашивает переключатель, слайдер и фокус поля.
|
|
163
|
+
|
|
160
164
|
### Особенности конструктора
|
|
161
165
|
|
|
162
166
|
- Предоставляет визуальные элементы управления для всех доступных свойств
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Описание
|
|
4
4
|
|
|
5
|
+
> **Размеры.** Ширина — 100% контейнера (в конструкторе — ширина ячейки сетки), без собственного ограничения; высота — по содержимому, в сетке конструктора виджет занимает одну строку. Мастер-цвет (`componentClass`) перекрашивает и шапку таблиц внутри виджета (`--table-header-color`).
|
|
6
|
+
|
|
5
7
|
Смарт-виджет информации об устройстве. Как и `WidgetWiFi`, не привязывается к одной произвольной
|
|
6
8
|
переменной устройства — работает с фиксированным набором полей, соответствующих реальной
|
|
7
9
|
прошивочной конвенции (`main_config.c`, группа `CFG`, ProdFactory-ESP): `DevSN`, `DevID`, `DevFW`,
|
|
@@ -14,29 +16,29 @@
|
|
|
14
16
|
|
|
15
17
|
## Пропсы
|
|
16
18
|
|
|
17
|
-
| Название | Тип | По умолчанию | Описание
|
|
18
|
-
| ---------------- | ----------------------------------------- | -------------------------------------- |
|
|
19
|
-
| `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента
|
|
20
|
-
| `componentClass` | `string` | `""` | "Мастер-цвет" виджета — роль вида `"bg-red"` (см. `optionsStore.COLOR_OPTIONS`)
|
|
21
|
-
| `label` | `{ name?: string }` | `{ name: "Информация об устройстве" }` | Заголовок карточки
|
|
22
|
-
| `value` | `IWidgetDeviceInfoConfig` | внутренний дефолт | Текущая информация об устройстве
|
|
23
|
-
| `collapsed` | `boolean` | `false`
|
|
24
|
-
| `persistKey` | `string` | `undefined`
|
|
25
|
-
| `onSave` | `(info: IWidgetDeviceInfoConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — редактируемые поля одним пакетом
|
|
26
|
-
| `onRestart` | `() => void` | `undefined` | Срабатывает при нажатии "Перезагрузить"
|
|
19
|
+
| Название | Тип | По умолчанию | Описание |
|
|
20
|
+
| ---------------- | ----------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента |
|
|
22
|
+
| `componentClass` | `string` | `""` | "Мастер-цвет" виджета — роль вида `"bg-red"` (см. `optionsStore.COLOR_OPTIONS`) |
|
|
23
|
+
| `label` | `{ name?: string }` | `{ name: "Информация об устройстве" }` | Заголовок карточки |
|
|
24
|
+
| `value` | `IWidgetDeviceInfoConfig` | внутренний дефолт | Текущая информация об устройстве |
|
|
25
|
+
| `collapsed` | `boolean` | `false` | Свёрнуто ли тело виджета (заголовок остаётся); поддерживает двустороннее связывание (`$bindable`) |
|
|
26
|
+
| `persistKey` | `string` | `undefined` | Ключ для сохранения состояния `collapsed` в `localStorage` — при заданном ключе состояние сворачивания переживает перезагрузку страницы |
|
|
27
|
+
| `onSave` | `(info: IWidgetDeviceInfoConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — редактируемые поля одним пакетом |
|
|
28
|
+
| `onRestart` | `() => void` | `undefined` | Срабатывает при нажатии "Перезагрузить" |
|
|
27
29
|
|
|
28
30
|
### IWidgetDeviceInfoConfig
|
|
29
31
|
|
|
30
|
-
| Поле | Тип | Только чтение | Описание
|
|
31
|
-
| ---------- | -------- | :-----------: |
|
|
32
|
-
| `DevSN` | `string` | да | Серийный номер устройства
|
|
33
|
-
| `DevID` | `string` | да | ID устройства
|
|
34
|
-
| `DevFW` | `number` | да | Версия ПО
|
|
35
|
-
| `RunCnt` | `number` | да | Счётчик запусков
|
|
36
|
-
| `DevName` | `string` | нет | Имя устройства
|
|
37
|
-
| `HostName` | `string` | нет | Имя хоста
|
|
38
|
-
| `WebUser` | `string` | нет | Имя пользователя Web-панели
|
|
39
|
-
| `WebPsw` | `string` | нет | Пароль Web-панели
|
|
32
|
+
| Поле | Тип | Только чтение | Описание |
|
|
33
|
+
| ---------- | -------- | :-----------: | ----------------------------- |
|
|
34
|
+
| `DevSN` | `string` | да | Серийный номер устройства |
|
|
35
|
+
| `DevID` | `string` | да | ID устройства |
|
|
36
|
+
| `DevFW` | `number` | да | Версия ПО |
|
|
37
|
+
| `RunCnt` | `number` | да | Счётчик запусков |
|
|
38
|
+
| `DevName` | `string` | нет | Имя устройства |
|
|
39
|
+
| `HostName` | `string` | нет | Имя хоста |
|
|
40
|
+
| `WebUser` | `string` | нет | Имя пользователя Web-панели |
|
|
41
|
+
| `WebPsw` | `string` | нет | Пароль Web-панели |
|
|
40
42
|
| `StaIP` | `string` | да | Текущий IP-адрес в режиме STA |
|
|
41
43
|
| `ApIP` | `string` | да | Текущий IP-адрес в режиме AP |
|
|
42
44
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Описание
|
|
4
4
|
|
|
5
|
+
> **Размеры.** Ширина — 100% контейнера (в конструкторе — ширина ячейки сетки), без собственного ограничения; высота — по содержимому, в сетке конструктора виджет занимает одну строку. Мастер-цвет (`componentClass`) перекрашивает и шапку таблиц внутри виджета (`--table-header-color`).
|
|
6
|
+
|
|
5
7
|
Смарт-виджет файловой системы устройства: занятость памяти, список файлов с удалением и закачка
|
|
6
8
|
нового файла с прогрессом. Работает с одним объектом `CFG.FS` ровно в том виде, в каком его отдаёт
|
|
7
9
|
прошивка (`FS_WriteState`, ProdFactory-ESP), и с одной командой `FS`. Компонент ничего не знает про
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Описание
|
|
4
4
|
|
|
5
|
+
> **Размеры.** Ширина — 100% контейнера (в конструкторе — ширина ячейки сетки), без собственного ограничения; высота — по содержимому, в сетке конструктора виджет занимает одну строку. Мастер-цвет (`componentClass`) перекрашивает и шапку таблиц внутри виджета (`--table-header-color`).
|
|
6
|
+
|
|
5
7
|
Отладочный смарт-виджет памяти и задач FreeRTOS: занятость каждой кучи и минимальный запас стека
|
|
6
8
|
каждой задачи. Работает с одним объектом `StackInfo` ровно в том виде, в каком его отдаёт
|
|
7
9
|
`API_GetStackInfo` (ProdFactory-ESP и ProdFactory-STM — контракт общий), и с одним запросом
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Описание
|
|
4
4
|
|
|
5
|
+
> **Размеры.** Ширина — 100% контейнера (в конструкторе — ширина ячейки сетки), без собственного ограничения; высота — по содержимому, в сетке конструктора виджет занимает одну строку. Мастер-цвет (`componentClass`) перекрашивает и шапку таблиц внутри виджета (`--table-header-color`).
|
|
6
|
+
|
|
5
7
|
Смарт-виджет настройки WiFi (режимы STA / AP / STA+AP). В отличие от примитивов конструктора,
|
|
6
8
|
не привязывается к одной произвольной переменной устройства через Select — работает с
|
|
7
9
|
фиксированным набором полей, соответствующих реальной прошивочной конвенции (`main_config.c`,
|
|
@@ -25,7 +27,7 @@ DeviceStore/WebSocket — только `value`/`onSave`, вся отправка
|
|
|
25
27
|
| `allowedModes` | `number[]` | `[1, 2, 3]` | Какие режимы показывать в сегмент-контроле (1=STA, 2=AP, 3=STA+AP) |
|
|
26
28
|
| `confirmOnAP` | `boolean` | `true` | Показывать предупреждение при сохранении чистого режима AP |
|
|
27
29
|
| `collapsed` | `boolean` | `false` | Свёрнуто ли тело виджета (заголовок остаётся); поддерживает двустороннее связывание (`$bindable`) |
|
|
28
|
-
| `persistKey` | `string` | `undefined` | Ключ для сохранения состояния `collapsed` в `localStorage` — при заданном ключе сворачивание переживает перезагрузку страницы
|
|
30
|
+
| `persistKey` | `string` | `undefined` | Ключ для сохранения состояния `collapsed` в `localStorage` — при заданном ключе сворачивание переживает перезагрузку страницы |
|
|
29
31
|
| `onScan` | `() => Promise<{ id: string; name: string; value: string }[]>` | `undefined` | Обработчик сканирования сетей (в проде — `Header: GET, Argument: APsList`) |
|
|
30
32
|
| `onSave` | `(config: IWidgetWiFiConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — конфиг уходит одним пакетом |
|
|
31
33
|
|