@cloud-ru/ds-theme 1.0.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/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +365 -0
- package/dist/cjs/components/ChildThemeProvider/ChildThemeProvider.d.ts +24 -0
- package/dist/cjs/components/ChildThemeProvider/ChildThemeProvider.js +25 -0
- package/dist/cjs/components/ChildThemeProvider/index.d.ts +1 -0
- package/dist/cjs/components/ChildThemeProvider/index.js +17 -0
- package/dist/cjs/components/RootThemeProvider/RootThemeProvider.d.ts +49 -0
- package/dist/cjs/components/RootThemeProvider/RootThemeProvider.js +30 -0
- package/dist/cjs/components/RootThemeProvider/index.d.ts +1 -0
- package/dist/cjs/components/RootThemeProvider/index.js +17 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.d.ts +28 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.js +16 -0
- package/dist/cjs/components/ThemeProvider/index.d.ts +1 -0
- package/dist/cjs/components/ThemeProvider/index.js +17 -0
- package/dist/cjs/components/ThemeScope/ThemeScope.d.ts +32 -0
- package/dist/cjs/components/ThemeScope/ThemeScope.js +65 -0
- package/dist/cjs/components/ThemeScope/index.d.ts +1 -0
- package/dist/cjs/components/ThemeScope/index.js +17 -0
- package/dist/cjs/components/index.d.ts +3 -0
- package/dist/cjs/components/index.js +19 -0
- package/dist/cjs/constants/appearance.d.ts +22 -0
- package/dist/cjs/constants/appearance.js +27 -0
- package/dist/cjs/constants/colorScheme.d.ts +17 -0
- package/dist/cjs/constants/colorScheme.js +23 -0
- package/dist/cjs/constants/index.d.ts +2 -0
- package/dist/cjs/constants/index.js +18 -0
- package/dist/cjs/context/appearanceContext.d.ts +20 -0
- package/dist/cjs/context/appearanceContext.js +22 -0
- package/dist/cjs/context/index.d.ts +4 -0
- package/dist/cjs/context/index.js +7 -0
- package/dist/cjs/context/themeContext.d.ts +12 -0
- package/dist/cjs/context/themeContext.js +15 -0
- package/dist/cjs/hooks/index.d.ts +4 -0
- package/dist/cjs/hooks/index.js +20 -0
- package/dist/cjs/hooks/useApplyCustomTheme.d.ts +27 -0
- package/dist/cjs/hooks/useApplyCustomTheme.js +39 -0
- package/dist/cjs/hooks/useColorScheme.d.ts +37 -0
- package/dist/cjs/hooks/useColorScheme.js +80 -0
- package/dist/cjs/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
- package/dist/cjs/hooks/useIsomorphicLayoutEffect.js +16 -0
- package/dist/cjs/hooks/useThemeClassnames.d.ts +11 -0
- package/dist/cjs/hooks/useThemeClassnames.js +18 -0
- package/dist/cjs/hooks/useThemeConfig.d.ts +24 -0
- package/dist/cjs/hooks/useThemeConfig.js +15 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/keys.d.ts +4 -0
- package/dist/cjs/keys.js +19 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/ssr.d.ts +15 -0
- package/dist/cjs/ssr.js +38 -0
- package/dist/cjs/store/globalStore.d.ts +29 -0
- package/dist/cjs/store/globalStore.js +72 -0
- package/dist/cjs/store/index.d.ts +1 -0
- package/dist/cjs/store/index.js +17 -0
- package/dist/cjs/types/appearance.d.ts +23 -0
- package/dist/cjs/types/appearance.js +3 -0
- package/dist/cjs/types/colorScheme.d.ts +5 -0
- package/dist/cjs/types/colorScheme.js +2 -0
- package/dist/cjs/types/index.d.ts +2 -0
- package/dist/cjs/types/index.js +18 -0
- package/dist/cjs/utils/colorSchemeFromHeaders.d.ts +22 -0
- package/dist/cjs/utils/colorSchemeFromHeaders.js +44 -0
- package/dist/cjs/utils/colorSchemeStorage.d.ts +35 -0
- package/dist/cjs/utils/colorSchemeStorage.js +85 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteCss.d.ts +11 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteCss.js +35 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteVars.d.ts +11 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteVars.js +42 -0
- package/dist/cjs/utils/customTheme/color/apca.d.ts +4 -0
- package/dist/cjs/utils/customTheme/color/apca.js +62 -0
- package/dist/cjs/utils/customTheme/color/colorSpace.d.ts +5 -0
- package/dist/cjs/utils/customTheme/color/colorSpace.js +72 -0
- package/dist/cjs/utils/customTheme/color/conversions.d.ts +17 -0
- package/dist/cjs/utils/customTheme/color/conversions.js +101 -0
- package/dist/cjs/utils/customTheme/color/hex.d.ts +10 -0
- package/dist/cjs/utils/customTheme/color/hex.js +34 -0
- package/dist/cjs/utils/customTheme/color/index.d.ts +4 -0
- package/dist/cjs/utils/customTheme/color/index.js +20 -0
- package/dist/cjs/utils/customTheme/color/oklch.d.ts +3 -0
- package/dist/cjs/utils/customTheme/color/oklch.js +21 -0
- package/dist/cjs/utils/customTheme/color/types.d.ts +40 -0
- package/dist/cjs/utils/customTheme/color/types.js +2 -0
- package/dist/cjs/utils/customTheme/constants.d.ts +31 -0
- package/dist/cjs/utils/customTheme/constants.js +49 -0
- package/dist/cjs/utils/customTheme/generateBrandPalette.d.ts +10 -0
- package/dist/cjs/utils/customTheme/generateBrandPalette.js +25 -0
- package/dist/cjs/utils/customTheme/index.d.ts +4 -0
- package/dist/cjs/utils/customTheme/index.js +20 -0
- package/dist/cjs/utils/getThemeClassnames.d.ts +17 -0
- package/dist/cjs/utils/getThemeClassnames.js +38 -0
- package/dist/cjs/utils/index.d.ts +7 -0
- package/dist/cjs/utils/index.js +23 -0
- package/dist/cjs/utils/mergeAppearance.d.ts +7 -0
- package/dist/cjs/utils/mergeAppearance.js +17 -0
- package/dist/cjs/utils/resolveColorScheme.d.ts +7 -0
- package/dist/cjs/utils/resolveColorScheme.js +15 -0
- package/dist/cjs/utils/themeBootstrap.d.ts +12 -0
- package/dist/cjs/utils/themeBootstrap.js +19 -0
- package/dist/esm/components/ChildThemeProvider/ChildThemeProvider.d.ts +24 -0
- package/dist/esm/components/ChildThemeProvider/ChildThemeProvider.js +22 -0
- package/dist/esm/components/ChildThemeProvider/index.d.ts +1 -0
- package/dist/esm/components/ChildThemeProvider/index.js +1 -0
- package/dist/esm/components/RootThemeProvider/RootThemeProvider.d.ts +49 -0
- package/dist/esm/components/RootThemeProvider/RootThemeProvider.js +27 -0
- package/dist/esm/components/RootThemeProvider/index.d.ts +1 -0
- package/dist/esm/components/RootThemeProvider/index.js +1 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.d.ts +28 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.js +13 -0
- package/dist/esm/components/ThemeProvider/index.d.ts +1 -0
- package/dist/esm/components/ThemeProvider/index.js +1 -0
- package/dist/esm/components/ThemeScope/ThemeScope.d.ts +32 -0
- package/dist/esm/components/ThemeScope/ThemeScope.js +59 -0
- package/dist/esm/components/ThemeScope/index.d.ts +1 -0
- package/dist/esm/components/ThemeScope/index.js +1 -0
- package/dist/esm/components/index.d.ts +3 -0
- package/dist/esm/components/index.js +3 -0
- package/dist/esm/constants/appearance.d.ts +22 -0
- package/dist/esm/constants/appearance.js +24 -0
- package/dist/esm/constants/colorScheme.d.ts +17 -0
- package/dist/esm/constants/colorScheme.js +20 -0
- package/dist/esm/constants/index.d.ts +2 -0
- package/dist/esm/constants/index.js +2 -0
- package/dist/esm/context/appearanceContext.d.ts +20 -0
- package/dist/esm/context/appearanceContext.js +18 -0
- package/dist/esm/context/index.d.ts +4 -0
- package/dist/esm/context/index.js +2 -0
- package/dist/esm/context/themeContext.d.ts +12 -0
- package/dist/esm/context/themeContext.js +11 -0
- package/dist/esm/hooks/index.d.ts +4 -0
- package/dist/esm/hooks/index.js +4 -0
- package/dist/esm/hooks/useApplyCustomTheme.d.ts +27 -0
- package/dist/esm/hooks/useApplyCustomTheme.js +36 -0
- package/dist/esm/hooks/useColorScheme.d.ts +37 -0
- package/dist/esm/hooks/useColorScheme.js +76 -0
- package/dist/esm/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
- package/dist/esm/hooks/useIsomorphicLayoutEffect.js +13 -0
- package/dist/esm/hooks/useThemeClassnames.d.ts +11 -0
- package/dist/esm/hooks/useThemeClassnames.js +15 -0
- package/dist/esm/hooks/useThemeConfig.d.ts +24 -0
- package/dist/esm/hooks/useThemeConfig.js +12 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/keys.d.ts +4 -0
- package/dist/esm/keys.js +16 -0
- package/dist/esm/ssr.d.ts +15 -0
- package/dist/esm/ssr.js +15 -0
- package/dist/esm/store/globalStore.d.ts +29 -0
- package/dist/esm/store/globalStore.js +69 -0
- package/dist/esm/store/index.d.ts +1 -0
- package/dist/esm/store/index.js +1 -0
- package/dist/esm/types/appearance.d.ts +23 -0
- package/dist/esm/types/appearance.js +2 -0
- package/dist/esm/types/colorScheme.d.ts +5 -0
- package/dist/esm/types/colorScheme.js +1 -0
- package/dist/esm/types/index.d.ts +2 -0
- package/dist/esm/types/index.js +2 -0
- package/dist/esm/utils/colorSchemeFromHeaders.d.ts +22 -0
- package/dist/esm/utils/colorSchemeFromHeaders.js +40 -0
- package/dist/esm/utils/colorSchemeStorage.d.ts +35 -0
- package/dist/esm/utils/colorSchemeStorage.js +81 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteCss.d.ts +11 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteCss.js +32 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteVars.d.ts +11 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteVars.js +39 -0
- package/dist/esm/utils/customTheme/color/apca.d.ts +4 -0
- package/dist/esm/utils/customTheme/color/apca.js +59 -0
- package/dist/esm/utils/customTheme/color/colorSpace.d.ts +5 -0
- package/dist/esm/utils/customTheme/color/colorSpace.js +68 -0
- package/dist/esm/utils/customTheme/color/conversions.d.ts +17 -0
- package/dist/esm/utils/customTheme/color/conversions.js +91 -0
- package/dist/esm/utils/customTheme/color/hex.d.ts +10 -0
- package/dist/esm/utils/customTheme/color/hex.js +29 -0
- package/dist/esm/utils/customTheme/color/index.d.ts +4 -0
- package/dist/esm/utils/customTheme/color/index.js +4 -0
- package/dist/esm/utils/customTheme/color/oklch.d.ts +3 -0
- package/dist/esm/utils/customTheme/color/oklch.js +18 -0
- package/dist/esm/utils/customTheme/color/types.d.ts +40 -0
- package/dist/esm/utils/customTheme/color/types.js +1 -0
- package/dist/esm/utils/customTheme/constants.d.ts +31 -0
- package/dist/esm/utils/customTheme/constants.js +46 -0
- package/dist/esm/utils/customTheme/generateBrandPalette.d.ts +10 -0
- package/dist/esm/utils/customTheme/generateBrandPalette.js +22 -0
- package/dist/esm/utils/customTheme/index.d.ts +4 -0
- package/dist/esm/utils/customTheme/index.js +4 -0
- package/dist/esm/utils/getThemeClassnames.d.ts +17 -0
- package/dist/esm/utils/getThemeClassnames.js +34 -0
- package/dist/esm/utils/index.d.ts +7 -0
- package/dist/esm/utils/index.js +7 -0
- package/dist/esm/utils/mergeAppearance.d.ts +7 -0
- package/dist/esm/utils/mergeAppearance.js +14 -0
- package/dist/esm/utils/resolveColorScheme.d.ts +7 -0
- package/dist/esm/utils/resolveColorScheme.js +12 -0
- package/dist/esm/utils/themeBootstrap.d.ts +12 -0
- package/dist/esm/utils/themeBootstrap.js +16 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +45 -0
- package/src/components/ChildThemeProvider/ChildThemeProvider.tsx +54 -0
- package/src/components/ChildThemeProvider/index.ts +1 -0
- package/src/components/RootThemeProvider/RootThemeProvider.tsx +87 -0
- package/src/components/RootThemeProvider/index.ts +1 -0
- package/src/components/ThemeProvider/ThemeProvider.tsx +41 -0
- package/src/components/ThemeProvider/index.ts +1 -0
- package/src/components/ThemeScope/ThemeScope.tsx +106 -0
- package/src/components/ThemeScope/index.ts +1 -0
- package/src/components/index.ts +3 -0
- package/src/constants/appearance.ts +27 -0
- package/src/constants/colorScheme.ts +23 -0
- package/src/constants/index.ts +2 -0
- package/src/context/appearanceContext.tsx +39 -0
- package/src/context/index.ts +4 -0
- package/src/context/themeContext.ts +18 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useApplyCustomTheme.ts +56 -0
- package/src/hooks/useColorScheme.ts +131 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +17 -0
- package/src/hooks/useThemeClassnames.ts +18 -0
- package/src/hooks/useThemeConfig.tsx +44 -0
- package/src/index.ts +7 -0
- package/src/keys.ts +18 -0
- package/src/ssr.ts +25 -0
- package/src/store/globalStore.ts +93 -0
- package/src/store/index.ts +1 -0
- package/src/types/appearance.ts +29 -0
- package/src/types/colorScheme.ts +7 -0
- package/src/types/index.ts +2 -0
- package/src/utils/colorSchemeFromHeaders.ts +57 -0
- package/src/utils/colorSchemeStorage.ts +117 -0
- package/src/utils/customTheme/buildBrandPaletteCss.ts +37 -0
- package/src/utils/customTheme/buildBrandPaletteVars.ts +56 -0
- package/src/utils/customTheme/color/apca.ts +74 -0
- package/src/utils/customTheme/color/colorSpace.ts +82 -0
- package/src/utils/customTheme/color/conversions.ts +113 -0
- package/src/utils/customTheme/color/hex.ts +39 -0
- package/src/utils/customTheme/color/index.ts +4 -0
- package/src/utils/customTheme/color/oklch.ts +22 -0
- package/src/utils/customTheme/color/types.ts +32 -0
- package/src/utils/customTheme/constants.ts +57 -0
- package/src/utils/customTheme/generateBrandPalette.ts +30 -0
- package/src/utils/customTheme/index.ts +4 -0
- package/src/utils/getThemeClassnames.ts +40 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/mergeAppearance.ts +16 -0
- package/src/utils/resolveColorScheme.ts +15 -0
- package/src/utils/themeBootstrap.ts +18 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ThemeOverride } from '../types/colorScheme';
|
|
2
|
+
/**
|
|
3
|
+
* Точка расширения персиста цветовой схемы. DS не «зашивает» хранилище: дефолт — in-memory
|
|
4
|
+
* (`createInMemoryColorSchemeStorage`, без персиста между перезагрузками). Персист подключает
|
|
5
|
+
* потребитель явным адаптером: `createCookieColorSchemeStorage` (no-flash SSR) или собственный
|
|
6
|
+
* (localStorage / бэкенд-сессия — `read()` возвращает `undefined`, начальное даёт `initialOverride`
|
|
7
|
+
* из server-сессии, `write()` шлёт POST, `subscribe()` — push через SSE/ws). Контракт намеренно про
|
|
8
|
+
* одну ось `override`; brand/density персистит сам апп. Модель `useColorScheme` при любом адаптере
|
|
9
|
+
* не меняется.
|
|
10
|
+
*/
|
|
11
|
+
export type ColorSchemeStorage = {
|
|
12
|
+
/** Синхронно прочитать сохранённый override. Для бэкенд-адаптера — `undefined` (см. `initialOverride`). */
|
|
13
|
+
read(): ThemeOverride | undefined;
|
|
14
|
+
/** Персистнуть выбор (cookie / POST на бэкенд). */
|
|
15
|
+
write(next: ThemeOverride): void;
|
|
16
|
+
/** Подписка на внешние изменения (кросс-таб / push). Возвращает отписку. */
|
|
17
|
+
subscribe(onChange: () => void): () => void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Дефолтный storage-адаптер: держит выбор в памяти модуля на время жизни страницы. Переживает
|
|
21
|
+
* ремаунты и синхронит несколько хуков между собой, но НЕ персистит между перезагрузками — DS
|
|
22
|
+
* намеренно не пишет в cookie/localStorage без явного согласия потребителя. Для персиста подключите
|
|
23
|
+
* `createCookieColorSchemeStorage` (или свой адаптер) через проп `storage`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createInMemoryColorSchemeStorage(): ColorSchemeStorage;
|
|
26
|
+
/**
|
|
27
|
+
* Cookie storage-адаптер (opt-in): cookie как источник истины (читается и на сервере, и inline-
|
|
28
|
+
* bootstrap'ом) + `BroadcastChannel` для живой кросс-таб синхронизации. Нужен для no-flash SSR —
|
|
29
|
+
* подключается потребителем через `useColorScheme({ storage: createCookieColorSchemeStorage() })`.
|
|
30
|
+
* Создание SSR-безопасно: DOM/Channel трогаются только при вызове методов, не в фабрике.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createCookieColorSchemeStorage(options?: {
|
|
33
|
+
storageKey?: string;
|
|
34
|
+
channelName?: string;
|
|
35
|
+
}): ColorSchemeStorage;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createInMemoryColorSchemeStorage = createInMemoryColorSchemeStorage;
|
|
4
|
+
exports.createCookieColorSchemeStorage = createCookieColorSchemeStorage;
|
|
5
|
+
const colorScheme_1 = require("../constants/colorScheme");
|
|
6
|
+
/* eslint-disable @cloud-ru/ssr-safe-react/domApi -- guarded by isBrowser(); адаптеры трогают DOM только при вызове методов */
|
|
7
|
+
function isBrowser() {
|
|
8
|
+
return Boolean(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
9
|
+
}
|
|
10
|
+
function isOverride(value) {
|
|
11
|
+
return value === colorScheme_1.THEME_OVERRIDE.Light || value === colorScheme_1.THEME_OVERRIDE.Dark || value === colorScheme_1.THEME_OVERRIDE.System;
|
|
12
|
+
}
|
|
13
|
+
function readCookieOverride(storageKey) {
|
|
14
|
+
if (!isBrowser()) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const match = document.cookie.match(new RegExp(`(?:^|; )${storageKey}=([^;]*)`));
|
|
18
|
+
const value = match ? decodeURIComponent(match[1]) : undefined;
|
|
19
|
+
return isOverride(value) ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Дефолтный storage-адаптер: держит выбор в памяти модуля на время жизни страницы. Переживает
|
|
23
|
+
* ремаунты и синхронит несколько хуков между собой, но НЕ персистит между перезагрузками — DS
|
|
24
|
+
* намеренно не пишет в cookie/localStorage без явного согласия потребителя. Для персиста подключите
|
|
25
|
+
* `createCookieColorSchemeStorage` (или свой адаптер) через проп `storage`.
|
|
26
|
+
*/
|
|
27
|
+
function createInMemoryColorSchemeStorage() {
|
|
28
|
+
let value;
|
|
29
|
+
const listeners = new Set();
|
|
30
|
+
return {
|
|
31
|
+
read: () => value,
|
|
32
|
+
write: next => {
|
|
33
|
+
value = next;
|
|
34
|
+
listeners.forEach(listener => listener());
|
|
35
|
+
},
|
|
36
|
+
subscribe: onChange => {
|
|
37
|
+
listeners.add(onChange);
|
|
38
|
+
return () => {
|
|
39
|
+
listeners.delete(onChange);
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Cookie storage-адаптер (opt-in): cookie как источник истины (читается и на сервере, и inline-
|
|
46
|
+
* bootstrap'ом) + `BroadcastChannel` для живой кросс-таб синхронизации. Нужен для no-flash SSR —
|
|
47
|
+
* подключается потребителем через `useColorScheme({ storage: createCookieColorSchemeStorage() })`.
|
|
48
|
+
* Создание SSR-безопасно: DOM/Channel трогаются только при вызове методов, не в фабрике.
|
|
49
|
+
*/
|
|
50
|
+
function createCookieColorSchemeStorage(options) {
|
|
51
|
+
const storageKey = options?.storageKey ?? colorScheme_1.THEME_OVERRIDE_STORAGE_KEY;
|
|
52
|
+
const channelName = options?.channelName ?? `${storageKey}-channel`;
|
|
53
|
+
let channel = null;
|
|
54
|
+
const getChannel = () => {
|
|
55
|
+
if (channel) {
|
|
56
|
+
return channel;
|
|
57
|
+
}
|
|
58
|
+
if (!isBrowser() || typeof BroadcastChannel === 'undefined') {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
channel = new BroadcastChannel(channelName);
|
|
62
|
+
return channel;
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
read: () => readCookieOverride(storageKey),
|
|
66
|
+
write: next => {
|
|
67
|
+
if (!isBrowser()) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// max-age ~1 год, lax — cookie уходит на сервер для SSR-резолва следующей загрузки.
|
|
71
|
+
document.cookie = `${storageKey}=${encodeURIComponent(next)};path=/;max-age=31536000;samesite=lax`;
|
|
72
|
+
getChannel()?.postMessage(next);
|
|
73
|
+
},
|
|
74
|
+
subscribe: onChange => {
|
|
75
|
+
const ch = getChannel();
|
|
76
|
+
if (!ch) {
|
|
77
|
+
return () => { };
|
|
78
|
+
}
|
|
79
|
+
const handler = () => onChange();
|
|
80
|
+
ch.addEventListener('message', handler);
|
|
81
|
+
return () => ch.removeEventListener('message', handler);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/* eslint-enable @cloud-ru/ssr-safe-react/domApi */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Собирает текст CSS-правила, переопределяющего бренд-палитру из seed-цвета на бренд-классах.
|
|
3
|
+
*
|
|
4
|
+
* - Без `scope` — глобально (`:is(.sn-brandA,…){…}`), для white-label в корне приложения: побеждает
|
|
5
|
+
* по source order на всех бренд-классах страницы, включая порталы.
|
|
6
|
+
* - Со `scope` (CSS-селектор корня поддерева) — ограничивает область потомками и самим корнем; выше
|
|
7
|
+
* токенной специфичности, поэтому порядок не важен.
|
|
8
|
+
*
|
|
9
|
+
* Невалидный hex → `null`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildBrandPaletteCss(seedColor: string, scope?: string): string | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildBrandPaletteCss = buildBrandPaletteCss;
|
|
4
|
+
const appearance_1 = require("../../constants/appearance");
|
|
5
|
+
const buildBrandPaletteVars_1 = require("./buildBrandPaletteVars");
|
|
6
|
+
// Правило нацелено на сами бренд-классы (`.sn-brandA/B/C/D/E`), а не на один элемент inline: компоненты,
|
|
7
|
+
// переэмитящие набор `sn-*` на своих обёртках (Table/Stepper/Alert через `useThemeClassnames`), заново
|
|
8
|
+
// объявляют `--sn-brand-color-primary-*` из класса бренда — inline на предке они перекрыли бы, правило
|
|
9
|
+
// на том же бренд-классе — нет.
|
|
10
|
+
// Один seed переопределяет сразу все бренд-классы (потребитель не знает, в какой бренд-слот завернётся
|
|
11
|
+
// его поддерево). TODO FF-8813: возможность задавать seed индивидуально для каждого бренда (brandA/B/C/D/E).
|
|
12
|
+
const BRAND_CLASS_SELECTOR = `:is(${Object.values(appearance_1.BRAND)
|
|
13
|
+
.map(brand => `.sn-${brand}`)
|
|
14
|
+
.join(',')})`;
|
|
15
|
+
/**
|
|
16
|
+
* Собирает текст CSS-правила, переопределяющего бренд-палитру из seed-цвета на бренд-классах.
|
|
17
|
+
*
|
|
18
|
+
* - Без `scope` — глобально (`:is(.sn-brandA,…){…}`), для white-label в корне приложения: побеждает
|
|
19
|
+
* по source order на всех бренд-классах страницы, включая порталы.
|
|
20
|
+
* - Со `scope` (CSS-селектор корня поддерева) — ограничивает область потомками и самим корнем; выше
|
|
21
|
+
* токенной специфичности, поэтому порядок не важен.
|
|
22
|
+
*
|
|
23
|
+
* Невалидный hex → `null`.
|
|
24
|
+
*/
|
|
25
|
+
function buildBrandPaletteCss(seedColor, scope) {
|
|
26
|
+
const vars = (0, buildBrandPaletteVars_1.buildBrandPaletteVars)(seedColor);
|
|
27
|
+
if (!vars) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const declarations = Object.entries(vars)
|
|
31
|
+
.map(([name, value]) => `${name}:${value}`)
|
|
32
|
+
.join(';');
|
|
33
|
+
const selector = scope ? `${scope} ${BRAND_CLASS_SELECTOR},${scope}${BRAND_CLASS_SELECTOR}` : BRAND_CLASS_SELECTOR;
|
|
34
|
+
return `${selector}{${declarations}}`;
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Собирает карту CSS-переменных `--sn-brand-color-primary-*` из seed-цвета для инъекции в DOM.
|
|
3
|
+
* Кроме числовых тонов задаёт `transparent` (акцент с alpha) и `onAccentLight`/`onAccentDark`
|
|
4
|
+
* (текст на светлой/тёмной акцентной заливке). Семантический слой `--sn-theme-color-primary-*`
|
|
5
|
+
* каскадит из этих тонов, поэтому одна карта красит и light, и dark. Невалидный hex → `null`.
|
|
6
|
+
*
|
|
7
|
+
* Текст на акценте: если белый на акценте контрастен (APCA ≥ 50) — светлая схема берёт near-white,
|
|
8
|
+
* тёмная near-dark; если акцент слишком светлый для белого — обе схемы инвертируются. Одно решение
|
|
9
|
+
* по контрасту seed-акцента с белым, зеркальное для схем.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildBrandPaletteVars(seedColor: string): Record<string, string> | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildBrandPaletteVars = buildBrandPaletteVars;
|
|
4
|
+
const color_1 = require("./color");
|
|
5
|
+
const constants_1 = require("./constants");
|
|
6
|
+
const generateBrandPalette_1 = require("./generateBrandPalette");
|
|
7
|
+
const WHITE_HEX = '#ffffff';
|
|
8
|
+
// Порог APCA-контраста белого текста на акценте: ниже него акцент считается слишком светлым и текст
|
|
9
|
+
// инвертируется на тёмный.
|
|
10
|
+
const WHITE_ON_ACCENT_MIN_CONTRAST = 50;
|
|
11
|
+
/**
|
|
12
|
+
* Собирает карту CSS-переменных `--sn-brand-color-primary-*` из seed-цвета для инъекции в DOM.
|
|
13
|
+
* Кроме числовых тонов задаёт `transparent` (акцент с alpha) и `onAccentLight`/`onAccentDark`
|
|
14
|
+
* (текст на светлой/тёмной акцентной заливке). Семантический слой `--sn-theme-color-primary-*`
|
|
15
|
+
* каскадит из этих тонов, поэтому одна карта красит и light, и dark. Невалидный hex → `null`.
|
|
16
|
+
*
|
|
17
|
+
* Текст на акценте: если белый на акценте контрастен (APCA ≥ 50) — светлая схема берёт near-white,
|
|
18
|
+
* тёмная near-dark; если акцент слишком светлый для белого — обе схемы инвертируются. Одно решение
|
|
19
|
+
* по контрасту seed-акцента с белым, зеркальное для схем.
|
|
20
|
+
*/
|
|
21
|
+
function buildBrandPaletteVars(seedColor) {
|
|
22
|
+
const palette = (0, generateBrandPalette_1.generateBrandPalette)(seedColor);
|
|
23
|
+
if (!palette) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const vars = {};
|
|
27
|
+
for (const [tone, hex] of Object.entries(palette)) {
|
|
28
|
+
vars[`${constants_1.BRAND_PRIMARY_VAR_PREFIX}${tone}`] = hex;
|
|
29
|
+
}
|
|
30
|
+
const accent = palette[constants_1.PRIMARY_ACCENT_TONE];
|
|
31
|
+
const nearWhite = palette[constants_1.NEAR_WHITE_TONE];
|
|
32
|
+
const nearDark = palette[constants_1.NEAR_DARK_TONE];
|
|
33
|
+
const accentTooLightForWhite = (0, color_1.apcaContrast)(accent, WHITE_HEX) < WHITE_ON_ACCENT_MIN_CONTRAST;
|
|
34
|
+
vars[`${constants_1.BRAND_PRIMARY_VAR_PREFIX}transparent`] = `${accent}${constants_1.TRANSPARENT_ALPHA_SUFFIX}`;
|
|
35
|
+
vars[`${constants_1.BRAND_PRIMARY_VAR_PREFIX}onAccentLight`] = accentTooLightForWhite ? nearDark : nearWhite;
|
|
36
|
+
vars[`${constants_1.BRAND_PRIMARY_VAR_PREFIX}onAccentDark`] = accentTooLightForWhite ? nearWhite : nearDark;
|
|
37
|
+
// Activated-заливки = акцент с alpha (выбранная строка таблицы, active-состояния следуют за брендом).
|
|
38
|
+
vars[constants_1.BRAND_ACTIVATED_VAR.default] = `${accent}${constants_1.ACTIVATED_ALPHA_SUFFIX.default}`;
|
|
39
|
+
vars[constants_1.BRAND_ACTIVATED_VAR.hovered] = `${accent}${constants_1.ACTIVATED_ALPHA_SUFFIX.hovered}`;
|
|
40
|
+
vars[constants_1.BRAND_ACTIVATED_VAR.pressed] = `${accent}${constants_1.ACTIVATED_ALPHA_SUFFIX.pressed}`;
|
|
41
|
+
return vars;
|
|
42
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// APCA (Accessible Perceptual Contrast Algorithm), реализация SA98G (версия 0.0.98G-4g).
|
|
3
|
+
// Используется только для выбора читаемого текста на акцентной заливке при генерации палитры.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.apcaContrast = apcaContrast;
|
|
6
|
+
const hex_1 = require("./hex");
|
|
7
|
+
// Константы SA98G — менять нельзя, иначе разъедется с эталоном контраста APCA.
|
|
8
|
+
const MAIN_TRC = 2.4;
|
|
9
|
+
const S_RCO = 0.2126729;
|
|
10
|
+
const S_GCO = 0.7151522;
|
|
11
|
+
const S_BCO = 0.072175;
|
|
12
|
+
const NORM_BG = 0.56;
|
|
13
|
+
const NORM_TXT = 0.57;
|
|
14
|
+
const REV_TXT = 0.62;
|
|
15
|
+
const REV_BG = 0.65;
|
|
16
|
+
const BLK_THRS = 0.022;
|
|
17
|
+
const BLK_CLMP = 1.414;
|
|
18
|
+
const SCALE_BOW = 1.14;
|
|
19
|
+
const SCALE_WOB = 1.14;
|
|
20
|
+
const LO_BOW_OFFSET = 0.027;
|
|
21
|
+
const LO_WOB_OFFSET = 0.027;
|
|
22
|
+
const DELTA_Y_MIN = 0.0005;
|
|
23
|
+
const LO_CLIP = 0.1;
|
|
24
|
+
/** sRGB-каналы 0–255 → относительная яркость `Y` для APCA. */
|
|
25
|
+
function sRGBtoY([r, g, b]) {
|
|
26
|
+
const linearize = (channel) => Math.pow(channel / 255, MAIN_TRC);
|
|
27
|
+
return S_RCO * linearize(r) + S_GCO * linearize(g) + S_BCO * linearize(b);
|
|
28
|
+
}
|
|
29
|
+
/** Мягкий клэмп яркости у чёрного (soft black clamp). */
|
|
30
|
+
function softClampBlack(y) {
|
|
31
|
+
return y > BLK_THRS ? y : y + Math.pow(BLK_THRS - y, BLK_CLMP);
|
|
32
|
+
}
|
|
33
|
+
/** Сырой APCA-контраст `Lc` между яркостью текста и фона. Полярность важна, не переставлять аргументы. */
|
|
34
|
+
function apcaLc(txtY, bgY) {
|
|
35
|
+
if (Number.isNaN(txtY) || Number.isNaN(bgY) || Math.min(txtY, bgY) < 0 || Math.max(txtY, bgY) > 1.1) {
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
const text = softClampBlack(txtY);
|
|
39
|
+
const bg = softClampBlack(bgY);
|
|
40
|
+
if (Math.abs(bg - text) < DELTA_Y_MIN) {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
if (bg > text) {
|
|
44
|
+
// Тёмный текст на светлом фоне (BoW).
|
|
45
|
+
const sapc = (Math.pow(bg, NORM_BG) - Math.pow(text, NORM_TXT)) * SCALE_BOW;
|
|
46
|
+
return sapc < LO_CLIP ? 0 : (sapc - LO_BOW_OFFSET) * 100;
|
|
47
|
+
}
|
|
48
|
+
// Светлый текст на тёмном фоне (WoB) — отрицательный по контракту APCA.
|
|
49
|
+
const sapc = (Math.pow(bg, REV_BG) - Math.pow(text, REV_TXT)) * SCALE_WOB;
|
|
50
|
+
return sapc > -LO_CLIP ? 0 : (sapc + LO_WOB_OFFSET) * 100;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Абсолютный APCA-контраст `Lc` текста на фоне (оба — hex). Невалидный hex → `0`.
|
|
54
|
+
*/
|
|
55
|
+
function apcaContrast(backgroundHex, textHex) {
|
|
56
|
+
const bg = (0, hex_1.parseHex)(backgroundHex);
|
|
57
|
+
const text = (0, hex_1.parseHex)(textHex);
|
|
58
|
+
if (!bg || !text) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
return Math.round(Math.abs(apcaLc(sRGBtoY(text), sRGBtoY(bg))));
|
|
62
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { LCH, TColor } from './types';
|
|
2
|
+
/** LCH → разобранный цвет. `hex` вычисляется лениво (с загонкой в гамму при выходе за неё). */
|
|
3
|
+
export declare function lch2color(lch: LCH): TColor;
|
|
4
|
+
/** hex → разобранный цвет (OKLCH-компоненты + sRGB-каналы). Невалидный hex → `null`. */
|
|
5
|
+
export declare function hex2color(hex: string): TColor | null;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Разбор и синтез цветов в модели OKLCH. Алгоритм forceIntoGamut — CSS Color 4
|
|
3
|
+
// (Chris Lilley, https://svgees.us/).
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.lch2color = lch2color;
|
|
6
|
+
exports.hex2color = hex2color;
|
|
7
|
+
const conversions_1 = require("./conversions");
|
|
8
|
+
const hex_1 = require("./hex");
|
|
9
|
+
const oklch_1 = require("./oklch");
|
|
10
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
11
|
+
const xyz2rgb = (xyz) => (0, conversions_1.gamSRGB)((0, conversions_1.xyzToLinSRGB)(xyz));
|
|
12
|
+
const rgb2xyz = (rgb) => (0, conversions_1.linSRGBToXYZ)((0, conversions_1.linSRGB)(rgb));
|
|
13
|
+
const lch2rgb = (lch) => xyz2rgb(oklch_1.oklch.lch2xyz(lch));
|
|
14
|
+
const rgb2lch = (rgb) => oklch_1.oklch.xyz2lch(rgb2xyz(rgb));
|
|
15
|
+
function isWithinGamut(rgb) {
|
|
16
|
+
const epsilon = 0.000005;
|
|
17
|
+
return rgb.every(channel => channel >= 0 - epsilon && channel <= 1 + epsilon);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Загоняет LCH-цвет в sRGB-гамму: держит `l` и `h`, бинарным поиском ужимает `c` до границы гаммы.
|
|
21
|
+
*/
|
|
22
|
+
function forceIntoGamut(lch) {
|
|
23
|
+
let rgb = lch2rgb(lch);
|
|
24
|
+
if (isWithinGamut(rgb)) {
|
|
25
|
+
return rgb;
|
|
26
|
+
}
|
|
27
|
+
const [l, , h] = lch;
|
|
28
|
+
let hiC = lch[1];
|
|
29
|
+
let loC = 0;
|
|
30
|
+
let c = lch[1] / 2;
|
|
31
|
+
const epsilon = 0.0001;
|
|
32
|
+
while (hiC - loC > epsilon) {
|
|
33
|
+
rgb = lch2rgb([l, c, h]);
|
|
34
|
+
if (isWithinGamut(rgb)) {
|
|
35
|
+
loC = c;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
hiC = c;
|
|
39
|
+
}
|
|
40
|
+
c = (hiC + loC) / 2;
|
|
41
|
+
}
|
|
42
|
+
return rgb;
|
|
43
|
+
}
|
|
44
|
+
/** LCH → разобранный цвет. `hex` вычисляется лениво (с загонкой в гамму при выходе за неё). */
|
|
45
|
+
function lch2color(lch) {
|
|
46
|
+
const srgb = xyz2rgb(oklch_1.oklch.lch2xyz(lch));
|
|
47
|
+
const withinSRGB = isWithinGamut(srgb);
|
|
48
|
+
const [r, g, b] = srgb.map(channel => clamp(channel * 255, 0, 255));
|
|
49
|
+
const [l, c, h] = lch;
|
|
50
|
+
return {
|
|
51
|
+
l,
|
|
52
|
+
c,
|
|
53
|
+
h,
|
|
54
|
+
r,
|
|
55
|
+
g,
|
|
56
|
+
b,
|
|
57
|
+
get hex() {
|
|
58
|
+
return (0, hex_1.srgbToHex)(withinSRGB ? srgb : forceIntoGamut(lch));
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** hex → разобранный цвет (OKLCH-компоненты + sRGB-каналы). Невалидный hex → `null`. */
|
|
63
|
+
function hex2color(hex) {
|
|
64
|
+
const parsed = (0, hex_1.parseHex)(hex);
|
|
65
|
+
if (!parsed) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const normalized = parsed.map(channel => channel / 255);
|
|
69
|
+
const [l, c, h] = rgb2lch(normalized);
|
|
70
|
+
const [r, g, b] = parsed;
|
|
71
|
+
return { l, c, h, r, g, b, hex: (0, hex_1.srgbToHex)(normalized) };
|
|
72
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { LAB, LCH, RGB, XYZ } from './types';
|
|
2
|
+
/** XYZ (относительно D65) → OKLab. `L` в диапазоне `[0,1]`. */
|
|
3
|
+
export declare function xyzToOklab(xyz: XYZ): LAB;
|
|
4
|
+
/** OKLab → XYZ (относительно D65). */
|
|
5
|
+
export declare function oklabToXyz(lab: LAB): XYZ;
|
|
6
|
+
/** OKLab → OKLCH. Hue возвращается в градусах `[0,360)`. */
|
|
7
|
+
export declare function oklabToOklch(lab: LAB): LCH;
|
|
8
|
+
/** OKLCH → OKLab. */
|
|
9
|
+
export declare function oklchToOklab(lch: LCH): LAB;
|
|
10
|
+
/** sRGB → linear-light sRGB (обратная gamma-коррекция), расширенная функция для отрицательных значений. */
|
|
11
|
+
export declare function linSRGB(rgb: RGB): RGB;
|
|
12
|
+
/** linear-light sRGB → sRGB (gamma-коррекция), расширенная функция для отрицательных значений. */
|
|
13
|
+
export declare function gamSRGB(rgb: RGB): RGB;
|
|
14
|
+
/** linear-light sRGB → CIE XYZ (белая точка D65). */
|
|
15
|
+
export declare function linSRGBToXYZ(rgb: RGB): XYZ;
|
|
16
|
+
/** CIE XYZ → linear-light sRGB. */
|
|
17
|
+
export declare function xyzToLinSRGB(xyz: XYZ): RGB;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Матрицы и формулы преобразований цветовых пространств — из CSS Color 4
|
|
3
|
+
// (https://drafts.csswg.org/css-color-4/conversions.js) и работы Björn Ottosson по OKLab
|
|
4
|
+
// (https://bottosson.github.io/posts/oklab/). Константы менять нельзя; тип-контракт 3×3-матрицы
|
|
5
|
+
// фиксирован, чтобы обойтись без `any`/`@ts-ignore` в обобщённом умножении.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.xyzToOklab = xyzToOklab;
|
|
8
|
+
exports.oklabToXyz = oklabToXyz;
|
|
9
|
+
exports.oklabToOklch = oklabToOklch;
|
|
10
|
+
exports.oklchToOklab = oklchToOklab;
|
|
11
|
+
exports.linSRGB = linSRGB;
|
|
12
|
+
exports.gamSRGB = gamSRGB;
|
|
13
|
+
exports.linSRGBToXYZ = linSRGBToXYZ;
|
|
14
|
+
exports.xyzToLinSRGB = xyzToLinSRGB;
|
|
15
|
+
/** Умножение матрицы 3×3 на вектор из трёх компонентов. */
|
|
16
|
+
function mulV(m, v) {
|
|
17
|
+
return [
|
|
18
|
+
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
|
|
19
|
+
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
|
|
20
|
+
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
const XYZ_TO_LMS = [
|
|
24
|
+
[0.8190224432164319, 0.3619062562801221, -0.12887378261216414],
|
|
25
|
+
[0.0329836671980271, 0.9292868468965546, 0.03614466816999844],
|
|
26
|
+
[0.048177199566046255, 0.26423952494422764, 0.6335478258136937],
|
|
27
|
+
];
|
|
28
|
+
const LMS_TO_OKLAB = [
|
|
29
|
+
[0.2104542553, 0.793617785, -0.0040720468],
|
|
30
|
+
[1.9779984951, -2.428592205, 0.4505937099],
|
|
31
|
+
[0.0259040371, 0.7827717662, -0.808675766],
|
|
32
|
+
];
|
|
33
|
+
const LMS_TO_XYZ = [
|
|
34
|
+
[1.2268798733741557, -0.5578149965554813, 0.28139105017721583],
|
|
35
|
+
[-0.04057576262431372, 1.1122868293970594, -0.07171106666151701],
|
|
36
|
+
[-0.07637294974672142, -0.4214933239627914, 1.5869240244272418],
|
|
37
|
+
];
|
|
38
|
+
// prettier-ignore
|
|
39
|
+
const OKLAB_TO_LMS = [
|
|
40
|
+
// eslint-disable-next-line no-loss-of-precision
|
|
41
|
+
[0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339],
|
|
42
|
+
// eslint-disable-next-line no-loss-of-precision
|
|
43
|
+
[1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402],
|
|
44
|
+
// eslint-disable-next-line no-loss-of-precision
|
|
45
|
+
[1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399],
|
|
46
|
+
];
|
|
47
|
+
const LIN_SRGB_TO_XYZ = [
|
|
48
|
+
[0.41239079926595934, 0.357584339383878, 0.1804807884018343],
|
|
49
|
+
[0.21263900587151027, 0.715168678767756, 0.07219231536073371],
|
|
50
|
+
[0.01933081871559182, 0.11919477979462598, 0.9505321522496607],
|
|
51
|
+
];
|
|
52
|
+
const XYZ_TO_LIN_SRGB = [
|
|
53
|
+
[3.2409699419045226, -1.537383177570094, -0.4986107602930034],
|
|
54
|
+
[-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
|
|
55
|
+
[0.05563007969699366, -0.20397695888897652, 1.0569715142428786],
|
|
56
|
+
];
|
|
57
|
+
/** XYZ (относительно D65) → OKLab. `L` в диапазоне `[0,1]`. */
|
|
58
|
+
function xyzToOklab(xyz) {
|
|
59
|
+
const lms = mulV(XYZ_TO_LMS, xyz);
|
|
60
|
+
const lmsCbrt = [Math.cbrt(lms[0]), Math.cbrt(lms[1]), Math.cbrt(lms[2])];
|
|
61
|
+
return mulV(LMS_TO_OKLAB, lmsCbrt);
|
|
62
|
+
}
|
|
63
|
+
/** OKLab → XYZ (относительно D65). */
|
|
64
|
+
function oklabToXyz(lab) {
|
|
65
|
+
const lms = mulV(OKLAB_TO_LMS, lab);
|
|
66
|
+
const lmsCubed = [lms[0] ** 3, lms[1] ** 3, lms[2] ** 3];
|
|
67
|
+
return mulV(LMS_TO_XYZ, lmsCubed);
|
|
68
|
+
}
|
|
69
|
+
/** OKLab → OKLCH. Hue возвращается в градусах `[0,360)`. */
|
|
70
|
+
function oklabToOklch(lab) {
|
|
71
|
+
const hue = (Math.atan2(lab[2], lab[1]) * 180) / Math.PI;
|
|
72
|
+
return [lab[0], Math.sqrt(lab[1] ** 2 + lab[2] ** 2), hue >= 0 ? hue : hue + 360];
|
|
73
|
+
}
|
|
74
|
+
/** OKLCH → OKLab. */
|
|
75
|
+
function oklchToOklab(lch) {
|
|
76
|
+
return [lch[0], lch[1] * Math.cos((lch[2] * Math.PI) / 180), lch[1] * Math.sin((lch[2] * Math.PI) / 180)];
|
|
77
|
+
}
|
|
78
|
+
/** sRGB → linear-light sRGB (обратная gamma-коррекция), расширенная функция для отрицательных значений. */
|
|
79
|
+
function linSRGB(rgb) {
|
|
80
|
+
return rgb.map(val => {
|
|
81
|
+
const sign = val < 0 ? -1 : 1;
|
|
82
|
+
const abs = Math.abs(val);
|
|
83
|
+
return abs < 0.04045 ? val / 12.92 : sign * Math.pow((abs + 0.055) / 1.055, 2.4);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** linear-light sRGB → sRGB (gamma-коррекция), расширенная функция для отрицательных значений. */
|
|
87
|
+
function gamSRGB(rgb) {
|
|
88
|
+
return rgb.map(val => {
|
|
89
|
+
const sign = val < 0 ? -1 : 1;
|
|
90
|
+
const abs = Math.abs(val);
|
|
91
|
+
return abs > 0.0031308 ? sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055) : 12.92 * val;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** linear-light sRGB → CIE XYZ (белая точка D65). */
|
|
95
|
+
function linSRGBToXYZ(rgb) {
|
|
96
|
+
return mulV(LIN_SRGB_TO_XYZ, rgb);
|
|
97
|
+
}
|
|
98
|
+
/** CIE XYZ → linear-light sRGB. */
|
|
99
|
+
function xyzToLinSRGB(xyz) {
|
|
100
|
+
return mulV(XYZ_TO_LIN_SRGB, xyz);
|
|
101
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RGB } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Разбирает hex-строку в sRGB-каналы `[r, g, b]` в диапазоне 0–255. Поддерживает `#rgb`, `#rgba`,
|
|
4
|
+
* `#rrggbb`, `#rrggbbaa` (с ведущим `#` и без). Alpha-канал игнорируется. Невалидная строка → `null`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseHex(hex: string): RGB | null;
|
|
7
|
+
/** Проверяет, что строка — валидный hex-цвет (`#rgb` / `#rgba` / `#rrggbb` / `#rrggbbaa`). */
|
|
8
|
+
export declare function isValidHex(hex: string): boolean;
|
|
9
|
+
/** sRGB-каналы 0–1 → hex-строка `#rrggbb`. Каналы клампятся в диапазон. */
|
|
10
|
+
export declare function srgbToHex([r, g, b]: RGB): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseHex = parseHex;
|
|
4
|
+
exports.isValidHex = isValidHex;
|
|
5
|
+
exports.srgbToHex = srgbToHex;
|
|
6
|
+
const SHORT_HEX = /^#?([\da-f])([\da-f])([\da-f])([\da-f])?$/i;
|
|
7
|
+
const LONG_HEX = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})([\da-f]{2})?$/i;
|
|
8
|
+
/**
|
|
9
|
+
* Разбирает hex-строку в sRGB-каналы `[r, g, b]` в диапазоне 0–255. Поддерживает `#rgb`, `#rgba`,
|
|
10
|
+
* `#rrggbb`, `#rrggbbaa` (с ведущим `#` и без). Alpha-канал игнорируется. Невалидная строка → `null`.
|
|
11
|
+
*/
|
|
12
|
+
function parseHex(hex) {
|
|
13
|
+
const trimmed = hex.trim();
|
|
14
|
+
const short = SHORT_HEX.exec(trimmed);
|
|
15
|
+
if (short) {
|
|
16
|
+
return [parseInt(short[1] + short[1], 16), parseInt(short[2] + short[2], 16), parseInt(short[3] + short[3], 16)];
|
|
17
|
+
}
|
|
18
|
+
const long = LONG_HEX.exec(trimmed);
|
|
19
|
+
if (long) {
|
|
20
|
+
return [parseInt(long[1], 16), parseInt(long[2], 16), parseInt(long[3], 16)];
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/** Проверяет, что строка — валидный hex-цвет (`#rgb` / `#rgba` / `#rrggbb` / `#rrggbbaa`). */
|
|
25
|
+
function isValidHex(hex) {
|
|
26
|
+
return parseHex(hex) !== null;
|
|
27
|
+
}
|
|
28
|
+
/** sRGB-каналы 0–1 → hex-строка `#rrggbb`. Каналы клампятся в диапазон. */
|
|
29
|
+
function srgbToHex([r, g, b]) {
|
|
30
|
+
const toHex = (channel) => Math.round(Math.min(255, Math.max(0, channel * 255)))
|
|
31
|
+
.toString(16)
|
|
32
|
+
.padStart(2, '0');
|
|
33
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./hex"), exports);
|
|
19
|
+
__exportStar(require("./colorSpace"), exports);
|
|
20
|
+
__exportStar(require("./apca"), exports);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.oklch = void 0;
|
|
4
|
+
const conversions_1 = require("./conversions");
|
|
5
|
+
// L компонента OKLCH хранится наружу в процентах [0–100], внутри модели — в [0–1].
|
|
6
|
+
function toDisplayOKLCH([l, c, h]) {
|
|
7
|
+
return [l * 100, c, h];
|
|
8
|
+
}
|
|
9
|
+
function fromDisplayOKLCH([l, c, h]) {
|
|
10
|
+
return [l / 100, c, h];
|
|
11
|
+
}
|
|
12
|
+
/** Модель OKLCH: перевод LCH↔XYZ через OKLab. `L` наружу — в процентах. */
|
|
13
|
+
exports.oklch = {
|
|
14
|
+
lch2xyz: (lch) => (0, conversions_1.oklabToXyz)((0, conversions_1.oklchToOklab)(fromDisplayOKLCH(lch))),
|
|
15
|
+
xyz2lch: (xyz) => toDisplayOKLCH((0, conversions_1.oklabToOklch)((0, conversions_1.xyzToOklab)(xyz))),
|
|
16
|
+
ranges: {
|
|
17
|
+
l: { min: 0, max: 100, step: 0.5 },
|
|
18
|
+
c: { min: 0, max: 0.33, step: 0.005 },
|
|
19
|
+
h: { min: 0, max: 360, step: 0.5 },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Вектор из трёх чисел — канал цвета в любом из пространств (RGB/XYZ/LAB/LCH). */
|
|
2
|
+
export type Vec3 = [number, number, number];
|
|
3
|
+
/** Матрица 3×3 для линейных преобразований цветовых пространств. */
|
|
4
|
+
export type Mat3 = [Vec3, Vec3, Vec3];
|
|
5
|
+
export type XYZ = Vec3;
|
|
6
|
+
export type LAB = Vec3;
|
|
7
|
+
export type LCH = Vec3;
|
|
8
|
+
export type RGB = Vec3;
|
|
9
|
+
/** Разобранный цвет: OKLCH-компоненты (`l`,`c`,`h`), sRGB-каналы 0–255 (`r`,`g`,`b`) и hex. */
|
|
10
|
+
export type TColor = {
|
|
11
|
+
l: number;
|
|
12
|
+
c: number;
|
|
13
|
+
h: number;
|
|
14
|
+
r: number;
|
|
15
|
+
g: number;
|
|
16
|
+
b: number;
|
|
17
|
+
hex: string;
|
|
18
|
+
};
|
|
19
|
+
/** Модель цветового пространства LCH: конвертеры в/из XYZ и диапазоны компонентов. */
|
|
20
|
+
export type TLchModel = {
|
|
21
|
+
ranges: {
|
|
22
|
+
l: {
|
|
23
|
+
min: number;
|
|
24
|
+
max: number;
|
|
25
|
+
step: number;
|
|
26
|
+
};
|
|
27
|
+
c: {
|
|
28
|
+
min: number;
|
|
29
|
+
max: number;
|
|
30
|
+
step: number;
|
|
31
|
+
};
|
|
32
|
+
h: {
|
|
33
|
+
min: number;
|
|
34
|
+
max: number;
|
|
35
|
+
step: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
xyz2lch(xyz: XYZ): LCH;
|
|
39
|
+
lch2xyz(lch: LCH): XYZ;
|
|
40
|
+
};
|