poe-svelte-ui-lib 1.30.0 → 1.31.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.
package/README.md CHANGED
@@ -1 +1,52 @@
1
- Набор компонентов на Svelte в режиме рун
1
+ # poe-svelte-ui-lib
2
+
3
+ Набор UI-компонентов на Svelte 5 (в режиме рун) для конструктора интерфейсов устройств: поля ввода, селекторы, таблицы, графики, виджеты устройства (WiFi, информация об устройстве) и вспомогательные примитивы (модалки, перетаскивание, drag&drop-конструктор свойств).
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ npm install poe-svelte-ui-lib
9
+ ```
10
+
11
+ ### Требования (peerDependencies)
12
+
13
+ - `svelte ^5.0.0`
14
+ - `tailwindcss ^4.0.0` — компоненты стилизуются исключительно Tailwind-утилитами и CSS-переменными темы, без собственного скомпилированного CSS в пакете
15
+
16
+ ### Подключение стилей и темы
17
+
18
+ Пакет не поставляет готовый CSS-файл — цветовые токены (`--back-color`, `--accent-color`, `--red-color` и т.д.) и типографика ожидаются от проекта-потребителя. Возьмите `src/app.css` этого репозитория как стартовый шаблон и скопируйте в свой проект (палитру можно переопределить своими значениями — имена переменных и структура классов `bg-*`/`border-*` должны остаться прежними, чтобы компоненты стилизовались корректно). В `app.css` потребителя также нужно указать Tailwind, откуда сканировать классы из собранного пакета:
19
+
20
+ ```css
21
+ @import "tailwindcss";
22
+ @source "../node_modules/poe-svelte-ui-lib/dist";
23
+ ```
24
+
25
+ Справочник по регулярным выражениям (`docs/info/RegExp.md`) и протокол обмена данными с устройством (`docs/info/ExchangeRules.md`) — вспомогательные материалы для тех, кто настраивает валидацию полей и работу с `DeviceStore`.
26
+
27
+ ## Использование
28
+
29
+ ```svelte
30
+ <script lang="ts">
31
+ import * as UI from "poe-svelte-ui-lib"
32
+
33
+ let value = $state("")
34
+ </script>
35
+
36
+ <UI.Input label={{ name: "Имя" }} bind:value type="text" />
37
+ <UI.Button content={{ name: "Сохранить" }} onClick={() => console.log(value)} />
38
+ ```
39
+
40
+ Полный список компонентов и типов — в `src/lib/index.ts`. У каждого компонента есть README рядом с исходником (`src/lib/<Component>/README.md`, для `Modal`/`Dragging` — `src/lib/<Component>.README.md`) с описанием пропсов, примерами и внутренним устройством; живая витрина всех компонентов — `npm run dev` → `/components/all`.
41
+
42
+ ## Разработка
43
+
44
+ ```bash
45
+ npm run dev # витрина компонентов (SvelteKit dev-сервер)
46
+ npm run build # сборка пакета (svelte-kit sync + svelte-package + publint) → dist/
47
+ npm run Formatting # prettier --write .
48
+ ```
49
+
50
+ ---
51
+
52
+ Лицензия: MIT — см. `LICENSE`.
@@ -11,8 +11,8 @@
11
11
  | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
12
  | `wrapperClass` | `string` | `""` | CSS-классы для обёртки компонента |
13
13
  | `type` | `"line" \| "empty"` | `"line"` | `line` — видимая полоса, `empty` — чистый отступ без полосы |
14
- | `width` | `number` | `2` | Толщина полосы в пикселях (применяется только при `type="line"`) |
15
- | `color` | `string` | `"bg-max"` | Класс цвета полосы из общей палитры (`bg-max`, `bg-red`, `bg-yellow`, `bg-green`, `bg-blue`, `bg-purple`, `bg-gray`) — сам по себе только переопределяет `--bg-color`, красит его рядом стоящий статический класс `bg-(--bg-color)` (как в `Button.svelte`) |
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
16
  | `anchor` | `"top" \| "center" \| "bottom"` | `"center"` | Положение полосы внутри выделенной компоненту высоты сетки |
17
17
 
18
18
  ## События
@@ -0,0 +1,74 @@
1
+ # Dragging Component
2
+
3
+ ## Описание
4
+
5
+ Перетаскивание элементов списка мышью — самостоятельная "ручка" (иконка `MoveVerticalIcon`), которая при зажатии и перемещении мыши переставляет элемент внутри переданного массива (`array`) и визуально показывает призрак перетаскиваемого элемента и плейсхолдер на месте вставки. Компонент не рендерит сам список — только ручку; сортировкой самого массива и его перерисовкой занимается вызывающий код через `onUpdate`.
6
+
7
+ Лежит прямо в `src/lib/Dragging.svelte` (без отдельной папки), у него нет парного `*Props.svelte` — это низкоуровневый примитив для чужих конструкторских панелей (например `TabsSettings`/`PeriodChartProps` используют его для сортировки вкладок/уровней), а не то, что добавляется на холст конструктора напрямую.
8
+
9
+ ## Пропсы
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`) |
20
+
21
+ ## События
22
+
23
+ Компонент не генерирует DOM-события — используйте `onUpdate` для сохранения нового порядка `array`.
24
+
25
+ ## Пример
26
+
27
+ Список из N строк, каждая — `<div id="item-{i}">...</div>` внутри общего контейнера; ручка перетаскивания — `UI.Dragging` с тем же `elementsId`/`elementIndex`, что и у `id` строки:
28
+
29
+ ```svelte
30
+ <script>
31
+ import * as UI from "poe-svelte-ui-lib"
32
+
33
+ let container = $state()
34
+ let items = $state(["Первый", "Второй", "Третий"])
35
+
36
+ const handleUpdate = (arr) => {
37
+ items = arr
38
+ }
39
+ </script>
40
+
41
+ <div bind:this={container} class="flex flex-col gap-1">
42
+ {#each items as item, index (item)}
43
+ <div id={`item-${index}`} class="flex items-center gap-2 bg-(--field-color) p-2">
44
+ <UI.Dragging {container} array={items} elementIndex={index} onUpdate={handleUpdate} />
45
+ <span>{item}</span>
46
+ </div>
47
+ {/each}
48
+ </div>
49
+ ```
50
+
51
+ ## Внутренняя архитектура
52
+
53
+ ### Реактивность
54
+
55
+ - `dragState` (`$state`) — служебное состояние текущего перетаскивания: перетаскиваемый элемент, экранные координаты, DOM-узлы призрака/плейсхолдера, целевой индекс вставки
56
+ - `commandItems` (`$state`) — снимок всех DOM-элементов списка на момент начала перетаскивания (`container.querySelectorAll`), используется для определения элемента под курсором
57
+ - `$effect` навешивает/снимает глобальные обработчики `mousemove`/`mouseup` на весь документ, пока идёт перетаскивание (`dragState.isDragging`)
58
+
59
+ ### Директивы
60
+
61
+ - `onmousedown` на ручке — запускает перетаскивание (`startDrag`), если в массиве больше одного элемента
62
+ - Глобальные `mousemove`/`mouseup` (см. выше) — двигают призрак/плейсхолдер (`processDrag`) и завершают перестановку (`endDrag`)
63
+
64
+ ### Слоты
65
+
66
+ - Компонент не использует слоты — контент фиксирован (иконка `MoveVerticalIcon`)
67
+
68
+ ## Заметки
69
+
70
+ ### Ограничения
71
+
72
+ - При единственном элементе в `array` (`array.length == 1`) ручка визуально приглушена (`opacity-50`) и перетаскивание не запускается
73
+ - Работает только с `MouseEvent` — нет отдельной поддержки touch/pointer-событий и клавиатурной перестановки (ручка фокусируема как `role="button"`, но `onkeydown` не обрабатывается — известное ограничение доступности)
74
+ - `container`/`elementIndex`/`array` обязательны — компонент не проверяет их наличие и упадёт при обращении к `array.length` в классе, если `array` не передан
@@ -10,6 +10,7 @@
10
10
  | ------------------- | -------------------------------------------- | --------------------- | ------------------------------------------------ |
11
11
  | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
12
  | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
13
+ | `componentClass` | `string` | `""` | Дополнительные CSS-классы для элемента с именем выбранного файла |
13
14
  | `label` | `object` | `-` | Настройки заголовка компонента |
14
15
  | `label.name` | `string` | `""` | Текст заголовка компонента |
15
16
  | `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
@@ -18,9 +18,9 @@
18
18
  | `disabled` | `boolean` | `false` | Отключает ввод: блокирует взаимодействие с элементом и визуально затемняет компонент |
19
19
  | `readonly` | `boolean` | `false` | Режим только для чтения: позволяет выделять и копировать текст, но запрещает редактирование |
20
20
  | `value` | `string \| number` | `undefined` | Текущее значение поля; поддерживает двустороннее связывание (`$bindable`) |
21
- | `range` | `object` | `undefined` | Диапазон битов для типа `bitMode`: `start`/`end` — индексы, используемые для расчёта `number.maxNum` (`2^(end - start + 1) - 1`) |
22
- | `range.start` | `number` | `undefined` | Начальный индекс бита |
23
- | `range.end` | `number` | `undefined` | Конечный индекс бита |
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
24
  | `maxlength` | `number` | `100` | Максимальное количество символов, разрешённых для ввода |
25
25
  | `textareaRows` | `number` | `3` | Количество видимых строк для типа `text-area` |
26
26
  | `placeholder` | `string` | `""` | Текст-подсказка, отображаемый в пустом поле ввода |
@@ -0,0 +1,104 @@
1
+ # Modal Component
2
+
3
+ ## Описание
4
+
5
+ Модальное окно — оверлей с подложкой (40% чёрного + blur), закрывающийся по клику вне окна, по `Escape` или по кнопке-крестику в шапке. Поддерживает стек: несколько одновременно открытых модалок укладываются по `z-index` в порядке открытия, `Escape`/клик-вне обрабатывает только самая верхняя (`isTopmost`). Содержимое передаётся через обязательный snippet `main`, футер (кнопки действий) — через необязательный `footer`.
6
+
7
+ Компонент лежит прямо в `src/lib/Modal.svelte` (без отдельной папки, в отличие от большинства других компонентов библиотеки), поэтому у него нет парного файла `*Props.svelte` — конструкторской панели для него не предусмотрено.
8
+
9
+ ## Пропсы
10
+
11
+ | Название | Тип | По умолчанию | Описание |
12
+ | -------------- | ---------- | ----------------------------- | ---------------------------------------------------------------------------------------------- |
13
+ | `isOpen` | `boolean` | `false` | Открыто ли модальное окно; поддерживает двустороннее связывание (`$bindable`) |
14
+ | `title` | `string` | `undefined` | Заголовок в шапке окна |
15
+ | `wrapperClass` | `string` | `""` | CSS-классы для окна (например для другой ширины/фона) |
16
+ | `mainClass` | `string` | `""` | CSS-классы для контейнера содержимого (область под `main`) |
17
+ | `width` | `string` | `""` | Явная ширина окна (CSS-значение, например `"600px"`); по умолчанию берётся из `wrapperClass`/`w-300` |
18
+ | `main` | `Snippet` | `-` (обязателен) | Содержимое окна |
19
+ | `footer` | `Snippet` | `undefined` | Содержимое подвала (обычно кнопки действий, выравниваются в ряд справа налево) |
20
+ | `onCancel` | `() => void` | `() => (isOpen = false)` | Вызывается при закрытии окна (крестик, клик вне окна, `Escape`) |
21
+
22
+ ## События
23
+
24
+ Компонент не генерирует DOM-события — используйте `onCancel` для реакции на закрытие.
25
+
26
+ ## Примеры
27
+
28
+ ### Базовое окно
29
+
30
+ ```svelte
31
+ <script>
32
+ import * as UI from "poe-svelte-ui-lib"
33
+
34
+ let isOpen = $state(false)
35
+ </script>
36
+
37
+ <UI.Button content={{ name: "Открыть" }} onClick={() => (isOpen = true)} />
38
+
39
+ <UI.Modal bind:isOpen title="Заголовок окна">
40
+ {#snippet main()}
41
+ <p>Содержимое модального окна.</p>
42
+ {/snippet}
43
+ </UI.Modal>
44
+ ```
45
+
46
+ ### С футером (кнопки действий)
47
+
48
+ ```svelte
49
+ <script>
50
+ import * as UI from "poe-svelte-ui-lib"
51
+
52
+ let isOpen = $state(false)
53
+ </script>
54
+
55
+ <UI.Modal bind:isOpen title="Подтверждение" width="400px">
56
+ {#snippet main()}
57
+ <p>Вы уверены?</p>
58
+ {/snippet}
59
+ {#snippet footer()}
60
+ <UI.Button content={{ name: "Подтвердить" }} onClick={() => (isOpen = false)} />
61
+ <UI.Button content={{ name: "Отмена" }} wrapperClass="bg-gray" onClick={() => (isOpen = false)} />
62
+ {/snippet}
63
+ </UI.Modal>
64
+ ```
65
+
66
+ ## Стек модалок (`ModalStack`)
67
+
68
+ Экспортируется отдельно из пакета (`src/lib/ModalStackStore.ts`) — обычный Svelte-стор со списком идентификаторов открытых модалок в порядке открытия:
69
+
70
+ ```ts
71
+ export const ModalStack = {
72
+ subscribe: ..., // Writable<string[]>["subscribe"]
73
+ open: (id: string) => void, // добавляет id в конец стека
74
+ close: (id: string) => void, // убирает id из стека
75
+ }
76
+ ```
77
+
78
+ Каждый экземпляр `Modal.svelte` сам регистрируется в `ModalStack` при открытии/закрытии (по случайному `crypto.randomUUID()`, не связанному с пропсами) — вручную вызывать `ModalStack.open`/`close` не требуется, это внутренний механизм для вычисления `z-index` и того, какая модалка "самая верхняя" (обрабатывает `Escape`/клик вне себя). Использовать `ModalStack` напрямую имеет смысл только если нужно узнать, сколько модалок сейчас открыто, или в каком порядке — например `$ModalStack.length > 0` для блокировки скролла `body`.
79
+
80
+ ## Внутренняя архитектура
81
+
82
+ ### Реактивность
83
+
84
+ - `modalId` — случайный идентификатор экземпляра, генерируется один раз при создании компонента
85
+ - `zIndex` (`$derived.by`) — вычисляется по позиции `modalId` в `$ModalStack` (`100 + indexInStack`)
86
+ - `isTopmost` (`$derived`) — правда, если этот экземпляр последний в стеке
87
+ - `$effect` синхронизирует `isOpen` с `ModalStack.open`/`close`, включая очистку при размонтировании
88
+
89
+ ### Директивы
90
+
91
+ - `transition:fade` для подложки, `transition:scale` для окна
92
+ - `data-modal`/`data-modal-backdrop` — атрибуты для распознавания "клика внутри" и "клика по подложке"
93
+ - `data-ui-portal` — элементы, вынесенные в портал (например выпадающий список `Select`), не считаются кликом "снаружи" модалки
94
+
95
+ ### Слоты
96
+
97
+ - `main` (обязателен) и `footer` (опционален) — оба snippet, не `children`/default slot
98
+
99
+ ## Заметки
100
+
101
+ ### Ограничения
102
+
103
+ - `main` обязателен — компонент не поддерживает вызов без содержимого через обычные дочерние элементы (`children`), только через `{#snippet main()}...{/snippet}`
104
+ - Нет собственной конструкторской панели (`*Props.svelte`) — используется напрямую в коде приложения, не как примитив визуального конструктора
@@ -13,7 +13,7 @@
13
13
  | Название | Тип | По умолчанию | Описание |
14
14
  | ------------------- | --------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
15
  | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
16
- | `wrapperClass` | `string` | `"bg-blue"` | CSS-классы обёртки — через `bg-*` задаёт цвет переключателя и столбиков |
16
+ | `wrapperClass` | `string` | `""` | CSS-классы обёртки — через `bg-*` задаёт цвет переключателя и столбиков (по умолчанию нейтрально, без декоративной заливки) |
17
17
  | `label` | `{ name?: string; class?: string }` | `{ name: "", class: "" }` | Заголовок над графиком |
18
18
  | `levels` | `IPeriodChartLevel[]` | 4 демо-уровня со случайными данными | Уровни детализации, порядок = порядок вкладок переключателя; открывается всегда на первом (индекс 0) |
19
19
  | `levels[].name` | `string` | `-` | Подпись вкладки, напр. `"Час"` |
@@ -10,21 +10,20 @@
10
10
  | -------------- | --------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
11
11
  | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
12
  | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
13
+ | `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого поля/кнопок выбора |
13
14
  | `disabled` | `boolean` | `false` | Состояние отключения компонента |
14
15
  | `label` | `object` | `-` | Настройки заголовка компонента |
15
16
  | `label.name` | `string` | `""` | Текст заголовка компонента |
16
17
  | `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
17
18
  | `type` | `"select" \| "buttons" \| "input"` | `"select"` | Тип отображения: `select` — выпадающий список с кнопкой, `buttons` — горизонтальный ряд кнопок, `input` — поле ввода с автодополнением и поиском |
18
19
  | `multiSelect` | `boolean` | `false` | Режим множественного выбора: работает только с `type: "buttons"`; при `true` позволяет выбирать несколько опций, значение передаётся как массив |
19
- | `listHeight` | `string` | `undefined` | Максимальная высота выпадающего списка опций (для типов `select`/`input`) |
20
- | `value` | `IOption<T> \| IOption<T>[] \| null` | `null` | Текущее выбранное значение(я); поддерживает двустороннее связывание (`$bindable`) |
20
+ | `listHeight` | `string` | `"16rem"` | Максимальная высота выпадающего списка опций (для типов `select`/`input`) |
21
+ | `value` | `IOption<T> \| IOption<T>[] \| undefined` | `undefined` | Текущее выбранное значение(я); поддерживает двустороннее связывание (`$bindable`) |
21
22
  | `options` | `IOption<T>[]` | `[]` | Массив доступных опций |
22
- | `bitMode` | `boolean` | `false` | Режим битового выбора: значение и `range` интерпретируются как битовая маска |
23
- | `range` | `object` | `undefined` | Диапазон битов, используемый в `bitMode` |
24
- | `range.start` | `number` | `-` | Начальный бит диапазона |
25
- | `range.end` | `number` | `-` | Конечный бит диапазона |
26
23
  | `onUpdate` | `(value: IOption<T> \| IOption<T>[]) => void` | `undefined` | Обработчик изменения значения |
27
24
 
25
+ > **`bitMode`/`range`**: эти поля объявлены в типе `ISelectProps` и настраиваются в конструкторской панели (`SelectProps.svelte`), но сам `Select.svelte` их не читает и не использует — они не оказывают никакого эффекта на голый `<UI.Select>`. Если нужен битовый выбор в реальном рантайме, интерпретируйте маску на стороне вызывающего кода и передавайте уже готовый `value`/`options`.
26
+
28
27
  ### Вложенные типы
29
28
 
30
29
  #### IOption
@@ -9,7 +9,7 @@
9
9
  | Название | Тип | По умолчанию | Описание |
10
10
  | -------------------- | ------------------------------------------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
11
  | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
12
- | `height` | `string` | `"2rem"` | Высота переключателя: определяет размеры трека и ползунка; ширина в режиме `horizontal` рассчитывается как `height * 2` |
12
+ | `height` | `string` | `"1.625rem"` | Высота переключателя: определяет размеры трека и ползунка; ширина в режиме `horizontal` рассчитывается как `height * 2` |
13
13
  | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
14
14
  | `disabled` | `boolean` | `false` | Состояние отключения компонента |
15
15
  | `label` | `object` | `-` | Настройки заголовка и подписей компонента |
@@ -20,6 +20,8 @@
20
20
  | `componentClass` | `string` | `""` | "Мастер-цвет" виджета — роль вида `"bg-red"` (см. `optionsStore.COLOR_OPTIONS`) |
21
21
  | `label` | `{ name?: string }` | `{ name: "Информация об устройстве" }` | Заголовок карточки |
22
22
  | `value` | `IWidgetDeviceInfoConfig` | внутренний дефолт | Текущая информация об устройстве |
23
+ | `collapsed` | `boolean` | `false` | Свёрнуто ли тело виджета (заголовок остаётся); поддерживает двустороннее связывание (`$bindable`) |
24
+ | `persistKey` | `string` | `undefined` | Ключ для сохранения состояния `collapsed` в `localStorage` — при заданном ключе состояние сворачивания переживает перезагрузку страницы |
23
25
  | `onSave` | `(info: IWidgetDeviceInfoConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — редактируемые поля одним пакетом |
24
26
  | `onRestart` | `() => void` | `undefined` | Срабатывает при нажатии "Перезагрузить" |
25
27
 
@@ -67,8 +67,9 @@
67
67
  иначе входящее значение затёрло бы то, что он ещё не успел сохранить. */
68
68
  $effect(() => {
69
69
  if (value !== undefined && !untrack(() => isDirty)) {
70
+ const snapshot = JSON.stringify(value)
70
71
  info = value
71
- savedSnapshot = JSON.stringify(info)
72
+ savedSnapshot = snapshot
72
73
  }
73
74
  })
74
75
 
@@ -24,6 +24,8 @@ DeviceStore/WebSocket — только `value`/`onSave`, вся отправка
24
24
  | `value` | `IWidgetWiFiConfig` | внутренний дефолт | Текущая конфигурация WiFi |
25
25
  | `allowedModes` | `number[]` | `[1, 2, 3]` | Какие режимы показывать в сегмент-контроле (1=STA, 2=AP, 3=STA+AP) |
26
26
  | `confirmOnAP` | `boolean` | `true` | Показывать предупреждение при сохранении чистого режима AP |
27
+ | `collapsed` | `boolean` | `false` | Свёрнуто ли тело виджета (заголовок остаётся); поддерживает двустороннее связывание (`$bindable`) |
28
+ | `persistKey` | `string` | `undefined` | Ключ для сохранения состояния `collapsed` в `localStorage` — при заданном ключе сворачивание переживает перезагрузку страницы |
27
29
  | `onScan` | `() => Promise<{ id: string; name: string; value: string }[]>` | `undefined` | Обработчик сканирования сетей (в проде — `Header: GET, Argument: APsList`) |
28
30
  | `onSave` | `(config: IWidgetWiFiConfig) => void` | `undefined` | Срабатывает при нажатии "Сохранить" — конфиг уходит одним пакетом |
29
31
 
@@ -114,8 +114,9 @@
114
114
  каждое изменение cfg от самого пользователя, только на смену value. */
115
115
  $effect(() => {
116
116
  if (value !== undefined && !untrack(() => isDirty)) {
117
+ const snapshot = JSON.stringify(value)
117
118
  cfg = value
118
- savedSnapshot = JSON.stringify(cfg)
119
+ savedSnapshot = snapshot
119
120
  }
120
121
  })
121
122
 
package/dist/index.d.ts CHANGED
@@ -46,4 +46,4 @@ export { default as WidgetDeviceInfoProps } from "./WidgetDeviceInfo/WidgetDevic
46
46
  export * from "./locales/i18n";
47
47
  export * from "./locales/translations";
48
48
  export * from "./ModalStackStore";
49
- export { type UIComponent, type Position, type IUIComponentHandler, type IButtonProps, type IAccordionProps, type IInputProps, type ISelectProps, type IOption, type ISwitchProps, type IColorPickerProps, type IDividerProps, type ISliderProps, type ITextFieldProps, type IMapProps, type IProgressBarProps, type IWidgetProps, type IWidgetWiFiProps, type IWidgetWiFiConfig, type IWidgetDeviceInfoProps, type IWidgetDeviceInfoConfig, type IGraphProps, type IGraphDataObject, type ITableHeader, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, type IDeviceGNSS, type ICarouselProps, type IReceivingDataObject, type IVideoViewerProps, type IPeriodChartProps, type IPeriodChartLevel, } from "./types";
49
+ export { type UIComponent, type Position, type IUIComponentHandler, type IButtonProps, type IAccordionProps, type IInputProps, type ISelectProps, type IOption, type ISwitchProps, type IColorPickerProps, type IDividerProps, type ISliderProps, type ITextFieldProps, type IMapProps, type IProgressBarProps, type IWidgetProps, type IWidgetWiFiProps, type IWidgetWiFiConfig, type IWidgetDeviceInfoProps, type IWidgetDeviceInfoConfig, type IGraphProps, type IGraphDataObject, type IGraphHistoryPoint, type IGraphHistorySeries, type ITableHeader, type ITableText, type ITableSelect, type ITableButton, type ITableProgressBar, type ITableImage, type ITableContent, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, type IDeviceGNSS, type ICarouselProps, type IReceivingDataObject, type IVideoViewerProps, type IPeriodChartProps, type IPeriodChartLevel, } from "./types";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.30.0",
3
+ "version": "1.31.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -30,7 +30,8 @@
30
30
  "runes"
31
31
  ],
32
32
  "peerDependencies": {
33
- "svelte": "^5.0.0"
33
+ "svelte": "^5.0.0",
34
+ "tailwindcss": "^4.0.0"
34
35
  },
35
36
  "dependencies": {
36
37
  "tailwind-merge": "^3.6.0"