poe-svelte-ui-lib 1.87.0 → 1.89.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 +1 -1
- package/dist/Accordion/Accordion.svelte +2 -2
- package/dist/Accordion/README.md +1 -1
- package/dist/Button/Button.svelte +2 -2
- package/dist/Button/ButtonProps.svelte +1 -1
- package/dist/Button/README.md +1 -1
- package/dist/CommonSnippets.svelte +3 -2
- package/dist/CopiedChip.svelte +1 -1
- package/dist/Divider/Divider.svelte +1 -1
- package/dist/Divider/README.md +1 -1
- package/dist/Dragging.svelte +4 -4
- package/dist/FileAttach/FileAttach.svelte +4 -4
- package/dist/Graph/Graph.svelte +10 -10
- package/dist/Graph/README.md +1 -1
- package/dist/Input/Input.svelte +2 -2
- package/dist/Input/InputProps.svelte +7 -3
- package/dist/Joystick/Joystick.svelte +1 -1
- package/dist/Modal.svelte +2 -2
- package/dist/PeriodChart/PeriodChart.svelte +3 -3
- package/dist/PeriodChart/PeriodChartProps.svelte +1 -1
- package/dist/ProgressBar/ProgressBar.svelte +1 -1
- package/dist/PropsGroup.svelte +1 -1
- package/dist/PropsListHeader.svelte +1 -1
- package/dist/PropsListModal.svelte +1 -1
- package/dist/Select/Select.svelte +4 -4
- package/dist/Select/SelectProps.svelte +2 -2
- package/dist/Slider/Slider.svelte +8 -8
- package/dist/Switch/Switch.svelte +3 -3
- package/dist/Switch/SwitchProps.svelte +1 -1
- package/dist/Table/Table.svelte +4 -4
- package/dist/Table/TableProps.svelte +3 -3
- package/dist/Table/columnWidth.js +1 -1
- package/dist/Tabs/Tabs.svelte +3 -3
- package/dist/Tabs/TabsProps.svelte +2 -2
- package/dist/TextField/TextField.svelte +2 -2
- package/dist/TextsEditor.svelte +3 -3
- package/dist/Tooltip.svelte +1 -1
- package/dist/Widget/Widget.svelte +3 -3
- package/dist/Widget/WidgetProps.svelte +1 -1
- package/dist/WidgetAccentPicker.svelte +1 -1
- package/dist/WidgetDeviceInfo/README.md +1 -1
- package/dist/WidgetDeviceInfo/WidgetDeviceInfo.svelte +9 -9
- package/dist/WidgetDeviceInfo/WidgetDeviceInfoProps.svelte +1 -1
- package/dist/WidgetFileSystem/README.md +1 -1
- package/dist/WidgetFileSystem/WidgetFileSystem.svelte +7 -7
- package/dist/WidgetFileSystem/WidgetFileSystemProps.svelte +1 -1
- package/dist/WidgetHeader.svelte +4 -4
- package/dist/WidgetMap/README.md +58 -58
- package/dist/WidgetMap/WidgetMap.svelte +18 -15
- package/dist/WidgetMap/WidgetMapProps.svelte +1 -1
- package/dist/WidgetMap/mapWrapper/Line.svelte +2 -2
- package/dist/WidgetMap/mapWrapper/MapLibre.svelte +3 -3
- package/dist/WidgetMap/mapWrapper/utils.js +2 -2
- package/dist/WidgetMap/mission/MapAgro.svelte +48 -31
- package/dist/WidgetMap/mission/MapRoute.svelte +8 -7
- package/dist/WidgetMap/mission/MissionLayers.svelte +3 -3
- package/dist/WidgetMap/mission/MissionSim.svelte +3 -3
- package/dist/WidgetMap/mission/SpinnerIcon.svelte +10 -0
- package/dist/WidgetMap/mission/SpinnerIcon.svelte.d.ts +18 -0
- package/dist/WidgetMap/mission/coverage.d.ts +1 -0
- package/dist/WidgetMap/mission/coverage.js +58 -5
- package/dist/WidgetMap/mission/plan.js +2 -2
- package/dist/WidgetMap/mission/progress.d.ts +1 -0
- package/dist/WidgetMap/mission/progress.js +4 -4
- package/dist/WidgetMap/mission/route.js +1 -1
- package/dist/WidgetPFD/README.md +9 -9
- package/dist/WidgetPFD/WidgetPFD.svelte +26 -26
- package/dist/WidgetPFD/WidgetPFDProps.svelte +2 -2
- package/dist/WidgetRGBStrip/ColorPicker.svelte +1 -1
- package/dist/WidgetRGBStrip/README.md +1 -1
- package/dist/WidgetRGBStrip/WidgetRGBStrip.svelte +7 -7
- package/dist/WidgetRGBStrip/WidgetRGBStripProps.svelte +1 -1
- package/dist/WidgetStackInfo/README.md +1 -1
- package/dist/WidgetStackInfo/WidgetStackInfo.svelte +10 -10
- package/dist/WidgetStackInfo/WidgetStackInfoProps.svelte +1 -1
- package/dist/WidgetWiFi/README.md +1 -1
- package/dist/WidgetWiFi/WidgetWiFi.svelte +11 -11
- package/dist/WidgetWiFi/WidgetWiFiProps.svelte +1 -1
- package/dist/bitField.js +1 -1
- package/dist/dragStyle.js +3 -3
- package/dist/hints.js +4 -4
- package/dist/libIcons/propsIcons.js +1 -1
- package/dist/locales/i18n.js +9 -9
- package/dist/locales/index.js +1 -1
- package/dist/locales/languages.js +4 -4
- package/dist/locales/localize.js +7 -7
- package/dist/locales/translations.js +246 -284
- package/dist/options.js +2 -2
- package/dist/protocols/can.js +6 -6
- package/dist/protocols/cbor/decoder.js +2 -2
- package/dist/protocols/cbor/encoder.js +1 -1
- package/dist/protocols/cbor/json.js +2 -2
- package/dist/protocols/cbor/value.js +4 -4
- package/dist/protocols/crc.js +2 -2
- package/dist/protocols/dictionary.js +2 -2
- package/dist/protocols/dictionary.yaml +5 -5
- package/dist/protocols/dictionaryCodes.js +1 -1
- package/dist/protocols/index.js +6 -6
- package/dist/protocols/packet.js +11 -11
- package/dist/protocols/plotGroups.js +2 -2
- package/dist/protocols/xPoeDebug.js +8 -8
- package/dist/protocols/xPoeDebugView.js +8 -8
- package/dist/theme.css +12 -12
- package/dist/types.js +1 -1
- package/dist/widgetAccent.js +4 -4
- package/dist/widgetCollapse.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ npm install poe-svelte-ui-lib
|
|
|
74
74
|
|---|---|
|
|
75
75
|
| `component` | Компонент интерфейса и его свойства |
|
|
76
76
|
| `onPropertyChange` | Сообщает об изменении свойств, имени, доступа или события |
|
|
77
|
-
| `forConstructor` | `true`
|
|
77
|
+
| `forConstructor` | `true` - полная панель конструктора, `false` - упрощённая для редактирования |
|
|
78
78
|
|
|
79
79
|
## Протоколы
|
|
80
80
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Accordion/Accordion.svelte
|
|
1
|
+
<!-- $lib/Accordion/Accordion.svelte - заголовок не увеличивается при раскрытии, радиус 14. Мастер-цвет (componentClass) - как у смарт-виджетов: иконка и текст заголовка цветом, фон заголовка - его мягким оттенком. -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { slide } from "svelte/transition"
|
|
4
4
|
import type { IAccordionProps } from "../types"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
transition:slide={{ duration: 200 }}
|
|
27
27
|
>
|
|
28
28
|
<div class="overflow-hidden rounded-[14px] border border-(--hairline-color) bg-(--back-color)">
|
|
29
|
-
<!-- Фон заголовка
|
|
29
|
+
<!-- Фон заголовка - мягкий оттенок мастер-цвета (светлый в светлой теме, тёмный в тёмной), текст - мастер-цвет с цветом шрифта 60/40, раскрытое содержимое остаётся на чистом фоне (--back-color). -->
|
|
30
30
|
<button
|
|
31
31
|
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))]"
|
|
32
32
|
onclick={toggle}
|
package/dist/Accordion/README.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
| `id` | `string` | `crypto.randomUUID()` | Идентификатор |
|
|
27
27
|
| `isOpen` | `boolean` | `false` | Открыт ли. Можно связать через `bind:isOpen` |
|
|
28
28
|
| `wrapperClass` | `string` | `""` | Классы обёртки |
|
|
29
|
-
| `componentClass` | `string` | `""` | Мастер-цвет: `bg-red`, `bg-green` и другие из `COLOR_OPTIONS`. Пусто
|
|
29
|
+
| `componentClass` | `string` | `""` | Мастер-цвет: `bg-red`, `bg-green` и другие из `COLOR_OPTIONS`. Пусто - акцент темы |
|
|
30
30
|
| `size` | `{ width, height }` | `1 × 1` | Сетка содержимого: колонки и строки |
|
|
31
31
|
| `label.name` | `string` | `""` | Заголовок |
|
|
32
32
|
| `label.class` | `string` | `"text-left"` | Классы текста заголовка |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Button/Button.svelte
|
|
1
|
+
<!-- $lib/Button/Button.svelte - кнопка: роль по значению bg-*, радиус 10px (иконка без подписи - круг), высота 32px, кольцо фокуса, иконка в потоке через flex + gap -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { onMount } from "svelte"
|
|
4
4
|
import type { IButtonProps } from "../types"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
const isIconOnly = $derived(!!content?.icon && !content.name?.trim())
|
|
41
41
|
|
|
42
|
-
/* bg-(--bg-color) намеренно не входит в roleClass: он приклеивается к готовому className вне twMerge (см. разметку ниже)
|
|
42
|
+
/* bg-(--bg-color) намеренно не входит в roleClass: он приклеивается к готовому className вне twMerge (см. разметку ниже) - иначе twMerge видит его и явный bg-* из componentClass (bg-red, bg-max…) как конфликтующие утилиты одной группы и вырезает один из них, из-за чего заливка кнопки пропадает. */
|
|
43
43
|
const roleBg = $derived.by(() => {
|
|
44
44
|
if (disabled) return "bg-(--gray-color)"
|
|
45
45
|
if (role === "plain") return "bg-transparent"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
forConstructor?: boolean
|
|
24
24
|
} = $props()
|
|
25
25
|
|
|
26
|
-
/* Argument пуст
|
|
26
|
+
/* Argument пуст - поле ниже лишь визуально показывает "ModCfg", реальное значение нужно проставить в состояние */
|
|
27
27
|
$effect(() => {
|
|
28
28
|
if (!component.eventHandler?.Argument) onPropertyChange({ eventHandler: { Argument: "ModCfg" } })
|
|
29
29
|
})
|
package/dist/Button/README.md
CHANGED
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
|---|---|---|
|
|
66
66
|
| `component` | `UIComponent & { properties: Partial<IButtonProps> }` | Компонент и его свойства |
|
|
67
67
|
| `onPropertyChange` | `(updates) => void` | Сообщает об изменении свойств |
|
|
68
|
-
| `forConstructor` | `boolean` | `true`
|
|
68
|
+
| `forConstructor` | `boolean` | `true` - полная панель конструктора, `false` - упрощённая для редактирования |
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
onUpdate={(value) => {
|
|
54
54
|
updateProperty("id", (value as UI.IOption).value as string, component, onPropertyChange)
|
|
55
55
|
onPropertyChange({
|
|
56
|
-
|
|
56
|
+
/* \u041f\u043e\u0434\u043f\u0438\u0441\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u0438\u0437 \u043e\u0431\u043b\u0430\u043a\u0430 \u00ab<id> - <\u0438\u043c\u044f>\u00bb: \u0438\u043c\u044f - \u0432\u0441\u0451 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044f; \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0438 \u0434\u043b\u0438\u043d\u043d\u043e\u0435 \u0442\u0438\u0440\u0435 (\u2014) \u0441\u0442\u0430\u0440\u044b\u0445 \u043f\u043e\u0434\u043f\u0438\u0441\u0435\u0439 */
|
|
57
|
+
name: (value as UI.IOption).name?.match(/\s[\u2014-]\s(.*)/)?.[1]?.trim(),
|
|
57
58
|
eventHandler: { Variables: [(value as UI.IOption).value as string] },
|
|
58
59
|
})
|
|
59
60
|
}}
|
|
@@ -238,7 +239,7 @@
|
|
|
238
239
|
icons: { array: [string, string[]][]; selectArray?: false }
|
|
239
240
|
icon: string | string[]
|
|
240
241
|
updateProperty: (icon: string | string[]) => {}
|
|
241
|
-
/* compact
|
|
242
|
+
/* compact - для строк списков в одну линию: квадратная кнопка с текущей иконкой и подсказкой вместо кнопки с текстом */
|
|
242
243
|
compact?: boolean
|
|
243
244
|
})}
|
|
244
245
|
<div class={initialValue.compact ? "relative flex shrink-0 items-center gap-1" : "relative mt-6 flex w-full gap-2"}>
|
package/dist/CopiedChip.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/CopiedChip.svelte
|
|
1
|
+
<!-- $lib/CopiedChip.svelte - общая всплывающая метка «Скопировано» после клика на кнопку копирования -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { T } from "./locales/i18n"
|
|
4
4
|
import { fade } from "svelte/transition"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Divider/Divider.svelte
|
|
1
|
+
<!-- $lib/Divider/Divider.svelte - волосяная линия вместо скруглённой полосы 2px. -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { twMerge } from "tailwind-merge"
|
|
4
4
|
import type { IDividerProps } from "../types"
|
package/dist/Divider/README.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
| `wrapperClass` | `string` | `""` | Классы обёртки |
|
|
26
26
|
| `type` | `"line" \| "empty"` | `"line"` | Линия или пустой отступ |
|
|
27
27
|
| `width` | `number` | `1` | Толщина линии, пикселей |
|
|
28
|
-
| `color` | `string` | `""` | Цвет: `bg-max`, `bg-red`, `bg-yellow`, `bg-green`, `bg-blue`, `bg-purple`, `bg-gray`. Пусто
|
|
28
|
+
| `color` | `string` | `""` | Цвет: `bg-max`, `bg-red`, `bg-yellow`, `bg-green`, `bg-blue`, `bg-purple`, `bg-gray`. Пусто - нейтральный |
|
|
29
29
|
| `anchor` | `"top" \| "center" \| "bottom"` | `"center"` | Где стоит линия по высоте |
|
|
30
30
|
|
|
31
31
|
Колбэков нет.
|
package/dist/Dragging.svelte
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
/* Перетаскивание с MouseEvent */
|
|
41
41
|
const startDrag = async (e: MouseEvent) => {
|
|
42
|
-
/* Строка ищется только внутри своего списка
|
|
42
|
+
/* Строка ищется только внутри своего списка - id вида item-N есть и у других списков на странице */
|
|
43
43
|
const movedElement = container.querySelector(
|
|
44
44
|
containerIndex == null ? `#${elementsId}-${elementIndex}` : `#${elementsId}-${elementIndex}-${containerIndex}`,
|
|
45
45
|
) as HTMLElement
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
const element = movedElement.cloneNode(true) as HTMLElement
|
|
50
50
|
const rect = movedElement.getBoundingClientRect()
|
|
51
51
|
|
|
52
|
-
/* Визуальный эффект перетаскивания
|
|
52
|
+
/* Визуальный эффект перетаскивания - общий для библиотеки и приложений (dragStyle.ts) */
|
|
53
53
|
element.id = "drag-element"
|
|
54
54
|
element.style.position = "fixed"
|
|
55
55
|
element.style.zIndex = "9999"
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
const ghost = dragState.ghostElement
|
|
94
94
|
ghost.style.top = `${e.clientY - dragState.offsetY}px`
|
|
95
95
|
|
|
96
|
-
/* Место вставки
|
|
96
|
+
/* Место вставки - перед первой строкой, середина которой ниже курсора (иначе - в конец): меняется только при пересечении середины строки, без дрожания на границах. Соседние строки сдвигаются плавно (animateReorder) */
|
|
97
97
|
const items = commandItems.filter((item) =>
|
|
98
98
|
dragState.containerIndex === null ? true : Number(item.id.split("-")[2]) === dragState.containerIndex,
|
|
99
99
|
) as HTMLElement[]
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
let target = items[items.length - 1]
|
|
102
102
|
for (const item of items) {
|
|
103
103
|
const rect = item.getBoundingClientRect()
|
|
104
|
-
/* Положение в раскладке, без сдвига идущей анимации
|
|
104
|
+
/* Положение в раскладке, без сдвига идущей анимации - иначе движущаяся строка снова «пересекала» бы курсор */
|
|
105
105
|
const transform = getComputedStyle(item).transform
|
|
106
106
|
const shift = transform && transform !== "none" ? new DOMMatrixReadOnly(transform).m42 : 0
|
|
107
107
|
if (e.clientY < rect.top - shift + rect.height / 2) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { twMerge } from "tailwind-merge"
|
|
5
5
|
import Button from "../Button/Button.svelte"
|
|
6
6
|
|
|
7
|
-
/* Иконка «открыть файл» для type "button"
|
|
7
|
+
/* Иконка «открыть файл» для type "button" - документ со стрелкой вверх, контур 1.75 как у остальных кнопок */
|
|
8
8
|
const OPEN_FILE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M12 18v-6"/><path d="m9 15 3-3 3 3"/></svg>`
|
|
9
9
|
|
|
10
10
|
let {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
onChange(event, file)
|
|
52
|
-
/* Кнопка «Открыть файл»
|
|
52
|
+
/* Кнопка «Открыть файл» - повторный выбор того же файла тоже должен срабатывать */
|
|
53
53
|
if (type === "button") input.value = ""
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
}
|
|
60
60
|
</script>
|
|
61
61
|
|
|
62
|
-
<!-- По design-handoff (раздел 9): зона сброса
|
|
62
|
+
<!-- По design-handoff (раздел 9): зона сброса - пунктир 1 px с радиусом 12 вместо залитой кнопки, высота как у Input; «Выбрать файл» - акцентный текст, имя файла - обычным цветом, пока не выбрано - бледным. -->
|
|
63
63
|
<div class={twMerge(`flex flex-col items-center gap-1`, wrapperClass)}>
|
|
64
64
|
{#if label.name}
|
|
65
65
|
<span class={twMerge(`w-full text-[12px] font-semibold text-(--muted-color)`, label.class)}>{label.name}</span>
|
|
66
66
|
{/if}
|
|
67
67
|
|
|
68
68
|
{#if type === "button"}
|
|
69
|
-
<!-- Иконочная кнопка с подсказкой, как UI.Button; выбор файла
|
|
69
|
+
<!-- Иконочная кнопка с подсказкой, как UI.Button; выбор файла - скрытый input за ней -->
|
|
70
70
|
<Button {componentClass} {disabled} content={{ icon: icon ?? OPEN_FILE_ICON, info }} onClick={triggerFileInput} />
|
|
71
71
|
<input id={ID} type="file" class="hidden" {accept} {disabled} onchange={handleFileChange} />
|
|
72
72
|
{:else if type === "image"}
|
package/dist/Graph/Graph.svelte
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<script module lang="ts">
|
|
3
3
|
import type { IOption } from "../types"
|
|
4
4
|
|
|
5
|
-
/* Константы и настройки
|
|
5
|
+
/* Константы и настройки - экспортируются для переиспользования в GraphProps.svelte (редактор конструктора) */
|
|
6
6
|
export const REFRESH_OPTIONS: IOption[] = [
|
|
7
7
|
{ id: "RefreshOption-AUTO", name: "AUTO", value: 0, class: "" },
|
|
8
8
|
{ id: "RefreshOption-50", name: "50", value: 50, class: "" },
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
/* Тултип при наведении
|
|
75
|
+
/* Тултип при наведении - время и значения всех серий в ближайшей к курсору точке */
|
|
76
76
|
let tooltip = $state<{ visible: boolean; x: number; y: number; time: number; items: { name: string; value: number; color: string }[] }>({
|
|
77
77
|
visible: false,
|
|
78
78
|
x: 0,
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
drawAllGraphs()
|
|
156
156
|
}, selectedRefreshRate)
|
|
157
157
|
} else if (selectedRefreshRate == 0 && !isTest) {
|
|
158
|
-
/* AUTO: streamingData не несёт признак "новый пакет"
|
|
158
|
+
/* AUTO: streamingData не несёт признак "новый пакет" - опрашиваем каждые 10мс, рисуем точку только при реальном изменении значений */
|
|
159
159
|
intervalId = setInterval(() => {
|
|
160
160
|
if (!streamingData.data || streamingData.data.length === 0) return
|
|
161
161
|
const newValues = (streamingData.data as IGraphDataObject[]).map((dataset) => dataset.value)
|
|
@@ -198,7 +198,7 @@
|
|
|
198
198
|
return () => resizeObserver.disconnect()
|
|
199
199
|
})
|
|
200
200
|
|
|
201
|
-
/* Общая геометрия осей
|
|
201
|
+
/* Общая геометрия осей - переиспользуется отрисовкой и обработчиком наведения мыши, чтобы не дублировать формулы */
|
|
202
202
|
const computeGeometry = () => {
|
|
203
203
|
const padding = { top: 10, right: 10, bottom: 20, left: 35 }
|
|
204
204
|
const graphWidth = width - padding.left - padding.right
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
const maxX = allPoints.length ? Math.max(...allPoints.map((p) => p.x)) : 1
|
|
210
210
|
const timeSpan = maxX - minX || 1
|
|
211
211
|
|
|
212
|
-
/* Авто-масштаб по данным (±10%): статический
|
|
212
|
+
/* Авто-масштаб по данным (±10%): статический - разово по массиву, живой - через сглаженный liveYRange (updateLiveYRange) */
|
|
213
213
|
let yMin: number
|
|
214
214
|
let yMax: number
|
|
215
215
|
if (isHistoryMode) {
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
return { padding, graphWidth, graphHeight, minX, maxX, timeSpan, yMin, yMax, ySpan, getX, getY }
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
/* Бинарный поиск ближайшей по времени точки
|
|
233
|
+
/* Бинарный поиск ближайшей по времени точки - точки в graphData всегда отсортированы по x (по построению) */
|
|
234
234
|
const findNearestPoint = (points: { x: number; y: number }[], t: number): { x: number; y: number } | null => {
|
|
235
235
|
if (points.length === 0) return null
|
|
236
236
|
let lo = 0
|
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
|
|
247
247
|
const pad2 = (n: number) => String(n).padStart(2, "0")
|
|
248
248
|
|
|
249
|
-
/* Компактная адаптивная подпись сетки по оси X: относительное время для живого режима, дата/время
|
|
249
|
+
/* Компактная адаптивная подпись сетки по оси X: относительное время для живого режима, дата/время - для статического */
|
|
250
250
|
const formatAxisLabel = (t: number, spanMs: number): string => {
|
|
251
251
|
if (!isHistoryMode) return `${((t - Date.now()) / 1000).toFixed(0)}s`
|
|
252
252
|
const d = new Date(t)
|
|
@@ -302,7 +302,7 @@
|
|
|
302
302
|
drawAllGraphs()
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
/* Прозрачность линий сетки
|
|
305
|
+
/* Прозрачность линий сетки - общая с PeriodChart (border-(--faint-color)/40) */
|
|
306
306
|
const GRID_ALPHA = 0.4
|
|
307
307
|
|
|
308
308
|
const drawAllGraphs = () => {
|
|
@@ -390,7 +390,7 @@
|
|
|
390
390
|
{/if}
|
|
391
391
|
|
|
392
392
|
<div class="flex w-full min-w-0 flex-row gap-4">
|
|
393
|
-
<!-- График: холст абсолютно поверх контейнера
|
|
393
|
+
<!-- График: холст абсолютно поверх контейнера - его пиксельная ширина не распирает контейнер, иначе в сетке/flex с 1fr ширина растёт по кругу (ResizeObserver) -->
|
|
394
394
|
<!-- design-handoff, раздел 9: рамка --hairline-color без тени, тултип в оформлении общего Tooltip.svelte -->
|
|
395
395
|
<div bind:this={container} class="relative h-64 min-w-0 grow overflow-hidden rounded-[14px] border border-(--hairline-color)">
|
|
396
396
|
<canvas class="absolute inset-0 h-full w-full bg-(--back-color)" bind:this={canvas} onmousemove={handleMouseMove} onmouseleave={handleMouseLeave}
|
|
@@ -425,7 +425,7 @@
|
|
|
425
425
|
/>
|
|
426
426
|
{/if}
|
|
427
427
|
|
|
428
|
-
<!-- Переменные и их значение: сетка, а не таблица
|
|
428
|
+
<!-- Переменные и их значение: сетка, а не таблица - имя обрезается «…», значение не переносится -->
|
|
429
429
|
<div class="flex flex-col gap-1">
|
|
430
430
|
<h5 class="text-[12px] font-semibold text-(--muted-color)">{$T("ui.graph.values")}</h5>
|
|
431
431
|
<div class="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2 gap-y-1 text-[13px]">
|
package/dist/Graph/README.md
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
| `wrapperClass` | `string` | `""` | Классы обёртки |
|
|
46
46
|
| `label.name`, `label.class` | `string` | `""` | Заголовок и его классы |
|
|
47
47
|
| `streamingData.data` | `{ name, value }[] \| null` | `[]` | Текущие значения линий для живого режима |
|
|
48
|
-
| `refreshRate` | `number` | `0` | Период добавления точек, мс. `0`
|
|
48
|
+
| `refreshRate` | `number` | `0` | Период добавления точек, мс. `0` - по изменению значений |
|
|
49
49
|
| `historyData` | `{ name, points: { value, timestamp }[] }[]` | `[]` | Готовый ряд для статического режима. `timestamp` в миллисекундах, как `Date.now()` |
|
|
50
50
|
| `isTest` | `boolean` | `false` | Режим имитации: случайные данные, точка каждые 50 мс |
|
|
51
51
|
|
package/dist/Input/Input.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Input/Input.svelte
|
|
1
|
+
<!-- $lib/Input/Input.svelte - радиус 10, высота 32 (h-8), выравнивание по левому краю, кольцо фокуса. -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { T } from "../locales/i18n"
|
|
4
4
|
import type { IInputProps } from "../types"
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
|
|
87
87
|
<div class={twMerge(`relative flex w-full flex-col gap-1 ${type === "text-area" ? "h-full" : ""}`, wrapperClass)}>
|
|
88
88
|
{#if label.name}
|
|
89
|
-
<!-- Метка слева, 12/600
|
|
89
|
+
<!-- Метка слева, 12/600 - вместо центрированного h5 -->
|
|
90
90
|
<span class={twMerge(`text-[12px] font-semibold text-(--muted-color)`, label.class)}>{label.name}</span>
|
|
91
91
|
{/if}
|
|
92
92
|
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
const initialAlign = $derived(findOptionByClass($optionsStore.TEXT_ALIGN_OPTIONS, component.properties.label?.class, "text-"))
|
|
34
34
|
|
|
35
35
|
/* Обновление свойства */
|
|
36
|
-
/* Подсказка битового режима «Максимальное число: N» сразу на всех языках
|
|
36
|
+
/* Подсказка битового режима «Максимальное число: N» сразу на всех языках - иначе записалась бы на языке интерфейса конструктора */
|
|
37
37
|
const maxNumInfo = (range: { start: number; end: number }) => {
|
|
38
38
|
const max = Math.pow(2, range.end - range.start + 1) - 1
|
|
39
39
|
const row = translations["constructor.props.maxnum"]
|
|
40
40
|
return Object.fromEntries(LANGUAGE_CODES.map((lang) => [lang, `${row[lang] || row[DEFAULT_LANGUAGE]}: ${max}`]))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/* Диапазон битов поля bitMode; без range в свойствах
|
|
43
|
+
/* Диапазон битов поля bitMode; без range в свойствах - один нулевой бит */
|
|
44
44
|
const bitRange = $derived(component.properties.range ?? { start: 0, end: 0 })
|
|
45
45
|
|
|
46
46
|
const updateProperty = (path: string, value: string | object | boolean | number | RegExp) => {
|
|
@@ -105,7 +105,11 @@
|
|
|
105
105
|
value={VARIABLE_OPTIONS.find((opt) => opt.value === component.properties.id)}
|
|
106
106
|
onUpdate={(value) => {
|
|
107
107
|
updateProperty("id", (value as IOption).value as string)
|
|
108
|
-
|
|
108
|
+
/* \u041f\u043e\u0434\u043f\u0438\u0441\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 \u0438\u0437 \u043e\u0431\u043b\u0430\u043a\u0430 \u00ab<id> - <\u0438\u043c\u044f>\u00bb: \u0438\u043c\u044f - \u0432\u0441\u0451 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u0435\u043b\u044f; \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442\u0441\u044f \u0438 \u0434\u043b\u0438\u043d\u043d\u043e\u0435 \u0442\u0438\u0440\u0435 (\u2014) \u0441\u0442\u0430\u0440\u044b\u0445 \u043f\u043e\u0434\u043f\u0438\u0441\u0435\u0439 */
|
|
109
|
+
onPropertyChange({
|
|
110
|
+
name: (value as IOption).name?.match(/\s[\u2014-]\s(.*)/)?.[1]?.trim(),
|
|
111
|
+
eventHandler: { Variables: [(value as IOption).value as string] },
|
|
112
|
+
})
|
|
109
113
|
}}
|
|
110
114
|
/>
|
|
111
115
|
{/snippet}
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
</div>
|
|
203
203
|
</div>
|
|
204
204
|
|
|
205
|
-
<!-- Кнопка 0
|
|
205
|
+
<!-- Кнопка 0 - 32 px по общему правилу кнопок-иконок (design-handoff, раздел 9) -->
|
|
206
206
|
<div class="absolute {label.name ? 'top-4' : 'top-0'} w-55 flex items-center justify-end">
|
|
207
207
|
<button
|
|
208
208
|
class="flex size-[32px] cursor-pointer items-center justify-center rounded-full border border-(--hairline-color) bg-(--back-color) p-0.5 text-[15px] font-semibold
|
package/dist/Modal.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Modal.svelte
|
|
1
|
+
<!-- $lib/Modal.svelte - модальное окно: радиус 20, шапка и подвал через линии, подложка 40% с размытием, стек окон, закрытие по Escape и клику вне окна -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { T } from "./locales/i18n"
|
|
4
4
|
import { type Snippet } from "svelte"
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
</div>
|
|
105
105
|
|
|
106
106
|
{#if footer}
|
|
107
|
-
<!-- Подтверждающее действие
|
|
107
|
+
<!-- Подтверждающее действие - крайнее справа; row-reverse сохранён, поэтому порядок сниппетов в вызовах менять не нужно -->
|
|
108
108
|
<div class="flex flex-row-reverse items-center justify-start gap-2 border-t border-(--hairline-color) bg-(--container-color) px-5 py-3">
|
|
109
109
|
{@render footer?.()}
|
|
110
110
|
</div>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<!-- $lib/PeriodChart/PeriodChart.svelte
|
|
1
|
+
<!-- $lib/PeriodChart/PeriodChart.svelte - столбики за период и сегментированный переключатель периода, акцент только у выбранного -->
|
|
2
2
|
<script module lang="ts">
|
|
3
3
|
import type { IPeriodChartLevel } from "../types"
|
|
4
4
|
|
|
5
5
|
const randomLevelData = (n: number) => Array.from({ length: n }, () => Math.round(Math.random() * 100))
|
|
6
6
|
|
|
7
|
-
/* Демо-уровни, когда levels не заданы; name
|
|
7
|
+
/* Демо-уровни, когда levels не заданы; name - ключ перевода */
|
|
8
8
|
export const DEFAULT_LEVELS: IPeriodChartLevel[] = [
|
|
9
9
|
{ name: "ui.period.year", data: randomLevelData(12) },
|
|
10
10
|
{ name: "ui.period.month", data: randomLevelData(31) },
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
return { step, top: step * count, ticks: Array.from({ length: count + 1 }, (_, i) => step * (count - i)) }
|
|
52
52
|
})
|
|
53
53
|
|
|
54
|
-
/* Подпись деления: k/M для больших значений, число знаков после запятой
|
|
54
|
+
/* Подпись деления: k/M для больших значений, число знаков после запятой - по величине шага */
|
|
55
55
|
const formatTick = (v: number) => {
|
|
56
56
|
const [div, suffix] = axis.top >= 1e6 ? [1e6, "M"] : axis.top >= 1e4 ? [1e3, "k"] : [1, ""]
|
|
57
57
|
const step = axis.step / div
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
.filter((s) => s.length > 0)
|
|
48
48
|
const formatStrings = (arr: string[] | undefined): string => (arr || []).join(", ")
|
|
49
49
|
|
|
50
|
-
/* eventHandler.Variables
|
|
50
|
+
/* eventHandler.Variables - список всех PWR.*-переменных со всех уровней, чтобы рантайм подписался на них разом */
|
|
51
51
|
const updateLevels = (levels: IPeriodChartLevel[]) => {
|
|
52
52
|
updateProperty("levels", levels, component, onPropertyChange)
|
|
53
53
|
onPropertyChange({ eventHandler: { Variables: levels.map((l) => l.variable).filter((v): v is string => !!v) } })
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/ProgressBar/ProgressBar.svelte
|
|
1
|
+
<!-- $lib/ProgressBar/ProgressBar.svelte - полоса 6px, подпись и значение над ней -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { twMerge } from "tailwind-merge"
|
|
4
4
|
import type { IProgressBarProps, IReceivingDataObject } from "../types"
|
package/dist/PropsGroup.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/PropsGroup.svelte
|
|
1
|
+
<!-- $lib/PropsGroup.svelte - карточка-группа полей для панелей свойств конструктора (*Props.svelte). Визуальный язык взят из уже принятого вложенного оформления смарт-виджетов (WidgetWiFi.svelte/WidgetDeviceInfo.svelte: rounded-[10px] border bg-(--back-color) p-3 + подпись text-[12px] font-semibold text-(--muted-color)) - общий premium-вид панелей вместо голых flex-колонок без оформления. -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { twMerge } from "tailwind-merge"
|
|
4
4
|
import type { Snippet } from "svelte"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/PropsListHeader.svelte
|
|
1
|
+
<!-- $lib/PropsListHeader.svelte - подписи колонок над однострочными элементами списка в окнах свойств (вместо подписи у каждого поля); gridClass - тот же шаблон колонок, что у строк списка, пустая подпись - колонка без заголовка (ручка, удаление) -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
let { gridClass, captions }: { gridClass: string; captions: string[] } = $props()
|
|
4
4
|
</script>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/PropsListModal.svelte
|
|
1
|
+
<!-- $lib/PropsListModal.svelte - вынос "тяжёлых" списковых секций панелей свойств (столбцы Table, опции ProgressBar/Select/Switch/Tabs, уровни PeriodChart) в модалку вместо попытки втиснуть их в узкий сайдбар конструктора (ConstructorUI.svelte). Кнопка-триггер показывает счётчик элементов. -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { T } from "./locales/i18n"
|
|
4
4
|
import type { Snippet } from "svelte"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Select/Select.svelte
|
|
1
|
+
<!-- $lib/Select/Select.svelte - радиус 10, высота 36, выравнивание по левому краю, список - единственный элемент с тенью, группа type="buttons" без разрывов. -->
|
|
2
2
|
<script lang="ts" generics="T = unknown">
|
|
3
3
|
import { slide } from "svelte/transition"
|
|
4
4
|
import { onMount } from "svelte"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
let portalElement: HTMLDivElement | undefined = $state()
|
|
15
15
|
let anchorRect: { top: number; bottom: number; left: number; width: number } | null = $state(null)
|
|
16
16
|
|
|
17
|
-
/* Список вариантов выносится в document.body (портал), чтобы его не обрезал overflow-hidden/overflow-auto ближайшего скроллящегося предка (например, панель свойств конструктора). Позиция считается от dropdownElement и обновляется, пока список открыт, при скролле/резайзе где угодно на странице. Атрибут data-ui-portal на контейнере
|
|
17
|
+
/* Список вариантов выносится в document.body (портал), чтобы его не обрезал overflow-hidden/overflow-auto ближайшего скроллящегося предка (например, панель свойств конструктора). Позиция считается от dropdownElement и обновляется, пока список открыт, при скролле/резайзе где угодно на странице. Атрибут data-ui-portal на контейнере - чтобы Modal.svelte не принимал клик по опции (физически лежащей вне [data-modal]) за клик "снаружи" и не закрывался. */
|
|
18
18
|
function portal(node: HTMLElement) {
|
|
19
19
|
document.body.appendChild(node)
|
|
20
20
|
return {
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
onUpdate?.(selectedOption)
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/* Общая база поля
|
|
127
|
+
/* Общая база поля - та же, что у Input */
|
|
128
128
|
const fieldBase = `h-8 w-full rounded-[10px] border border-(--border-color) bg-(--field-color) px-3 text-left text-[14px]
|
|
129
129
|
transition-[border-color,box-shadow] duration-150 outline-none
|
|
130
130
|
focus:border-(--accent-color) focus:shadow-(--focus-shadow-color)`
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
{@const selected = value !== null && isSelected(option)}
|
|
210
210
|
{@const hasOwnColor = !!option.class?.split(/\s+/).find((c) => c.startsWith("bg-"))}
|
|
211
211
|
<!-- Заливка вне twMerge, чтобы не конфликтовать с bg-* из option.class: опции со своим цветом показывают его всегда и отмечаются рамкой, обычный сегмент-контрол заливает только выбранный пункт -->
|
|
212
|
-
<!-- Обёртка
|
|
212
|
+
<!-- Обёртка - позиционирующий предок подсказки; без overflow-hidden у группы, чтобы подсказка не обрезалась -->
|
|
213
213
|
<div class="relative flex {fitContent ? '' : 'min-w-0 flex-1'}">
|
|
214
214
|
<button
|
|
215
215
|
id={option.id}
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
: ($optionsStore.SELECT_VALUE_TYPE_OPTIONS[0] as ValueTypeOption),
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
/* Диапазон битов bitMode; без range в свойствах
|
|
47
|
+
/* Диапазон битов bitMode; без range в свойствах - один нулевой бит */
|
|
48
48
|
const bitRange = $derived(component.properties.range ?? { start: 0, end: 0 })
|
|
49
49
|
|
|
50
50
|
let Header: IOption = $derived(
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
),
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
/* Argument пуст
|
|
68
|
+
/* Argument пуст - поле ниже лишь визуально показывает "ModCfg", реальное значение нужно проставить в состояние */
|
|
69
69
|
$effect(() => {
|
|
70
70
|
if (!component.eventHandler?.Argument) onPropertyChange({ eventHandler: { Argument: "ModCfg" } })
|
|
71
71
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Slider/Slider.svelte
|
|
1
|
+
<!-- $lib/Slider/Slider.svelte - дорожка 4px, ползунок 18px; в диапазонном режиме оба <input> на всю ширину с одинаковыми min/max наложены друг на друга, события ловит только ::thumb, клик по пустой дорожке ползунок не двигает -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { T } from "../locales/i18n"
|
|
4
4
|
import type { ISliderProps } from "../types"
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
let {
|
|
8
8
|
id = crypto.randomUUID(),
|
|
9
|
-
/* Было "bg-blue"
|
|
9
|
+
/* Было "bg-blue" - декоративная заливка. Цвет заливки дорожки берётся из --bg-color, который задаётся классом bg-* в вызове; без него используется акцент. */
|
|
10
10
|
wrapperClass = "",
|
|
11
11
|
label = { name: "", class: "" },
|
|
12
12
|
type = "single",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
let lowerValue = $derived(isRange && Array.isArray(value) ? value[0] : number.minNum)
|
|
26
26
|
let upperValue = $derived(isRange && Array.isArray(value) ? value[1] : number.maxNum)
|
|
27
27
|
|
|
28
|
-
/* Позиция значения в процентах
|
|
28
|
+
/* Позиция значения в процентах - для отрисовки заливки */
|
|
29
29
|
const pct = (v: number) => {
|
|
30
30
|
const span = Math.abs((number.maxNum ?? 10) - (number.minNum ?? 0)) || 1
|
|
31
31
|
return Math.max(0, Math.min(100, ((v - (number.minNum ?? 0)) / span) * 100))
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
return Number(num.toFixed(2))
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/* Единые классы для нативного ползунка: высота input РАВНА высоте thumb (18px)
|
|
75
|
-
/* Бегунок: было border-(--hairline-color) (почти прозрачная линия) + мягкая тень
|
|
74
|
+
/* Единые классы для нативного ползунка: высота input РАВНА высоте thumb (18px) - тогда центрирование по вертикали отдаётся обычному flexbox родителя (items-center), без ручного margin-top на ::-webkit-slider-thumb. Такой margin-top пришлось бы подбирать под конкретную высоту трека, и он даёт разный результат в Chromium и Safari/WebKit - при равных высотах input и thumb смещения нет по определению, это работает одинаково везде. */
|
|
75
|
+
/* Бегунок: было border-(--hairline-color) (почти прозрачная линия) + мягкая тень - на светлом фоне белый кружок буквально сливался с дорожкой. Рамка в 2px цветом заливки даёт чёткий, "премиальный" контур вместо почти невидимой линии. */
|
|
76
76
|
const THUMB = `w-full appearance-none bg-transparent h-[18px]
|
|
77
77
|
[&::-webkit-slider-runnable-track]:h-[18px] [&::-webkit-slider-runnable-track]:bg-transparent
|
|
78
78
|
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-[18px]
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
[&::-moz-range-thumb]:shadow-[0_1px_4px_rgb(16_24_40/0.35)]
|
|
86
86
|
focus-visible:outline-none`
|
|
87
87
|
|
|
88
|
-
/* Двойной слайдер: оба input на всю ширину трека с ОДИНАКОВЫМ min/max, наложены друг на друга. pointer-events выключен на самом input и включён только на ::thumb (класс .range-overlay ниже)
|
|
88
|
+
/* Двойной слайдер: оба input на всю ширину трека с ОДИНАКОВЫМ min/max, наложены друг на друга. pointer-events выключен на самом input и включён только на ::thumb (класс .range-overlay ниже) - тянуть можно исключительно за конкретный бегунок. Tailwind не генерирует CSS для сочетания произвольного варианта псевдоэлемента с pointer-events-*, поэтому это обычный style-блок ниже, а не класс. */
|
|
89
89
|
</script>
|
|
90
90
|
|
|
91
91
|
<div class={twMerge(`relative flex w-full flex-col gap-1.5`, wrapperClass)}>
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
<div class="flex w-full items-center gap-3">
|
|
97
97
|
<!-- Дорожка -->
|
|
98
98
|
<div id={`${id}-${crypto.randomUUID().slice(0, 6)}`} class="relative flex h-8 flex-1 items-center {disabled ? 'cursor-not-allowed opacity-45' : ''}">
|
|
99
|
-
<!-- Фон дорожки 6px
|
|
99
|
+
<!-- Фон дорожки 6px - --container-color здесь давал слишком слабый контраст с фоном страницы (#f0f2f5 на белом), дорожка визуально пропадала; --border-color заметно контрастнее и совпадает с рамкой счётчика ниже. -->
|
|
100
100
|
<div class="pointer-events-none absolute inset-x-0 top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-(--border-color)"></div>
|
|
101
101
|
<!-- Заливка -->
|
|
102
102
|
{#if isRange}
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
</div>
|
|
219
219
|
|
|
220
220
|
<style>
|
|
221
|
-
/* Двойной слайдер: оба input наложены на всю ширину трека, дорожка каждого не должна перехватывать клик
|
|
221
|
+
/* Двойной слайдер: оба input наложены на всю ширину трека, дорожка каждого не должна перехватывать клик - только его собственный thumb. */
|
|
222
222
|
.range-overlay {
|
|
223
223
|
pointer-events: none;
|
|
224
224
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- $lib/Switch/Switch.svelte
|
|
1
|
+
<!-- $lib/Switch/Switch.svelte - тумблер, геометрия считается от пропа height; чекбокс 20px с радиусом 6 -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import { twMerge } from "tailwind-merge"
|
|
4
4
|
import type { ISwitchProps } from "../types"
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
{#if type !== "checkbox"}
|
|
59
59
|
<div class={twMerge(`relative flex w-full flex-col items-center justify-center gap-1.5`, wrapperClass)}>
|
|
60
60
|
{#if label.name}
|
|
61
|
-
<!-- Подпись по умолчанию по центру
|
|
61
|
+
<!-- Подпись по умолчанию по центру - над переключателем, который тоже по центру; выравнивание из label.class важнее -->
|
|
62
62
|
<h5 class={twMerge(`w-full text-center text-[12px] font-semibold text-(--muted-color)`, label.class)}>{label.name}</h5>
|
|
63
63
|
{/if}
|
|
64
64
|
<div class="flex w-full {type == 'vertical' ? 'flex-wrap' : ''} items-center justify-around gap-4">
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
disabled={option.disabled}
|
|
91
91
|
onchange={() => handleToggle(index)}
|
|
92
92
|
/>
|
|
93
|
-
<!-- Дорожка: акцент во включённом состоянии, нейтральная серая в выключенном. Фолбэк var(--accent-color)
|
|
93
|
+
<!-- Дорожка: акцент во включённом состоянии, нейтральная серая в выключенном. Фолбэк var(--accent-color) - большинство вызовов Switch не передают wrapperClass с ролью цвета (bg-blue и т.п.), без фолбэка --bg-color остаётся не задан и дорожка становится невидимой при включении. -->
|
|
94
94
|
<span
|
|
95
95
|
class="relative flex items-center rounded-full transition-colors duration-200
|
|
96
96
|
{checkedOptions[index] ? 'bg-(--bg-color,var(--accent-color))' : 'bg-(--border-color)'}
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
{/if}
|
|
99
99
|
{#each component.properties.options || [] as option, index (option.id)}
|
|
100
100
|
<div class="grid {BIT_GRID} items-center gap-3 rounded-lg border border-(--hairline-color) bg-(--container-color)/60 px-2 py-1.5">
|
|
101
|
-
<!-- Имя варианта
|
|
101
|
+
<!-- Имя варианта - короткая метка бита (до 4 символов) в битовом режиме, не переводится -->
|
|
102
102
|
<UI.Input
|
|
103
103
|
value={option.name}
|
|
104
104
|
maxlength={4}
|