@labpics/colors 0.1.0 → 0.3.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
@@ -1,242 +1,248 @@
1
- # @labpics/colors
2
-
3
- Адаптивные цветовые роли для дизайн-системы. Получает фоновый цвет и тему — возвращает полный набор ролей (`--lab-label-primary`, `--lab-icon`, `--lab-border-base`, …) как `#RRGGBB`-значения CSS-переменных. Ядро написано на Rust и скомпилировано в WebAssembly; пакет не имеет runtime-зависимостей.
4
-
5
- Ядро возвращает **данные**, не затрагивает DOM. Три вспомогательные функции переводят эти данные в живые CSS-переменные: `applyTheme` (разовое применение), `watchTheme` (реактивное — обновляется при изменении фона) и `adaptTheme` (плавная адаптация для фона, меняющегося каждый кадр).
6
-
7
- ---
8
-
9
- ## Установка
10
-
11
- Пакет публикуется в npm-реестр Labpics:
12
-
13
- ```sh
14
- npm install @labpics/colors
15
- ```
16
-
17
- При сборке из монорепо:
18
-
19
- ```sh
20
- npm run build # → pkg/ (wasm + JS-обёртка + .d.ts)
21
- ```
22
-
23
- ---
24
-
25
- ## Как использовать
26
-
27
- ### Разовое применение
28
-
29
- ```ts
30
- import init, { LabColors, applyTheme } from "@labpics/colors";
31
-
32
- await init(); // загрузить WASM-модуль (один раз)
33
- const engine = new LabColors(); // движок по умолчанию
34
-
35
- const result = engine.resolveTheme("#FFFFFF", "light");
36
- // result.vars → { "--lab-label-primary": "#1a1a1a", "--lab-icon": "#5b5b5b", ... }
37
- // result.roles → детали каждой роли (hex, контраст, флаги)
38
-
39
- applyTheme(document.documentElement, result); // записать все --lab-* в элемент
40
- ```
41
-
42
- ### Реактивное отслеживание
43
-
44
- `watchTheme` синхронизирует переменные с фактическим фоном элемента автоматически.
45
-
46
- ```ts
47
- import init, { LabColors, watchTheme } from "@labpics/colors";
48
-
49
- await init();
50
- const colors = new LabColors();
51
-
52
- const panel = document.querySelector(".panel") as HTMLElement;
53
- const watcher = watchTheme(panel, { colors, theme: "light" });
54
- // panel теперь несёт правильные --lab-* для своего фона
55
-
56
- watcher.setTheme("dark"); // переключить тему
57
- watcher.refresh(); // принудительная пересинхронизация
58
- watcher.stop(); // отключить наблюдателя
59
- ```
60
-
61
- Поддерживает два режима изменений:
62
-
63
- - **Дискретные** (переключение темы, обновление класса, DOM-мутация) — отслеживаются `MutationObserver` автоматически.
64
- - **Непрерывные** (CSS-анимация, кадровый фон, который никогда не мутирует `inline style`) — управляются вручную через `refresh()` из собственного цикла `requestAnimationFrame`. `refresh()` дёшев: пересчитывает только при реальном изменении строки фона.
65
-
66
- ### Плавная адаптация к анимированному фону
67
-
68
- `watchTheme` пересчитывает весь набор при каждом изменении фона. Для фона, меняющегося **каждый кадр** (анимация, параллакс, размытый фон), `adaptTheme` — более элегантный вариант: проверяет дёшево каждый кадр, пересчитывает и плавно переходит к новым цветам только когда контраст реально ухудшается.
69
-
70
- ```ts
71
- import init, { LabColors, adaptTheme, effectiveBackground } from "@labpics/colors";
72
-
73
- await init();
74
- const colors = new LabColors();
75
-
76
- const surface = document.querySelector(".hero") as HTMLElement;
77
- const adaptive = adaptTheme(surface, {
78
- colors,
79
- theme: "light",
80
- background: () => effectiveBackground(surface, { fallback: "#101012" }),
81
- });
82
-
83
- adaptive.start(); // запустить внутренний requestAnimationFrame-цикл
84
- adaptive.setTheme("dark"); // смена темы применяется мгновенно
85
- adaptive.stop(); // остановить цикл
86
- ```
87
-
88
- Для фона с градиентом или изображением можно передать несколько образцов — цвета будут корректны для наиболее сложного из них:
89
-
90
- ```ts
91
- adaptTheme(hero, {
92
- colors,
93
- theme: "light",
94
- strict: true,
95
- background: () => sampleBackdrop(hero), // например ["#0B0B0E", "#3A3A40"]
96
- });
97
- ```
98
-
99
- ---
100
-
101
- ## Темы
102
-
103
- | Имя темы | Назначение |
104
- |----------|------------|
105
- | `"light"` | Светлая тема |
106
- | `"dark"` | Тёмная тема |
107
- | `"light-ic"` | Светлая с повышенным контрастом |
108
- | `"dark-ic"` | Тёмная с повышенным контрастом |
109
-
110
- ---
111
-
112
- ## Справочник публичного API
113
-
114
- ### `new LabColors()`
115
-
116
- Создаёт движок с таблицей ролей и условиями наблюдения по умолчанию. Повторные одинаковые вызовы `resolveTheme` обслуживаются из внутреннего кэша.
117
-
118
- ---
119
-
120
- ### `engine.resolveTheme(bgHex, theme): ResolvedTheme`
121
-
122
- - `bgHex` — фон в формате `#RGB` или `#RRGGBB`.
123
- - `theme` — `"light" | "dark" | "light-ic" | "dark-ic"`.
124
-
125
- Возвращает объект `ResolvedTheme`:
126
-
127
- ```ts
128
- interface ResolvedTheme {
129
- theme: ThemeName;
130
- background: string; // нормализованный #RRGGBB
131
- vars: Record<string, string>; // достижимые роли: "--lab-<ключ>" → hex
132
- roles: Record<string, RoleResult>; // все роли с деталями
133
- }
134
-
135
- type RoleResult = SolvedColor | NoneRole | UnreachableRole;
136
- ```
137
-
138
- Каждая роль — одно из трёх состояний:
139
-
140
- - `SolvedColor` — цвет найден (`kind: "color"`, поля `hex`, `lc`, `wcagRatio`, …).
141
- - `NoneRole` — роль намеренно пустая по дизайну (`kind: "none"`), не ошибка.
142
- - `UnreachableRole` — ни один цвет не удовлетворяет требованиям для этого фона (`kind: "unreachable"`).
143
-
144
- Недостижимость отдельных ролей — **часть успешного результата**. Отклоняет весь вызов (как `Error`) только при невалидном аргументе:
145
-
146
- | Код ошибки | Причина |
147
- |------------|---------|
148
- | `invalid_background` | `bgHex` не является `#RGB` или `#RRGGBB` |
149
- | `unknown_theme` | `theme` не входит в список допустимых |
150
-
151
- ---
152
-
153
- ### `applyTheme(element, result): void`
154
-
155
- Записывает все достижимые роли из `result.vars` в `element.style` через `setProperty`. Устаревшие `--lab-*` от предыдущего вызова сбрасываются перед записью — роль, потерявшая достижимость, не остаётся висеть.
156
-
157
- ---
158
-
159
- ### `watchTheme(element, options): WatchController`
160
-
161
- Синхронизирует `--lab-*` переменные с фактическим фоном элемента. Применяет немедленно при создании и возвращает контроллер.
162
-
163
- ```ts
164
- interface WatchThemeOptions {
165
- colors: LabColors;
166
- theme: ThemeName;
167
- background?: string | (() => string); // явный фон (если автоматический невозможен)
168
- target?: HTMLElement; // куда писать переменные (по умолчанию: element)
169
- fallback?: string; // фон при полностью прозрачной цепочке (по умолчанию "#FFFFFF")
170
- observe?: boolean; // авто-обновление при DOM-мутациях (по умолчанию true)
171
- }
172
-
173
- interface WatchController {
174
- refresh(force?: boolean): ResolvedTheme | null; // пересчитать и применить, если фон изменился
175
- setTheme(theme: ThemeName): void; // переключить тему и применить
176
- background(): string; // последний вычисленный фон (hex)
177
- stop(): void; // отключить наблюдателей
178
- }
179
- ```
180
-
181
- Для поверхности над изображением, градиентом или размытым фоном — где автоматическое вычисление невозможно — передайте явный `background` (hex-строку или функцию, возвращающую hex).
182
-
183
- ---
184
-
185
- ### `adaptTheme(element, options): AdaptController`
186
-
187
- Режим с гистерезисом: дешёвая проверка каждый кадр, пересчёт и плавный переход только при устойчивом ухудшении контраста. Применяет немедленно и возвращает контроллер.
188
-
189
- ```ts
190
- interface AdaptThemeOptions {
191
- colors: LabColors;
192
- theme: ThemeName;
193
- background?: string | string[] | (() => string | string[]);
194
- // один hex или несколько образцов фона (наихудший учитывается)
195
- target?: HTMLElement; // куда писать переменные (по умолчанию: element)
196
- fallback?: string; // фон при прозрачной цепочке (по умолчанию "#FFFFFF")
197
- dropFraction?: number; // запас контраста до пересчёта (по умолчанию 0.2)
198
- sustainMs?: number; // минимальное время удержания нарушения (по умолчанию 120)
199
- dwellMs?: number; // минимальный интервал между пересчётами (по умолчанию 250)
200
- easeMs?: number; // длительность перехода (по умолчанию 280; уменьшается при reduced-motion)
201
- strict?: boolean; // держать минимальный контраст на каждом кадре перехода (по умолчанию false)
202
- reducedMotion?: boolean; // переопределить системную настройку
203
- }
204
-
205
- interface AdaptController {
206
- tick(now?: number): void; // один шаг (или использовать start())
207
- setTheme(theme: ThemeName): void; // переключить тему мгновенно
208
- start(): void; // запустить внутренний requestAnimationFrame-цикл
209
- stop(): void; // остановить цикл
210
- current(): Record<string, string>; // текущие применённые --lab-* переменные
211
- }
212
- ```
213
-
214
- Управляйте через `start()` (внутренний rAF-цикл) или вызывайте `tick()` из собственного цикла. Смена темы применяется мгновенно — это осознанное намерение, а не дрейф.
215
-
216
- ---
217
-
218
- ### `effectiveBackground(element, options?): string`
219
-
220
- Возвращает непрозрачный `#RRGGBB` — цвет, который наблюдатель реально видит за содержимым `element`. Обходит цепочку предков и выполняет альфа-композитинг каждого `background-color` до получения непрозрачного результата, поверх `fallback` (по умолчанию белый).
221
-
222
- ```ts
223
- const bg = effectiveBackground(panel); // например "#0F1014"
224
- const bg2 = effectiveBackground(panel, { fallback: "#101012" });
225
- ```
226
-
227
- **Честное ограничение:** работает только с сплошными и полупрозрачными `background-color` — не с `background-image`, градиентами, размытым фоном или видео. Для таких поверхностей передайте фон явно.
228
-
229
- Дополнительно экспортируются вспомогательные функции для работы со слоями: `parseCssColor`, `compositeOver`, `compositeStackToHex`, `toHex` и `oklabLerp` (перцептуально равномерная интерполяция между двумя hex-значениями — траектория цвета при переходе).
230
-
231
- ---
232
-
233
- ## Размер бандла
234
-
235
- | Артефакт | gzip | brotli |
236
- |----------|------|--------|
237
- | `labcolors_bg.wasm` | ~54 КБ | ~47 КБ |
238
- | `labcolors.js` (JS-обёртка) | ~3 КБ | ~3 КБ |
239
-
240
- Вспомогательные функции (`applyTheme`, `watchTheme`, `adaptTheme`, `effectiveBackground`) — несколько сотен байт чистого JavaScript, поддерживают tree-shaking через именованные экспорты `./watch-theme`, `./adapt-theme` и `./effective-bg`.
241
-
242
- Требует современных браузеров (2023+).
1
+ # @labpics/colors
2
+
3
+ Адаптивные цветовые роли для дизайн-системы. Получает фоновый цвет и тему — возвращает полный набор ролей (`--lab-label-primary`, `--lab-icon`, `--lab-border-base`, …) как `#RRGGBB`-значения CSS-переменных. Ядро написано на Rust и скомпилировано в WebAssembly; пакет не имеет runtime-зависимостей.
4
+
5
+ Ядро возвращает **данные**, не затрагивает DOM. Три вспомогательные функции переводят эти данные в живые CSS-переменные: `applyTheme` (разовое применение), `watchTheme` (реактивное — обновляется при изменении фона) и `adaptTheme` (плавная адаптация для фона, меняющегося каждый кадр).
6
+
7
+ ---
8
+
9
+ ## Установка
10
+
11
+ Пакет публикуется в npm-реестр Labpics:
12
+
13
+ ```sh
14
+ npm install @labpics/colors
15
+ ```
16
+
17
+ При сборке из монорепо:
18
+
19
+ ```sh
20
+ npm run build # → pkg/ (wasm + JS-обёртка + .d.ts)
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Как использовать
26
+
27
+ ### Разовое применение
28
+
29
+ ```ts
30
+ import init, { LabColors, applyTheme } from "@labpics/colors";
31
+
32
+ await init(); // загрузить WASM-модуль (один раз)
33
+ const engine = new LabColors(); // движок по умолчанию
34
+
35
+ const result = engine.resolveTheme("#FFFFFF", "light");
36
+ // result.vars → { "--lab-label-primary": "#1a1a1a", "--lab-icon": "#5b5b5b", ... }
37
+ // result.roles → детали каждой роли (hex, контраст, флаги)
38
+
39
+ applyTheme(document.documentElement, result); // записать все --lab-* в элемент
40
+ ```
41
+
42
+ ### Реактивное отслеживание
43
+
44
+ `watchTheme` синхронизирует переменные с фактическим фоном элемента автоматически.
45
+
46
+ ```ts
47
+ import init, { LabColors, watchTheme } from "@labpics/colors";
48
+
49
+ await init();
50
+ const colors = new LabColors();
51
+
52
+ const panel = document.querySelector(".panel") as HTMLElement;
53
+ const watcher = watchTheme(panel, { colors, theme: "light" });
54
+ // panel теперь несёт правильные --lab-* для своего фона
55
+
56
+ watcher.setTheme("dark"); // переключить тему
57
+ watcher.refresh(); // принудительная пересинхронизация
58
+ watcher.stop(); // отключить наблюдателя
59
+ ```
60
+
61
+ Поддерживает два режима изменений:
62
+
63
+ - **Дискретные** (переключение темы, обновление класса, DOM-мутация) — отслеживаются `MutationObserver` автоматически.
64
+ - **Непрерывные** (CSS-анимация, кадровый фон, который никогда не мутирует `inline style`) — управляются вручную через `refresh()` из собственного цикла `requestAnimationFrame`. `refresh()` дёшев: пересчитывает только при реальном изменении строки фона.
65
+
66
+ ### Плавная адаптация к анимированному фону
67
+
68
+ `watchTheme` пересчитывает весь набор при каждом изменении фона. Для фона, меняющегося **каждый кадр** (анимация, параллакс, размытый фон), `adaptTheme` — более элегантный вариант: проверяет дёшево каждый кадр, пересчитывает и плавно переходит к новым цветам только когда контраст реально ухудшается.
69
+
70
+ ```ts
71
+ import init, { LabColors, adaptTheme, effectiveBackground } from "@labpics/colors";
72
+
73
+ await init();
74
+ const colors = new LabColors();
75
+
76
+ const surface = document.querySelector(".hero") as HTMLElement;
77
+ const adaptive = adaptTheme(surface, {
78
+ colors,
79
+ theme: "light",
80
+ background: () => effectiveBackground(surface, { fallback: "#101012" }),
81
+ });
82
+
83
+ adaptive.start(); // запустить внутренний requestAnimationFrame-цикл
84
+ adaptive.setTheme("dark"); // смена темы применяется мгновенно
85
+ adaptive.stop(); // остановить цикл
86
+ ```
87
+
88
+ Для фона с градиентом или изображением можно передать несколько образцов — цвета будут корректны для наиболее сложного из них:
89
+
90
+ ```ts
91
+ adaptTheme(hero, {
92
+ colors,
93
+ theme: "light",
94
+ strict: true,
95
+ background: () => sampleBackdrop(hero), // например ["#0B0B0E", "#3A3A40"]
96
+ });
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Темы
102
+
103
+ | Имя темы | Назначение |
104
+ |----------|------------|
105
+ | `"light"` | Светлая тема |
106
+ | `"dark"` | Тёмная тема |
107
+ | `"light-ic"` | Светлая с повышенным контрастом |
108
+ | `"dark-ic"` | Тёмная с повышенным контрастом |
109
+
110
+ ---
111
+
112
+ ## Справочник публичного API
113
+
114
+ ### `new LabColors()`
115
+
116
+ Создаёт движок с таблицей ролей и условиями наблюдения по умолчанию. Повторные одинаковые вызовы `resolveTheme` обслуживаются из внутреннего кэша.
117
+
118
+ ---
119
+
120
+ ### `engine.resolveTheme(bgHex, theme): ResolvedTheme`
121
+
122
+ - `bgHex` — фон в формате `#RGB` или `#RRGGBB`.
123
+ - `theme` — `"light" | "dark" | "light-ic" | "dark-ic"`.
124
+
125
+ Возвращает объект `ResolvedTheme`:
126
+
127
+ ```ts
128
+ interface ResolvedTheme {
129
+ theme: ThemeName;
130
+ background: string; // нормализованный #RRGGBB
131
+ vars: Record<string, string>; // достижимые роли: "--lab-<ключ>" → hex
132
+ roles: Record<string, RoleResult>; // все роли с деталями
133
+ }
134
+
135
+ type RoleResult = SolvedColor | NoneRole | UnreachableRole;
136
+ ```
137
+
138
+ Каждая роль — одно из трёх состояний:
139
+
140
+ - `SolvedColor` — цвет найден (`kind: "color"`, поля `hex`, `lc`, `wcagRatio`, …).
141
+ - `NoneRole` — роль намеренно пустая по дизайну (`kind: "none"`), не ошибка.
142
+ - `UnreachableRole` — ни один цвет не удовлетворяет требованиям для этого фона (`kind: "unreachable"`).
143
+
144
+ Недостижимость отдельных ролей — **часть успешного результата**. Отклоняет весь вызов (как `Error`) только при невалидном аргументе:
145
+
146
+ | Код ошибки | Причина |
147
+ |------------|---------|
148
+ | `invalid_background` | `bgHex` не является `#RGB` или `#RRGGBB` |
149
+ | `unknown_theme` | `theme` не входит в список допустимых |
150
+
151
+ ---
152
+
153
+ ### `applyTheme(element, result): void`
154
+
155
+ Записывает все достижимые роли из `result.vars` в `element.style` через `setProperty`. Устаревшие `--lab-*` от предыдущего вызова сбрасываются перед записью — роль, потерявшая достижимость, не остаётся висеть.
156
+
157
+ ---
158
+
159
+ ### `watchTheme(element, options): WatchController`
160
+
161
+ Синхронизирует `--lab-*` переменные с фактическим фоном элемента. Применяет немедленно при создании и возвращает контроллер.
162
+
163
+ ```ts
164
+ interface WatchThemeOptions {
165
+ colors: LabColors;
166
+ theme: ThemeName;
167
+ background?: string | (() => string); // явный фон (если автоматический невозможен)
168
+ target?: HTMLElement; // куда писать переменные (по умолчанию: element)
169
+ fallback?: string; // фон при полностью прозрачной цепочке (по умолчанию "#FFFFFF")
170
+ observe?: boolean; // авто-обновление при DOM-мутациях (по умолчанию true)
171
+ }
172
+
173
+ interface WatchController {
174
+ refresh(force?: boolean): ResolvedTheme | null; // пересчитать и применить, если фон изменился
175
+ setTheme(theme: ThemeName): void; // переключить тему и применить
176
+ background(): string; // последний вычисленный фон (hex)
177
+ stop(): void; // отключить наблюдателей
178
+ }
179
+ ```
180
+
181
+ Для поверхности над изображением, градиентом или размытым фоном — где автоматическое вычисление невозможно — передайте явный `background` (hex-строку или функцию, возвращающую hex).
182
+
183
+ ---
184
+
185
+ ### `adaptTheme(element, options): AdaptController`
186
+
187
+ Режим с гистерезисом: дешёвая проверка каждый кадр, пересчёт и плавный переход только при устойчивом ухудшении контраста. Применяет немедленно и возвращает контроллер.
188
+
189
+ ```ts
190
+ interface AdaptThemeOptions {
191
+ colors: LabColors;
192
+ theme: ThemeName;
193
+ background?: string | string[] | (() => string | string[]);
194
+ // один hex или несколько образцов фона (наихудший учитывается)
195
+ target?: HTMLElement; // куда писать переменные (по умолчанию: element)
196
+ fallback?: string; // фон при прозрачной цепочке (по умолчанию "#FFFFFF")
197
+ dropFraction?: number; // запас контраста до пересчёта (по умолчанию 0.2)
198
+ sustainMs?: number; // минимальное время удержания нарушения (по умолчанию 120)
199
+ dwellMs?: number; // минимальный интервал между пересчётами (по умолчанию 250)
200
+ easeMs?: number; // длительность перехода (по умолчанию 280; уменьшается при reduced-motion)
201
+ strict?: boolean; // держать минимальный контраст на каждом кадре перехода (по умолчанию false)
202
+ reducedMotion?: boolean; // переопределить системную настройку
203
+ }
204
+
205
+ interface AdaptController {
206
+ tick(now?: number): void; // один шаг (или использовать start())
207
+ setTheme(theme: ThemeName): void; // переключить тему мгновенно
208
+ start(): void; // запустить внутренний requestAnimationFrame-цикл
209
+ stop(): void; // остановить цикл
210
+ current(): Record<string, string>; // текущие применённые --lab-* переменные
211
+ }
212
+ ```
213
+
214
+ Управляйте через `start()` (внутренний rAF-цикл) или вызывайте `tick()` из собственного цикла. Смена темы применяется мгновенно — это осознанное намерение, а не дрейф.
215
+
216
+ ---
217
+
218
+ ### `effectiveBackground(element, options?): string`
219
+
220
+ Возвращает непрозрачный `#RRGGBB` — цвет, который наблюдатель реально видит за содержимым `element`. Обходит цепочку предков и выполняет альфа-композитинг каждого `background-color` до получения непрозрачного результата, поверх `fallback` (по умолчанию белый).
221
+
222
+ ```ts
223
+ const bg = effectiveBackground(panel); // например "#0F1014"
224
+ const bg2 = effectiveBackground(panel, { fallback: "#101012" });
225
+ ```
226
+
227
+ **Честное ограничение:** работает только с сплошными и полупрозрачными `background-color` — не с `background-image`, градиентами, размытым фоном или видео. Для таких поверхностей передайте фон явно.
228
+
229
+ Дополнительно экспортируются вспомогательные функции для работы со слоями: `parseCssColor`, `compositeOver`, `compositeStackToHex`, `toHex` и `oklabLerp` (перцептуально равномерная интерполяция между двумя hex-значениями — траектория цвета при переходе).
230
+
231
+ ---
232
+
233
+ ## Размер бандла
234
+
235
+ | Артефакт | raw | gzip | brotli |
236
+ |----------|-----|------|--------|
237
+ | `labcolors_bg.wasm` | ~313 КБ | ~138 КБ | ~116 КБ |
238
+ | `labcolors.js` (JS-обёртка) | ~17 КБ | ~4 КБ | ~4 КБ |
239
+
240
+ Это ВЕСЬ движок: перцептивная модель CAM16, солверы контраста, лестницы, граница конфига. `.wasm` — не JS-байты бандла, а ассет: он **не на критическом пути рендера** — грузится параллельно, компилируется потоково вне главного треда, кэшируется браузером после первой загрузки. Вспомогательные функции (`applyTheme`, `watchTheme`, `adaptTheme`, `effectiveBackground`) — несколько сотен байт чистого JavaScript с tree-shaking через именованные экспорты.
241
+
242
+ Требует современных браузеров (2023+): сборщики Vite / webpack 5 / Next штатно понимают `new URL('….wasm', import.meta.url)` (вывод wasm-pack). В node передавайте wasm-байты напрямую в `init({ module_or_path })`.
243
+
244
+ ### Для аудиторов цепочки поставки
245
+
246
+ - **Network access (Socket и др.):** единственный `fetch` в пакете (`pkg/labcolors.js`) загружает СОБСТВЕННЫЙ `.wasm`-файл пакета при `init(url)` — стандартный лоадер wasm-bindgen. Ни внешних адресов, ни отправки данных, ни исполнения при импорте. В node-пути (передача байтов) `fetch` не вызывается.
247
+ - **Bundlephobia `BuildError`:** их webpack-конвейер не умеет `.wasm`-ассеты («loader customization needed») — так падает почти любой WASM-пакет. Реальные размеры — в таблице выше (замер CI-шага `report bundle size`).
248
+ - **Zero runtime JS-dependencies:** npm-поле `dependencies` пусто — транзитивной JS/npm-цепочки поставки нет. Rust-крейты сборки (`serde`, `serde_json` и др.) компилируются ВНУТРЬ `.wasm` (учтены в его размере выше); их цепочка аудируется на стороне сборки — `cargo audit` (RustSec) в CI lab-colors.
package/adapt-theme.d.ts CHANGED
@@ -1,67 +1,67 @@
1
- // Public types for the adaptive hysteresis controller.
2
-
3
- import type { LabColors, ThemeName } from "./pkg/labcolors.js";
4
-
5
- export interface AdaptThemeOptions {
6
- /** An initialised engine — needs `resolveTheme` AND `recheckContrast`. */
7
- colors: Pick<LabColors, "resolveTheme" | "recheckContrast">;
8
- theme: ThemeName;
9
- /**
10
- * Explicit effective background, overriding the ancestor-composite. A single
11
- * hex is one solid surface; an array (or a function returning one) is a set of
12
- * worst-case samples of a varying backdrop (gradient / image / video the
13
- * caller sampled), and the colours are held legible against the hardest
14
- * sample. With one sample this is identical to plain single-background mode.
15
- */
16
- background?: string | string[] | (() => string | string[]);
17
- /** Element to write the `--lab-*` variables onto. Defaults to the watched element. */
18
- target?: HTMLElement;
19
- /** Base colour when the ancestor chain is fully translucent. Default `"#FFFFFF"`. */
20
- fallback?: string;
21
- /** Fraction of a role's contrast surplus that may be lost before a re-solve. Default `0.2`. */
22
- dropFraction?: number;
23
- /** A breach must persist this many ms before re-solving (debounce). Default `120`. */
24
- sustainMs?: number;
25
- /** Minimum ms between re-solves (dwell / rate cap). Default `250`. */
26
- dwellMs?: number;
27
- /** Crossfade duration in ms. Default `280` (capped to a short fade under reduced motion). */
28
- easeMs?: number;
29
- /**
30
- * Hold each role's WCAG legal floor every frame of the ease: an interpolated
31
- * colour is only shown while it still clears the role's `legalFloor` against
32
- * the live background (for text on animated content / `prefers-contrast`).
33
- * Default `false` (the free ease — brief surplus dips, destination always legal).
34
- */
35
- strict?: boolean;
36
- /** Override reduced-motion detection (default reads `matchMedia`). */
37
- reducedMotion?: boolean;
38
- /** Clock injection (default `performance.now`/`Date.now`). */
39
- now?: () => number;
40
- /** Window-like host (rAF, matchMedia). Defaults to `globalThis`. */
41
- win?: Window;
42
- /** Injection seam for the computed style of an element (testing). */
43
- getStyle?: (element: unknown) => { getPropertyValue(property: string): string };
44
- /** Injection seam for an element's parent (testing). */
45
- parentOf?: (element: unknown) => unknown;
46
- }
47
-
48
- export interface AdaptController {
49
- /** Drive one step. Cheap (a re-check); re-solves only on a sustained breach. */
50
- tick(now?: number): void;
51
- /** Switch theme INSTANTLY (intent, not drift) — bypasses the hysteresis. */
52
- setTheme(theme: ThemeName): void;
53
- /** Begin an internal `requestAnimationFrame` loop. */
54
- start(): void;
55
- /** Stop the loop. */
56
- stop(): void;
57
- /** The currently-applied `--lab-*` variables. */
58
- current(): Record<string, string>;
59
- }
60
-
61
- /**
62
- * Keep an element's `--lab-*` variables adapting to its (changing) background
63
- * **lazily and smoothly**: re-check each frame, hold while colours still pass,
64
- * and re-solve + ease only when contrast stably degrades. The calm, cheap
65
- * alternative to re-solving every frame.
66
- */
67
- export declare function adaptTheme(element: HTMLElement, options: AdaptThemeOptions): AdaptController;
1
+ // Public types for the adaptive hysteresis controller.
2
+
3
+ import type { LabColors, ThemeName } from "./pkg/labcolors.js";
4
+
5
+ export interface AdaptThemeOptions {
6
+ /** An initialised engine — needs `resolveTheme` AND `recheckContrast`. */
7
+ colors: Pick<LabColors, "resolveTheme" | "recheckContrast">;
8
+ theme: ThemeName;
9
+ /**
10
+ * Explicit effective background, overriding the ancestor-composite. A single
11
+ * hex is one solid surface; an array (or a function returning one) is a set of
12
+ * worst-case samples of a varying backdrop (gradient / image / video the
13
+ * caller sampled), and the colours are held legible against the hardest
14
+ * sample. With one sample this is identical to plain single-background mode.
15
+ */
16
+ background?: string | string[] | (() => string | string[]);
17
+ /** Element to write the `--lab-*` variables onto. Defaults to the watched element. */
18
+ target?: HTMLElement;
19
+ /** Base colour when the ancestor chain is fully translucent. Default `"#FFFFFF"`. */
20
+ fallback?: string;
21
+ /** Fraction of a role's contrast surplus that may be lost before a re-solve. Default `0.2`. */
22
+ dropFraction?: number;
23
+ /** A breach must persist this many ms before re-solving (debounce). Default `120`. */
24
+ sustainMs?: number;
25
+ /** Minimum ms between re-solves (dwell / rate cap). Default `250`. */
26
+ dwellMs?: number;
27
+ /** Crossfade duration in ms. Default `280` (capped to a short fade under reduced motion). */
28
+ easeMs?: number;
29
+ /**
30
+ * Hold each role's WCAG legal floor every frame of the ease: an interpolated
31
+ * colour is only shown while it still clears the role's `legalFloor` against
32
+ * the live background (for text on animated content / `prefers-contrast`).
33
+ * Default `false` (the free ease — brief surplus dips, destination always legal).
34
+ */
35
+ strict?: boolean;
36
+ /** Override reduced-motion detection (default reads `matchMedia`). */
37
+ reducedMotion?: boolean;
38
+ /** Clock injection (default `performance.now`/`Date.now`). */
39
+ now?: () => number;
40
+ /** Window-like host (rAF, matchMedia). Defaults to `globalThis`. */
41
+ win?: Window;
42
+ /** Injection seam for the computed style of an element (testing). */
43
+ getStyle?: (element: unknown) => { getPropertyValue(property: string): string };
44
+ /** Injection seam for an element's parent (testing). */
45
+ parentOf?: (element: unknown) => unknown;
46
+ }
47
+
48
+ export interface AdaptController {
49
+ /** Drive one step. Cheap (a re-check); re-solves only on a sustained breach. */
50
+ tick(now?: number): void;
51
+ /** Switch theme INSTANTLY (intent, not drift) — bypasses the hysteresis. */
52
+ setTheme(theme: ThemeName): void;
53
+ /** Begin an internal `requestAnimationFrame` loop. */
54
+ start(): void;
55
+ /** Stop the loop. */
56
+ stop(): void;
57
+ /** The currently-applied `--lab-*` variables. */
58
+ current(): Record<string, string>;
59
+ }
60
+
61
+ /**
62
+ * Keep an element's `--lab-*` variables adapting to its (changing) background
63
+ * **lazily and smoothly**: re-check each frame, hold while colours still pass,
64
+ * and re-solve + ease only when contrast stably degrades. The calm, cheap
65
+ * alternative to re-solving every frame.
66
+ */
67
+ export declare function adaptTheme(element: HTMLElement, options: AdaptThemeOptions): AdaptController;