poe-svelte-ui-lib 1.16.5 → 1.17.2

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.
@@ -65,7 +65,8 @@
65
65
  component.type === "ProgressBar" ||
66
66
  component.type === "TextField" ||
67
67
  component.type === "Graph" ||
68
- component.type === "Map"
68
+ component.type === "Map" ||
69
+ component.type === "PeriodChart"
69
70
  ? $optionsStore.ACCESS_OPTION.filter((o) => o.value !== "viewOnly")
70
71
  : $optionsStore.ACCESS_OPTION}
71
72
  value={$optionsStore.ACCESS_OPTION.find((o) => o.value === component.access)}
@@ -0,0 +1,107 @@
1
+ <!-- $lib/ElementsUI/PeriodChart.svelte -->
2
+ <script module lang="ts">
3
+ import type { IPeriodChartLevel } from "../types"
4
+
5
+ /* Демо-данные по умолчанию — компонент выглядит осмысленно уже без пропсов, как у Tabs/Switch */
6
+ const randomLevelData = (n: number) => Array.from({ length: n }, () => Math.round(Math.random() * 100))
7
+
8
+ export const DEFAULT_LEVELS: IPeriodChartLevel[] = [
9
+ { name: "Год", data: randomLevelData(12) },
10
+ { name: "Месяц", data: randomLevelData(31) },
11
+ { name: "Сутки", data: randomLevelData(24) },
12
+ { name: "Час", data: randomLevelData(6) },
13
+ ]
14
+ </script>
15
+
16
+ <script lang="ts">
17
+ import { twMerge } from "tailwind-merge"
18
+ import type { IPeriodChartProps } from "../types"
19
+
20
+ let {
21
+ id = crypto.randomUUID(),
22
+ wrapperClass = "bg-blue",
23
+ label = { name: "", class: "" },
24
+ levels = DEFAULT_LEVELS,
25
+ unit = "",
26
+ onLevelChange = () => {},
27
+ }: IPeriodChartProps = $props()
28
+
29
+ let currentLevelIndex = $state(0)
30
+ $effect(() => {
31
+ if (currentLevelIndex >= levels.length) currentLevelIndex = 0
32
+ })
33
+
34
+ const currentLevel = $derived<IPeriodChartLevel | undefined>(levels[currentLevelIndex])
35
+ const values = $derived(currentLevel?.data ?? [])
36
+ const maxValue = $derived(values.length ? Math.max(...values, 0) * 1.1 || 1 : 1)
37
+
38
+ const selectLevel = (index: number) => {
39
+ if (index === currentLevelIndex) return
40
+ currentLevelIndex = index
41
+ onLevelChange(index, levels[index])
42
+ }
43
+
44
+ const barLabel = (i: number) => currentLevel?.labels?.[i] ?? String(i + 1)
45
+ const barHeightPercent = (value: number) => Math.max((value / maxValue) * 100, value > 0 ? 2 : 0)
46
+
47
+ /* Высота зоны столбиков — h-40 (10rem = 160px по умолчанию), должна совпадать с классом ниже.
48
+ * Если бар ниже этого порога, число физически не влезает внутрь (обрежется overflow-hidden) —
49
+ * рисуем его НАД столбиком вместо "внутри". */
50
+ const BAR_ZONE_PX = 160
51
+ const MIN_LABEL_PX = 18
52
+ const isSmallBar = (value: number) => (barHeightPercent(value) / 100) * BAR_ZONE_PX < MIN_LABEL_PX
53
+ </script>
54
+
55
+ <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge("relative flex w-full flex-col items-center justify-center gap-2", wrapperClass)}>
56
+ {#if label.name}
57
+ <h5 class={twMerge("w-full px-4 text-center", label.class)}>{label.name}</h5>
58
+ {/if}
59
+
60
+ <!-- Переключатель уровней детализации -->
61
+ <div class="flex w-full items-center justify-center gap-1 rounded-xl bg-(--bg-color) p-1">
62
+ {#each levels as level, index (level.name)}
63
+ <button
64
+ type="button"
65
+ class={twMerge(
66
+ "flex-1 cursor-pointer rounded-lg px-3 py-1.5 text-sm font-semibold transition-colors duration-150",
67
+ index === currentLevelIndex ? "bg-(--back-color) shadow-sm" : "text-(--shadow-color)/75 hover:text-(--shadow-color)",
68
+ )}
69
+ onclick={() => selectLevel(index)}
70
+ >
71
+ {level.name}
72
+ </button>
73
+ {/each}
74
+ </div>
75
+
76
+ <!-- Столбчатый график текущего уровня -->
77
+ <div class="relative flex w-full flex-col rounded-xl border border-(--border-color) bg-(--back-color) p-3">
78
+ {#if values.length === 0}
79
+ <div class="flex h-48 w-full items-center justify-center text-(--shadow-color)/60">Нет данных</div>
80
+ {:else}
81
+ <div class="flex h-40 w-full items-end gap-1">
82
+ {#each values as value, i}
83
+ <div class="relative flex h-full flex-1 flex-col items-center justify-end" role="img" aria-label={`${barLabel(i)}: ${value}${unit}`}>
84
+ {#if isSmallBar(value)}
85
+ <span class="mb-0.5 text-xs font-semibold text-(--shadow-color)">{Math.round(value)}</span>
86
+ {/if}
87
+ <div
88
+ class="flex w-full items-center justify-center overflow-hidden rounded-t-sm bg-(--bg-color) transition-[height] duration-150"
89
+ style="height: {barHeightPercent(value)}%"
90
+ >
91
+ {#if !isSmallBar(value)}
92
+ <span class="px-0.5 text-xs font-semibold text-white">{Math.round(value)}</span>
93
+ {/if}
94
+ </div>
95
+ </div>
96
+ {/each}
97
+ </div>
98
+
99
+ <!-- Подписи по оси X — всегда видны, без наведения -->
100
+ <div class="mt-1 flex w-full gap-1">
101
+ {#each values as _, i}
102
+ <div class="flex-1 truncate text-center text-[10px] font-bold text-(--shadow-color)/75">{barLabel(i)}</div>
103
+ {/each}
104
+ </div>
105
+ {/if}
106
+ </div>
107
+ </div>
@@ -0,0 +1,6 @@
1
+ import type { IPeriodChartLevel } from "../types";
2
+ export declare const DEFAULT_LEVELS: IPeriodChartLevel[];
3
+ import type { IPeriodChartProps } from "../types";
4
+ declare const PeriodChart: import("svelte").Component<IPeriodChartProps, {}, "">;
5
+ type PeriodChart = ReturnType<typeof PeriodChart>;
6
+ export default PeriodChart;
@@ -0,0 +1,175 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte"
3
+ import { T } from "../locales/i18n"
4
+ import { updateProperty, type IOption, type IPeriodChartLevel, type IPeriodChartProps, type IUIComponentHandler, type UIComponent } from "../types"
5
+ import * as UI from ".."
6
+ import { optionsStore } from "../options"
7
+ import ButtonAdd from "../libIcons/ButtonAdd.svelte"
8
+ import ButtonDelete from "../libIcons/ButtonDelete.svelte"
9
+ import CommonSnippets from "../CommonSnippets.svelte"
10
+
11
+ const {
12
+ component,
13
+ onPropertyChange,
14
+ forConstructor = true,
15
+ } = $props<{
16
+ component: UIComponent & { properties: Partial<IPeriodChartProps> }
17
+ onPropertyChange: (updates: Partial<{ properties?: string | object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void
18
+ forConstructor?: boolean
19
+ }>()
20
+
21
+ const DeviceVariables = getContext<{ id: string; value: string; name: string }[]>("DeviceVariables")
22
+ let VARIABLE_OPTIONS = $derived(DeviceVariables && Array.isArray(DeviceVariables) ? DeviceVariables : [])
23
+
24
+ let itemsContainer: HTMLDivElement | null = $state(null)
25
+
26
+ const initialColor = $derived(
27
+ $optionsStore.COLOR_OPTIONS.find((c) =>
28
+ (c.value as string).includes(component.properties.wrapperClass?.split(" ").find((cls: string) => cls.startsWith("bg-"))),
29
+ ),
30
+ )
31
+
32
+ /* levels[].data/labels хранятся как number[]/string[], редактируются одной строкой через запятую (fallback для литеральных демо-данных) */
33
+ const parseNumbers = (text: string): number[] =>
34
+ text
35
+ .split(",")
36
+ .map((s) => s.trim())
37
+ .filter((s) => s.length > 0)
38
+ .map(Number)
39
+ .filter((n) => !Number.isNaN(n))
40
+ const formatNumbers = (arr: number[] | undefined): string => (arr || []).join(", ")
41
+ const parseStrings = (text: string): string[] =>
42
+ text
43
+ .split(",")
44
+ .map((s) => s.trim())
45
+ .filter((s) => s.length > 0)
46
+ const formatStrings = (arr: string[] | undefined): string => (arr || []).join(", ")
47
+
48
+ /* eventHandler.Variables — список всех PWR.*-переменных со всех уровней, чтобы рантайм подписался на них разом */
49
+ const updateLevels = (levels: IPeriodChartLevel[]) => {
50
+ updateProperty("levels", levels, component, onPropertyChange)
51
+ onPropertyChange({ eventHandler: { Variables: levels.map((l) => l.variable).filter((v): v is string => !!v) } })
52
+ }
53
+ </script>
54
+
55
+ {#snippet PeriodChartUnit()}
56
+ <UI.Input
57
+ label={{ name: $T("constructor.props.units") }}
58
+ value={component.properties.unit}
59
+ onUpdate={(value) => updateProperty("unit", value, component, onPropertyChange)}
60
+ />
61
+ {/snippet}
62
+
63
+ {#snippet PeriodChartLevels()}
64
+ <hr class="border-gray-400" />
65
+
66
+ <div class="space-y-4" bind:this={itemsContainer}>
67
+ <div class="m-0 flex items-center justify-center gap-2">
68
+ <h4>{$T("constructor.props.periodchart.title")}</h4>
69
+ <UI.Button
70
+ wrapperClass="w-8"
71
+ content={{ icon: ButtonAdd }}
72
+ onClick={() => {
73
+ const newLevel: IPeriodChartLevel = { name: `Level ${(component.properties?.levels?.length ?? 0) + 1}`, data: [] }
74
+ const levels = [...(component.properties?.levels || []), newLevel]
75
+ updateLevels(levels)
76
+ }}
77
+ />
78
+ </div>
79
+
80
+ {#each component.properties.levels || [] as level, index}
81
+ <div id="item-{index}" class="m-0 flex items-end justify-around gap-2 border-gray-400">
82
+ <UI.Dragging
83
+ wrapperClass="w-9"
84
+ container={itemsContainer}
85
+ array={component.properties.levels}
86
+ elementIndex={index}
87
+ onUpdate={(updatedArray) => updateLevels(updatedArray)}
88
+ />
89
+ <UI.Input
90
+ label={{ name: $T("constructor.props.optionname") }}
91
+ wrapperClass="w-1/5"
92
+ value={level.name}
93
+ onUpdate={(value) => {
94
+ const levels = [...(component.properties?.levels || [])]
95
+ levels[index] = { ...levels[index], name: value as string }
96
+ updateLevels(levels)
97
+ }}
98
+ />
99
+ <UI.Select
100
+ label={{ name: $T("constructor.props.variable") }}
101
+ wrapperClass="w-1/5"
102
+ type="input"
103
+ options={VARIABLE_OPTIONS}
104
+ value={VARIABLE_OPTIONS.find((opt) => opt.value === level.variable)}
105
+ onUpdate={(value) => {
106
+ const levels = [...(component.properties?.levels || [])]
107
+ levels[index] = { ...levels[index], variable: (value as IOption).value as string }
108
+ updateLevels(levels)
109
+ }}
110
+ />
111
+ <UI.Input
112
+ label={{ name: $T("constructor.props.periodchart.data") }}
113
+ wrapperClass="w-1/5"
114
+ value={formatNumbers(level.data)}
115
+ onUpdate={(value) => {
116
+ const levels = [...(component.properties?.levels || [])]
117
+ levels[index] = { ...levels[index], data: parseNumbers(value as string) }
118
+ updateLevels(levels)
119
+ }}
120
+ />
121
+ <UI.Input
122
+ label={{ name: $T("constructor.props.periodchart.labels") }}
123
+ wrapperClass="w-1/5"
124
+ value={formatStrings(level.labels)}
125
+ onUpdate={(value) => {
126
+ const levels = [...(component.properties?.levels || [])]
127
+ levels[index] = { ...levels[index], labels: parseStrings(value as string) }
128
+ updateLevels(levels)
129
+ }}
130
+ />
131
+
132
+ {#if (component.properties.levels?.length ?? 0) > 1}
133
+ <UI.Button
134
+ wrapperClass="w-8"
135
+ content={{ icon: ButtonDelete }}
136
+ onClick={() => {
137
+ const levels = [...(component.properties?.levels || [])]
138
+ levels.splice(index, 1)
139
+ updateLevels(levels)
140
+ }}
141
+ />
142
+ {/if}
143
+ </div>
144
+ {/each}
145
+ <div id="item-{component.properties.levels?.length ?? 0}" class="min-h-4"></div>
146
+ </div>
147
+ {/snippet}
148
+
149
+ {#if forConstructor}
150
+ <div class="relative mb-4 flex flex-row items-start justify-center">
151
+ <div class="flex w-1/2 flex-col px-2">
152
+ <CommonSnippets snippet="Access" {component} {onPropertyChange} />
153
+ <CommonSnippets snippet="Colors" initialValue={{ color: initialColor, uselessColors: ["bg-max"] }} {component} {onPropertyChange} />
154
+ <CommonSnippets snippet="Label" {component} {onPropertyChange} />
155
+ </div>
156
+ <div class="flex w-1/2 flex-col px-2">
157
+ {@render PeriodChartUnit()}
158
+ </div>
159
+ </div>
160
+ {@render PeriodChartLevels()}
161
+ {:else}
162
+ <div class="relative mb-4 flex flex-row items-start justify-center">
163
+ <div class="flex w-1/2 flex-col px-2">
164
+ <CommonSnippets snippet="Identificator" {component} {onPropertyChange} />
165
+ <CommonSnippets snippet="Access" {component} {onPropertyChange} />
166
+ <CommonSnippets snippet="Label" {component} {onPropertyChange} />
167
+ </div>
168
+ <div class="flex w-1/2 flex-col px-2">
169
+ <CommonSnippets snippet="WrapperClass" {component} {onPropertyChange} />
170
+ <CommonSnippets snippet="Colors" initialValue={{ color: initialColor, uselessColors: ["bg-max"] }} {component} {onPropertyChange} />
171
+ {@render PeriodChartUnit()}
172
+ </div>
173
+ </div>
174
+ {@render PeriodChartLevels()}
175
+ {/if}
@@ -0,0 +1,16 @@
1
+ import { type IPeriodChartProps, type IUIComponentHandler, type UIComponent } from "../types";
2
+ type $$ComponentProps = {
3
+ component: UIComponent & {
4
+ properties: Partial<IPeriodChartProps>;
5
+ };
6
+ onPropertyChange: (updates: Partial<{
7
+ properties?: string | object;
8
+ name?: string;
9
+ access?: string;
10
+ eventHandler?: IUIComponentHandler;
11
+ }>) => void;
12
+ forConstructor?: boolean;
13
+ };
14
+ declare const PeriodChartProps: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type PeriodChartProps = ReturnType<typeof PeriodChartProps>;
16
+ export default PeriodChartProps;
@@ -0,0 +1,163 @@
1
+ # PeriodChart Component
2
+
3
+ ## Описание
4
+
5
+ Столбчатый график с переключателем уровней детализации (например Год / Месяц / Сутки / Час) —
6
+ универсальный компонент общего назначения, не привязан к конкретному домену данных. Принимает
7
+ набор уровней (`levels`), каждый со своим массивом значений и подписей столбиков; переключение
8
+ между уровнями меняет только то, какой массив рисуется — сам компонент ничего не запрашивает,
9
+ данные всех уровней передаются через пропсы целиком (как `historyData`/`streamingData` у `Graph`).
10
+
11
+ ## Пропсы
12
+
13
+ | Название | Тип | По умолчанию | Описание |
14
+ | ----------------- | ----------------------------------------------------- | ----------------------- | -------------------------------------------------------------------------- |
15
+ | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
16
+ | `wrapperClass` | `string` | `"bg-blue"` | CSS-классы обёртки — через `bg-*` задаёт цвет переключателя и столбиков |
17
+ | `label` | `{ name?: string; class?: string }` | `{ name: "", class: "" }` | Заголовок над графиком |
18
+ | `levels` | `IPeriodChartLevel[]` | 4 демо-уровня со случайными данными | Уровни детализации, порядок = порядок вкладок переключателя; открывается всегда на первом (индекс 0) |
19
+ | `levels[].name` | `string` | `-` | Подпись вкладки, напр. `"Час"` |
20
+ | `levels[].variable` | `string` | `undefined` | Имя переменной устройства, откуда берутся `data` этого уровня, напр. `"PWR.EHour"` — используется конструктором для подписки, сам компонент по нему ничего не запрашивает |
21
+ | `levels[].data` | `number[]` | `-` | Значения — длина массива = число столбиков, ничем не ограничена |
22
+ | `levels[].labels` | `string[]` | `undefined` | Подписи столбиков по X; если не заданы — используется индекс+1 |
23
+ | `unit` | `string` | `""` | Единицы измерения, добавляются к значению в `aria-label`, напр. `"Вт·ч"` |
24
+ | `onLevelChange` | `(index: number, level: IPeriodChartLevel) => void` | `() => {}` | Вызывается при переключении вкладки — только уведомление, без фетча внутри |
25
+
26
+ ## События
27
+
28
+ Компонент не генерирует DOM-события — используйте колбэк `onLevelChange` для реакции на смену
29
+ уровня (например чтобы догрузить свежие данные для только что выбранного уровня).
30
+
31
+ ## Примеры
32
+
33
+ ### Базовое использование (демо-данные)
34
+
35
+ ```svelte
36
+ <script>
37
+ import * as UI from "poe-svelte-ui-lib"
38
+ </script>
39
+
40
+ <UI.PeriodChart label={{ name: "Расход энергии" }} />
41
+ ```
42
+
43
+ ### Реальные данные с устройства (например BL0910: PWR.EYear/EMonth/EDay/EHour)
44
+
45
+ `variable` в каждом уровне — это имя Cfg-ключа устройства (`PWR.EHour` и т.п.), по которому
46
+ конструктор облака подписывается через `eventHandler.Variables` и подставляет актуальный `data`;
47
+ сам компонент значение `variable` не читает, только рисует уже готовый `data`.
48
+
49
+ ```svelte
50
+ <script>
51
+ import * as UI from "poe-svelte-ui-lib"
52
+
53
+ let levels = [
54
+ { name: "Год", variable: "PWR.EYear", data: deviceData.EYear, labels: ["янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек"] },
55
+ { name: "Месяц", variable: "PWR.EMonth", data: deviceData.EMonth },
56
+ { name: "Сутки", variable: "PWR.EDay", data: deviceData.EDay },
57
+ { name: "Час", variable: "PWR.EHour", data: deviceData.EHour, labels: ["0-10", "10-20", "20-30", "30-40", "40-50", "50-60"] },
58
+ ]
59
+ </script>
60
+
61
+ <UI.PeriodChart {levels} unit=" Вт·ч" onLevelChange={(index, level) => console.log("Переключились на", level.name)} />
62
+ ```
63
+
64
+ ## Внутренняя архитектура
65
+
66
+ ### Реактивность
67
+
68
+ - `currentLevelIndex` (`$state`) — индекс активной вкладки, всегда инициализируется `0` (первый уровень)
69
+ - `currentLevel`/`values` (`$derived`) — текущий уровень и его массив значений
70
+ - `maxValue` (`$derived`) — максимум текущего массива с отступом ×1.1 для автомасштаба высоты столбиков
71
+
72
+ ### Сторы и зависимости
73
+
74
+ - Используется `twMerge` для объединения Tailwind CSS классов
75
+
76
+ ### Директивы
77
+
78
+ - `#each` для отображения вкладок переключателя, столбиков графика и подписей по X
79
+ - `#if` для пустого состояния (уровень без данных)
80
+
81
+ ### Слоты
82
+
83
+ - Компонент не использует внешних слотов
84
+
85
+ ## Заметки
86
+
87
+ ### Адаптивность
88
+
89
+ - Столбики растягиваются на всю ширину контейнера (`flex-1` на каждый), высота — в процентах от
90
+ зоны столбиков фиксированной высоты (`h-40`); значение (округлено до целого, `Math.round`)
91
+ выводится прямо внутри столбика, по центру — видно всегда, без наведения. Для маленьких значений
92
+ столбик слишком низкий, чтобы число влезло внутрь (`overflow-hidden` его обрежет) — если высота
93
+ столбика меньше `MIN_LABEL_PX` (18px из `BAR_ZONE_PX`=160px = `h-40`), число рисуется НАД
94
+ столбиком вместо "внутри". Подписи (`labels[i]`/индекс+1) выводятся отдельной строкой под этой
95
+ зоной, тоже без наведения
96
+ - Никакого тултипа — компонент не реагирует на hover вообще
97
+ - Полностью на CSS-переменных темы (`--back-color`, `--border-color`, `--shadow-color`,
98
+ `--bg-color` через `wrapperClass`) — тёмная/светлая тема не требует отдельной настройки
99
+
100
+ ### Ограничения
101
+
102
+ - Компонент презентационный — не хранит историю, не опрашивает устройство сам; при смене
103
+ `levels` извне (например после ответа на `onLevelChange`) график перерисовывается автоматически
104
+ - Значение внутри столбика округляется до целого только для отображения (`Math.round`) — исходные
105
+ дробные данные в `levels[].data` не изменяются; `unit` в самом столбике не показывается (мало
106
+ места), только в `aria-label` для доступности
107
+
108
+ ### Производительность
109
+
110
+ - Никакого `<canvas>` — чистый HTML/CSS, подходит для умеренного числа столбиков (проверено на
111
+ вплоть до 31); для сотен/тысяч точек стоит использовать `Graph`
112
+
113
+ ## Конструктор свойств (PeriodChartProps.svelte)
114
+
115
+ ### Описание
116
+
117
+ Компонент `PeriodChartProps.svelte` предоставляет визуальный интерфейс для редактирования свойств
118
+ `PeriodChart`. Поддерживает два режима отображения: для конструктора и для редактирования —
119
+ идентично остальным компонентам библиотеки (`ProgressBarProps`, `TabsProps`).
120
+
121
+ ### Пропсы конструктора
122
+
123
+ | Название | Тип | По умолчанию | Описание |
124
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------- |
125
+ | `component` | `UIComponent & { properties: Partial<IPeriodChartProps> }` | `-` | Объект компонента с его свойствами |
126
+ | `onPropertyChange` | `(updates: Partial<{ properties?: string \| object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void` | `-` | Коллбэк для обновления свойств компонента |
127
+ | `forConstructor` | `boolean` | `true` | Режим отображения (для конструктора или редактирования) |
128
+
129
+ ### Особенности конструктора
130
+
131
+ - Общие поля через `CommonSnippets`: `Access` (без `viewOnly` — компонент чисто отображающий, как
132
+ `Graph`/`Tabs`/`ProgressBar`), `Colors` (пишет в `wrapperClass`, тот же цвет уходит на
133
+ переключатель и столбики), `Label`; в режиме редактирования дополнительно `Identificator`,
134
+ `WrapperClass`
135
+ - `unit` — отдельное текстовое поле ввода
136
+ - Редактор уровней (`levels[]`) — тот же паттерн повторяющегося списка, что у `TabsSettings`/
137
+ `ProgressBarOptions`: перетаскивание (`UI.Dragging`) для порядка вкладок, добавление/удаление
138
+ через `ButtonAdd`/`ButtonDelete`, на каждый уровень — поля `name`, `variable`, `data` и
139
+ `labels`. `variable` — тот же `Select` по списку переменных устройства (`DeviceVariables` из
140
+ контекста), что и у `Graph`/`CommonSnippets.Variable`, только по одному на каждый уровень; при
141
+ любом изменении списка уровней редактор пересобирает `eventHandler.Variables` как объединение
142
+ всех непустых `variable` со всех уровней — так рантайм подписывается сразу на все нужные ключи.
143
+ `data`/`labels` остаются полями для литеральных демо-данных — массивы чисел/строк вводятся одной
144
+ строкой через запятую (`"10, 20, 30"`) и парсятся на лету
145
+
146
+ ### Использование
147
+
148
+ ```svelte
149
+ <script>
150
+ import * as UI from "poe-svelte-ui-lib"
151
+
152
+ let component = $state({
153
+ id: crypto.randomUUID(),
154
+ type: "PeriodChart",
155
+ access: "full",
156
+ properties: { levels: [{ name: "Час", data: [1, 2, 3, 4, 5, 6] }] },
157
+ position: { row: 0, col: 0, width: 0, height: 0 },
158
+ parentId: "",
159
+ })
160
+ </script>
161
+
162
+ <UI.PeriodChartProps {component} onPropertyChange={(updates) => (component = { ...component, ...updates })} forConstructor={true} />
163
+ ```
@@ -54,10 +54,10 @@
54
54
 
55
55
  $effect(() => {
56
56
  if (Array.isArray(value)) {
57
- lowerValue = value[0]
58
- upperValue = value[1]
57
+ lowerValue = roundToClean(value[0])
58
+ upperValue = roundToClean(value[1])
59
59
  } else if (typeof value === "number") {
60
- singleValue = value
60
+ singleValue = roundToClean(value)
61
61
  }
62
62
  })
63
63
 
@@ -128,7 +128,7 @@
128
128
  remoteImgSrc = null
129
129
  return
130
130
  }
131
- const blob = frame instanceof Blob ? frame : new Blob([frame], { type: "image/jpeg" })
131
+ const blob = frame instanceof Blob ? frame : new Blob([new Uint8Array(frame)], { type: "image/jpeg" })
132
132
  const url = URL.createObjectURL(blob)
133
133
  remoteImgSrc = url
134
134
  return () => URL.revokeObjectURL(url)
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export { default as JoystickProps } from "./Joystick/JoystickProps.svelte";
19
19
  export { default as Modal } from "./Modal.svelte";
20
20
  export { default as Map } from "./Map/Map.svelte";
21
21
  export { default as MapProps } from "./Map/MapProps.svelte";
22
+ export { default as PeriodChart } from "./PeriodChart/PeriodChart.svelte";
23
+ export { default as PeriodChartProps } from "./PeriodChart/PeriodChartProps.svelte";
22
24
  export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte";
23
25
  export { default as ProgressBarProps } from "./ProgressBar/ProgressBarProps.svelte";
24
26
  export { default as Select } from "./Select/Select.svelte";
@@ -40,4 +42,4 @@ export { default as WidgetProps } from "./Widget/WidgetProps.svelte";
40
42
  export * from "./locales/i18n";
41
43
  export * from "./locales/translations";
42
44
  export * from "./ModalStackStore";
43
- 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 IGraphProps, type IGraphDataObject, type ITableHeader, type ITableProps, type ITabsProps, type IJoystickProps, type IFileAttachProps, type IDeviceGNSS, type ICarouselProps, type IReceivingDataObject, type IVideoViewerProps, } from "./types";
45
+ 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 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";
package/dist/index.js CHANGED
@@ -20,6 +20,8 @@ export { default as JoystickProps } from "./Joystick/JoystickProps.svelte";
20
20
  export { default as Modal } from "./Modal.svelte";
21
21
  export { default as Map } from "./Map/Map.svelte";
22
22
  export { default as MapProps } from "./Map/MapProps.svelte";
23
+ export { default as PeriodChart } from "./PeriodChart/PeriodChart.svelte";
24
+ export { default as PeriodChartProps } from "./PeriodChart/PeriodChartProps.svelte";
23
25
  export { default as ProgressBar } from "./ProgressBar/ProgressBar.svelte";
24
26
  export { default as ProgressBarProps } from "./ProgressBar/ProgressBarProps.svelte";
25
27
  export { default as Select } from "./Select/Select.svelte";
@@ -1,3 +1,5 @@
1
+ <script lang="ts"></script>
2
+
1
3
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"
2
4
  ><clipPath id="svgIDa"><circle cx="256" cy="256" r="256" /></clipPath><g clip-path="url(#svgIDa)"
3
5
  ><path
@@ -1,18 +1,5 @@
1
- export default FlagEn;
2
- type FlagEn = SvelteComponent<{
3
- [x: string]: never;
4
- }, {
5
- [evt: string]: CustomEvent<any>;
6
- }, {}> & {
7
- $$bindings?: string | undefined;
8
- };
9
- declare const FlagEn: $$__sveltets_2_IsomorphicComponent<{
10
- [x: string]: never;
11
- }, {
12
- [evt: string]: CustomEvent<any>;
13
- }, {}, {}, string>;
14
1
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
- new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
16
3
  $$bindings?: Bindings;
17
4
  } & Exports;
18
5
  (internal: unknown, props: {
@@ -24,3 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
24
11
  };
25
12
  z_$$bindings?: Bindings;
26
13
  }
14
+ declare const FlagEn: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type FlagEn = InstanceType<typeof FlagEn>;
18
+ export default FlagEn;
@@ -1,3 +1,5 @@
1
+ <script lang="ts"></script>
2
+
1
3
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"
2
4
  ><mask id="circleFlagsRu0"><circle cx="256" cy="256" r="256" fill="#fff" /></mask><g mask="url(#circleFlagsRu0)"
3
5
  ><path fill="#0052b4" d="M512 170v172l-256 32L0 342V170l256-32z" /><path fill="#eee" d="M512 0v170H0V0Z" /><path
@@ -1,18 +1,5 @@
1
- export default FlagRu;
2
- type FlagRu = SvelteComponent<{
3
- [x: string]: never;
4
- }, {
5
- [evt: string]: CustomEvent<any>;
6
- }, {}> & {
7
- $$bindings?: string | undefined;
8
- };
9
- declare const FlagRu: $$__sveltets_2_IsomorphicComponent<{
10
- [x: string]: never;
11
- }, {
12
- [evt: string]: CustomEvent<any>;
13
- }, {}, {}, string>;
14
1
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
- new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
16
3
  $$bindings?: Bindings;
17
4
  } & Exports;
18
5
  (internal: unknown, props: {
@@ -24,3 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
24
11
  };
25
12
  z_$$bindings?: Bindings;
26
13
  }
14
+ declare const FlagRu: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type FlagRu = InstanceType<typeof FlagRu>;
18
+ export default FlagRu;
@@ -1,3 +1,5 @@
1
+ <script lang="ts"></script>
2
+
1
3
  <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"
2
4
  ><mask id="circleFlagsCn0"><circle cx="256" cy="256" r="256" fill="#fff" /></mask><g mask="url(#circleFlagsCn0)"
3
5
  ><path fill="#d80027" d="M0 0h512v512H0z" /><path
@@ -1,18 +1,5 @@
1
- export default FlagZh;
2
- type FlagZh = SvelteComponent<{
3
- [x: string]: never;
4
- }, {
5
- [evt: string]: CustomEvent<any>;
6
- }, {}> & {
7
- $$bindings?: string | undefined;
8
- };
9
- declare const FlagZh: $$__sveltets_2_IsomorphicComponent<{
10
- [x: string]: never;
11
- }, {
12
- [evt: string]: CustomEvent<any>;
13
- }, {}, {}, string>;
14
1
  interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
15
- new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
16
3
  $$bindings?: Bindings;
17
4
  } & Exports;
18
5
  (internal: unknown, props: {
@@ -24,3 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
24
11
  };
25
12
  z_$$bindings?: Bindings;
26
13
  }
14
+ declare const FlagZh: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type FlagZh = InstanceType<typeof FlagZh>;
18
+ export default FlagZh;
@@ -183,6 +183,9 @@ const translations = {
183
183
  "constructor.props.progressbar.title": ["Полосы прогресса", "Progress bars", "进度条"],
184
184
  "constructor.props.bits.title": ["Биты", "Bits", "位"],
185
185
  "constructor.props.tabs.title": ["Вкладки меню", "Menu tabs", "菜单标签"],
186
+ "constructor.props.periodchart.title": ["Уровни детализации", "Detail levels", "详细级别"],
187
+ "constructor.props.periodchart.data": ["Данные (через запятую)", "Data (comma-separated)", "数据(逗号分隔)"],
188
+ "constructor.props.periodchart.labels": ["Подписи столбиков (через запятую)", "Bar labels (comma-separated)", "柱标签(逗号分隔)"],
186
189
  "constructor.props.valuetype": ["Тип значения", "Value type", "值类型"],
187
190
  "constructor.props.action": ["Действие при переключении", "Action on switch", "切换时的操作"],
188
191
  "constructor.props.caption.left": ["Надпись слева", "Left caption", "左侧标题"],
package/dist/types.d.ts CHANGED
@@ -16,10 +16,10 @@ export declare const updateComponent: (component: UIComponent, updates: Partial<
16
16
  }>) => {
17
17
  access: "full" | "viewOnly" | "hidden" | undefined;
18
18
  name: string | undefined;
19
- properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IProgressBarProps | ISelectProps<unknown> | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps;
19
+ properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps<unknown> | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps;
20
20
  eventHandler: IUIComponentHandler | undefined;
21
21
  id: string;
22
- type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget";
22
+ type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget";
23
23
  position: Required<Position>;
24
24
  parentId: string;
25
25
  };
@@ -27,8 +27,8 @@ export interface UIComponent {
27
27
  id: string;
28
28
  name?: string;
29
29
  access?: "full" | "viewOnly" | "hidden";
30
- type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget";
31
- properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IProgressBarProps | ISelectProps | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps;
30
+ type: "Accordion" | "Button" | "ColorPicker" | "Divider" | "FileAttach" | "Graph" | "Input" | "Joystick" | "Map" | "PeriodChart" | "ProgressBar" | "Select" | "Slider" | "Switch" | "Table" | "Tabs" | "TextField" | "VideoViewer" | "Widget";
31
+ properties: IAccordionProps | IButtonProps | IColorPickerProps | IDividerProps | IFileAttachProps | IGraphProps | IInputProps | IJoystickProps | IMapProps | IPeriodChartProps | IProgressBarProps | ISelectProps | ISliderProps | ISwitchProps | ITableProps<object> | ITabsProps | ITextFieldProps | IWidgetProps;
32
32
  position: Required<Position>;
33
33
  parentId: string;
34
34
  eventHandler?: IUIComponentHandler;
@@ -164,6 +164,23 @@ export interface IGraphProps {
164
164
  refreshRate?: number;
165
165
  historyData?: IGraphHistorySeries[];
166
166
  }
167
+ export interface IPeriodChartLevel {
168
+ name: string;
169
+ variable?: string;
170
+ data: number[];
171
+ labels?: string[];
172
+ }
173
+ export interface IPeriodChartProps {
174
+ id?: string;
175
+ wrapperClass?: string;
176
+ label?: {
177
+ name?: string;
178
+ class?: string;
179
+ };
180
+ levels?: IPeriodChartLevel[];
181
+ unit?: string;
182
+ onLevelChange?: (index: number, level: IPeriodChartLevel) => void;
183
+ }
167
184
  export interface IInputProps {
168
185
  id?: string;
169
186
  type?: "text" | "password" | "number" | "text-area" | "bitMode";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.16.5",
3
+ "version": "1.17.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,10 +0,0 @@
1
- import { type Readable } from "svelte/store";
2
- export declare const Language: import("svelte/store").Writable<string>;
3
- export declare function setLanguage(newLang: string): void;
4
- export declare const LOCALES: {
5
- id: string;
6
- name: string;
7
- value: string;
8
- component: import("svelte/legacy").LegacyComponentType;
9
- }[];
10
- export declare const T: Readable<(key: string) => string>;