poe-svelte-ui-lib 1.43.0 → 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +39 -5
  2. package/dist/Accordion/Accordion.svelte +18 -10
  3. package/dist/Accordion/Accordion.svelte.d.ts +1 -1
  4. package/dist/Accordion/AccordionProps.svelte +9 -0
  5. package/dist/Accordion/README.md +17 -15
  6. package/dist/Button/ButtonProps.svelte +7 -18
  7. package/dist/CommonSnippets.svelte +3 -16
  8. package/dist/CopiedChip.svelte +2 -1
  9. package/dist/Input/Input.svelte +7 -6
  10. package/dist/Input/InputProps.svelte +2 -25
  11. package/dist/Joystick/JoystickProps.svelte +0 -15
  12. package/dist/Modal.svelte +2 -1
  13. package/dist/PeriodChart/PeriodChart.svelte +9 -6
  14. package/dist/PeriodChart/PeriodChartProps.svelte +0 -22
  15. package/dist/ProgressBar/ProgressBarProps.svelte +2 -16
  16. package/dist/PropsListModal.svelte +2 -1
  17. package/dist/Select/SelectProps.svelte +0 -10
  18. package/dist/Slider/Slider.svelte +5 -4
  19. package/dist/Switch/SwitchProps.svelte +1 -19
  20. package/dist/Table/Table.svelte +1 -1
  21. package/dist/Table/TableProps.svelte +1 -21
  22. package/dist/Tabs/README.md +1 -1
  23. package/dist/Tabs/Tabs.svelte +7 -9
  24. package/dist/Tabs/Tabs.svelte.d.ts +1 -1
  25. package/dist/Tabs/TabsProps.svelte +3 -10
  26. package/dist/TextField/TextFieldProps.svelte +2 -5
  27. package/dist/TextsEditor.svelte +86 -0
  28. package/dist/TextsEditor.svelte.d.ts +13 -0
  29. package/dist/Widget/Widget.svelte +5 -4
  30. package/dist/Widget/WidgetProps.svelte +0 -37
  31. package/dist/WidgetAccentPicker.svelte +3 -2
  32. package/dist/WidgetDeviceInfo/README.md +10 -10
  33. package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +22 -16
  34. package/dist/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte +7 -10
  35. package/dist/WidgetFileSystem/README.md +16 -16
  36. package/dist/WidgetFileSystem/WidgetFileSystem.svelte +29 -19
  37. package/dist/WidgetFileSystem/WidgetFileSystemProps.svelte +9 -12
  38. package/dist/WidgetMap/README.md +15 -15
  39. package/dist/WidgetMap/WidgetMap.svelte +2 -2
  40. package/dist/WidgetMap/WidgetMapProps.svelte +5 -8
  41. package/dist/WidgetPFD/README.md +14 -14
  42. package/dist/WidgetPFD/WidgetPFD.svelte +106 -131
  43. package/dist/WidgetPFD/WidgetPFDProps.svelte +10 -13
  44. package/dist/WidgetRGBStrip/README.md +12 -12
  45. package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte +27 -31
  46. package/dist/WidgetRGBStrip/WidgetRGBStripProps.svelte +3 -6
  47. package/dist/WidgetStackInfo/README.md +13 -13
  48. package/dist/WidgetStackInfo/WidgetStackInfo.svelte +35 -25
  49. package/dist/WidgetStackInfo/WidgetStackInfoProps.svelte +8 -11
  50. package/dist/WidgetWiFi/WidgetWiFi.svelte +56 -20
  51. package/dist/WidgetWiFi/WidgetWiFiProps.svelte +9 -12
  52. package/dist/index.d.ts +3 -0
  53. package/dist/index.js +3 -0
  54. package/dist/locales/i18n.d.ts +2 -1
  55. package/dist/locales/i18n.js +4 -3
  56. package/dist/locales/localize.d.ts +13 -0
  57. package/dist/locales/localize.js +70 -0
  58. package/dist/locales/translations.js +337 -6
  59. package/dist/types.d.ts +1 -0
  60. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,35 @@
1
1
  # poe-svelte-ui-lib
2
2
 
3
- Набор UI-компонентов на Svelte 5 (в режиме рун) для конструктора интерфейсов устройств: поля ввода, селекторы, таблицы, графики, виджеты устройства (WiFi, информация об устройстве) и вспомогательные примитивы (модалки, перетаскивание, drag&drop-конструктор свойств).
3
+ Библиотека компонентов на Svelte 5 (руны) для интерфейсов устройств компании. Один и тот же компонент выглядит и ведёт себя одинаково везде, где он используется:
4
+
5
+ - **DevCloud** — облако: конструктор GUI устройства, панель управления, документация;
6
+ - **WebApp** — локальный веб-интерфейс прошивок ProdFactory-ESP, встроенный в устройство;
7
+ - **SYP-DevTerminal** — настольный терминал разработчика.
8
+
9
+ Витрина с живыми примерами: [oleg-perevyshin.github.io/ElementsUI](https://oleg-perevyshin.github.io/ElementsUI/).
10
+
11
+ ## Состав
12
+
13
+ **Смарт-виджеты** — готовые блоки под конкретную задачу, работают с группой полей прошивки по префиксу, переменные привязывать не нужно; шапка со сворачиванием и мастер-цветом:
14
+
15
+ - `WidgetWiFi` — настройка сети: режим, клиент, точка доступа, поиск сетей;
16
+ - `WidgetDeviceInfo` — паспорт устройства и основные настройки, перезагрузка;
17
+ - `WidgetFileSystem` — память, список файлов, удаление и закачка;
18
+ - `WidgetStackInfo` — отладка: свободная память и запас стека задач;
19
+ - `WidgetRGBStrip` — адресная светодиодная лента: цвет, эффекты, параметры ленты;
20
+ - `WidgetPFD` — пилотажный дисплей: авиагоризонт, ленты, маршрут, связь, сообщения автопилота;
21
+ - `WidgetMap` — карта объектов с траекториями, несколько групп-объектов на одной карте.
22
+
23
+ **Компоненты** — привязываются к одной переменной устройства: `Input`, `Select`, `Switch`, `Slider`, `Button`, `Joystick`, `ProgressBar`, `Graph`, `PeriodChart`, `Table`, `FileAttach`, `VideoViewer`, `TextField`, `Widget` (карточка одного значения).
24
+
25
+ **Раскладка** — `Accordion`, `Tabs`, `Divider`, `Carousel`, `Modal`.
26
+
27
+ ## Как устроено
28
+
29
+ - **Компонент только отображает и сообщает.** Он получает значения через пропсы и отдаёт изменения колбэками (`onUpdate`, `onChange`, `onSave`…). С сетью, WebSocket и `DeviceStore` работает приложение (`GUIPreview` в DevCloud и WebApp): значения переменных устройства хранятся под ключами `<ModSN>|<ключ>`, смарт-виджеты читают свою группу (`MAV.Lat`, `CFG.DevName`…).
30
+ - **Конструктор свойств.** У каждого компонента есть `<Компонент>Props.svelte` — панель свойств для конструктора DevCloud.
31
+ - **Описания.** Короткое описание (i) — `component.info.<Тип>` в переводах (ru/en/zh), его показывают конструктор облака и витрина; развёрнутое — README рядом с исходником компонента.
32
+ - **Тема** — один файл `poe-svelte-ui-lib/theme.css` для всех приложений: палитра светлой и тёмной темы, типографика, Markdown, роли заливки.
4
33
 
5
34
  ## Установка
6
35
 
@@ -11,7 +40,7 @@ npm install poe-svelte-ui-lib
11
40
  ### Требования (peerDependencies)
12
41
 
13
42
  - `svelte ^5.0.0`
14
- - `tailwindcss ^4.0.0` — компоненты стилизуются исключительно Tailwind-утилитами и CSS-переменными темы, без собственного скомпилированного CSS в пакете
43
+ - `tailwindcss ^4.0.0` — компоненты стилизуются Tailwind-утилитами и CSS-переменными темы; сама тема поставляется файлом `theme.css`
15
44
 
16
45
  ### Подключение стилей и темы
17
46
 
@@ -26,7 +55,7 @@ npm install poe-svelte-ui-lib
26
55
  /* дальше — только собственные отличия приложения, например :root { min-width: 640px; } */
27
56
  ```
28
57
 
29
- **Предпросмотр темы** — страница «Тема» витрины (`src/routes/components/theme`): один и тот же интерфейс облака (шапка устройства, палитра токенов, кнопки во всех ролях, поля, таблица, виджеты, типографика, Markdown) на одной странице; тема переключается глобально кнопкой в шапке витрины. Правьте `theme.css` и смотрите результат там.
58
+ **Предпросмотр темы** — вкладка «Тема» на главной витрины (`src/routes/ThemePreview.svelte`): типовой интерфейс облака (шапка устройства, палитра токенов, кнопки во всех ролях, поля, таблица, виджеты, типографика, Markdown); тема переключается кнопкой в шапке витрины. Правьте `theme.css` и смотрите результат там.
30
59
 
31
60
  Текст в Markdown, таблицы и код переносятся по ширине контейнера — горизонтальной прокрутки быть не должно, только вертикальная. Markdown рендерится с `breaks: true` (как в облаке), поэтому абзац и пункт списка пишутся одной строкой — ручной перенос внутри абзаца превращается в разрыв строки.
32
61
 
@@ -48,7 +77,7 @@ npm install poe-svelte-ui-lib
48
77
 
49
78
  Необязательный токен `--table-header-color` — фон шапки `Table` (по умолчанию `--container-color`); смарт-виджеты задают его сами из мастер-цвета.
50
79
 
51
- Справочник по регулярным выражениям (`docs/info/RegExp.md`) и протокол обмена данными с устройством (`docs/info/ExchangeRules.md`) — вспомогательные материалы для тех, кто настраивает валидацию полей и работу с `DeviceStore`.
80
+ Справочник по регулярным выражениям для проверки полей ввода — вкладка «Регулярные выражения» на главной витрины (`src/routes/RegExp.md`). Протокол обмена с устройством описан в документации DevCloud (разделы «Протокол устройства» и «Обмен данными с устройством»).
52
81
 
53
82
  ## Использование
54
83
 
@@ -63,7 +92,7 @@ npm install poe-svelte-ui-lib
63
92
  <UI.Button content={{ name: "Сохранить" }} onClick={() => console.log(value)} />
64
93
  ```
65
94
 
66
- Полный список компонентов и типов — в `src/lib/index.ts`. У каждого компонента есть README рядом с исходником (`src/lib/<Component>/README.md`, для `Modal`/`Dragging` — `src/lib/<Component>.README.md`) с описанием пропсов, примерами и внутренним устройством; живая витрина всех компонентов — `npm run dev` → `/components/all`.
95
+ Полный список компонентов и типов — в `src/lib/index.ts`. У каждого компонента есть README рядом с исходником (`src/lib/<Component>/README.md`, для `Modal`/`Dragging` — `src/lib/<Component>.README.md`) с описанием пропсов, примерами и внутренним устройством; живая витрина — `npm run dev`: на главной вкладки «Обзор» (этот README), «Тема» (предпросмотр `theme.css`) и «Регулярные выражения», дальше страница на каждый компонент.
67
96
 
68
97
  **Витрина повторяет конструктор DevCloud:** слева компонент и код (прижат к низу), справа панель свойств шириной `24rem` с тем же оформлением, что в облаке, — переполнение подписей видно сразу. Кнопка (i) в шапке панели открывает описание компонента.
69
98
 
@@ -81,6 +110,11 @@ npm install poe-svelte-ui-lib
81
110
 
82
111
  **README компонента** (`src/lib/<Компонент>/README.md`, вкладка «Инфо» витрины) — развёрнутая документация для разработчика. Первые разделы повторяют описание (i) и не должны ему противоречить, дальше идут разработческие: `## Назначение` (плюс заметка о размерах) → `## Пакет данных` (плюс `### Протокол устройства` / пример кода прошивки) → `## Ключи` (таблица «Ключ / Описание», те же формулировки, что в (i)) → `## Особенности` (поведение, ограничения) → `## Пропсы` (таблица «Название / Тип / По умолчанию / Описание», колбэки — `### Колбэки`) → `## Примеры` (Svelte) → `## Конструктор свойств (<Компонент>Props.svelte)` → `## Внутреннее устройство`.
83
112
 
113
+ **Языки (ru/en/zh) — два слоя:**
114
+
115
+ - **Встроенные тексты компонентов** (подписи полей виджетов, кнопки, подсказки, `aria-label`) — только через словарь `src/lib/locales/translations.ts` и `$T("ключ")`; строка с числами — параметрами: `$T("widget.fs.files", { n: files.length })` на ключ `"Файлов: {n}"`. Русских литералов в разметке и скриптах компонентов быть не должно. Списки вариантов с подписями — `$derived`, иначе не переключатся при смене языка. Исключение — шкалы прибора `WidgetPFD`: там общепринятые авиационные сокращения (`SPD`, `ALT`, `AS`, `VS`, `NO DATA`) на всех языках, переводятся только подсказки и строки состояния.
116
+ - **Тексты, введённые в конструкторе** (`label.name`, `placeholder`, `options[].name`, вкладки, заголовки колонок, подписи переключателя и т.п.) — тип `LocalizedText` (`locales/localize.ts`): простая строка (только `ru`) или объект `{ ru, en, zh }`; при отсутствии перевода показывается `ru`, затем первый непустой. Компоненты получают уже готовые строки: приложение разрешает описание GUI функцией `localizeDeep(UIBlocks, язык)` перед рендером. Окна свойств (`*Props.svelte`) тексты не показывают и не правят — у каждого компонента одна кнопка «Тексты и переводы» (`TextsEditor`): модальное окно само собирает все тексты компонента (`collectTexts` по полям `name`, `label`, `labels`, `placeholder`, `text`, `info`, `title`, `captionLeft`/`captionRight`, `units`/`unit`, `footer`, `hiddenInfo`, `alt`; технические `id`, `class`, `icon`, `keys`, данные — никогда; у `Switch` варианты — короткие метки битов битового режима, их имя правится в окне свойств и не переводится — `NOT_TEXTS` в `TextsEditor.svelte`) и даёт по три поля RU/EN/ZH. Сохранение (`packLocalized`): пусто — `""`, только RU — строка, иначе объект из заполненных языков. Читая текст в окне свойств для логики, берите `localizeText(поле, "ru")` — поле может быть объектом. Заголовок смарт-виджета по умолчанию пустой — виджет показывает своё название из словаря на текущем языке.
117
+
84
118
  **Иконки вариантов панелей свойств** — `src/lib/libIcons/propsIcons.ts` (контурные SVG 24×24 строками), подставляются в `IOption.icon` в `options.ts`.
85
119
 
86
120
  ## Разработка
@@ -1,14 +1,16 @@
1
- <!-- $lib/Accordion/Accordion.svelte — заголовок больше не увеличивается при раскрытии,
2
- тень на hover заменена фоном, радиус 14. Логика toggle не тронута. -->
1
+ <!-- $lib/Accordion/Accordion.svelte — заголовок не увеличивается при раскрытии, радиус 14.
2
+ Мастер-цвет (componentClass) — как у смарт-виджетов: иконка и текст заголовка цветом, фон заголовка — его мягким оттенком. -->
3
3
  <script lang="ts">
4
4
  import { slide } from "svelte/transition"
5
5
  import type { IAccordionProps } from "../types"
6
6
  import { twMerge } from "tailwind-merge"
7
+ import { widgetAccentStyle } from "../widgetAccent"
7
8
 
8
9
  let {
9
10
  id = crypto.randomUUID(),
10
- isOpen = false,
11
+ isOpen = $bindable(false),
11
12
  wrapperClass = "",
13
+ componentClass = "",
12
14
  size = { width: 1, height: 1 },
13
15
  label = { name: "", class: "text-left", icon: null },
14
16
  children,
@@ -18,18 +20,24 @@
18
20
  const toggle = () => (isOpen = !isOpen)
19
21
  </script>
20
22
 
21
- <div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class={twMerge(`w-full`, wrapperClass)} transition:slide={{ duration: 200 }}>
23
+ <div
24
+ id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
25
+ class={twMerge(`w-full`, wrapperClass)}
26
+ style={widgetAccentStyle(componentClass)}
27
+ transition:slide={{ duration: 200 }}
28
+ >
22
29
  <div class="overflow-hidden rounded-[14px] border border-(--hairline-color) bg-(--back-color)">
23
- <!-- Заголовок темнее контента (--container-color), а не наоборот: так он читается
24
- как отдельная кликабельная полоса, а раскрытое содержимое остаётся на чистом
25
- светлом фоне (--back-color) — там часто лежат вложенные карточки/поля. -->
30
+ <!-- Фон заголовка — мягкий оттенок мастер-цвета (светлый в светлой теме, тёмный в тёмной), текст — мастер-цвет с цветом шрифта 60/40,
31
+ раскрытое содержимое остаётся на чистом фоне (--back-color). -->
26
32
  <button
27
- class="flex w-full cursor-pointer items-center justify-between gap-3 bg-(--container-color) px-4 py-3 text-left transition-colors duration-150 hover:bg-(--border-color)"
33
+ class="flex w-full cursor-pointer items-center justify-between gap-3 bg-(--accent-soft) px-4 py-3 text-left text-[color-mix(in_srgb,var(--accent-color)_60%,var(--font-color))] transition-colors duration-150 hover:bg-[color-mix(in_srgb,var(--accent-color)_12%,var(--accent-soft))]"
28
34
  onclick={toggle}
29
35
  >
30
36
  <div class="flex min-w-0 flex-1 items-center gap-3">
31
37
  {#if label?.icon}
32
- <span class="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-(--muted-color) [&_svg]:h-full [&_svg]:w-full">
38
+ <span
39
+ class="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-[color-mix(in_srgb,var(--accent-color)_60%,var(--font-color))] [&_svg]:h-full [&_svg]:w-full"
40
+ >
33
41
  {#if typeof label?.icon === "string"}
34
42
  {@html label.icon}
35
43
  {:else}
@@ -43,7 +51,7 @@
43
51
 
44
52
  <svg
45
53
  xmlns="http://www.w3.org/2000/svg"
46
- class="h-[14px] w-[14px] shrink-0 text-(--faint-color) transition-transform duration-200"
54
+ class="h-[14px] w-[14px] shrink-0 text-[color-mix(in_srgb,var(--accent-color)_60%,var(--font-color))] transition-transform duration-200"
47
55
  style="transform: rotate({isOpen ? 180 : 0}deg)"
48
56
  viewBox="0 0 24 24"
49
57
  >
@@ -1,4 +1,4 @@
1
1
  import type { IAccordionProps } from "../types";
2
- declare const Accordion: import("svelte").Component<IAccordionProps, {}, "">;
2
+ declare const Accordion: import("svelte").Component<IAccordionProps, {}, "isOpen">;
3
3
  type Accordion = ReturnType<typeof Accordion>;
4
4
  export default Accordion;
@@ -9,6 +9,7 @@
9
9
  import CrossIcon from "../libIcons/CrossIcon.svelte"
10
10
  import CommonSnippets from "../CommonSnippets.svelte"
11
11
  import PropsGroup from "../PropsGroup.svelte"
12
+ import WidgetAccentPicker from "../WidgetAccentPicker.svelte"
12
13
 
13
14
  const {
14
15
  component,
@@ -113,6 +114,10 @@
113
114
  />
114
115
  </PropsGroup>
115
116
  <PropsGroup label={$T("constructor.props.group.appearance")} contentClass="items-center">
117
+ <WidgetAccentPicker
118
+ value={component.properties.componentClass ?? ""}
119
+ onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
120
+ />
116
121
  {@render AccordionBackgroundImage()}
117
122
  </PropsGroup>
118
123
  </div>
@@ -141,6 +146,10 @@
141
146
  {@render AccordionIsOpen()}
142
147
  </PropsGroup>
143
148
  <PropsGroup label={$T("constructor.props.group.appearance")} contentClass="items-center">
149
+ <WidgetAccentPicker
150
+ value={component.properties.componentClass ?? ""}
151
+ onUpdate={(value) => updateProperty("componentClass", value, component, onPropertyChange)}
152
+ />
144
153
  {@render AccordionBackgroundImage()}
145
154
  </PropsGroup>
146
155
  </div>
@@ -12,6 +12,7 @@
12
12
 
13
13
  - Перетащите компоненты внутрь — у аккордеона своя сетка, её размер задаётся при создании и ресайзе
14
14
  - Заголовок с иконкой открывает и закрывает содержимое
15
+ - Мастер-цвет — как у смарт-виджетов (`widgetAccentStyle`): иконка, текст и стрелка заголовка — цветом из палитры, смешанным с цветом шрифта (читается в обеих темах), фон заголовка — его мягким оттенком (`--<цвет>-soft`: очень светлый в светлой теме, тёмный в тёмной); без выбора — акцент темы. Цвет из `label.class` перекрывает цвет текста
15
16
  - По высоте занимает одну строку сетки и растягивается по содержимому
16
17
 
17
18
  ### Адаптивность
@@ -30,20 +31,21 @@
30
31
 
31
32
  ## Пропсы
32
33
 
33
- | Название | Тип | По умолчанию | Описание |
34
- | -------------- | ------------------------------------------------------ | --------------------- | ------------------------------------------------------------- |
35
- | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
36
- | `isOpen` | `boolean` | `false` | Состояние открытия/закрытия аккордеона |
37
- | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
38
- | `size` | `object` | `-` | Размеры сетки содержимого (количество колонок и строк) |
39
- | `size.height` | `number` | `1` | Количество строк в сетке содержимого |
40
- | `size.width` | `number` | `1` | Количество колонок в сетке содержимого |
41
- | `label` | `object` | `-` | Настройки заголовка аккордеона |
42
- | `label.name` | `string` | `""` | Текст заголовка аккордеона |
43
- | `label.class` | `string` | `"text-left"` | CSS-классы для стилизации текста заголовка |
44
- | `label.icon` | `ConstructorOfATypedSvelteComponent \| string \| null` | `null` | Иконка, отображаемая в заголовке (SVG строка или компонент) |
45
- | `children` | `Snippet` | `undefined` | Слот для содержимого, которое будет отображаться при открытии |
46
- | `image` | `string` | `""` | URL изображения для фона содержимого аккордеона |
34
+ | Название | Тип | По умолчанию | Описание |
35
+ | ---------------- | ------------------------------------------------------ | --------------------- | ---------------------------------------------------------------------------------------- |
36
+ | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
37
+ | `isOpen` | `boolean` | `false` | Состояние открытия/закрытия; `$bindable` — `bind:isOpen` получает раскрытие по клику |
38
+ | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
39
+ | `componentClass` | `string` | `""` | Мастер-цвет: роль `bg-red`, `bg-green`... из палитры `COLOR_OPTIONS`; `""` — акцент темы |
40
+ | `size` | `object` | `-` | Размеры сетки содержимого (количество колонок и строк) |
41
+ | `size.height` | `number` | `1` | Количество строк в сетке содержимого |
42
+ | `size.width` | `number` | `1` | Количество колонок в сетке содержимого |
43
+ | `label` | `object` | `-` | Настройки заголовка аккордеона |
44
+ | `label.name` | `string` | `""` | Текст заголовка аккордеона |
45
+ | `label.class` | `string` | `"text-left"` | CSS-классы для стилизации текста заголовка |
46
+ | `label.icon` | `ConstructorOfATypedSvelteComponent \| string \| null` | `null` | Иконка, отображаемая в заголовке (SVG строка или компонент) |
47
+ | `children` | `Snippet` | `undefined` | Слот для содержимого, которое будет отображаться при открытии |
48
+ | `image` | `string` | `""` | URL изображения для фона содержимого аккордеона |
47
49
 
48
50
  ### Колбэки
49
51
 
@@ -121,7 +123,7 @@
121
123
 
122
124
  ### Что настраивается
123
125
 
124
- - Заголовок, иконка, «Открыт по умолчанию» — состояние при загрузке интерфейса
126
+ - Заголовок, иконка, мастер-цвет заголовка, «Открыт по умолчанию» — состояние при загрузке интерфейса
125
127
  - Картинка фона — изображение под содержимым (например схема устройства), поверх неё располагаются компоненты
126
128
 
127
129
  ## Внутреннее устройство
@@ -1,5 +1,7 @@
1
1
  <!-- $lib/ElementsUI/ButtonProps.svelte -->
2
2
  <script lang="ts">
3
+ import TextsEditor from "../TextsEditor.svelte"
4
+ import { localizeText } from "../locales/localize"
3
5
  import { T } from "../locales/i18n"
4
6
  import { type UIComponent, type IButtonProps, type IOption, updateProperty, type IUIComponentHandler } from "../types"
5
7
  import * as UI from ".."
@@ -40,11 +42,11 @@
40
42
  )
41
43
  const updateIconProperty = (propertyName: string, value: string) => {
42
44
  updateProperty(propertyName, value, component, onPropertyChange)
43
- if (component.properties.content.icon && !component.properties.content.name?.trim())
45
+ if (component.properties.content.icon && !localizeText(component.properties.content.name, "ru").trim())
44
46
  updateProperty("componentClass", twMerge((component.properties as UI.IButtonProps).componentClass, "w-fit p-2"), component, onPropertyChange)
45
47
  else if (
46
- (component.properties.content.icon && component.properties.content.name?.trim()) ||
47
- (!component.properties.content.icon && !component.properties.content.name?.trim())
48
+ (component.properties.content.icon && localizeText(component.properties.content.name, "ru").trim()) ||
49
+ (!component.properties.content.icon && !localizeText(component.properties.content.name, "ru").trim())
48
50
  )
49
51
  updateProperty("componentClass", twMerge((component.properties as UI.IButtonProps).componentClass, "w-full px-1 py-1"), component, onPropertyChange)
50
52
  }
@@ -100,15 +102,11 @@
100
102
  {/snippet}
101
103
 
102
104
  {#snippet ButtonName()}
103
- <UI.Input
104
- label={{ name: $T("constructor.props.name") }}
105
- value={component.properties.content.name}
106
- onUpdate={(value) => updateIconProperty("content.name", value as string)}
107
- />
105
+ <TextsEditor {component} {onPropertyChange} />
108
106
  {/snippet}
109
107
 
110
108
  {#snippet ButtonHeight()}
111
- {#if !(!component.properties.content.name?.trim() && component.properties.content.icon)}
109
+ {#if !(!localizeText(component.properties.content.name, "ru").trim() && component.properties.content.icon)}
112
110
  <UI.Select
113
111
  label={{ name: $T("constructor.props.height") }}
114
112
  type="buttons"
@@ -120,14 +118,6 @@
120
118
  {/if}
121
119
  {/snippet}
122
120
 
123
- {#snippet ButtonInfo()}
124
- <UI.Input
125
- label={{ name: $T("constructor.props.info") }}
126
- value={component.properties.content.info.text}
127
- onUpdate={(value) => updateProperty("content.info.text", value as string, component, onPropertyChange)}
128
- />
129
- {/snippet}
130
-
131
121
  {#snippet ButtonInfoSide()}
132
122
  <UI.Select
133
123
  label={{ name: $T("constructor.props.info.side") }}
@@ -197,7 +187,6 @@
197
187
  </PropsGroup>
198
188
  <PropsGroup label={$T("constructor.props.group.content")}>
199
189
  {@render ButtonName()}
200
- {@render ButtonInfo()}
201
190
  {@render ButtonInfoSide()}
202
191
  </PropsGroup>
203
192
  <PropsGroup label={$T("constructor.props.group.appearance")}>
@@ -5,6 +5,7 @@
5
5
  import { optionsStore } from "./options"
6
6
  import { updateProperty } from "./types"
7
7
  import { twMerge } from "tailwind-merge"
8
+ import TextsEditor from "./TextsEditor.svelte"
8
9
 
9
10
  let {
10
11
  snippet = "",
@@ -87,21 +88,7 @@
87
88
  {/snippet}
88
89
 
89
90
  {#snippet Label()}
90
- <UI.Input
91
- label={{ name: $T("constructor.props.label") }}
92
- value={(
93
- component.properties as
94
- | UI.IAccordionProps
95
- | UI.IInputProps
96
- | UI.ISwitchProps
97
- | UI.ISliderProps
98
- | UI.IGraphProps
99
- | UI.ITableProps<object>
100
- | UI.IFileAttachProps
101
- | UI.IJoystickProps
102
- ).label?.name}
103
- onUpdate={(value) => updateProperty("label.name", value as string, component, onPropertyChange)}
104
- />
91
+ <TextsEditor {component} {onPropertyChange} />
105
92
  {/snippet}
106
93
 
107
94
  {#snippet LabelAlign(initialAlign: UI.IOption<string>)}
@@ -203,7 +190,7 @@
203
190
  <UI.TextField
204
191
  wrapperClass="mt-5 shadow-(--border-shadow-color) bg-(--blue-color)/20"
205
192
  content={{
206
- name: "Выберите битовый диапазон в рамках глобальной переменной для данного компонента.",
193
+ name: $T("ui.bit_range"),
207
194
  }}
208
195
  background
209
196
  />
@@ -5,6 +5,7 @@
5
5
  ComponentExample — опечатка в классе позиционирования (right-1..5,
6
6
  невалидный Tailwind-класс, физически ничего не позиционировал). -->
7
7
  <script lang="ts">
8
+ import { T } from "./locales/i18n"
8
9
  import { fade } from "svelte/transition"
9
10
 
10
11
  let { show = false, class: className = "" }: { show?: boolean; class?: string } = $props()
@@ -15,6 +16,6 @@
15
16
  class="{className} absolute flex items-center gap-1 rounded-lg bg-(--green-soft) px-2 py-1 text-[11px] font-bold whitespace-nowrap text-(--green-color)"
16
17
  transition:fade={{ duration: 150 }}
17
18
  >
18
- Скопировано
19
+ {$T("ui.copied")}
19
20
  </span>
20
21
  {/if}
@@ -2,6 +2,7 @@
2
2
  кольцо фокуса вместо размытой тени. Вся логика (regExp, roundToClean,
3
3
  handleInputChange, счётчик, копирование) не тронута. -->
4
4
  <script lang="ts">
5
+ import { T } from "../locales/i18n"
5
6
  import type { IInputProps } from "../types"
6
7
  import { twMerge } from "tailwind-merge"
7
8
  import InfoIcon from "../libIcons/InfoIcon.svelte"
@@ -142,7 +143,7 @@
142
143
  type="button"
143
144
  class="absolute right-1.5 flex size-7 cursor-pointer items-center justify-center rounded-lg border-none bg-transparent text-(--faint-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color)"
144
145
  onclick={() => (showPassword = !showPassword)}
145
- aria-label={showPassword ? "Скрыть пароль" : "Показать пароль"}
146
+ aria-label={showPassword ? $T("ui.password.hide") : $T("ui.password.show")}
146
147
  >
147
148
  {#if showPassword}
148
149
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
@@ -176,7 +177,7 @@
176
177
  isCopied = true
177
178
  setTimeout(() => (isCopied = false), 1000)
178
179
  }}
179
- aria-label="Копировать текст"
180
+ aria-label={$T("ui.copy_text")}
180
181
  >
181
182
  {#if !isCopied}
182
183
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
@@ -208,7 +209,7 @@
208
209
  value = roundToClean(Number(value) + (number.step ?? 1))
209
210
  onUpdate(value as number)
210
211
  }}
211
- aria-label="Увеличить">▲</button
212
+ aria-label={$T("ui.increase")}>▲</button
212
213
  >
213
214
  <button
214
215
  class="flex h-1/2 w-full items-center justify-center text-[11px] leading-none text-(--muted-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color)"
@@ -223,7 +224,7 @@
223
224
  value = roundToClean(Number(value) - (number.step ?? 1))
224
225
  onUpdate(value as number)
225
226
  }}
226
- aria-label="Уменьшить">▼</button
227
+ aria-label={$T("ui.decrease")}>▼</button
227
228
  >
228
229
  </div>
229
230
  {/if}
@@ -236,7 +237,7 @@
236
237
  : ''} {disabled ? 'opacity-50' : 'cursor-pointer hover:text-(--accent-color)'}"
237
238
  onmouseenter={() => (showInfo = true)}
238
239
  onmouseleave={() => (showInfo = false)}
239
- aria-label={showInfo ? "Скрыть инфо" : "Показать инфо"}
240
+ aria-label={showInfo ? $T("ui.info.hide") : $T("ui.info.show")}
240
241
  >
241
242
  <InfoIcon />
242
243
  </button>
@@ -251,6 +252,6 @@
251
252
  лишнего зазора, когда ошибки нет) и не абсолютным позиционированием (не наезжает на
252
253
  соседний контент при плотной вертикальной раскладке) — просто раздвигает то, что ниже,
253
254
  как везде в вебе. -->
254
- <span class="block text-[12px] font-semibold text-(--red-color)">Неверный формат</span>
255
+ <span class="block text-[12px] font-semibold text-(--red-color)">{$T("ui.invalid_format")}</span>
255
256
  {/if}
256
257
  </div>
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import TextsEditor from "../TextsEditor.svelte"
2
3
  import { getContext } from "svelte"
3
4
  import { T } from "../locales/i18n"
4
5
  import type { IInputProps, UIComponent, IOption, IUIComponentHandler } from "../types"
@@ -111,11 +112,7 @@
111
112
  {/snippet}
112
113
 
113
114
  {#snippet InputLabel()}
114
- <UI.Input
115
- label={{ name: $T("constructor.props.label") }}
116
- value={component.properties.label.name}
117
- onUpdate={(value) => updateProperty("label.name", value as string)}
118
- />
115
+ <TextsEditor {component} {onPropertyChange} />
119
116
  {/snippet}
120
117
 
121
118
  {#snippet InputLabelAlign()}
@@ -257,22 +254,6 @@
257
254
  {/if}
258
255
  {/snippet}
259
256
 
260
- {#snippet InputPlaceholder()}
261
- <UI.Input
262
- label={{ name: $T("constructor.props.placeholder") }}
263
- value={component.properties.placeholder as string}
264
- onUpdate={(value) => updateProperty("placeholder", value)}
265
- />
266
- {/snippet}
267
-
268
- {#snippet InputInfo()}
269
- <UI.Input
270
- label={{ name: $T("constructor.props.info") }}
271
- value={component.properties.help.info as string}
272
- onUpdate={(value) => updateProperty("help.info", value)}
273
- />
274
- {/snippet}
275
-
276
257
  {#snippet InputSettings()}
277
258
  {#if component.properties.type !== "bitMode"}
278
259
  <UI.Select
@@ -381,8 +362,6 @@
381
362
  </PropsGroup>
382
363
  <PropsGroup label={$T("constructor.props.group.value")}>
383
364
  {@render InputValue()}
384
- {@render InputPlaceholder()}
385
- {@render InputInfo()}
386
365
  {@render InputSettings()}
387
366
  <CommonSnippets snippet="BitModeInfo" {component} {onPropertyChange} />
388
367
  </PropsGroup>
@@ -410,8 +389,6 @@
410
389
  <CommonSnippets snippet="BitModeInfo" {component} {onPropertyChange} />
411
390
  </PropsGroup>
412
391
  <PropsGroup label={$T("constructor.props.group.behavior")}>
413
- {@render InputPlaceholder()}
414
- {@render InputInfo()}
415
392
  {@render InputAutocomplete()}
416
393
  {@render InputSettings()}
417
394
  {@render InputDisabled()}
@@ -39,21 +39,6 @@
39
39
  { name: $T("constructor.props.joystick.roll.axe"), info: $T("constructor.props.joystick.axes.info"), regExp: /^[\p{L}0-9\-_"':{}]*$/u },
40
40
  ]}
41
41
  <div class="flex flex-col gap-1">
42
- <UI.Input
43
- label={{ name: axesOptions[index].name }}
44
- value={axe.name}
45
- help={{ info: axesOptions[index].info, regExp: axesOptions[index].regExp }}
46
- maxlength={20}
47
- onUpdate={(value) => {
48
- updateProperty(
49
- "axes",
50
- component.properties.axes.map((a: any, i: number) => (i === index ? { ...a, name: value } : a)),
51
- component,
52
- onPropertyChange,
53
- )
54
- }}
55
- />
56
-
57
42
  <CommonSnippets
58
43
  snippet="MinMaxStep"
59
44
  initialValue={{
package/dist/Modal.svelte CHANGED
@@ -1,6 +1,7 @@
1
1
  <!-- $lib/Modal.svelte — радиус 20, шапка и подвал через линии,
2
2
  подложка 40% + blur. Логика стека, Escape и клика вне окна не тронута. -->
3
3
  <script lang="ts">
4
+ import { T } from "./locales/i18n"
4
5
  import { type Snippet } from "svelte"
5
6
  import { fade, scale } from "svelte/transition"
6
7
  import { twMerge } from "tailwind-merge"
@@ -96,7 +97,7 @@
96
97
  <button
97
98
  class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-lg text-(--faint-color) transition-colors duration-150 hover:bg-(--container-color) hover:text-(--font-color) [&_svg]:size-4"
98
99
  onclick={onCancel}
99
- aria-label="Закрыть"
100
+ aria-label={$T("ui.close")}
100
101
  >
101
102
  <CrossIcon />
102
103
  </button>
@@ -5,15 +5,17 @@
5
5
 
6
6
  const randomLevelData = (n: number) => Array.from({ length: n }, () => Math.round(Math.random() * 100))
7
7
 
8
+ /* Демо-уровни, когда levels не заданы; name — ключ перевода */
8
9
  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) },
10
+ { name: "ui.period.year", data: randomLevelData(12) },
11
+ { name: "ui.period.month", data: randomLevelData(31) },
12
+ { name: "ui.period.day", data: randomLevelData(24) },
13
+ { name: "ui.period.hour", data: randomLevelData(6) },
13
14
  ]
14
15
  </script>
15
16
 
16
17
  <script lang="ts">
18
+ import { T } from "../locales/i18n"
17
19
  import { twMerge } from "tailwind-merge"
18
20
  import type { IPeriodChartProps } from "../types"
19
21
 
@@ -23,7 +25,7 @@
23
25
  вызовы, которым нужен цвет, по-прежнему могут передать bg-* явно. */
24
26
  wrapperClass = "",
25
27
  label = { name: "", class: "" },
26
- levels = DEFAULT_LEVELS,
28
+ levels: levelsProp,
27
29
  unit = "",
28
30
  onLevelChange = () => {},
29
31
  }: IPeriodChartProps = $props()
@@ -35,6 +37,7 @@
35
37
  if (currentLevelIndex >= levels.length) currentLevelIndex = 0
36
38
  })
37
39
 
40
+ const levels = $derived(levelsProp ?? DEFAULT_LEVELS.map((level) => ({ ...level, name: $T(level.name) })))
38
41
  const currentLevel = $derived<IPeriodChartLevel | undefined>(levels[currentLevelIndex])
39
42
  const values = $derived(currentLevel?.data ?? [])
40
43
 
@@ -103,7 +106,7 @@
103
106
  <!-- Столбчатый график -->
104
107
  <div class="relative flex w-full flex-col gap-1.5 rounded-[12px] border border-(--hairline-color) bg-(--back-color) p-3.5">
105
108
  {#if values.length === 0}
106
- <div class="flex h-40 w-full items-center justify-center text-[13px] text-(--faint-color)">Нет данных</div>
109
+ <div class="flex h-40 w-full items-center justify-center text-[13px] text-(--faint-color)">{$T("ui.no_data")}</div>
107
110
  {:else}
108
111
  <div class="flex w-full gap-2">
109
112
  <!-- Шкала Y: подписи нулевой высоты, центр каждой ровно на своей линии сетки -->
@@ -49,14 +49,6 @@
49
49
  }
50
50
  </script>
51
51
 
52
- {#snippet PeriodChartUnit()}
53
- <UI.Input
54
- label={{ name: $T("constructor.props.units") }}
55
- value={component.properties.unit}
56
- onUpdate={(value) => updateProperty("unit", value, component, onPropertyChange)}
57
- />
58
- {/snippet}
59
-
60
52
  {#snippet PeriodChartLevels()}
61
53
  <PropsGroup label={$T("constructor.props.periodchart.title")} wrapperClass="mt-3">
62
54
  {#snippet headerActions()}
@@ -85,16 +77,6 @@
85
77
  onUpdate={(updatedArray) => updateLevels(updatedArray)}
86
78
  />
87
79
  <div class="flex flex-wrap items-end gap-2">
88
- <UI.Input
89
- label={{ name: $T("constructor.props.optionname") }}
90
- wrapperClass="min-w-28 flex-1"
91
- value={level.name}
92
- onUpdate={(value) => {
93
- const levels = [...(component.properties?.levels || [])]
94
- levels[index] = { ...levels[index], name: value as string }
95
- updateLevels(levels)
96
- }}
97
- />
98
80
  <UI.Select
99
81
  label={{ name: $T("constructor.props.variable") }}
100
82
  wrapperClass="min-w-32 flex-1"
@@ -153,9 +135,6 @@
153
135
  <CommonSnippets snippet="Colors" initialValue={{ color: initialColor, uselessColors: ["bg-max"] }} {component} {onPropertyChange} />
154
136
  <CommonSnippets snippet="Label" {component} {onPropertyChange} />
155
137
  </PropsGroup>
156
- <PropsGroup label={$T("constructor.props.group.value")}>
157
- {@render PeriodChartUnit()}
158
- </PropsGroup>
159
138
  </div>
160
139
  {@render PeriodChartLevels()}
161
140
  {:else}
@@ -168,7 +147,6 @@
168
147
  <PropsGroup label={$T("constructor.props.group.value")}>
169
148
  <CommonSnippets snippet="WrapperClass" {component} {onPropertyChange} />
170
149
  <CommonSnippets snippet="Colors" initialValue={{ color: initialColor, uselessColors: ["bg-max"] }} {component} {onPropertyChange} />
171
- {@render PeriodChartUnit()}
172
150
  </PropsGroup>
173
151
  </div>
174
152
  {@render PeriodChartLevels()}