poe-svelte-ui-lib 1.47.0 → 1.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -110,7 +110,7 @@ npm install poe-svelte-ui-lib
110
110
 
111
111
  **README компонента** (`src/lib/<Компонент>/README.md`, вкладка «Инфо» витрины) — развёрнутая документация для разработчика. Первые разделы повторяют описание (i) и не должны ему противоречить, дальше идут разработческие: `## Назначение` (плюс заметка о размерах) → `## Пакет данных` (плюс `### Протокол устройства` / пример кода прошивки) → `## Ключи` (таблица «Ключ / Описание», те же формулировки, что в (i)) → `## Особенности` (поведение, ограничения) → `## Пропсы` (таблица «Название / Тип / По умолчанию / Описание», колбэки — `### Колбэки`) → `## Примеры` (Svelte) → `## Конструктор свойств (<Компонент>Props.svelte)` → `## Внутреннее устройство`.
112
112
 
113
- **Языки — единственный источник в библиотеке.** Список языков — `LANGUAGES` в `locales/languages.ts`: код, название и флаг (SVG-строка), без Svelte — годится и для серверного кода. **Новый язык — одна запись в `LANGUAGES`**: переключатели языка во всех приложениях (`LOCALES`), колонки окна «Тексты и переводы», многоязычные тексты GUI и словари подхватывают его сами; пока перевода нет — показывается текст языка по умолчанию (`DEFAULT_LANGUAGE`, `ru`). Словари (`Translations`) — строки-объекты по кодам `{ ru: "…", en: "…", zh: "…" }`: обязателен только язык по умолчанию, порядок не важен; недостающие переводы показывает `findMissingTranslations(словарь)`. `locales/i18n.ts` — текущий язык `Language`/`setLanguage` (неизвестный код не применяется, выбор сохраняется в `localStorage` `AppLanguage`), `restoreLanguage()` (сохранённый или язык браузера; стартовое значение всегда язык по умолчанию — серверный рендер совпадает с гидратацией, поэтому SSR-приложения вызывают его в `onMount` корневого layout, SPA — при старте), `LOCALES` (с флагом `flag`) и механизм перевода `createTranslator(словарь)`. Приложения (DevCloud, WebApp, терминал) свой список языков, хранилище и функцию перевода не держат: берут их из библиотеки и строят свой `$T` из своего словаря — `export const T = createTranslator(translations)`. Одно `setLanguage` переключает и компоненты библиотеки, и приложение. Многоязычные тексты GUI распознаются по виду ключей (коды языков), поэтому данные с языком, убранным из списка, не ломаются.
113
+ **Языки — единственный источник в библиотеке.** Список языков — `LANGUAGES` в `locales/languages.ts`: код, название и флаг (SVG-строка), без Svelte — годится и для серверного кода. **Новый язык — одна запись в `LANGUAGES`**: переключатели языка во всех приложениях (`LOCALES`), колонки окна «Тексты и переводы», многоязычные тексты GUI и словари подхватывают его сами; пока перевода нет — показывается текст языка по умолчанию (`DEFAULT_LANGUAGE`, `ru`). Словари (`Translations`) — строки-объекты по кодам `{ ru: "…", en: "…", zh: "…" }`: обязателен только язык по умолчанию, порядок не важен; недостающие переводы показывает `findMissingTranslations(словарь)`. `locales/i18n.ts` — текущий язык `Language`/`setLanguage` (неизвестный код не применяется, выбор сохраняется в `localStorage` `AppLanguage`), `restoreLanguage()` (сохранённый или язык браузера; стартовое значение всегда язык по умолчанию — серверный рендер совпадает с гидратацией, поэтому SSR-приложения вызывают его в `onMount` корневого layout, SPA — при старте), `LOCALES` (с флагом `flag`) и механизм перевода `createTranslator(словарь)`. Код вне компонентов берёт текущий язык через `getLanguage()` (например, для заголовка `Accept-Language`), ключ хранилища и язык по умолчанию в приложениях не повторяются; серверный код переводит функцией `translate(словарь, язык, ключ)` на язык запроса, разобранный `resolveLanguage(Accept-Language)`. Приложения (DevCloud, WebApp, терминал) свой список языков, хранилище и функцию перевода не держат: берут их из библиотеки и строят свой `$T` из своего словаря — `export const T = createTranslator(translations)`. Одно `setLanguage` переключает и компоненты библиотеки, и приложение. Многоязычные тексты GUI распознаются по виду ключей (коды языков), поэтому данные с языком, убранным из списка, не ломаются.
114
114
 
115
115
  **Языки (ru/en/zh) — два слоя:**
116
116
 
@@ -38,7 +38,8 @@
38
38
 
39
39
  /* Перетаскивание с MouseEvent */
40
40
  const startDrag = async (e: MouseEvent) => {
41
- const movedElement = document.querySelector(
41
+ /* Строка ищется только внутри своего списка — id вида item-N есть и у других списков на странице */
42
+ const movedElement = container.querySelector(
42
43
  containerIndex == null ? `#${elementsId}-${elementIndex}` : `#${elementsId}-${elementIndex}-${containerIndex}`,
43
44
  ) as HTMLElement
44
45
 
@@ -38,6 +38,7 @@
38
38
  | `size.height` | `number` | `6` | Количество строк в сетке содержимого |
39
39
  | `activeTab` | `number` | `0` | Индекс активной вкладки; `$bindable` — `bind:activeTab` получает выбор по клику, изменение снаружи переключает вкладку |
40
40
  | `items` | `{ name?: string; icon?: ConstructorOfATypedSvelteComponent \| string; class?: string; children?: Snippet; onClick?: () => {}; disabled?: boolean }[]` | `[{ name: "tab 1", icon: "", class: "" }, { name: "tab 2", icon: "", class: "" }]` | Массив элементов вкладок |
41
+ | `items[].id` | `string` | `undefined` | Стабильный id вкладки: окно свойств выдаёт его каждой вкладке, конструктор по нему переносит содержимое при перестановке/удалении вкладок (в GUI содержимое привязано к номеру вкладки — последний символ id дочернего компонента) |
41
42
  | `items[].name` | `string` | `undefined` | Название (заголовок) вкладки |
42
43
  | `items[].icon` | `ConstructorOfATypedSvelteComponent \| string` | `undefined` | Иконка вкладки (SVG строка или Svelte-компонент) |
43
44
  | `items[].class` | `string` | `undefined` | CSS-классы для стилизации вкладки; `flex-row`/`flex-col`/`flex-row-reverse`/`flex-col-reverse` — позиция иконки относительно текста; `flex-1` — вкладка растягивается на равную долю ширины (учитывает отступы между кнопками), `w-auto` — ширина по контенту |
@@ -127,6 +128,8 @@
127
128
 
128
129
  ## Конструктор свойств (TabsProps.svelte)
129
130
 
131
+ Окно «Вкладки» — список вкладок: у каждой строки заголовок «Вкладка N · название на текущем языке» (или «без названия»), иконка, «Отключить», перетаскивание за ручку слева и удаление; названия правятся в «Тексты и переводы». Новая вкладка создаётся без названия.
132
+
130
133
  ### Описание
131
134
 
132
135
  Компонент `TabsProps.svelte` предоставляет визуальный интерфейс для редактирования свойств компонента вкладок. Поддерживает два режима отображения: для конструктора и для редактирования.
@@ -11,6 +11,8 @@
11
11
  import CommonSnippets from "../CommonSnippets.svelte"
12
12
  import PropsGroup from "../PropsGroup.svelte"
13
13
  import PropsListModal from "../PropsListModal.svelte"
14
+ import { Language } from "../locales/i18n"
15
+ import { localizeText } from "../locales/localize"
14
16
 
15
17
  const {
16
18
  component,
@@ -24,6 +26,18 @@
24
26
 
25
27
  let itemsContainer: HTMLDivElement | null = $state(null)
26
28
 
29
+ /* У каждой вкладки — стабильный id: по нему конструктор переносит её содержимое при перестановке и удалении вкладок */
30
+ $effect(() => {
31
+ const items = component.properties.items ?? []
32
+ if (items.every((item: { id?: string }) => item.id)) return
33
+ updateProperty(
34
+ "items",
35
+ items.map((item: { id?: string }) => (item.id ? item : { ...item, id: crypto.randomUUID() })),
36
+ component,
37
+ onPropertyChange,
38
+ )
39
+ })
40
+
27
41
  const initialColor = $derived(findColorOption($optionsStore.COLOR_OPTIONS, component.properties.wrapperClass))
28
42
 
29
43
  const initialPosition = $derived.by(() => {
@@ -91,8 +105,9 @@
91
105
  wrapperClass="w-8"
92
106
  content={{ icon: ButtonAdd }}
93
107
  onClick={() => {
94
- const newItem: { name: string; icon: string; class: string } = {
95
- name: `Tab ${component.properties?.items.length + 1}`,
108
+ const newItem: { id: string; name: string; icon: string; class: string } = {
109
+ id: crypto.randomUUID(),
110
+ name: "",
96
111
  class: `text-${initialColor?.value.slice(3)}-500 ${initialPosition?.value}`,
97
112
  icon: "",
98
113
  }
@@ -122,6 +137,11 @@
122
137
  }}
123
138
  />
124
139
  <div class="flex flex-wrap items-end gap-2">
140
+ <!-- Какая это вкладка: номер и название на текущем языке (название правится в «Тексты и переводы») -->
141
+ <div class="flex w-full items-baseline gap-2 text-[13px]">
142
+ <span class="font-semibold">{$T("constructor.props.tabs.item", { n: index + 1 })}</span>
143
+ <span class="truncate text-(--muted-color)">{localizeText(tab.name, $Language) || $T("constructor.props.tabs.untitled")}</span>
144
+ </div>
125
145
  <div class="relative min-w-32">
126
146
  <CommonSnippets
127
147
  snippet="IconsLib"
@@ -1,6 +1,7 @@
1
1
  import { type Readable } from "svelte/store";
2
2
  import { type LanguageCode, type Translations } from "./languages";
3
3
  export declare const Language: import("svelte/store").Writable<string>;
4
+ export declare const getLanguage: () => LanguageCode;
4
5
  export declare function restoreLanguage(): void;
5
6
  export declare function setLanguage(newLang: string): void;
6
7
  export declare const LOCALES: {
@@ -11,5 +12,6 @@ export declare const LOCALES: {
11
12
  }[];
12
13
  export type TranslateParams = Record<string, string | number>;
13
14
  export type Translator = (key: string, params?: TranslateParams) => string;
15
+ export declare const translate: (dictionary: Translations, locale: string, key: string, params?: TranslateParams) => string;
14
16
  export declare const createTranslator: (dictionary: Translations) => Readable<Translator>;
15
17
  export declare const T: Readable<Translator>;
@@ -1,6 +1,6 @@
1
- import { derived, writable } from "svelte/store";
1
+ import { derived, get, writable } from "svelte/store";
2
2
  import translations from "./translations";
3
- import { DEFAULT_LANGUAGE, LANGUAGES, isLanguageCode } from "./languages";
3
+ import { DEFAULT_LANGUAGE, LANGUAGES, isLanguageCode, resolveLanguage } from "./languages";
4
4
  /* Язык и механизм перевода — общие для библиотеки и приложений: приложение берёт отсюда Language/setLanguage/LOCALES и строит свой $T
5
5
  из своего словаря через createTranslator (см. README «Языки»); переключение языка одним setLanguage меняет и компоненты, и приложение */
6
6
  /*
@@ -8,6 +8,8 @@ import { DEFAULT_LANGUAGE, LANGUAGES, isLanguageCode } from "./languages";
8
8
  гидратации); сохранённый выбор применяет restoreLanguage(): в SSR-приложениях — в onMount корневого layout, в SPA — при старте
9
9
  */
10
10
  export const Language = writable(DEFAULT_LANGUAGE);
11
+ /* Текущий язык — для заголовков запросов (Accept-Language) и прочего кода вне компонентов */
12
+ export const getLanguage = () => resolveLanguage(get(Language));
11
13
  /* Сохранённый язык → язык браузера; неизвестный код не применяется */
12
14
  export function restoreLanguage() {
13
15
  if (typeof window === "undefined")
@@ -44,8 +46,8 @@ export const LOCALES = LANGUAGES.map((language) => ({
44
46
  value: language.value,
45
47
  flag: language.flag,
46
48
  }));
47
- /* Перевод по словарю: текущий язык → язык по умолчанию → сам ключ */
48
- const translate = (dictionary, locale, key, params) => {
49
+ /* Перевод по словарю на заданный язык: язык → язык по умолчанию → сам ключ; без хранилища — для серверного кода (язык запроса) */
50
+ export const translate = (dictionary, locale, key, params) => {
49
51
  const row = dictionary[key];
50
52
  const text = (row && (row[locale] || row[DEFAULT_LANGUAGE])) || key;
51
53
  return params ? text.replace(/\{(\w+)\}/g, (match, name) => (name in params ? String(params[name]) : match)) : text;
@@ -17,6 +17,7 @@ export declare const LANGUAGE_CODES: LanguageCode[];
17
17
  export type Translation = Record<typeof DEFAULT_LANGUAGE, string> & Partial<Record<LanguageCode, string>>;
18
18
  export type Translations = Record<string, Translation>;
19
19
  export declare const isLanguageCode: (value: unknown) => value is LanguageCode;
20
+ export declare const resolveLanguage: (value: string | null | undefined) => LanguageCode;
20
21
  export declare const findMissingTranslations: (dictionary: Translations) => {
21
22
  key: string;
22
23
  languages: LanguageCode[];
@@ -26,6 +26,11 @@ export const DEFAULT_LANGUAGE = "ru";
26
26
  /* Коды в порядке LANGUAGES — порядок показа в переключателях и колонках переводов */
27
27
  export const LANGUAGE_CODES = LANGUAGES.map((language) => language.value);
28
28
  export const isLanguageCode = (value) => LANGUAGE_CODES.includes(value);
29
+ /* Код языка из произвольного значения (код, заголовок Accept-Language «en-US,en;q=0.9», язык браузера); неизвестный — язык по умолчанию */
30
+ export const resolveLanguage = (value) => {
31
+ const code = value?.split(/[,;]/)[0]?.trim().split("-")[0]?.toLowerCase();
32
+ return isLanguageCode(code) ? code : DEFAULT_LANGUAGE;
33
+ };
29
34
  /* Ключи словаря без перевода на какой-либо язык — для проверки полноты словаря при добавлении языка */
30
35
  export const findMissingTranslations = (dictionary) => Object.entries(dictionary)
31
36
  .map(([key, row]) => ({ key, languages: LANGUAGE_CODES.filter((code) => !row[code]) }))
@@ -304,7 +304,9 @@ const translations = {
304
304
  "constructor.props.options.title": { ru: "Опции компонента", en: "Component options", zh: "组件选项" },
305
305
  "constructor.props.progressbar.title": { ru: "Полосы прогресса", en: "Progress bars", zh: "进度条" },
306
306
  "constructor.props.bits.title": { ru: "Биты", en: "Bits", zh: "位" },
307
- "constructor.props.tabs.title": { ru: "Вкладки меню", en: "Menu tabs", zh: "菜单标签" },
307
+ "constructor.props.tabs.title": { ru: "Вкладки", en: "Tabs", zh: "标签页" },
308
+ "constructor.props.tabs.item": { ru: "Вкладка {n}", en: "Tab {n}", zh: "标签页 {n}" },
309
+ "constructor.props.tabs.untitled": { ru: "без названия", en: "untitled", zh: "未命名" },
308
310
  "constructor.props.periodchart.title": { ru: "Уровни детализации", en: "Detail levels", zh: "详细级别" },
309
311
  "constructor.props.periodchart.data": { ru: "Данные (через пробел)", en: "Data (comma-separated)", zh: "数据(逗号分隔)" },
310
312
  "constructor.props.periodchart.labels": { ru: "Подписи столбиков (через пробел)", en: "Bar labels (comma-separated)", zh: "柱标签(逗号分隔)" },
package/dist/types.d.ts CHANGED
@@ -396,6 +396,7 @@ export interface ITabsProps {
396
396
  };
397
397
  activeTab?: number;
398
398
  items: {
399
+ id?: string;
399
400
  name?: string;
400
401
  icon?: ConstructorOfATypedSvelteComponent | string;
401
402
  class?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.47.0",
3
+ "version": "1.49.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"