poe-svelte-ui-lib 1.59.0 → 1.61.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
@@ -59,6 +59,8 @@ npm install poe-svelte-ui-lib
59
59
 
60
60
  **Названия смарт-виджетов** — в словаре библиотеки на всех языках (WiFi, «Об устройстве», «Файловая система», «Стек и задачи», «RGB лента», «Пилотажный дисплей», «Карта»); пустой `label.name` показывает название на языке пользователя, `widgetTitle(type)` отдаёт его сразу на всех языках `{ ru, en, zh }` — так конструктор заполняет заголовок нового виджета.
61
61
 
62
+ **Шапка смарт-виджета** (`WidgetHeader`): значок, заголовок и сворачивание слева, «?» в правом краю; правая часть (режимы, «Обновить» и т. п.) стоит в той же строке, а если не помещается — уходит второй строкой под заголовок и там переносится.
63
+
62
64
  **Подсказки смарт-виджетов** — `hints.ts`: у каждого виджета своя кнопка «?» в правом краю шапки (`WidgetHeader` с пропом `hints`), по умолчанию подсказки выключены, выбор оператора запоминается в браузере для виджетов этого типа (`hintsSetting("WidgetPFD")`, ключ `localStorage` `WidgetHints.<тип>`, пишется только по нажатию «?»), глобального переключателя нет. Элемент получает подсказку действием `use:hint={{ text, enabled: $hints }}` — всплывающее окно в стиле темы над элементом; тексты — в словаре библиотеки (`widget.*.hint.*`), только там, где смысл поля не очевиден.
63
65
 
64
66
  **Предпросмотр темы** — вкладка «Тема» на главной витрины (`src/routes/ThemePreview.svelte`): палитра токенов и состояния, затем все компоненты и виджеты в порядке меню витрины — каждый тип в своём разделе с живыми демо-данными (графики, видео, полёт для PFD и карты), в конце типографика и Markdown; тема переключается кнопкой в шапке витрины. Правьте `theme.css` и смотрите результат там.
@@ -37,21 +37,22 @@
37
37
 
38
38
  ## Пропсы
39
39
 
40
- | Название | Тип | По умолчанию | Описание |
41
- | ---------------- | --------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
42
- | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
43
- | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
44
- | `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого поля/кнопок выбора |
45
- | `disabled` | `boolean` | `false` | Состояние отключения компонента |
46
- | `label` | `object` | `-` | Настройки заголовка компонента |
47
- | `label.name` | `string` | `""` | Текст заголовка компонента |
48
- | `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
49
- | `type` | `"select" \| "buttons" \| "input"` | `"select"` | Тип отображения: `select` — выпадающий список с кнопкой, `buttons` — горизонтальный ряд кнопок, `input` — поле ввода с автодополнением и поиском |
50
- | `multiSelect` | `boolean` | `false` | Режим множественного выбора: работает только с `type: "buttons"`; при `true` позволяет выбирать несколько опций, значение передаётся как массив |
51
- | `listHeight` | `string` | `"16rem"` | Максимальная высота выпадающего списка опций (для типов `select`/`input`) |
52
- | `value` | `IOption<T> \| IOption<T>[] \| undefined` | `undefined` | Текущее выбранное значение(я); поддерживает двустороннее связывание (`$bindable`) |
53
- | `options` | `IOption<T>[]` | `[]` | Массив доступных опций |
54
- | `onUpdate` | `(value: IOption<T> \| IOption<T>[]) => void` | `undefined` | Обработчик изменения значения |
40
+ | Название | Тип | По умолчанию | Описание |
41
+ | ---------------- | --------------------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
+ | `id` | `string` | `crypto.randomUUID()` | Уникальный идентификатор компонента |
43
+ | `wrapperClass` | `string` | `""` | Дополнительные CSS-классы для обертки компонента |
44
+ | `componentClass` | `string` | `""` | Дополнительные CSS-классы для самого поля/кнопок выбора |
45
+ | `disabled` | `boolean` | `false` | Состояние отключения компонента |
46
+ | `label` | `object` | `-` | Настройки заголовка компонента |
47
+ | `label.name` | `string` | `""` | Текст заголовка компонента |
48
+ | `label.class` | `string` | `""` | CSS-классы для стилизации текста заголовка |
49
+ | `type` | `"select" \| "buttons" \| "input"` | `"select"` | Тип отображения: `select` — выпадающий список с кнопкой, `buttons` — горизонтальный ряд кнопок, `input` — поле ввода с автодополнением и поиском |
50
+ | `multiSelect` | `boolean` | `false` | Режим множественного выбора: работает только с `type: "buttons"`; при `true` позволяет выбирать несколько опций, значение передаётся как массив |
51
+ | `fitContent` | `boolean` | `false` | Для `type="buttons"`: группа по ширине содержимого — все кнопки одинаковой ширины по самой длинной подписи, текст без переноса и обрезки (шапки, переключатели режимов) |
52
+ | `listHeight` | `string` | `"16rem"` | Максимальная высота выпадающего списка опций (для типов `select`/`input`) |
53
+ | `value` | `IOption<T> \| IOption<T>[] \| undefined` | `undefined` | Текущее выбранное значение(я); поддерживает двустороннее связывание (`$bindable`) |
54
+ | `options` | `IOption<T>[]` | `[]` | Массив доступных опций |
55
+ | `onUpdate` | `(value: IOption<T> \| IOption<T>[]) => void` | `undefined` | Обработчик изменения значения |
55
56
 
56
57
  > **`bitMode`/`range`**: эти поля объявлены в типе `ISelectProps` и настраиваются в конструкторской панели (`SelectProps.svelte`), но сам `Select.svelte` их не читает и не использует — они не оказывают никакого эффекта на голый `<UI.Select>`. Если нужен битовый выбор в реальном рантайме, интерпретируйте маску на стороне вызывающего кода и передавайте уже готовый `value`/`options`.
57
58
 
@@ -55,6 +55,7 @@
55
55
  disabled = false,
56
56
  label = { name: "", class: "" },
57
57
  multiSelect = false,
58
+ fitContent = false,
58
59
  listHeight = "16rem",
59
60
  type = "select",
60
61
  value = $bindable(),
@@ -206,7 +207,9 @@
206
207
  <!-- Сплошная группа: общий контур, разделители внутри, без зазоров и теней -->
207
208
  <div
208
209
  id={`${id}-${crypto.randomUUID().slice(0, 6)}`}
209
- class="flex w-full rounded-[10px] border border-(--border-color) {disabled ? 'opacity-45' : ''}"
210
+ class="{fitContent ? 'grid w-max auto-cols-fr grid-flow-col' : 'flex w-full'} rounded-[10px] border border-(--border-color) {disabled
211
+ ? 'opacity-45'
212
+ : ''}"
210
213
  role="group"
211
214
  >
212
215
  {#each options as option, index (option.id)}
@@ -221,7 +224,7 @@
221
224
  для обычного сегмент-контрола без своего цвета (Access, Header и т.п.) поведение
222
225
  прежнее — акцентная заливка только у выбранного пункта. -->
223
226
  <!-- Обёртка — позиционирующий предок подсказки; overflow-hidden у группы убран, иначе подсказка обрезается -->
224
- <div class="relative flex min-w-0 flex-1">
227
+ <div class="relative flex {fitContent ? '' : 'min-w-0 flex-1'}">
225
228
  <button
226
229
  id={option.id}
227
230
  class="{twMerge(
@@ -237,7 +240,8 @@
237
240
  ? 'text-(--on-accent)'
238
241
  : 'text-(--font-color) hover:bg-(--container-color)'
239
242
  }
240
- ${option.disabled || disabled ? 'cursor-not-allowed text-(--faint-color)' : 'cursor-pointer'}`,
243
+ ${option.disabled || disabled ? 'cursor-not-allowed text-(--faint-color)' : 'cursor-pointer'}
244
+ ${fitContent ? 'px-3 whitespace-nowrap' : ''}`,
241
245
  option.class,
242
246
  )} {hasOwnColor ? 'bg-(--bg-color)' : selected ? 'bg-(--bg-color,var(--accent-color))' : 'bg-(--back-color)'}"
243
247
  onclick={(e) => selectOption(option, e)}
@@ -27,42 +27,57 @@
27
27
  } = $props()
28
28
 
29
29
  const Icon = $derived(icon)
30
+
31
+ /* Правая часть уходит второй строкой, если не помещается рядом с заголовком (место под значок, заголовок и «?») */
32
+ const TITLE_MIN_PX = 180
33
+ let headerWidth = $state(0)
34
+ let rightWidth = $state(0)
35
+ let wrapped = $derived(!!right && headerWidth > 0 && rightWidth + TITLE_MIN_PX + (hints ? 34 : 0) > headerWidth)
30
36
  </script>
31
37
 
32
- <!-- Фиксированная высота 32 px без переноса — свёрнутые виджеты одной высоты при любом содержимом справа; «?» — в правом краю, после содержимого шапки -->
33
- <div class="flex h-[32px] items-center justify-between gap-3">
34
- <button
35
- type="button"
36
- class="flex min-w-0 flex-1 cursor-pointer items-center gap-2 border-none bg-transparent p-0 text-left"
37
- onclick={() => (collapsed = !collapsed)}
38
- aria-expanded={!collapsed}
39
- >
40
- <span class="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-(--accent-color) [&_svg]:h-full [&_svg]:w-full">
41
- <Icon />
42
- </span>
43
- <span class="min-w-0 truncate text-[14px] font-semibold">{label}</span>
44
- <span
45
- class="flex h-[14px] w-[14px] shrink-0 items-center justify-center text-(--faint-color) transition-transform duration-150 [&_svg]:h-full [&_svg]:w-full"
46
- style="transform: rotate({collapsed ? 0 : 90}deg)"
47
- >
48
- <ArrowIcon />
49
- </span>
50
- </button>
51
- {#if right}
52
- <div class="flex min-w-0 shrink-0 items-center justify-end">
53
- {@render right()}
54
- </div>
55
- {/if}
56
- {#if hints}
38
+ <!-- Первая строка фиксированной высоты 32 px — свёрнутые виджеты одной высоты; «?» — в правом краю.
39
+ Правая часть стоит в первой строке, а если не помещается — второй строкой под заголовком (выравнивание вправо) -->
40
+ <div class="flex flex-col gap-2" bind:clientWidth={headerWidth}>
41
+ <div class="flex h-[32px] items-center justify-between gap-3">
57
42
  <button
58
43
  type="button"
59
- class="flex size-[22px] shrink-0 cursor-pointer items-center justify-center rounded-full border text-[12px] font-bold transition-colors duration-150
60
- {$hints
61
- ? 'border-(--accent-color) bg-(--accent-soft) text-(--accent-color)'
62
- : 'border-(--hairline-color) bg-transparent text-(--faint-color) hover:text-(--muted-color)'}"
63
- title={$hints ? $T("widget.hints.on") : $T("widget.hints.off")}
64
- aria-pressed={$hints}
65
- onclick={() => hints.update((on) => !on)}>?</button
44
+ class="flex min-w-0 flex-1 cursor-pointer items-center gap-2 border-none bg-transparent p-0 text-left"
45
+ onclick={() => (collapsed = !collapsed)}
46
+ aria-expanded={!collapsed}
66
47
  >
48
+ <span class="flex h-[18px] w-[18px] shrink-0 items-center justify-center text-(--accent-color) [&_svg]:h-full [&_svg]:w-full">
49
+ <Icon />
50
+ </span>
51
+ <span class="min-w-0 truncate text-[14px] font-semibold">{label}</span>
52
+ <span
53
+ class="flex h-[14px] w-[14px] shrink-0 items-center justify-center text-(--faint-color) transition-transform duration-150 [&_svg]:h-full [&_svg]:w-full"
54
+ style="transform: rotate({collapsed ? 0 : 90}deg)"
55
+ >
56
+ <ArrowIcon />
57
+ </span>
58
+ </button>
59
+ {#if right && !wrapped}
60
+ <div class="flex w-max shrink-0 items-center justify-end" bind:clientWidth={rightWidth}>
61
+ {@render right()}
62
+ </div>
63
+ {/if}
64
+ {#if hints}
65
+ <button
66
+ type="button"
67
+ class="flex size-[22px] shrink-0 cursor-pointer items-center justify-center rounded-full border text-[12px] font-bold transition-colors duration-150
68
+ {$hints
69
+ ? 'border-(--accent-color) bg-(--accent-soft) text-(--accent-color)'
70
+ : 'border-(--hairline-color) bg-transparent text-(--faint-color) hover:text-(--muted-color)'}"
71
+ title={$hints ? $T("widget.hints.on") : $T("widget.hints.off")}
72
+ aria-pressed={$hints}
73
+ onclick={() => hints.update((on) => !on)}>?</button
74
+ >
75
+ {/if}
76
+ </div>
77
+ {#if right && wrapped}
78
+ <!-- Во второй строке элементы могут переноситься; естественная ширина правой части — из первой строки -->
79
+ <div class="flex max-w-full flex-wrap items-center justify-end gap-2">
80
+ {@render right()}
81
+ </div>
67
82
  {/if}
68
83
  </div>
@@ -129,7 +129,7 @@
129
129
  <!-- Заголовок -->
130
130
  <WidgetHeader icon={MemoryIcon} label={label?.name || $T("widget.stack.title")} bind:collapsed {hints}>
131
131
  {#snippet right()}
132
- <div class="flex items-center gap-2" use:hint={{ text: $T("widget.stack.hint.poll"), enabled: $hints }}>
132
+ <div class="flex flex-wrap items-center justify-end gap-2" use:hint={{ text: $T("widget.stack.hint.poll"), enabled: $hints }}>
133
133
  <UI.Select
134
134
  wrapperClass="w-64"
135
135
  type="buttons"
package/dist/types.d.ts CHANGED
@@ -247,6 +247,7 @@ export interface ISelectProps<T = unknown> {
247
247
  };
248
248
  type?: "select" | "buttons" | "input";
249
249
  multiSelect?: boolean;
250
+ fitContent?: boolean;
250
251
  listHeight?: string;
251
252
  value?: IOption<T> | IOption<T>[] | null;
252
253
  options?: IOption<T>[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-svelte-ui-lib",
3
- "version": "1.59.0",
3
+ "version": "1.61.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Oleg-Perevyshin/ElementsUI.git"