poe-svelte-ui-lib 1.34.0 → 1.35.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.
@@ -7,8 +7,11 @@
7
7
  import type { IOption, ISelectProps } from "../types"
8
8
  import { twMerge } from "tailwind-merge"
9
9
  import { T } from "../locales/i18n"
10
+ import Tooltip from "../Tooltip.svelte"
10
11
 
11
12
  let isDropdownOpen = $state(false)
13
+ /* Вариант-иконка под курсором в type="buttons": его name показывается подсказкой */
14
+ let hoveredOptionId: string | null = $state(null)
12
15
  let dropdownElement: HTMLDivElement
13
16
  let portalElement: HTMLDivElement | undefined = $state()
14
17
  let anchorRect: { top: number; bottom: number; left: number; width: number } | null = $state(null)
@@ -121,10 +124,7 @@
121
124
  const selectedOption = options.find((option) => option.name?.toString() === searchValue) || {
122
125
  id: `input-${Date.now()}`,
123
126
  name: searchValue,
124
- value:
125
- typeof options[0]?.value == "number" && searchValue !== "" && !isNaN(Number(searchValue))
126
- ? (Number(searchValue) as T)
127
- : (searchValue as T),
127
+ value: typeof options[0]?.value == "number" && searchValue !== "" && !isNaN(Number(searchValue)) ? (Number(searchValue) as T) : (searchValue as T),
128
128
  }
129
129
  value = selectedOption
130
130
  onUpdate?.(selectedOption)
@@ -206,7 +206,7 @@
206
206
  <!-- Сплошная группа: общий контур, разделители внутри, без зазоров и теней -->
207
207
  <div
208
208
  id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
209
- class="flex w-full overflow-hidden rounded-[10px] border border-(--border-color) {disabled ? 'opacity-45' : ''}"
209
+ class="flex w-full rounded-[10px] border border-(--border-color) {disabled ? 'opacity-45' : ''}"
210
210
  role="group"
211
211
  >
212
212
  {#each options as option, index (option.id)}
@@ -220,10 +220,12 @@
220
220
  в ряд одинаковых белых кнопок. Выбор у таких опций отмечается рамкой, а не заливкой;
221
221
  для обычного сегмент-контрола без своего цвета (Access, Header и т.п.) поведение
222
222
  прежнее — акцентная заливка только у выбранного пункта. -->
223
- <button
224
- id={option.id}
225
- class="{twMerge(
226
- `flex h-8 min-w-0 flex-1 items-center justify-center px-3 py-2 text-[13px] font-semibold transition-colors duration-150
223
+ <!-- Обёртка — позиционирующий предок подсказки; overflow-hidden у группы убран, иначе подсказка обрезается -->
224
+ <div class="relative flex min-w-0 flex-1">
225
+ <button
226
+ id={option.id}
227
+ class="{twMerge(
228
+ `flex h-8 w-full min-w-0 items-center justify-center px-3 py-2 text-[13px] font-semibold transition-colors duration-150
227
229
  ${index === 0 ? 'rounded-l-[9px]' : ''} ${index === options.length - 1 ? 'rounded-r-[9px]' : ''}
228
230
  ${index > 0 ? 'border-l border-(--border-color)' : ''}
229
231
  ${
@@ -236,16 +238,25 @@
236
238
  : 'text-(--font-color) hover:bg-(--container-color)'
237
239
  }
238
240
  ${option.disabled || disabled ? 'cursor-not-allowed text-(--faint-color)' : 'cursor-pointer'}`,
239
- option.class,
240
- )} {hasOwnColor ? 'bg-(--bg-color)' : selected ? 'bg-(--bg-color,var(--accent-color))' : 'bg-(--back-color)'}"
241
- onclick={(e) => selectOption(option, e)}
242
- disabled={option.disabled || disabled}
243
- aria-pressed={!!isSelected(option)}
244
- >
245
- {#if option.name}
246
- <span class="block truncate">{option.name}</span>
241
+ option.class,
242
+ )} {hasOwnColor ? 'bg-(--bg-color)' : selected ? 'bg-(--bg-color,var(--accent-color))' : 'bg-(--back-color)'}"
243
+ onclick={(e) => selectOption(option, e)}
244
+ disabled={option.disabled || disabled}
245
+ aria-pressed={!!isSelected(option)}
246
+ aria-label={option.icon ? option.name : undefined}
247
+ onmouseenter={() => (hoveredOptionId = option.icon && option.name ? option.id : null)}
248
+ onmouseleave={() => (hoveredOptionId = null)}
249
+ >
250
+ {#if option.icon}
251
+ <span class="flex h-[18px] min-w-[18px] items-center justify-center leading-none [&_svg]:h-full [&_svg]:w-full">{@html option.icon}</span>
252
+ {:else if option.name}
253
+ <span class="block truncate">{option.name}</span>
254
+ {/if}
255
+ </button>
256
+ {#if option.icon && option.name}
257
+ <Tooltip show={hoveredOptionId === option.id} text={option.name} side="top" />
247
258
  {/if}
248
- </button>
259
+ </div>
249
260
  {/each}
250
261
  </div>
251
262
  {:else if type === "input"}
@@ -80,7 +80,7 @@
80
80
  </script>
81
81
 
82
82
  <div
83
- class={twMerge("flex w-full max-w-3xl flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
83
+ class={twMerge("flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
84
84
  style={accentStyle}
85
85
  >
86
86
  <!-- Заголовок -->
@@ -107,7 +107,7 @@
107
107
  </script>
108
108
 
109
109
  <div
110
- class={twMerge("relative flex w-full max-w-3xl flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
110
+ class={twMerge("relative flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
111
111
  style={accentStyle}
112
112
  >
113
113
  <!-- Заголовок -->
@@ -105,7 +105,7 @@
105
105
  </script>
106
106
 
107
107
  <div
108
- class={twMerge("relative flex w-full max-w-3xl flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
108
+ class={twMerge("relative flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
109
109
  style={accentStyle}
110
110
  >
111
111
  <!-- Заголовок -->
@@ -135,7 +135,7 @@
135
135
  </script>
136
136
 
137
137
  <div
138
- class={twMerge("relative flex w-full max-w-3xl flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
138
+ class={twMerge("relative flex w-full flex-col gap-4 rounded-2xl border border-(--hairline-color) bg-(--container-color) p-4", wrapperClass)}
139
139
  style={accentStyle}
140
140
  >
141
141
  <!-- Заголовок и переключатель режима -->
@@ -0,0 +1,36 @@
1
+ export declare const PROPS_ICONS: {
2
+ alignLeft: string;
3
+ alignCenter: string;
4
+ alignRight: string;
5
+ alignJustify: string;
6
+ bold: string;
7
+ italic: string;
8
+ underline: string;
9
+ highlight: string;
10
+ arrowUp: string;
11
+ arrowDown: string;
12
+ arrowLeft: string;
13
+ arrowRight: string;
14
+ anchorTop: string;
15
+ anchorCenter: string;
16
+ anchorBottom: string;
17
+ square: string;
18
+ circle: string;
19
+ toggleHorizontal: string;
20
+ toggleVertical: string;
21
+ checkbox: string;
22
+ sliderSingle: string;
23
+ sliderRange: string;
24
+ dividerLine: string;
25
+ dividerSpace: string;
26
+ fitCover: string;
27
+ fitContain: string;
28
+ file: string;
29
+ image: string;
30
+ sort: string;
31
+ copy: string;
32
+ ellipsis: string;
33
+ tooltip: string;
34
+ modal: string;
35
+ lock: string;
36
+ };
@@ -0,0 +1,47 @@
1
+ /* $lib/libIcons/propsIcons.ts — иконки вариантов панелей свойств (IOption.icon): контурные 24×24 в стиле остальных иконок библиотеки */
2
+ const svg = (d) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="${d}"/></svg>`;
3
+ export const PROPS_ICONS = {
4
+ /* Выравнивание текста и блока */
5
+ alignLeft: svg("M15 12H3M17 18H3M21 6H3"),
6
+ alignCenter: svg("M17 12H7M19 18H5M21 6H3"),
7
+ alignRight: svg("M21 12H9M21 18H7M21 6H3"),
8
+ alignJustify: svg("M3 12h18M3 18h18M3 6h18"),
9
+ /* Начертание текста */
10
+ bold: svg("M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"),
11
+ italic: svg("M19 4h-9M14 20H5M15 4 9 20"),
12
+ underline: svg("M6 4v6a6 6 0 0 0 12 0V4M4 20h16"),
13
+ highlight: svg("m9 11-6 6v3h9l3-3m7-5-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4"),
14
+ /* Стороны и направления */
15
+ arrowUp: svg("m5 12 7-7 7 7M12 19V5"),
16
+ arrowDown: svg("M12 5v14m7-7-7 7-7-7"),
17
+ arrowLeft: svg("m12 19-7-7 7-7m7 7H5"),
18
+ arrowRight: svg("M5 12h14m-7-7 7 7-7 7"),
19
+ /* Привязка по вертикали */
20
+ anchorTop: svg("M5 3h14M18 13l-6-6-6 6M12 7v14"),
21
+ anchorCenter: svg("M3 12h18M8 4l4 4 4-4M8 20l4-4 4 4"),
22
+ anchorBottom: svg("M12 17V3M6 11l6 6 6-6M19 21H5"),
23
+ /* Формы и ориентация */
24
+ square: svg("M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"),
25
+ circle: svg("M12 2a10 10 0 1 0 0 20 10 10 0 1 0 0-20z"),
26
+ toggleHorizontal: svg("M8 5h8a7 7 0 0 1 0 14H8A7 7 0 0 1 8 5zm8 4a3 3 0 1 0 0 6 3 3 0 1 0 0-6z"),
27
+ toggleVertical: svg("M5 8a7 7 0 0 1 14 0v8a7 7 0 0 1-14 0zm7-3a3 3 0 1 0 0 6 3 3 0 1 0 0-6z"),
28
+ checkbox: svg("M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm4 9 2 2 4-4"),
29
+ /* Слайдер и разделитель */
30
+ sliderSingle: svg("M3 12h2m6 0h10M8 9a3 3 0 1 0 0 6 3 3 0 1 0 0-6z"),
31
+ sliderRange: svg("M3 12h1m6 0h4m6 0h1M7 9a3 3 0 1 0 0 6 3 3 0 1 0 0-6zm10 0a3 3 0 1 0 0 6 3 3 0 1 0 0-6z"),
32
+ dividerLine: svg("M3 12h18"),
33
+ dividerSpace: svg("M4 6v12M20 6v12M8 12h8"),
34
+ /* Вписывание изображения */
35
+ fitCover: svg("M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m8 0h3a2 2 0 0 0 2-2v-3"),
36
+ fitContain: svg("M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3m8 0v-3a2 2 0 0 1 2-2h3"),
37
+ /* Файлы */
38
+ file: svg("M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zm0 0v6h6"),
39
+ image: svg("M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm4 6h.01M21 15l-5-5L5 21"),
40
+ /* Настройки ячеек и полей */
41
+ sort: svg("m3 16 4 4 4-4M7 20V4m14 4-4-4-4 4m4-4v16"),
42
+ copy: svg("M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2zM4 16a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2"),
43
+ ellipsis: svg("M5 12h.01M12 12h.01M19 12h.01"),
44
+ tooltip: svg("M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"),
45
+ modal: svg("M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zM2 9h20"),
46
+ lock: svg("M7 11V7a5 5 0 0 1 10 0v4M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2z"),
47
+ };
@@ -13,55 +13,120 @@ const translations = {
13
13
  /* Документация компонентов для ⓘ в конструкторе (приложение читает через UI.T) — механизм общий
14
14
  для всех типов компонентов, заполнено пока только там, где есть что объяснить (фиксированная
15
15
  прошивочная конвенция у смарт-виджетов) */
16
- "component.info.WidgetWiFi": [
17
- "📶 Виджет работает с фиксированным набором полей группы `CFG` на устройстве — привязка переменных вручную не нужна.\n\n**📡 Режим клиента (STA)**\n- `WiFiMode`, `StaSSID`, `StaPSK`\n- `StaticIP`, `StaIP`, `StaMS`, `StaGW`\n\n**📡 Точка доступа (AP)**\n- `ApSSID`, `ApPSK`\n- `ApIP`, `ApMS`, `ApGW`\n\n🔍 Сканирование сетей отправляет `Header: GET`, `Argument: APsList` — ответ устройства ожидается в поле `CFG.StaSSID-S`.\n\n⚙️ Если на устройстве используется другое имя группы вместо `CFG`, поменяйте префикс (ID) в настройках виджета.",
18
- "📶 The widget works with a fixed set of fields in the device's `CFG` group — no manual variable binding needed.\n\n**📡 Client mode (STA)**\n- `WiFiMode`, `StaSSID`, `StaPSK`\n- `StaticIP`, `StaIP`, `StaMS`, `StaGW`\n\n**📡 Access point (AP)**\n- `ApSSID`, `ApPSK`\n- `ApIP`, `ApMS`, `ApGW`\n\n🔍 Network scanning sends `Header: GET`, `Argument: APsList` — the device response is expected in the `CFG.StaSSID-S` field.\n\n⚙️ If the device uses a different group name instead of `CFG`, change the prefix (ID) in the widget settings.",
19
- "📶 该组件使用设备 `CFG` 分组下的一组固定字段——无需手动绑定变量。\n\n**📡 客户端模式(STA)**\n- `WiFiMode`、`StaSSID`、`StaPSK`\n- `StaticIP`、`StaIP`、`StaMS`、`StaGW`\n\n**📡 接入点(AP)**\n- `ApSSID`、`ApPSK`\n- `ApIP`、`ApMS`、`ApGW`\n\n🔍 扫描网络会发送 `Header: GET`、`Argument: APsList`——设备应在 `CFG.StaSSID-S` 字段中返回结果。\n\n⚙️ 如果设备使用的分组名称不是 `CFG`,请在组件设置中修改前缀(ID)。",
16
+ "component.info.Accordion": [
17
+ "🎯 **Аккордеон** — сворачиваемый блок для группировки компонентов: настройки, диагностика, редко используемые разделы.\n\n**🧩 Как пользоваться**\n- Перетащите компоненты внутрь аккордеона — у него своя сетка, размер задаётся при создании и ресайзе\n- Заголовок с иконкой открывает и закрывает содержимое\n- «Открыт по умолчанию» — состояние при загрузке интерфейса\n\n**🎨 Оформление**\n- Картинка фона — изображение под содержимым (например схема устройства), поверх неё располагаются компоненты\n\n💡 Сам аккордеон ничего не отправляет и не читает с устройства. По высоте занимает одну строку сетки и растягивается по содержимому.",
18
+ '🎯 **Accordion** — a collapsible block for grouping components: settings, diagnostics, rarely used sections.\n\n**🧩 How to use**\n- Drag components inside — the accordion has its own grid, sized on creation and resize\n- The header with an icon opens and closes the content\n- "Open by default" — state when the interface loads\n\n**🎨 Appearance**\n- Background image — shown under the content (e.g. a device diagram), components are placed on top\n\n💡 The accordion itself neither sends nor reads anything from the device. It takes one grid row and grows with its content.',
19
+ "🎯 **折叠面板** —— 用于分组组件的可折叠区块:设置、诊断、不常用的部分。\n\n**🧩 使用方法**\n- 将组件拖入其中 —— 折叠面板有自己的网格,大小在创建和调整时设定\n- 带图标的标题用于展开和收起内容\n- 「默认展开」—— 界面加载时的状态\n\n**🎨 外观**\n- 背景图片 —— 显示在内容下方(例如设备示意图),组件叠放在其上\n\n💡 折叠面板本身不向设备发送也不读取任何数据。它占网格的一行,并随内容伸展。",
20
20
  ],
21
- "component.info.WidgetDeviceInfo": [
22
- "ℹ️ Виджет работает с фиксированным набором полей группы `CFG` на устройстве — привязка переменных вручную не нужна.\n\n**🔒 Только чтение**\n- `DevSN`, `DevID`, `DevFW`, `RunCnt`\n- `StaIP`, `ApIP` — текущие IP-адреса в режимах STA/AP\n\n**✏️ Редактируемые** (сохраняются одним пакетом по кнопке «Сохранить»)\n- `DevName`, `HostName`, `WebUser`, `WebPsw`\n\n🔌 Кнопка «Перезагрузить» отправляет `Argument: Restart`.\n\n⚙️ Если на устройстве используется другое имя группы вместо `CFG`, поменяйте префикс (ID) в настройках виджета.",
23
- 'ℹ️ The widget works with a fixed set of fields in the device\'s `CFG` group — no manual variable binding needed.\n\n**🔒 Read-only**\n- `DevSN`, `DevID`, `DevFW`, `RunCnt`\n- `StaIP`, `ApIP` — current IP addresses in STA/AP mode\n\n**✏️ Editable** (saved as one packet via the "Save" button)\n- `DevName`, `HostName`, `WebUser`, `WebPsw`\n\n🔌 The "Restart" button sends `Argument: Restart`.\n\n⚙️ If the device uses a different group name instead of `CFG`, change the prefix (ID) in the widget settings.',
24
- "ℹ️ 该组件使用设备 `CFG` 分组下的一组固定字段——无需手动绑定变量。\n\n**🔒 只读字段**\n- `DevSN`、`DevID`、`DevFW`、`RunCnt`\n- `StaIP`、`ApIP` —— STA/AP 模式下的当前 IP 地址\n\n**✏️ 可编辑字段**(通过「保存」按钮一次性发送)\n- `DevName`、`HostName`、`WebUser`、`WebPsw`\n\n🔌 「重启」按钮会发送 `Argument: Restart`。\n\n⚙️ 如果设备使用的分组名称不是 `CFG`,请在组件设置中修改前缀(ID)。",
21
+ "component.info.Button": [
22
+ '🎯 **Кнопка** — отправляет команду на устройство по нажатию.\n\n**📤 Что уходит в пакете**\n- **Заголовок** и **Аргумент** — команда, например `SET` + `Restart`\n- **Переменные** — текущие значения других компонентов. Так делается кнопка «Сохранить»: поля с аргументом `NoSend` редактируются без отправки, а кнопка отправляет их все одним пакетом\n- **Фиксированное значение** (JSON) — отправляется как есть вместо переменных, например `{"Mode": 2}`\n\n**🎨 Оформление**\n- Текст, иконка, цвет, подсказка при наведении\n\n💡 Кнопка не показывает значение с устройства — только отправляет.',
23
+ '🎯 **Button** — sends a command to the device when pressed.\n\n**📤 What goes into the packet**\n- **Header** and **Argument** — the command, e.g. `SET` + `Restart`\n- **Variables** — current values of other components. This is how a "Save" button works: fields with the `NoSend` argument are edited without sending, and the button sends them all in one packet\n- **Fixed value** (JSON) — sent as is instead of variables, e.g. `{"Mode": 2}`\n\n**🎨 Appearance**\n- Text, icon, color, hover tooltip\n\n💡 The button doesn\'t show a device value — it only sends.',
24
+ '🎯 **按钮** —— 按下时向设备发送命令。\n\n**📤 数据包内容**\n- **标头** 和 **参数** —— 命令,例如 `SET` + `Restart`\n- **变量** —— 其他组件的当前值。「保存」按钮就是这样实现的:参数为 `NoSend` 的字段编辑时不发送,由按钮一次性全部发出\n- **固定值**(JSON)—— 原样发送以代替变量,例如 `{"Mode": 2}`\n\n**🎨 外观**\n- 文本、图标、颜色、悬停提示\n\n💡 按钮不显示设备值 —— 只负责发送。',
25
25
  ],
26
- "component.info.WidgetFileSystem": [
27
- "🗂️ Виджет работает с одним объектом `FS` группы `CFG` на устройстве — привязка переменных вручную не нужна.\n\n**📊 Занятость**\n- `Total`, `Used`, `Free`\n\n**📄 Файлы**\n- `List` — путь и размер; «Удалить» отправляет `SET FS` с `Op: Delete` и `Name` (после подтверждения)\n\n**⬆️ Закачка**\n- `SET FS` с `Op`: `UploadStart` → `UploadChunk` → `UploadDone`, прогресс — `Upload.Progress`\n- имя файла не длиннее 31 символа (ограничение прошивки)\n\n🔄 «Обновить» отправляет `GET FS`.\n\n⚙️ Если у прошивки другие имена, поменяйте префикс (ID), поле и аргумент в настройках виджета.",
28
- '🗂️ The widget works with a single `FS` object of the device\'s `CFG` group — no manual variable binding needed.\n\n**📊 Usage**\n- `Total`, `Used`, `Free`\n\n**📄 Files**\n- `List` — path and size; "Delete" sends `SET FS` with `Op: Delete` and `Name` (after confirmation)\n\n**⬆️ Upload**\n- `SET FS` with `Op`: `UploadStart` → `UploadChunk` → `UploadDone`, progress — `Upload.Progress`\n- file name up to 31 characters (firmware limit)\n\n🔄 "Refresh" sends `GET FS`.\n\n⚙️ If the firmware uses different names, change the prefix (ID), field and argument in the widget settings.',
29
- "🗂️ 该组件使用设备 `CFG` 分组下的单个 `FS` 对象——无需手动绑定变量。\n\n**📊 占用**\n- `Total`、`Used`、`Free`\n\n**📄 文件**\n- `List` —— 路径和大小;「删除」在确认后发送带 `Op: Delete` 和 `Name` 的 `SET FS`\n\n**⬆️ 上传**\n- `SET FS`,`Op`:`UploadStart` → `UploadChunk` → `UploadDone`,进度 —— `Upload.Progress`\n- 文件名不超过 31 个字符(固件限制)\n\n🔄 「刷新」发送 `GET FS`。\n\n⚙️ 如果固件使用其他名称,请在组件设置中修改前缀(ID)、字段和参数。",
26
+ "component.info.ColorPicker": [
27
+ "🎯 **Выбор цвета** — палитра для задания цвета, например подсветки или RGB-ленты.\n\n**🔗 Связь с устройством**\n- **Имя переменной** — массив `[R, G, B]` (0–255) на устройстве, текущий цвет показывается из него\n- **📤 Отправка** — по событию из настроек: **Заголовок** (обычно `SET`), **Аргумент** (обычно `ModCfg`; `NoSend` — не отправлять сразу, значение уйдёт вместе с другим компонентом) и **Переменные** — какие ещё значения положить в тот же пакет.\n- Значение отправляется при выборе цвета на палитре",
28
+ "🎯 **Color picker** — a palette for setting a color, e.g. backlight or an RGB strip.\n\n**🔗 Device link**\n- **Variable name** — an `[R, G, B]` array (0–255) on the device, the current color is shown from it\n- **📤 Sending** — by the event in settings: **Header** (usually `SET`), **Argument** (usually `ModCfg`; `NoSend` — don't send right away, the value goes out with another component) and **Variables** — which other values to put in the same packet.\n- The value is sent when a color is picked on the palette",
29
+ "🎯 **颜色选择器** —— 用于设置颜色的调色板,例如背光或 RGB 灯带。\n\n**🔗 与设备的关联**\n- **变量名** —— 设备上的 `[R, G, B]` 数组(0–255),当前颜色从中显示\n- **📤 发送** —— 按设置中的事件:**标头**(通常为 `SET`)、**参数**(通常为 `ModCfg`;`NoSend` —— 不立即发送,值随其他组件一起发出)和 **变量** —— 同一数据包中还要包含哪些值。\n- 在调色板上选色时发送值",
30
30
  ],
31
- "component.info.WidgetStackInfo": [
32
- "🧠 Виджет работает с одним объектом `StackInfo` из ответа устройства — привязка переменных вручную не нужна.\n\n**📊 Память**\n- `Heap[]` — `Total`, `Free`, `MinFree` по каждой куче (ESP: Internal и Default, STM: Heap)\n\n**🧵 Задачи**\n- `Tasks[]` — `Name`, `FreeMin` (минимальный запас стека, байт), `Prio`, `State`, `Core` (только ESP)\n- малый запас стека подсвечивается\n\n🔄 «Обновить» и автоопрос отправляют `GET StackInfo`.\n\n⚙️ Если у прошивки другие имена, поменяйте поле и аргумент в настройках виджета.",
33
- '🧠 The widget works with a single `StackInfo` object from the device response — no manual variable binding needed.\n\n**📊 Memory**\n- `Heap[]` — `Total`, `Free`, `MinFree` per heap (ESP: Internal and Default, STM: Heap)\n\n**🧵 Tasks**\n- `Tasks[]` — `Name`, `FreeMin` (minimum stack headroom, bytes), `Prio`, `State`, `Core` (ESP only)\n- low stack headroom is highlighted\n\n🔄 "Refresh" and auto-polling send `GET StackInfo`.\n\n⚙️ If the firmware uses different names, change the field and argument in the widget settings.',
34
- "🧠 该组件使用设备响应中的单个 `StackInfo` 对象——无需手动绑定变量。\n\n**📊 内存**\n- `Heap[]` —— 每个堆的 `Total`、`Free`、`MinFree`(ESP:Internal 和 Default,STM:Heap)\n\n**🧵 任务**\n- `Tasks[]` —— `Name`、`FreeMin`(最小栈余量,字节)、`Prio`、`State`、`Core`(仅 ESP)\n- 栈余量不足时高亮显示\n\n🔄 「刷新」和自动轮询发送 `GET StackInfo`。\n\n⚙️ 如果固件使用其他名称,请在组件设置中修改字段和参数。",
31
+ "component.info.Divider": [
32
+ "🎯 **Разделитель** — визуально разделяет группы компонентов.\n\n**⚙️ Настройки**\n- Тип: **линия** или **пустое место** (отступ без линии)\n- Привязка: линия сверху, по центру или снизу ячейки\n- Цвет и толщина линии\n\n💡 Чисто визуальный элемент — с устройством не связан.",
33
+ "🎯 **Divider** — visually separates groups of components.\n\n**⚙️ Settings**\n- Type: **line** or **empty space** (gap without a line)\n- Anchor: line at the top, center or bottom of the cell\n- Line color and thickness\n\n💡 Purely visual — not linked to the device.",
34
+ "🎯 **分隔线** —— 在视觉上分隔组件组。\n\n**⚙️ 设置**\n- 类型:**线条** 或 **空白**(无线条的间距)\n- 位置:线条位于单元格顶部、中间或底部\n- 线条颜色和粗细\n\n💡 纯视觉元素 —— 与设备无关。",
35
35
  ],
36
- "component.info.Tabs": [
37
- "📑 Ширина и позиция иконки задаются через служебные CSS-классы каждой вкладки, а не отдельными полями.\n\n**↔️ Ширина кнопок** (переключатель «Авто/Равные»)\n- `w-auto` — по содержимому\n- `flex-1` — поровну между всеми вкладками\n\n**🔤 Позиция иконки**\n- `flex-row` / `flex-col` / `flex-row-reverse` / `flex-col-reverse`\n\n⚠️ Максимум 10 вкладок.",
38
- '📑 Tab width and icon position are set via each tab\'s internal CSS classes, not separate fields.\n\n**↔️ Button width** ("Auto/Equal" toggle)\n- `w-auto` — sized to content\n- `flex-1` — equal share among all tabs\n\n**🔤 Icon position**\n- `flex-row` / `flex-col` / `flex-row-reverse` / `flex-col-reverse`\n\n⚠️ Maximum 10 tabs.',
39
- "📑 每个标签页的宽度和图标位置通过内部 CSS 类设置,而非独立字段。\n\n**↔️ 按钮宽度**(「自动/均等」切换)\n- `w-auto` —— 按内容自适应\n- `flex-1` —— 在所有标签页间均分\n\n**🔤 图标位置**\n- `flex-row` / `flex-col` / `flex-row-reverse` / `flex-col-reverse`\n\n⚠️ 最多 10 个标签页。",
36
+ "component.info.FileAttach": [
37
+ "🎯 **Выбор файла** — закачивает выбранный файл в файловую систему устройства.\n\n**📤 Закачка**\n- После выбора файл передаётся частями командой `SET FS` (`UploadStart` → `UploadChunk` → `UploadDone`)\n- Имя файла — не длиннее 31 символа (ограничение прошивки)\n- «Допустимые файлы» (`accept`) — фильтр в окне выбора, например `.bin, .pem`\n\n**🎨 Вид**\n- **Файл** — кнопка с именем выбранного файла\n- **Изображение** — квадратное превью; выбранная картинка сразу показывается локально\n\n💡 Для полноценной работы с файлами (список, удаление, прогресс) используйте виджет «Файловая система».",
38
+ '🎯 **File picker** — uploads the chosen file to the device file system.\n\n**📤 Upload**\n- After selection the file is sent in chunks with `SET FS` (`UploadStart` → `UploadChunk` → `UploadDone`)\n- File name — up to 31 characters (firmware limit)\n- "Allowed files" (`accept`) — filter in the file dialog, e.g. `.bin, .pem`\n\n**🎨 View**\n- **File** — a button with the selected file name\n- **Image** — a square preview; the chosen picture is shown locally right away\n\n💡 For full file management (list, delete, progress) use the "File system" widget.',
39
+ "🎯 **文件选择** —— 将所选文件上传到设备文件系统。\n\n**📤 上传**\n- 选择后文件通过 `SET FS` 分块发送(`UploadStart` → `UploadChunk` → `UploadDone`)\n- 文件名 —— 不超过 31 个字符(固件限制)\n- 「允许的文件」(`accept`)—— 文件对话框中的过滤器,例如 `.bin, .pem`\n\n**🎨 视图**\n- **文件** —— 显示所选文件名的按钮\n- **图片** —— 方形预览;所选图片立即在本地显示\n\n💡 如需完整的文件管理(列表、删除、进度),请使用「文件系统」组件。",
40
+ ],
41
+ "component.info.Graph": [
42
+ '🎯 **График** — линии значений, обновляющиеся в реальном времени.\n\n**🔗 Данные**\n- **Имя переменной** — массив `[{ "name": "Temp", "value": 23.4 }, ...]`: каждое `name` — отдельная линия\n- Каждое обновление добавляет точку справа, старые точки уходят влево\n\n**⚙️ Настройки**\n- Тестовые данные — показывают график без устройства, удобно при вёрстке\n\n💡 Для накопленных по периодам значений (энергия за час/сутки) лучше «Диаграмма по периодам».',
43
+ '🎯 **Graph** — value lines updated in real time.\n\n**🔗 Data**\n- **Variable name** — an array `[{ "name": "Temp", "value": 23.4 }, ...]`: each `name` is a separate line\n- Every update adds a point on the right, old points move left\n\n**⚙️ Settings**\n- Test data — shows the graph without a device, handy while laying out\n\n💡 For values accumulated per period (energy per hour/day) the "Period chart" fits better.',
44
+ '🎯 **图表** —— 实时更新的数值曲线。\n\n**🔗 数据**\n- **变量名** —— 数组 `[{ "name": "Temp", "value": 23.4 }, ...]`:每个 `name` 是一条独立曲线\n- 每次更新在右侧添加一个点,旧点向左移动\n\n**⚙️ 设置**\n- 测试数据 —— 无需设备即可显示图表,便于排版\n\n💡 按周期累计的值(每小时/每天的能耗)更适合用「周期图表」。',
45
+ ],
46
+ "component.info.Input": [
47
+ "🎯 **Поле ввода** — показывает и изменяет значение переменной устройства.\n\n**🔗 Связь с устройством**\n- **Имя переменной** — например `CFG.DevName`; поле показывает текущее значение с устройства\n- Значение отправляется при каждом изменении — для текста удобнее аргумент `NoSend` и отдельная кнопка «Сохранить»\n- **📤 Отправка** — по событию из настроек: **Заголовок** (обычно `SET`), **Аргумент** (обычно `ModCfg`; `NoSend` — не отправлять сразу, значение уйдёт вместе с другим компонентом) и **Переменные** — какие ещё значения положить в тот же пакет.\n\n**🔤 Тип**\n- `Aa` текст, `•••` пароль, `123` число, `¶` многострочный текст\n- `01` битовое поле — редактирует биты с N по M целого числа, остальные биты не трогает\n\n**🛡️ Проверки**\n- Максимальная длина, диапазон чисел, выражение валидации — неверный ввод подсвечивается и не отправляется\n- Только чтение — поле для отображения, с кнопкой копирования",
48
+ '🎯 **Input field** — shows and changes a device variable.\n\n**🔗 Device link**\n- **Variable name** — e.g. `CFG.DevName`; the field shows the current device value\n- The value is sent on every change — for text, the `NoSend` argument plus a separate "Save" button is more convenient\n- **📤 Sending** — by the event in settings: **Header** (usually `SET`), **Argument** (usually `ModCfg`; `NoSend` — don\'t send right away, the value goes out with another component) and **Variables** — which other values to put in the same packet.\n\n**🔤 Type**\n- `Aa` text, `•••` password, `123` number, `¶` multiline text\n- `01` bit field — edits bits N to M of an integer, other bits stay intact\n\n**🛡️ Validation**\n- Max length, number range, validation expression — invalid input is highlighted and not sent\n- Read only — a display field with a copy button',
49
+ "🎯 **输入框** —— 显示并修改设备变量。\n\n**🔗 与设备的关联**\n- **变量名** —— 例如 `CFG.DevName`;输入框显示设备上的当前值\n- 每次修改都会发送值 —— 对于文本,使用 `NoSend` 参数加单独的「保存」按钮更方便\n- **📤 发送** —— 按设置中的事件:**标头**(通常为 `SET`)、**参数**(通常为 `ModCfg`;`NoSend` —— 不立即发送,值随其他组件一起发出)和 **变量** —— 同一数据包中还要包含哪些值。\n\n**🔤 类型**\n- `Aa` 文本,`•••` 密码,`123` 数字,`¶` 多行文本\n- `01` 位字段 —— 编辑整数的第 N 到第 M 位,其余位保持不变\n\n**🛡️ 校验**\n- 最大长度、数值范围、校验表达式 —— 无效输入会高亮且不发送\n- 只读 —— 带复制按钮的显示字段",
50
+ ],
51
+ "component.info.Joystick": [
52
+ "🎯 **Джойстик** — управление по нескольким осям, например движением или поворотом камеры.\n\n**🔗 Связь с устройством**\n- **Имя переменной** — массив значений по осям, например `[x, y, z]`\n- Кнопки направлений меняют значения осей на шаг чувствительности, центральная — своя команда; значение отправляется при каждом нажатии\n- **📤 Отправка** — по событию из настроек: **Заголовок** (обычно `SET`), **Аргумент** (обычно `ModCfg`; `NoSend` — не отправлять сразу, значение уйдёт вместе с другим компонентом) и **Переменные** — какие ещё значения положить в тот же пакет.\n\n**⚙️ Оси**\n- Для каждой оси — имя и диапазон (минимум / максимум)\n- Иконка центральной кнопки\n\n💡 Только чтение — джойстик показывает положение с устройства, но не управляет.",
53
+ "🎯 **Joystick** — multi-axis control, e.g. motion or camera pan.\n\n**🔗 Device link**\n- **Variable name** — an array of axis values, e.g. `[x, y, z]`\n- Direction buttons change axis values by the sensitivity step, the center one has its own command; the value is sent on every press\n- **📤 Sending** — by the event in settings: **Header** (usually `SET`), **Argument** (usually `ModCfg`; `NoSend` — don't send right away, the value goes out with another component) and **Variables** — which other values to put in the same packet.\n\n**⚙️ Axes**\n- For each axis — name and range (minimum / maximum)\n- Center button icon\n\n💡 Read only — the joystick shows the device position but doesn't control it.",
54
+ "🎯 **摇杆** —— 多轴控制,例如运动或摄像头转动。\n\n**🔗 与设备的关联**\n- **变量名** —— 各轴数值的数组,例如 `[x, y, z]`\n- 方向按钮按灵敏度步长改变轴值,中心按钮有自己的命令;每次按下都会发送值\n- **📤 发送** —— 按设置中的事件:**标头**(通常为 `SET`)、**参数**(通常为 `ModCfg`;`NoSend` —— 不立即发送,值随其他组件一起发出)和 **变量** —— 同一数据包中还要包含哪些值。\n\n**⚙️ 轴**\n- 每个轴 —— 名称和范围(最小值 / 最大值)\n- 中心按钮图标\n\n💡 只读 —— 摇杆显示设备位置,但不进行控制。",
55
+ ],
56
+ "component.info.Map": [
57
+ "🎯 **Карта** — показывает местоположение устройства.\n\n**🔗 Данные**\n- **Имя переменной** — объект навигации:\n - `NavLat`, `NavLon` — широта и долгота\n - `NavAlt` — высота, `NavHeading` — курс\n - `NavSatUse` — число спутников в решении\n - `DevName`, `DevSN` — подпись маркера\n\n💡 Только отображение, ничего не отправляет.",
58
+ "🎯 **Map** — shows the device location.\n\n**🔗 Data**\n- **Variable name** — a navigation object:\n - `NavLat`, `NavLon` — latitude and longitude\n - `NavAlt` — altitude, `NavHeading` — heading\n - `NavSatUse` — satellites used in the fix\n - `DevName`, `DevSN` — marker label\n\n💡 Display only, sends nothing.",
59
+ "🎯 **地图** —— 显示设备位置。\n\n**🔗 数据**\n- **变量名** —— 导航对象:\n - `NavLat`、`NavLon` —— 纬度和经度\n - `NavAlt` —— 高度,`NavHeading` —— 航向\n - `NavSatUse` —— 参与定位的卫星数\n - `DevName`、`DevSN` —— 标记标签\n\n💡 仅显示,不发送任何数据。",
60
+ ],
61
+ "component.info.PeriodChart": [
62
+ "🎯 **Диаграмма по периодам** — столбики накопленных значений с переключателем периода (час / сутки / месяц / год).\n\n**🔗 Данные**\n- Каждый **уровень** — своя вкладка переключателя со своей **переменной**: массив чисел, один столбик на элемент\n- Подписи столбиков задаются списком, иначе — номера 1, 2, 3…\n- **Единицы** — показываются в подсказке, например `Вт·ч`\n\n💡 Открывается всегда на первом уровне. Сама диаграмма ничего не запрашивает — данные приходят с устройства.",
63
+ "🎯 **Period chart** — bars of accumulated values with a period switch (hour / day / month / year).\n\n**🔗 Data**\n- Each **level** is a switch tab with its own **variable**: an array of numbers, one bar per element\n- Bar labels are set as a list, otherwise numbers 1, 2, 3…\n- **Units** — shown in the tooltip, e.g. `Wh`\n\n💡 Always opens on the first level. The chart requests nothing itself — data comes from the device.",
64
+ "🎯 **周期图表** —— 累计值柱状图,带周期切换(小时 / 天 / 月 / 年)。\n\n**🔗 数据**\n- 每个 **级别** 是一个切换标签,有自己的 **变量**:数字数组,每个元素一根柱\n- 柱的标签以列表设置,否则为编号 1、2、3…\n- **单位** —— 显示在提示中,例如 `Wh`\n\n💡 总是在第一个级别打开。图表本身不请求数据 —— 数据来自设备。",
65
+ ],
66
+ "component.info.ProgressBar": [
67
+ '🎯 **Шкала прогресса** — одна или несколько полос заполнения: уровень, заряд, загрузка.\n\n**🔗 Данные**\n- **Имя переменной** — массив `[{ "Value": 75 }, ...]`, по одному элементу на полосу\n- Полосы настраиваются в том же порядке: подпись и цвет каждой\n- Диапазон — минимум / максимум (по умолчанию 0–100)\n\n⚠️ Порядок полос в настройках и элементов в данных должен совпадать — связь по номеру, а не по имени.',
68
+ '🎯 **Progress bar** — one or more fill bars: level, charge, load.\n\n**🔗 Data**\n- **Variable name** — an array `[{ "Value": 75 }, ...]`, one element per bar\n- Bars are configured in the same order: label and color of each\n- Range — minimum / maximum (0–100 by default)\n\n⚠️ The order of bars in settings and elements in data must match — they are linked by position, not by name.',
69
+ '🎯 **进度条** —— 一个或多个填充条:液位、电量、负载。\n\n**🔗 数据**\n- **变量名** —— 数组 `[{ "Value": 75 }, ...]`,每根条一个元素\n- 条按相同顺序配置:每根的标签和颜色\n- 范围 —— 最小值 / 最大值(默认 0–100)\n\n⚠️ 设置中条的顺序必须与数据中元素的顺序一致 —— 按位置而非名称关联。',
70
+ ],
71
+ "component.info.Select": [
72
+ "🎯 **Список выбора** — выбор одного значения из вариантов.\n\n**🔗 Связь с устройством**\n- **Имя переменной** — выбранное значение\n- Варианты задаются в настройках или приходят с устройства переменной `<имя>-S` (массив `{ id, name, value }`) — так строится, например, список найденных Wi-Fi сетей\n- **📤 Отправка** — по событию из настроек: **Заголовок** (обычно `SET`), **Аргумент** (обычно `ModCfg`; `NoSend` — не отправлять сразу, значение уйдёт вместе с другим компонентом) и **Переменные** — какие ещё значения положить в тот же пакет.\n\n**🔤 Вид**\n- **Список** — выпадающий, **Кнопки** — все варианты в ряд, **С вводом** — список с ручным вводом\n- Битовое поле — значение записывается в биты с N по M целого числа\n\n💡 Для 2–4 коротких вариантов удобнее «Кнопки».",
73
+ '🎯 **Select** — picks one value from options.\n\n**🔗 Device link**\n- **Variable name** — the selected value\n- Options are set in settings or come from the device in the `<name>-S` variable (an array of `{ id, name, value }`) — this is how e.g. the found Wi-Fi networks list is built\n- **📤 Sending** — by the event in settings: **Header** (usually `SET`), **Argument** (usually `ModCfg`; `NoSend` — don\'t send right away, the value goes out with another component) and **Variables** — which other values to put in the same packet.\n\n**🔤 View**\n- **List** — dropdown, **Buttons** — all options in a row, **With input** — list with manual entry\n- Bit field — the value is written into bits N to M of an integer\n\n💡 For 2–4 short options "Buttons" is handier.',
74
+ "🎯 **下拉选择** —— 从选项中选择一个值。\n\n**🔗 与设备的关联**\n- **变量名** —— 选中的值\n- 选项在设置中指定,或由设备通过 `<名称>-S` 变量提供(`{ id, name, value }` 数组)—— 例如找到的 Wi-Fi 网络列表就是这样生成的\n- **📤 发送** —— 按设置中的事件:**标头**(通常为 `SET`)、**参数**(通常为 `ModCfg`;`NoSend` —— 不立即发送,值随其他组件一起发出)和 **变量** —— 同一数据包中还要包含哪些值。\n\n**🔤 视图**\n- **列表** —— 下拉,**按钮** —— 所有选项排成一行,**可输入** —— 可手动输入的列表\n- 位字段 —— 值写入整数的第 N 到第 M 位\n\n💡 2–4 个短选项用「按钮」更方便。",
75
+ ],
76
+ "component.info.Slider": [
77
+ "🎯 **Слайдер** — плавный выбор числа или диапазона.\n\n**🔗 Связь с устройством**\n- **Имя переменной** — число, а для диапазона — массив `[от, до]`\n- Значение отправляется при перемещении ручки и нажатии − / +\n- **📤 Отправка** — по событию из настроек: **Заголовок** (обычно `SET`), **Аргумент** (обычно `ModCfg`; `NoSend` — не отправлять сразу, значение уйдёт вместе с другим компонентом) и **Переменные** — какие ещё значения положить в тот же пакет.\n\n**⚙️ Настройки**\n- Тип: одно значение или диапазон\n- Минимум, максимум, шаг",
78
+ "🎯 **Slider** — smooth choice of a number or range.\n\n**🔗 Device link**\n- **Variable name** — a number, or an `[from, to]` array for a range\n- The value is sent while moving the handle and on − / + presses\n- **📤 Sending** — by the event in settings: **Header** (usually `SET`), **Argument** (usually `ModCfg`; `NoSend` — don't send right away, the value goes out with another component) and **Variables** — which other values to put in the same packet.\n\n**⚙️ Settings**\n- Type: single value or range\n- Minimum, maximum, step",
79
+ "🎯 **滑块** —— 平滑选择数值或范围。\n\n**🔗 与设备的关联**\n- **变量名** —— 数字,范围模式下为 `[起, 止]` 数组\n- 拖动滑块以及按 − / + 时发送值\n- **📤 发送** —— 按设置中的事件:**标头**(通常为 `SET`)、**参数**(通常为 `ModCfg`;`NoSend` —— 不立即发送,值随其他组件一起发出)和 **变量** —— 同一数据包中还要包含哪些值。\n\n**⚙️ 设置**\n- 类型:单值或范围\n- 最小值、最大值、步长",
40
80
  ],
41
81
  "component.info.Switch": [
42
- "🔘 Один и тот же компонент под тремя разными задачами — тип определяет, что означает `options`.\n\n**↔️/↕️ horizontal / vertical** — обычный слайдер 0/1, `options[0]` задаёт мастер-цвет.\n\n**☑️ checkbox** — простой булев тумблер, `options` можно не задавать.\n\n**🔢 bitMode** — `value` становится битовой маской, каждый элемент `options` управляет одним битом через свою `value` (номер бита 0-31); недоступен вместе с `checkbox`.",
43
- "🔘 One component, three different jobs — `type` decides what `options` means.\n\n**↔️/↕️ horizontal / vertical** — a plain 0/1 slider, `options[0]` sets the master color.\n\n**☑️ checkbox** — a simple boolean toggle, `options` may be omitted.\n\n**🔢 bitMode** — `value` becomes a bitmask, each `options` entry controls one bit via its `value` (bit number 0-31); not available together with `checkbox`.",
44
- "🔘 同一个组件承担三种不同职责——`type` 决定 `options` 的含义。\n\n**↔️/↕️ horizontal / vertical** —— 普通的 0/1 滑块,`options[0]` 设置主色。\n\n**☑️ checkbox** —— 简单的布尔开关,可省略 `options`。\n\n**🔢 bitMode** —— `value` 变为位掩码,每个 `options` 项通过自己的 `value`(位号 0-31)控制一个比特位;不能与 `checkbox` 同时使用。",
82
+ "🎯 **Переключатель** — включение и выключение: реле, режимы, флаги.\n\n**🔗 Связь с устройством**\n- **Имя переменной** — `0` или `1`\n- Значение отправляется сразу при переключении\n- **📤 Отправка** — по событию из настроек: **Заголовок** (обычно `SET`), **Аргумент** (обычно `ModCfg`; `NoSend` — не отправлять сразу, значение уйдёт вместе с другим компонентом) и **Переменные** — какие ещё значения положить в тот же пакет.\n\n**🔤 Вид**\n- Горизонтальный или вертикальный тумблер с подписями слева и справа\n- Флажок — компактный вариант\n\n**🔢 Битовый режим**\n- Переменная — битовая маска; каждый переключатель управляет своим битом (номер 0–31)",
83
+ "🎯 **Switch** — on/off: relays, modes, flags.\n\n**🔗 Device link**\n- **Variable name** — `0` or `1`\n- The value is sent immediately on toggle\n- **📤 Sending** — by the event in settings: **Header** (usually `SET`), **Argument** (usually `ModCfg`; `NoSend` — don't send right away, the value goes out with another component) and **Variables** — which other values to put in the same packet.\n\n**🔤 View**\n- Horizontal or vertical toggle with captions on both sides\n- Checkbox — a compact option\n\n**🔢 Bit mode**\n- The variable is a bit mask; each switch controls its own bit (number 0–31)",
84
+ "🎯 **开关** —— 开/关:继电器、模式、标志。\n\n**🔗 与设备的关联**\n- **变量名** —— `0` 或 `1`\n- 切换时立即发送值\n- **📤 发送** —— 按设置中的事件:**标头**(通常为 `SET`)、**参数**(通常为 `ModCfg`;`NoSend` —— 不立即发送,值随其他组件一起发出)和 **变量** —— 同一数据包中还要包含哪些值。\n\n**🔤 视图**\n- 水平或垂直拨动开关,两侧带标签\n- 复选框 —— 紧凑形式\n\n**🔢 位模式**\n- 变量为位掩码;每个开关控制自己的位(编号 0–31)",
45
85
  ],
46
86
  "component.info.Table": [
47
- "📊 Тип `type` определяет поведение буфера строк.\n\n**📋 table** — статическая таблица, `body` заменяется целиком при каждом обновлении.\n\n**📜 logger** — `dataBuffer.stashData` копит входящие строки в буфер (`dataBuffer.bufferSize` — сколько хранить), новые строки добавляются сверху, старые вытесняются.\n\n⚙️ `dataBuffer.visibleRows` ограничивает видимую высоту прокруткой, не количество данных.",
48
- "📊 The `type` field determines row-buffer behavior.\n\n**📋 table** — a static table, `body` is fully replaced on every update.\n\n**📜 logger** — `dataBuffer.stashData` accumulates incoming rows in a buffer (`dataBuffer.bufferSize` — how many to keep), new rows are added on top and old ones are evicted.\n\n⚙️ `dataBuffer.visibleRows` limits the visible scroll height, not the amount of data.",
49
- "📊 `type` 字段决定行缓冲区的行为。\n\n**📋 table** —— 静态表格,每次更新时整体替换 `body`。\n\n**📜 logger** —— `dataBuffer.stashData` 将传入的行累积到缓冲区(`dataBuffer.bufferSize` —— 保留多少条),新行添加在顶部,旧行被淘汰。\n\n⚙️ `dataBuffer.visibleRows` 限制可视滚动高度,而非数据量。",
87
+ "🎯 **Таблица** — список строк с данными устройства: файлы, журнал, параметры.\n\n**🔗 Данные**\n- **Имя переменной** — массив объектов, одна строка на объект\n- **Колонки** («Настроить») — для каждой ключ поля, ширина, выравнивание и тип ячейки\n\n**🧱 Типы ячеек**\n- Текст — с сортировкой, копированием, обрезкой «…», подсказкой и окном с полным текстом\n- Список, кнопка (отправляет своё событие), полоса прогресса, иконка\n\n**📜 Накопление данных**\n- Включено — новые строки дописываются в буфер (размер буфера), таблица работает как журнал\n- Добавление строк вверху или внизу, кнопка очистки, видимое число строк",
88
+ '🎯 **Table** — a list of device data rows: files, log, parameters.\n\n**🔗 Data**\n- **Variable name** — an array of objects, one row per object\n- **Columns** ("Configure") — for each: field key, width, alignment and cell type\n\n**🧱 Cell types**\n- Text — with sorting, copy, "…" truncation, tooltip and a full-text window\n- Select, button (sends its own event), progress bar, icon\n\n**📜 Data accumulation**\n- On — new rows are appended to a buffer (buffer size), the table works as a log\n- Adding rows at the top or bottom, clear button, visible rows count',
89
+ "🎯 **表格** —— 设备数据行列表:文件、日志、参数。\n\n**🔗 数据**\n- **变量名** —— 对象数组,每个对象一行\n- **列**(「配置」)—— 每列:字段键、宽度、对齐方式和单元格类型\n\n**🧱 单元格类型**\n- 文本 —— 支持排序、复制、「…」截断、提示和全文窗口\n- 下拉、按钮(发送自己的事件)、进度条、图标\n\n**📜 数据累积**\n- 开启时 —— 新行追加到缓冲区(缓冲区大小),表格作为日志使用\n- 在顶部或底部添加行、清除按钮、可见行数",
50
90
  ],
51
- "component.info.ProgressBar": [
52
- "📶 `items` и `value` — два независимых массива, связанные позиционно (по индексу, не по имени).\n\n- `items[i]` — статическое оформление полосы: `name` (подпись) и `class` (цвет через `bg-*`)\n- `value[i].Value` — текущее числовое значение этой же полосы (0-100 или в границах `number.minNum`/`maxNum`)\n\n⚠️ Порядок элементов в обоих массивах должен совпадать.",
53
- "📶 `items` and `value` are two independent arrays linked positionally (by index, not by name).\n\n- `items[i]` — the bar's static appearance: `name` (label) and `class` (color via `bg-*`)\n- `value[i].Value` — the current numeric value of that same bar (0-100 or within `number.minNum`/`maxNum`)\n\n⚠️ The order of elements in both arrays must match.",
54
- "📶 `items` 和 `value` 是两个通过位置(索引,而非名称)关联的独立数组。\n\n- `items[i]` —— 该进度条的静态外观:`name`(标签)和 `class`(通过 `bg-*` 设置颜色)\n- `value[i].Value` —— 同一进度条的当前数值(0-100 或在 `number.minNum`/`maxNum` 范围内)\n\n⚠️ 两个数组中元素的顺序必须一致。",
91
+ "component.info.Tabs": [
92
+ "🎯 **Вкладки** — несколько страниц компонентов в одном месте.\n\n**🧩 Как пользоваться**\n- Добавьте вкладки, у каждой — название и иконка\n- У каждой вкладки своя сетка: компоненты размещаются на активной вкладке конструктора\n\n**🎨 Оформление**\n- Ширина кнопок: по содержимому или поровну\n- Положение иконки относительно названия — слева, сверху, справа, снизу\n\n⚠️ Не больше 10 вкладок. С устройством не связаны.",
93
+ "🎯 **Tabs** — several pages of components in one place.\n\n**🧩 How to use**\n- Add tabs, each with a name and an icon\n- Every tab has its own grid: components are placed on the tab active in the constructor\n\n**🎨 Appearance**\n- Button width: by content or equal\n- Icon position relative to the name — left, top, right, bottom\n\n⚠️ Up to 10 tabs. Not linked to the device.",
94
+ "🎯 **标签页** —— 在同一位置放置多页组件。\n\n**🧩 使用方法**\n- 添加标签页,每页有名称和图标\n- 每个标签页有自己的网格:组件放在构造器中当前激活的标签页上\n\n**🎨 外观**\n- 按钮宽度:按内容或等宽\n- 图标相对名称的位置 —— 左、上、右、下\n\n⚠️ 最多 10 个标签页。与设备无关。",
55
95
  ],
56
- "component.info.FileAttach": [
57
- "📎 `type` меняет не только внешний вид, но и формат `currentImage`.\n\n**📄 file** — обычная кнопка выбора файла, показывает только имя.\n\n**🖼️ image** — превью-кнопка; `currentImage` ожидается как data-URI (`data:image/...`) или голая base64-строка (тогда PNG-префикс подставляется автоматически). После выбора нового файла показывается его локальное превью — до того, как `onChange` обработает загрузку и обновит `currentImage`.",
58
- "📎 `type` changes not only the appearance but also the `currentImage` format.\n\n**📄 file** — a plain file-picker button, shows only the file name.\n\n**🖼️ image** — a preview button; `currentImage` is expected to be a data URI (`data:image/...`) or a bare base64 string (the PNG prefix is then added automatically). After picking a new file, its local preview is shown right away — before `onChange` finishes the upload and updates `currentImage`.",
59
- "📎 `type` 不仅改变外观,还改变 `currentImage` 的格式。\n\n**📄 file** —— 普通文件选择按钮,仅显示文件名。\n\n**🖼️ image** —— 预览按钮;`currentImage` 应为 data URI(`data:image/...`)或纯 base64 字符串(此时会自动添加 PNG 前缀)。选择新文件后会立即显示本地预览——早于 `onChange` 完成上传并更新 `currentImage` 之前。",
96
+ "component.info.TextField": [
97
+ "🎯 **Текст** — надпись, заголовок или пояснение.\n\n**⚙️ Настройки**\n- Размер `S`–`XXL`\n- Начертание: жирный, курсив, подчёркнутый, фон\n- Выравнивание и цвет текста\n\n💡 Статичный текст — значения устройства не показывает. Для значения используйте поле ввода «Только чтение» или виджет.",
98
+ "🎯 **Text** — a caption, heading or note.\n\n**⚙️ Settings**\n- Size `S`–`XXL`\n- Style: bold, italic, underline, background\n- Alignment and text color\n\n💡 Static text — doesn't show device values. For a value use a read-only input or a widget.",
99
+ "🎯 **文本** —— 标签、标题或说明。\n\n**⚙️ 设置**\n- 大小 `S`–`XXL`\n- 样式:粗体、斜体、下划线、背景\n- 对齐方式和文本颜色\n\n💡 静态文本 —— 不显示设备值。如需显示值,请使用只读输入框或组件。",
100
+ ],
101
+ "component.info.VideoViewer": [
102
+ "🎯 **Видео** — показ видеопотока.\n\n**🎥 Источник**\n- **Камера** — камера этого компьютера или телефона, с выбором камеры\n- **С устройства** — кадры, которые устройство передаёт через облако по ключу потока\n\n💡 Видео не сохраняется — это просмотр в реальном времени.",
103
+ "🎯 **Video** — shows a video stream.\n\n**🎥 Source**\n- **Camera** — the camera of this computer or phone, with camera selection\n- **From device** — frames the device sends through the cloud by stream key\n\n💡 Video isn't recorded — it's real-time viewing.",
104
+ "🎯 **视频** —— 显示视频流。\n\n**🎥 来源**\n- **摄像头** —— 本电脑或手机的摄像头,可选择摄像头\n- **来自设备** —— 设备通过云端按流密钥发送的画面\n\n💡 视频不会录制 —— 仅实时观看。",
60
105
  ],
61
106
  "component.info.Widget": [
62
- "🔢 Диапазон значения задаётся `settings.number.minNum`/`maxNum` (по умолчанию 0/0). Иконки в `icons.array` распределяются по этому диапазону равномерно — при `icons.cycling: true` перебираются автоматически по таймеру вместо ручного выбора.",
63
- "🔢 The value range is set by `settings.number.minNum`/`maxNum` (defaults to 0/0). Icons in `icons.array` are spread evenly across this range — with `icons.cycling: true` they cycle automatically on a timer instead of manual selection.",
64
- "🔢 数值范围由 `settings.number.minNum`/`maxNum` 设置(默认 0/0)。`icons.array` 中的图标在此范围内均匀分布——当 `icons.cycling: true` 时,图标按计时器自动循环,而非手动选择。",
107
+ "🎯 **Индикатор** — карточка одного значения с крупной цифрой, иконкой и управлением.\n\n**🔗 Связь с устройством**\n- **Имя переменной** — число; карточка показывает его крупно, с единицами измерения\n- Изменение отправляется сразу\n- **📤 Отправка** — по событию из настроек: **Заголовок** (обычно `SET`), **Аргумент** (обычно `ModCfg`; `NoSend` — не отправлять сразу, значение уйдёт вместе с другим компонентом) и **Переменные** — какие ещё значения положить в тот же пакет.\n\n**🎛️ Управление**\n- **Поле** с кнопками − / +, **слайдер** или **переключатель** с подписями\n- Минимум, максимум и шаг\n\n**🖼️ Иконки**\n- Набор иконок распределяется по диапазону значения\n- Режим анимации — иконки сменяются тем быстрее, чем больше значение (например вентилятор)",
108
+ "🎯 **Indicator** — a single-value card with a big number, icon and control.\n\n**🔗 Device link**\n- **Variable name** — a number; the card shows it large, with units\n- Changes are sent immediately\n- **📤 Sending** — by the event in settings: **Header** (usually `SET`), **Argument** (usually `ModCfg`; `NoSend` — don't send right away, the value goes out with another component) and **Variables** — which other values to put in the same packet.\n\n**🎛️ Control**\n- **Field** with − / + buttons, **slider** or **switch** with captions\n- Minimum, maximum and step\n\n**🖼️ Icons**\n- The icon set is spread across the value range\n- Animation mode — icons cycle faster as the value grows (e.g. a fan)",
109
+ "🎯 **指示器** —— 单值卡片,带大号数字、图标和控件。\n\n**🔗 与设备的关联**\n- **变量名** —— 数字;卡片以大字体及单位显示\n- 修改立即发送\n- **📤 发送** —— 按设置中的事件:**标头**(通常为 `SET`)、**参数**(通常为 `ModCfg`;`NoSend` —— 不立即发送,值随其他组件一起发出)和 **变量** —— 同一数据包中还要包含哪些值。\n\n**🎛️ 控件**\n- 带 − / + 按钮的**输入框**、**滑块**或带标签的**开关**\n- 最小值、最大值和步长\n\n**🖼️ 图标**\n- 图标集按数值范围分布\n- 动画模式 —— 数值越大图标切换越快(例如风扇)",
110
+ ],
111
+ "component.info.WidgetWiFi": [
112
+ "🎯 **Виджет Wi-Fi** — полная настройка сети устройства: режим, подключение к роутеру, собственная точка доступа.\n\n**🔗 Связь с устройством** — фиксированные поля группы `CFG`, привязывать переменные не нужно\n- Режим `WiFiMode`: выключен, клиент, точка доступа, оба\n- **Клиент (STA)**: `StaSSID`, `StaPSK`, статический IP (`StaIP`, `StaMS`, `StaGW`)\n- **Точка доступа (AP)**: `ApSSID`, `ApPSK`, `ApIP`, `ApMS`, `ApGW`\n\n**📤 Отправка**\n- Поля редактируются локально, «Сохранить» отправляет всё одним пакетом\n- 🔍 Поиск сетей — `GET APsList`, найденные сети появляются в списке SSID\n- При включении только точки доступа виджет предупреждает: устройство может пропасть из локальной сети\n\n⚙️ Другое имя группы вместо `CFG` — поменяйте префикс (ID) и имена полей в настройках виджета.",
113
+ '🎯 **Wi-Fi widget** — full device network setup: mode, router connection, own access point.\n\n**🔗 Device link** — fixed fields of the `CFG` group, no variable binding needed\n- Mode `WiFiMode`: off, client, access point, both\n- **Client (STA)**: `StaSSID`, `StaPSK`, static IP (`StaIP`, `StaMS`, `StaGW`)\n- **Access point (AP)**: `ApSSID`, `ApPSK`, `ApIP`, `ApMS`, `ApGW`\n\n**📤 Sending**\n- Fields are edited locally, "Save" sends everything in one packet\n- 🔍 Network scan — `GET APsList`, found networks appear in the SSID list\n- When switching to access point only, the widget warns that the device may disappear from the local network\n\n⚙️ A group name other than `CFG` — change the prefix (ID) and field names in widget settings.',
114
+ "🎯 **Wi-Fi 组件** —— 完整的设备网络设置:模式、连接路由器、自建热点。\n\n**🔗 与设备的关联** —— `CFG` 分组的固定字段,无需绑定变量\n- 模式 `WiFiMode`:关闭、客户端、热点、两者\n- **客户端(STA)**:`StaSSID`、`StaPSK`、静态 IP(`StaIP`、`StaMS`、`StaGW`)\n- **热点(AP)**:`ApSSID`、`ApPSK`、`ApIP`、`ApMS`、`ApGW`\n\n**📤 发送**\n- 字段在本地编辑,「保存」一次性发送全部\n- 🔍 搜索网络 —— `GET APsList`,找到的网络出现在 SSID 列表中\n- 仅切换为热点时,组件会提示设备可能从局域网中消失\n\n⚙️ 若分组名不是 `CFG` —— 在组件设置中修改前缀(ID)和字段名。",
115
+ ],
116
+ "component.info.WidgetDeviceInfo": [
117
+ "🎯 **Виджет «Об устройстве»** — паспорт устройства и его основные настройки.\n\n**🔗 Связь с устройством** — фиксированные поля группы `CFG`\n- 🔒 Только чтение: серийный номер `DevSN`, `DevID`, версия ПО `DevFW`, счётчик запусков `RunCnt`, адреса `StaIP` и `ApIP`\n- ✏️ Редактируемые: имя `DevName`, имя хоста `HostName`, логин и пароль веб-интерфейса `WebUser` / `WebPsw`\n\n**📤 Отправка**\n- «Сохранить» отправляет изменённые поля одним пакетом\n- 🔌 «Перезагрузить» — команда `SET Restart`\n\n⚙️ Другое имя группы — поменяйте префикс (ID) в настройках виджета.",
118
+ '🎯 **Device info widget** — device passport and its main settings.\n\n**🔗 Device link** — fixed fields of the `CFG` group\n- 🔒 Read only: serial number `DevSN`, `DevID`, firmware version `DevFW`, run counter `RunCnt`, addresses `StaIP` and `ApIP`\n- ✏️ Editable: name `DevName`, host name `HostName`, web interface login and password `WebUser` / `WebPsw`\n\n**📤 Sending**\n- "Save" sends the changed fields in one packet\n- 🔌 "Restart" — the `SET Restart` command\n\n⚙️ A different group name — change the prefix (ID) in widget settings.',
119
+ "🎯 **设备信息组件** —— 设备档案及其主要设置。\n\n**🔗 与设备的关联** —— `CFG` 分组的固定字段\n- 🔒 只读:序列号 `DevSN`、`DevID`、固件版本 `DevFW`、启动次数 `RunCnt`、地址 `StaIP` 和 `ApIP`\n- ✏️ 可编辑:名称 `DevName`、主机名 `HostName`、网页界面登录名和密码 `WebUser` / `WebPsw`\n\n**📤 发送**\n- 「保存」一次性发送已修改的字段\n- 🔌 「重启」—— `SET Restart` 命令\n\n⚙️ 分组名不同 —— 在组件设置中修改前缀(ID)。",
120
+ ],
121
+ "component.info.WidgetFileSystem": [
122
+ "🎯 **Виджет «Файловая система»** — память устройства, список файлов, удаление и закачка.\n\n**🔗 Связь с устройством** — один объект `CFG.FS`, привязывать переменные не нужно\n- 📊 Занятость: `Total`, `Used`, `Free` — полоса и процент (оранжевый от 75%, красный от 90%)\n- 📄 Файлы: `List` — путь и размер; «Удалить» — `SET FS` с `Op: Delete` (после подтверждения)\n- ⬆️ Закачка: `SET FS` с `Op` `UploadStart` → `UploadChunk` → `UploadDone`, прогресс — `Upload.Progress`. Закачка с другого клиента тоже видна\n\n**⚠️ Ограничения**\n- Имя файла — не длиннее 31 символа\n- «Допустимые файлы» — фильтр в окне выбора\n\n🔄 «Обновить» — `GET FS`.",
123
+ '🎯 **File system widget** — device storage, file list, deletion and upload.\n\n**🔗 Device link** — a single `CFG.FS` object, no variable binding needed\n- 📊 Usage: `Total`, `Used`, `Free` — bar and percent (orange from 75%, red from 90%)\n- 📄 Files: `List` — path and size; "Delete" — `SET FS` with `Op: Delete` (after confirmation)\n- ⬆️ Upload: `SET FS` with `Op` `UploadStart` → `UploadChunk` → `UploadDone`, progress — `Upload.Progress`. An upload from another client is visible too\n\n**⚠️ Limits**\n- File name — up to 31 characters\n- "Allowed files" — filter in the file dialog\n\n🔄 "Refresh" — `GET FS`.',
124
+ "🎯 **文件系统组件** —— 设备存储、文件列表、删除和上传。\n\n**🔗 与设备的关联** —— 单个 `CFG.FS` 对象,无需绑定变量\n- 📊 占用:`Total`、`Used`、`Free` —— 进度条和百分比(75% 起橙色,90% 起红色)\n- 📄 文件:`List` —— 路径和大小;「删除」—— 带 `Op: Delete` 的 `SET FS`(确认后)\n- ⬆️ 上传:带 `Op` `UploadStart` → `UploadChunk` → `UploadDone` 的 `SET FS`,进度 —— `Upload.Progress`。其他客户端的上传也可见\n\n**⚠️ 限制**\n- 文件名 —— 不超过 31 个字符\n- 「允许的文件」—— 文件对话框中的过滤器\n\n🔄 「刷新」—— `GET FS`。",
125
+ ],
126
+ "component.info.WidgetStackInfo": [
127
+ "🎯 **Виджет «Память и задачи»** — отладка: свободная память и запас стека задач прошивки.\n\n**🔗 Связь с устройством** — один объект `StackInfo` из ответа на `GET StackInfo`\n- 📊 **Кучи** `Heap[]`: занято / всего, свободно и минимум свободного с момента старта; светлая часть полосы — пик занятости\n- 🧵 **Задачи** `Tasks[]`: имя, состояние, приоритет, ядро (ESP32), минимальный запас стека в байтах\n\n**⚙️ Настройки**\n- Порог запаса стека — меньше порога красным, меньше удвоенного — оранжевым\n- Автоопрос: выкл / 1 / 5 / 10 с; свёрнутый виджет не опрашивает\n\n💡 Задачи всегда отсортированы по запасу стека — самые рискованные сверху.",
128
+ "🎯 **Memory and tasks widget** — debugging: free memory and firmware task stack headroom.\n\n**🔗 Device link** — a single `StackInfo` object from the `GET StackInfo` response\n- 📊 **Heaps** `Heap[]`: used / total, free and minimum free since boot; the light part of the bar is the usage peak\n- 🧵 **Tasks** `Tasks[]`: name, state, priority, core (ESP32), minimum stack headroom in bytes\n\n**⚙️ Settings**\n- Stack headroom threshold — below it red, below double — orange\n- Auto-polling: off / 1 / 5 / 10 s; a collapsed widget doesn't poll\n\n💡 Tasks are always sorted by stack headroom — the riskiest on top.",
129
+ "🎯 **内存与任务组件** —— 调试用:空闲内存和固件任务的栈余量。\n\n**🔗 与设备的关联** —— 来自 `GET StackInfo` 响应的单个 `StackInfo` 对象\n- 📊 **堆** `Heap[]`:已用 / 总量、空闲以及启动以来的最小空闲;进度条浅色部分为占用峰值\n- 🧵 **任务** `Tasks[]`:名称、状态、优先级、核心(ESP32)、最小栈余量(字节)\n\n**⚙️ 设置**\n- 栈余量阈值 —— 低于阈值显示红色,低于两倍显示橙色\n- 自动轮询:关闭 / 1 / 5 / 10 秒;折叠的组件不轮询\n\n💡 任务始终按栈余量排序 —— 风险最高的在最上面。",
65
130
  ],
66
131
  /* Опции редактора свойств */
67
132
  "constructor.props.action.update": ["Обновить", "Update", "更新"],
@@ -78,8 +143,8 @@ const translations = {
78
143
  "constructor.props.type.text": ["Текст", "Text", "文本"],
79
144
  "constructor.props.type.password": ["Пароль", "Password", "密码"],
80
145
  "constructor.props.type.number": ["Число", "Number", "数字"],
81
- "constructor.props.type.textarea": ["Область ", "Textarea", "文本域"],
82
- "constructor.props.type.bitMode": ["Битовый ", "Bitwise", "位模式"],
146
+ "constructor.props.type.textarea": ["Многострочный текст", "Multiline text", "多行文本"],
147
+ "constructor.props.type.bitMode": ["Битовое поле", "Bit field", "位字段"],
83
148
  "constructor.props.type.single": ["Простой", "Simple", "简单"],
84
149
  "constructor.props.type.range": ["Диапазон", "Range", "范围"],
85
150
  "constructor.props.type.small": ["XS", "XS", "XS"],
@@ -89,7 +154,7 @@ const translations = {
89
154
  "constructor.props.type.massive": ["XL", "XL", "XL"],
90
155
  "constructor.props.type.select": ["Список", "List", "列表"],
91
156
  "constructor.props.type.buttons": ["Кнопки", "Buttons", "按钮组"],
92
- "constructor.props.type.selectput": ["Список+ввод", "List+input", "列表+输入"],
157
+ "constructor.props.type.selectput": ["С вводом", "With input", "可输入"],
93
158
  "constructor.props.valuetype.text": ["Строка", "String", "字符串"],
94
159
  "constructor.props.valuetype.number": ["Число", "Number", "数字"],
95
160
  "constructor.props.type.file": ["Файл", "File", "文件"],
@@ -224,6 +289,10 @@ const translations = {
224
289
  "constructor.props.placeholder": ["Заполнитель", "Placeholder", "占位符"],
225
290
  "constructor.props.info": ["Подсказка", "Hint", "提示"],
226
291
  "constructor.props.info.side": ["Положение подсказки", "Hint position", "提示位置"],
292
+ "constructor.props.iconpos.left": ["Иконка слева", "Icon on the left", "图标在左"],
293
+ "constructor.props.iconpos.top": ["Иконка сверху", "Icon on top", "图标在上"],
294
+ "constructor.props.iconpos.right": ["Иконка справа", "Icon on the right", "图标在右"],
295
+ "constructor.props.iconpos.bottom": ["Иконка снизу", "Icon at the bottom", "图标在下"],
227
296
  "constructor.props.info.top": ["Вверху", "Top", "上方"],
228
297
  "constructor.props.info.bottom": ["Внизу", "Bottom", "下方"],
229
298
  "constructor.props.info.left": ["Слева", "Left", "左侧"],
package/dist/options.d.ts CHANGED
@@ -21,6 +21,7 @@ export declare const optionsStore: import("svelte/store").Readable<{
21
21
  id: string;
22
22
  value: string;
23
23
  name: string;
24
+ icon: string;
24
25
  }[];
25
26
  SELECT_TYPE_OPTIONS: {
26
27
  id: string;
@@ -40,16 +41,19 @@ export declare const optionsStore: import("svelte/store").Readable<{
40
41
  id: string;
41
42
  value: string;
42
43
  name: string;
44
+ icon: string;
43
45
  }[];
44
46
  TEXTFIELD_SIZE_OPTIONS: {
45
47
  id: string;
46
48
  value: string;
47
49
  name: string;
50
+ icon: string;
48
51
  }[];
49
52
  TEXTFIELD_SETTINGS_OPTIONS: {
50
53
  id: string;
51
54
  value: string;
52
55
  name: string;
56
+ icon: string;
53
57
  }[];
54
58
  COLOR_OPTIONS: {
55
59
  id: string;
@@ -67,66 +71,79 @@ export declare const optionsStore: import("svelte/store").Readable<{
67
71
  id: string;
68
72
  value: string;
69
73
  name: string;
74
+ icon: string;
70
75
  }[];
71
76
  ALIGN_OPTIONS: {
72
77
  id: string;
73
78
  value: string;
74
79
  name: string;
80
+ icon: string;
75
81
  }[];
76
82
  JUSTIFY_ALIGN_OPTIONS: {
77
83
  id: string;
78
84
  value: string;
79
85
  name: string;
86
+ icon: string;
80
87
  }[];
81
88
  ICON_TEXT_POSITION_OPTIONS: {
82
89
  id: string;
83
90
  value: string;
84
91
  name: string;
92
+ icon: string;
85
93
  }[];
86
94
  TABLE_TEXT_SETTING_OPTIONS: {
87
95
  id: string;
88
96
  value: string;
89
97
  name: string;
98
+ icon: string;
90
99
  }[];
91
100
  HEIGHT_OPTIONS: {
92
101
  id: string;
93
102
  value: string;
94
103
  name: string;
104
+ icon: string;
95
105
  }[];
96
106
  FILE_ATTACH_TYPE_OPTIONS: {
97
107
  id: string;
98
108
  value: string;
99
109
  name: string;
110
+ icon: string;
100
111
  }[];
101
112
  DIVIDER_TYPE_OPTIONS: {
102
113
  id: string;
103
114
  value: string;
104
115
  name: string;
116
+ icon: string;
105
117
  }[];
106
118
  DIVIDER_ANCHOR_OPTIONS: {
107
119
  id: string;
108
120
  value: string;
109
121
  name: string;
122
+ icon: string;
110
123
  }[];
111
124
  FITMODE_OPTIONS: {
112
125
  id: string;
113
126
  value: string;
114
127
  name: string;
128
+ icon: string;
115
129
  }[];
116
130
  SWITCH_OPTIONS: {
117
131
  id: string;
118
132
  value: string;
119
133
  name: string;
134
+ icon: string;
120
135
  }[];
121
136
  INFO_SIDE_OPTIONS: {
122
137
  id: string;
123
138
  value: string;
124
139
  name: string;
140
+ icon: string;
125
141
  }[];
126
142
  FORM_OPTIONS: {
127
143
  id: string;
128
144
  value: string;
129
145
  name: string;
146
+ icon: string;
130
147
  }[];
131
148
  TABLE_CONTENT_TYPE_OPTIONS: {
132
149
  id: string;
@@ -137,6 +154,7 @@ export declare const optionsStore: import("svelte/store").Readable<{
137
154
  id: string;
138
155
  value: string;
139
156
  name: string;
157
+ icon: string;
140
158
  }[];
141
159
  BUFFER_SIZE_OPTIONS: {
142
160
  id: string;
package/dist/options.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // $lib/ElementsUI/options.ts
2
2
  import { derived } from "svelte/store";
3
3
  import { T } from "./locales/i18n";
4
+ import { PROPS_ICONS } from "./libIcons/propsIcons";
4
5
  function id() {
5
6
  return crypto.randomUUID();
6
7
  }
@@ -28,11 +29,11 @@ export const optionsStore = derived(T, ($T) => {
28
29
  { id: id(), value: "hidden", name: $T("constructor.props.type.hidden") },
29
30
  ],
30
31
  INPUT_TYPE_OPTIONS: [
31
- { id: id(), value: "text", name: $T("constructor.props.type.text") },
32
- { id: id(), value: "password", name: $T("constructor.props.type.password") },
33
- { id: id(), value: "number", name: $T("constructor.props.type.number") },
34
- { id: id(), value: "text-area", name: $T("constructor.props.type.textarea") },
35
- { id: id(), value: "bitMode", name: $T("constructor.props.type.bitMode") },
32
+ { id: id(), value: "text", name: $T("constructor.props.type.text"), icon: "Aa" },
33
+ { id: id(), value: "password", name: $T("constructor.props.type.password"), icon: "•••" },
34
+ { id: id(), value: "number", name: $T("constructor.props.type.number"), icon: "123" },
35
+ { id: id(), value: "text-area", name: $T("constructor.props.type.textarea"), icon: "¶" },
36
+ { id: id(), value: "bitMode", name: $T("constructor.props.type.bitMode"), icon: "01" },
36
37
  ],
37
38
  SELECT_TYPE_OPTIONS: [
38
39
  { id: id(), value: "select", name: $T("constructor.props.type.select") },
@@ -44,21 +45,21 @@ export const optionsStore = derived(T, ($T) => {
44
45
  { id: id(), value: "number", name: $T("constructor.props.valuetype.number") },
45
46
  ],
46
47
  SLIDER_TYPE_OPTIONS: [
47
- { id: id(), value: "single", name: $T("constructor.props.type.single") },
48
- { id: id(), value: "range", name: $T("constructor.props.type.range") },
48
+ { id: id(), value: "single", name: $T("constructor.props.type.single"), icon: PROPS_ICONS.sliderSingle },
49
+ { id: id(), value: "range", name: $T("constructor.props.type.range"), icon: PROPS_ICONS.sliderRange },
49
50
  ],
50
51
  TEXTFIELD_SIZE_OPTIONS: [
51
- { id: id(), value: "small", name: $T("constructor.props.type.small") },
52
- { id: id(), value: "base", name: $T("constructor.props.type.base") },
53
- { id: id(), value: "large", name: $T("constructor.props.type.large") },
54
- { id: id(), value: "huge", name: $T("constructor.props.type.huge") },
55
- { id: id(), value: "massive", name: $T("constructor.props.type.massive") },
52
+ { id: id(), value: "small", name: $T("constructor.props.type.small"), icon: "S" },
53
+ { id: id(), value: "base", name: $T("constructor.props.type.base"), icon: "M" },
54
+ { id: id(), value: "large", name: $T("constructor.props.type.large"), icon: "L" },
55
+ { id: id(), value: "huge", name: $T("constructor.props.type.huge"), icon: "XL" },
56
+ { id: id(), value: "massive", name: $T("constructor.props.type.massive"), icon: "XXL" },
56
57
  ],
57
58
  TEXTFIELD_SETTINGS_OPTIONS: [
58
- { id: id(), value: "bold", name: $T("constructor.props.bold") },
59
- { id: id(), value: "italic", name: $T("constructor.props.italic") },
60
- { id: id(), value: "underline", name: $T("constructor.props.underline") },
61
- { id: id(), value: "background", name: $T("constructor.props.background") },
59
+ { id: id(), value: "bold", name: $T("constructor.props.bold"), icon: PROPS_ICONS.bold },
60
+ { id: id(), value: "italic", name: $T("constructor.props.italic"), icon: PROPS_ICONS.italic },
61
+ { id: id(), value: "underline", name: $T("constructor.props.underline"), icon: PROPS_ICONS.underline },
62
+ { id: id(), value: "background", name: $T("constructor.props.background"), icon: PROPS_ICONS.highlight },
62
63
  ],
63
64
  /* Единая семантическая палитра (app.css: --X-color/--X-soft, светлая и тёмная тема уже учтены) —
64
65
  тот же набор ролей, что и в "мастер-цвете" виджетов конструктора (widgetAccent.ts на стороне
@@ -116,70 +117,70 @@ export const optionsStore = derived(T, ($T) => {
116
117
  },
117
118
  ],
118
119
  TEXT_ALIGN_OPTIONS: [
119
- { id: id(), value: "text-left", name: $T("constructor.props.align.left") },
120
- { id: id(), value: "text-center", name: $T("constructor.props.align.center") },
121
- { id: id(), value: "text-right", name: $T("constructor.props.align.right") },
122
- { id: id(), value: "text-justify", name: $T("constructor.props.align.justify") },
120
+ { id: id(), value: "text-left", name: $T("constructor.props.align.left"), icon: PROPS_ICONS.alignLeft },
121
+ { id: id(), value: "text-center", name: $T("constructor.props.align.center"), icon: PROPS_ICONS.alignCenter },
122
+ { id: id(), value: "text-right", name: $T("constructor.props.align.right"), icon: PROPS_ICONS.alignRight },
123
+ { id: id(), value: "text-justify", name: $T("constructor.props.align.justify"), icon: PROPS_ICONS.alignJustify },
123
124
  ],
124
125
  ALIGN_OPTIONS: [
125
- { id: id(), value: "left", name: $T("constructor.props.align.left") },
126
- { id: id(), value: "center", name: $T("constructor.props.align.center") },
127
- { id: id(), value: "right", name: $T("constructor.props.align.right") },
126
+ { id: id(), value: "left", name: $T("constructor.props.align.left"), icon: PROPS_ICONS.alignLeft },
127
+ { id: id(), value: "center", name: $T("constructor.props.align.center"), icon: PROPS_ICONS.alignCenter },
128
+ { id: id(), value: "right", name: $T("constructor.props.align.right"), icon: PROPS_ICONS.alignRight },
128
129
  ],
129
130
  JUSTIFY_ALIGN_OPTIONS: [
130
- { id: id(), value: "justify-start", name: $T("constructor.props.align.left") },
131
- { id: id(), value: "justify-center", name: $T("constructor.props.align.center") },
132
- { id: id(), value: "justify-end", name: $T("constructor.props.align.right") },
131
+ { id: id(), value: "justify-start", name: $T("constructor.props.align.left"), icon: PROPS_ICONS.alignLeft },
132
+ { id: id(), value: "justify-center", name: $T("constructor.props.align.center"), icon: PROPS_ICONS.alignCenter },
133
+ { id: id(), value: "justify-end", name: $T("constructor.props.align.right"), icon: PROPS_ICONS.alignRight },
133
134
  ],
134
135
  ICON_TEXT_POSITION_OPTIONS: [
135
- { id: id(), value: "flex-row", name: "←" },
136
- { id: id(), value: "flex-col", name: "↑" },
137
- { id: id(), value: "flex-row-reverse", name: "→" },
138
- { id: id(), value: "flex-col-reverse", name: "↓" },
136
+ { id: id(), value: "flex-row", name: $T("constructor.props.iconpos.left"), icon: PROPS_ICONS.arrowLeft },
137
+ { id: id(), value: "flex-col", name: $T("constructor.props.iconpos.top"), icon: PROPS_ICONS.arrowUp },
138
+ { id: id(), value: "flex-row-reverse", name: $T("constructor.props.iconpos.right"), icon: PROPS_ICONS.arrowRight },
139
+ { id: id(), value: "flex-col-reverse", name: $T("constructor.props.iconpos.bottom"), icon: PROPS_ICONS.arrowDown },
139
140
  ],
140
141
  TABLE_TEXT_SETTING_OPTIONS: [
141
- { id: id(), value: "sortable", name: $T("constructor.props.table.columns.sortable") },
142
- { id: id(), value: "copy", name: $T("constructor.props.copy") },
143
- { id: id(), value: "truncated", name: $T("constructor.props.table.columns.truncated") },
144
- { id: id(), value: "tooltip", name: $T("constructor.props.table.columns.tooltip") },
145
- { id: id(), value: "modal", name: $T("constructor.props.table.columns.modal") },
142
+ { id: id(), value: "sortable", name: $T("constructor.props.table.columns.sortable"), icon: PROPS_ICONS.sort },
143
+ { id: id(), value: "copy", name: $T("constructor.props.copy"), icon: PROPS_ICONS.copy },
144
+ { id: id(), value: "truncated", name: $T("constructor.props.table.columns.truncated"), icon: PROPS_ICONS.ellipsis },
145
+ { id: id(), value: "tooltip", name: $T("constructor.props.table.columns.tooltip"), icon: PROPS_ICONS.tooltip },
146
+ { id: id(), value: "modal", name: $T("constructor.props.table.columns.modal"), icon: PROPS_ICONS.modal },
146
147
  ],
147
148
  HEIGHT_OPTIONS: [
148
- { id: id(), value: "py-1", name: $T("constructor.props.height.small") },
149
- { id: id(), value: "py-2", name: $T("constructor.props.height.medium") },
150
- { id: id(), value: "py-3", name: $T("constructor.props.height.large") },
149
+ { id: id(), value: "py-1", name: $T("constructor.props.height.small"), icon: "S" },
150
+ { id: id(), value: "py-2", name: $T("constructor.props.height.medium"), icon: "M" },
151
+ { id: id(), value: "py-3", name: $T("constructor.props.height.large"), icon: "L" },
151
152
  ],
152
153
  FILE_ATTACH_TYPE_OPTIONS: [
153
- { id: id(), value: "file", name: $T("constructor.props.type.file") },
154
- { id: id(), value: "image", name: $T("constructor.props.type.image") },
154
+ { id: id(), value: "file", name: $T("constructor.props.type.file"), icon: PROPS_ICONS.file },
155
+ { id: id(), value: "image", name: $T("constructor.props.type.image"), icon: PROPS_ICONS.image },
155
156
  ],
156
157
  DIVIDER_TYPE_OPTIONS: [
157
- { id: id(), value: "line", name: $T("constructor.props.divider.type.line") },
158
- { id: id(), value: "empty", name: $T("constructor.props.divider.type.empty") },
158
+ { id: id(), value: "line", name: $T("constructor.props.divider.type.line"), icon: PROPS_ICONS.dividerLine },
159
+ { id: id(), value: "empty", name: $T("constructor.props.divider.type.empty"), icon: PROPS_ICONS.dividerSpace },
159
160
  ],
160
161
  DIVIDER_ANCHOR_OPTIONS: [
161
- { id: id(), value: "top", name: $T("constructor.props.divider.anchor.top") },
162
- { id: id(), value: "center", name: $T("constructor.props.divider.anchor.center") },
163
- { id: id(), value: "bottom", name: $T("constructor.props.divider.anchor.bottom") },
162
+ { id: id(), value: "top", name: $T("constructor.props.divider.anchor.top"), icon: PROPS_ICONS.anchorTop },
163
+ { id: id(), value: "center", name: $T("constructor.props.divider.anchor.center"), icon: PROPS_ICONS.anchorCenter },
164
+ { id: id(), value: "bottom", name: $T("constructor.props.divider.anchor.bottom"), icon: PROPS_ICONS.anchorBottom },
164
165
  ],
165
166
  FITMODE_OPTIONS: [
166
- { id: id(), value: "cover", name: $T("constructor.props.type.cover") },
167
- { id: id(), value: "contain", name: $T("constructor.props.type.contain") },
167
+ { id: id(), value: "cover", name: $T("constructor.props.type.cover"), icon: PROPS_ICONS.fitCover },
168
+ { id: id(), value: "contain", name: $T("constructor.props.type.contain"), icon: PROPS_ICONS.fitContain },
168
169
  ],
169
170
  SWITCH_OPTIONS: [
170
- { id: id(), value: "horizontal", name: $T("constructor.props.type.horizontal") },
171
- { id: id(), value: "vertical", name: $T("constructor.props.type.vertical") },
172
- { id: id(), value: "checkbox", name: $T("constructor.props.type.checkbox") },
171
+ { id: id(), value: "horizontal", name: $T("constructor.props.type.horizontal"), icon: PROPS_ICONS.toggleHorizontal },
172
+ { id: id(), value: "vertical", name: $T("constructor.props.type.vertical"), icon: PROPS_ICONS.toggleVertical },
173
+ { id: id(), value: "checkbox", name: $T("constructor.props.type.checkbox"), icon: PROPS_ICONS.checkbox },
173
174
  ],
174
175
  INFO_SIDE_OPTIONS: [
175
- { id: id(), value: "top", name: $T("constructor.props.info.top") },
176
- { id: id(), value: "bottom", name: $T("constructor.props.info.bottom") },
177
- { id: id(), value: "left", name: $T("constructor.props.info.left") },
178
- { id: id(), value: "right", name: $T("constructor.props.info.right") },
176
+ { id: id(), value: "top", name: $T("constructor.props.info.top"), icon: PROPS_ICONS.arrowUp },
177
+ { id: id(), value: "bottom", name: $T("constructor.props.info.bottom"), icon: PROPS_ICONS.arrowDown },
178
+ { id: id(), value: "left", name: $T("constructor.props.info.left"), icon: PROPS_ICONS.arrowLeft },
179
+ { id: id(), value: "right", name: $T("constructor.props.info.right"), icon: PROPS_ICONS.arrowRight },
179
180
  ],
180
181
  FORM_OPTIONS: [
181
- { id: id(), value: "square", name: $T("constructor.props.type.square") },
182
- { id: id(), value: "circle", name: $T("constructor.props.type.circle") },
182
+ { id: id(), value: "square", name: $T("constructor.props.type.square"), icon: PROPS_ICONS.square },
183
+ { id: id(), value: "circle", name: $T("constructor.props.type.circle"), icon: PROPS_ICONS.circle },
183
184
  ],
184
185
  TABLE_CONTENT_TYPE_OPTIONS: [
185
186
  { id: id(), value: "text", name: $T("constructor.props.table.type.text") },
@@ -189,8 +190,8 @@ export const optionsStore = derived(T, ($T) => {
189
190
  { id: id(), value: "image", name: $T("constructor.props.table.type.icon") },
190
191
  ],
191
192
  INPUT_SETTING_OPTIONS: [
192
- { id: id(), value: "readonly", name: $T("constructor.props.readonly") },
193
- { id: id(), value: "help.copyButton", name: $T("constructor.props.copy") },
193
+ { id: id(), value: "readonly", name: $T("constructor.props.readonly"), icon: PROPS_ICONS.lock },
194
+ { id: id(), value: "help.copyButton", name: $T("constructor.props.copy"), icon: PROPS_ICONS.copy },
194
195
  ],
195
196
  BUFFER_SIZE_OPTIONS: [
196
197
  { id: id(), value: 10, name: "10" },
package/dist/types.d.ts CHANGED
@@ -294,6 +294,7 @@ export interface IOption<T = unknown> {
294
294
  name?: string;
295
295
  class?: string;
296
296
  disabled?: boolean;
297
+ icon?: string;
297
298
  }
298
299
  export interface ISliderProps {
299
300
  id?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.34.0",
3
+ "version": "1.35.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,89 +0,0 @@
1
- <script lang="ts">
2
- import { Select, Switch, T, type IOption } from "./"
3
- import type { Snippet } from "svelte"
4
- import { fade } from "svelte/transition"
5
- import CopiedChip from "./CopiedChip.svelte"
6
-
7
- let {
8
- component,
9
- componentProps,
10
- examples,
11
- readmeHtml,
12
- codeText,
13
- forConstructor = $bindable(),
14
- }: { component: Snippet; componentProps: Snippet; examples?: Snippet; readmeHtml: string; codeText: string; forConstructor: boolean } = $props()
15
-
16
- let isCopied = $state(false)
17
- let snippetOptions = [
18
- { id: crypto.randomUUID(), value: "overview", name: $T("library.overview") },
19
- { id: crypto.randomUUID(), value: "examples", name: $T("library.examples") },
20
- { id: crypto.randomUUID(), value: "info", name: $T("library.info") },
21
- ]
22
-
23
- let mainSnippet: string | undefined = $state("overview")
24
- </script>
25
-
26
- <div class="flex h-full flex-col">
27
- <Select
28
- wrapperClass="w-1/2 mx-auto mb-3 shrink-0"
29
- type="buttons"
30
- options={snippetOptions}
31
- value={snippetOptions.find((o) => o.value == mainSnippet)}
32
- onUpdate={(value) => (mainSnippet = (value as IOption<string>).value)}
33
- />
34
- <div class="min-h-0 flex-1">
35
- {#key mainSnippet}
36
- {#if mainSnippet === "examples" && examples}
37
- {@render examples()}
38
- {:else if mainSnippet === "info"}
39
- <div class="h-full overflow-y-auto text-left">
40
- {@html readmeHtml}
41
- </div>
42
- {:else}
43
- <div class="flex h-full w-full flex-col">
44
- <div class="min-h-40 flex-[1]">
45
- {@render component()}
46
- </div>
47
- <Switch
48
- wrapperClass="w-60 self-end p-2 shrink-0"
49
- label={{ name: $T("library.for_constructor") }}
50
- value={forConstructor ? 1 : 0}
51
- options={[{ id: crypto.randomUUID(), value: 0 }]}
52
- onChange={(value) => (forConstructor = value == 0 ? false : true)}
53
- />
54
- <div class="border-t border-gray-500 shrink-0"></div>
55
- <div class="min-h-0 flex-[2] overflow-y-auto" transition:fade={{ duration: 200 }}>
56
- {@render componentProps()}
57
- <div class="relative mt-3">
58
- <button
59
- class="absolute top-2 right-3 flex cursor-pointer border-none bg-transparent"
60
- onclick={(e) => {
61
- e.preventDefault()
62
- navigator.clipboard.writeText(codeText)
63
- isCopied = true
64
- setTimeout(() => (isCopied = false), 1000)
65
- }}
66
- aria-label="Копировать текст"
67
- >
68
- <div class="size-6 text-sm [&_svg]:h-full [&_svg]:max-h-full [&_svg]:w-full [&_svg]:max-w-full">
69
- {#if !isCopied}
70
- <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
71
- <g fill="none" stroke="currentColor" stroke-width="1.5">
72
- <path
73
- d="M6 11c0-2.828 0-4.243.879-5.121C7.757 5 9.172 5 12 5h3c2.828 0 4.243 0 5.121.879C21 6.757 21 8.172 21 11v5c0 2.828 0 4.243-.879 5.121C19.243 22 17.828 22 15 22h-3c-2.828 0-4.243 0-5.121-.879C6 20.243 6 18.828 6 16z"
74
- />
75
- <path d="M6 19a3 3 0 0 1-3-3v-6c0-3.771 0-5.657 1.172-6.828S7.229 2 11 2h4a3 3 0 0 1 3 3" />
76
- </g>
77
- </svg>
78
- {/if}
79
- <CopiedChip show={isCopied} class="right-1.5 top-1/2 -translate-y-1/2" />
80
- </div>
81
- </button>
82
- <pre class="overflow-x-auto">{codeText}</pre>
83
- </div>
84
- </div>
85
- </div>
86
- {/if}
87
- {/key}
88
- </div>
89
- </div>
@@ -1,12 +0,0 @@
1
- import type { Snippet } from "svelte";
2
- type $$ComponentProps = {
3
- component: Snippet;
4
- componentProps: Snippet;
5
- examples?: Snippet;
6
- readmeHtml: string;
7
- codeText: string;
8
- forConstructor: boolean;
9
- };
10
- declare const ComponentExample: import("svelte").Component<$$ComponentProps, {}, "forConstructor">;
11
- type ComponentExample = ReturnType<typeof ComponentExample>;
12
- export default ComponentExample;