@rokkit/core 1.0.4 → 1.0.5
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/dist/color-space.d.ts +52 -0
- package/dist/constants.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/theme.d.ts +9 -7
- package/dist/utils.d.ts +0 -32
- package/package.json +2 -2
- package/src/color-space.js +308 -0
- package/src/constants.js +4 -0
- package/src/index.js +1 -0
- package/src/theme.ts +30 -45
- package/src/utils.js +0 -80
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for color space adapters.
|
|
3
|
+
*/
|
|
4
|
+
export class ColorSpace {
|
|
5
|
+
/**
|
|
6
|
+
* Factory method to create a ColorSpace adapter by name.
|
|
7
|
+
* @param {string} space - 'rgb', 'hsl', or 'oklch'
|
|
8
|
+
* @returns {ColorSpace}
|
|
9
|
+
*/
|
|
10
|
+
static create(space: string): ColorSpace;
|
|
11
|
+
/** @returns {string} */
|
|
12
|
+
get name(): string;
|
|
13
|
+
/** @returns {string} CSS color-mix interpolation space */
|
|
14
|
+
get mixSpace(): string;
|
|
15
|
+
/** @returns {string} CSS function name */
|
|
16
|
+
get fn(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Convert a hex color to bare channels in this space.
|
|
19
|
+
* @param {string} hex - #rrggbb
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
fromHex(_hex: any): string;
|
|
23
|
+
/**
|
|
24
|
+
* Wrap any input into a complete CSS color value.
|
|
25
|
+
* - hex → convert and wrap
|
|
26
|
+
* - bare channels → wrap in fn()
|
|
27
|
+
* - already wrapped → pass through
|
|
28
|
+
* - non-string / null / undefined → pass through
|
|
29
|
+
* - named colors → pass through
|
|
30
|
+
* @param {*} value
|
|
31
|
+
* @returns {*}
|
|
32
|
+
*/
|
|
33
|
+
wrap(_value: any): any;
|
|
34
|
+
/**
|
|
35
|
+
* Produce a color-mix expression for theme CSS variables with alpha support.
|
|
36
|
+
* @param {string} varName - CSS variable name (e.g. '--color-primary-500')
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
themeColor(varName: string): string;
|
|
40
|
+
}
|
|
41
|
+
export class RgbColorSpace extends ColorSpace {
|
|
42
|
+
fromHex(hex: any): string;
|
|
43
|
+
wrap(value: any): any;
|
|
44
|
+
}
|
|
45
|
+
export class HslColorSpace extends ColorSpace {
|
|
46
|
+
fromHex(hex: any): string;
|
|
47
|
+
wrap(value: any): any;
|
|
48
|
+
}
|
|
49
|
+
export class OklchColorSpace extends ColorSpace {
|
|
50
|
+
fromHex(hex: any): string;
|
|
51
|
+
wrap(value: any): any;
|
|
52
|
+
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -54,6 +54,7 @@ export namespace DEFAULT_KEYMAP {
|
|
|
54
54
|
}
|
|
55
55
|
export namespace DEFAULT_THEME_MAPPING {
|
|
56
56
|
let surface: string;
|
|
57
|
+
let ink: string;
|
|
57
58
|
let primary: string;
|
|
58
59
|
let secondary: string;
|
|
59
60
|
let tertiary: string;
|
|
@@ -77,5 +78,6 @@ export namespace TONE_MAP {
|
|
|
77
78
|
let z9: number;
|
|
78
79
|
let z10: number;
|
|
79
80
|
}
|
|
81
|
+
export const INVERTED_ROLES: Set<string>;
|
|
80
82
|
export const DEFAULT_STATE_ICONS: any;
|
|
81
83
|
export { defaultColors, syntaxColors, shades, defaultPalette } from "./colors/index";
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./utils.js";
|
|
|
4
4
|
export * from "./nested.js";
|
|
5
5
|
export * from "./string.js";
|
|
6
6
|
export * from "./theme.js";
|
|
7
|
+
export { ColorSpace } from "./color-space.js";
|
|
7
8
|
export { FieldMapper } from "./field-mapper.js";
|
|
8
9
|
export { createEmitter } from "./events.js";
|
|
9
10
|
export { getLineTypes } from "./connector.js";
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate shades for a color using css
|
|
2
|
+
* Generate shades for a color using css variable and a ColorSpace adapter.
|
|
3
3
|
*
|
|
4
4
|
* @param {string} name
|
|
5
|
-
* @param {string}
|
|
6
|
-
* @returns
|
|
5
|
+
* @param {string|import('./color-space').ColorSpace} space - color space name or adapter instance
|
|
6
|
+
* @returns {Record<string|number, string>}
|
|
7
7
|
*/
|
|
8
|
-
export declare function shadesOf(name: any,
|
|
9
|
-
DEFAULT:
|
|
8
|
+
export declare function shadesOf(name: any, space?: string): {
|
|
9
|
+
DEFAULT: string;
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* Constructs and returns the light and dark theme variants based on provided color mapping and color definitions.
|
|
13
13
|
*
|
|
14
14
|
* @param {Object} [mapping=DEFAULT_THEME_MAPPING] - An object mapping variant names to color property names.
|
|
15
15
|
* @param {Object} [colors=defaultColors] - The object containing default color definitions.
|
|
16
|
-
* @param {
|
|
16
|
+
* @param {string} [colorSpace] - Color space name for CSS variable values.
|
|
17
17
|
* @returns {Array<Array>} An array containing two arrays, one for the light theme variant and another for the dark theme.
|
|
18
18
|
*/
|
|
19
19
|
export declare function themeRules(mapping: {
|
|
20
20
|
surface: string;
|
|
21
|
+
ink: string;
|
|
21
22
|
primary: string;
|
|
22
23
|
secondary: string;
|
|
23
24
|
tertiary: string;
|
|
@@ -51,12 +52,13 @@ export declare class Theme {
|
|
|
51
52
|
#private;
|
|
52
53
|
/**
|
|
53
54
|
*
|
|
54
|
-
* @param {import('./types.js').ColorTheme & { colorSpace?:
|
|
55
|
+
* @param {import('./types.js').ColorTheme & { colorSpace?: string }} param0
|
|
55
56
|
*/
|
|
56
57
|
constructor({ colors, mapping, colorSpace }?: {
|
|
57
58
|
colors?: any;
|
|
58
59
|
mapping?: {
|
|
59
60
|
surface: string;
|
|
61
|
+
ink: string;
|
|
60
62
|
primary: string;
|
|
61
63
|
secondary: string;
|
|
62
64
|
tertiary: string;
|
package/dist/utils.d.ts
CHANGED
|
@@ -100,37 +100,6 @@ export function hex2oklch(hex: string): string;
|
|
|
100
100
|
* @returns {string} hex color string (#rrggbb)
|
|
101
101
|
*/
|
|
102
102
|
export function oklch2hex(L: number, C: number, H: number): string;
|
|
103
|
-
/**
|
|
104
|
-
* Convert hex string to HSL components: `{H} {S}% {L}%`
|
|
105
|
-
* @param {string} hex - 6-digit hex color (with or without #)
|
|
106
|
-
* @returns {string} space-separated HSL components for CSS `hsl(H S% L% / alpha)`
|
|
107
|
-
*/
|
|
108
|
-
export function hex2hsl(hex: string): string;
|
|
109
|
-
/** @typedef {'rgb' | 'hsl' | 'oklch'} ColorSpace */
|
|
110
|
-
/**
|
|
111
|
-
* Convert a hex color to the component format for a given color space.
|
|
112
|
-
* - rgb: "r,g,b" → for `rgba(var(--x), alpha)`
|
|
113
|
-
* - hsl: "H S% L%" → for `hsl(var(--x) / alpha)`
|
|
114
|
-
* - oklch: "L C H" → for `oklch(var(--x) / alpha)`
|
|
115
|
-
*
|
|
116
|
-
* @param {string} hex - 6-digit hex color (#rrggbb)
|
|
117
|
-
* @param {ColorSpace} space
|
|
118
|
-
* @returns {string}
|
|
119
|
-
*/
|
|
120
|
-
export function hexToComponents(hex: string, space: ColorSpace): string;
|
|
121
|
-
/**
|
|
122
|
-
* Convert a CSS color value to components for use in CSS variables.
|
|
123
|
-
*
|
|
124
|
-
* - Hex (#rrggbb): converted to space-appropriate components via hexToComponents.
|
|
125
|
-
* - oklch(...) strings: inner components extracted (e.g. "0.58 0.15 35") so the
|
|
126
|
-
* value is compatible with the oklch(var(--x) / alpha) wrapper pattern.
|
|
127
|
-
* - All other formats: returned as-is (no opacity-modifier support).
|
|
128
|
-
*
|
|
129
|
-
* @param {unknown} value
|
|
130
|
-
* @param {ColorSpace} [space]
|
|
131
|
-
* @returns {unknown}
|
|
132
|
-
*/
|
|
133
|
-
export function colorToRgb(value: unknown, space?: ColorSpace): unknown;
|
|
134
103
|
/**
|
|
135
104
|
* Detects whether an icon value is a CSS class (i-*) or literal text (kanji, emoji, etc.)
|
|
136
105
|
* @param {string | null | undefined} icon
|
|
@@ -144,4 +113,3 @@ export function isIconClass(icon: string | null | undefined): boolean;
|
|
|
144
113
|
* @returns {string|null} - Returns the original string if it's an image URL or image data, otherwise null
|
|
145
114
|
*/
|
|
146
115
|
export function getImage(str: string): string | null;
|
|
147
|
-
export type ColorSpace = "rgb" | "hsl" | "oklch";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Contains core utility functions and classes that can be used in various components.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,6 +46,6 @@
|
|
|
46
46
|
"ramda": "^0.32.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@rokkit/icons": "1.0.
|
|
49
|
+
"@rokkit/icons": "1.0.5"
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColorSpace adapter strategy pattern.
|
|
3
|
+
*
|
|
4
|
+
* Each adapter knows how to convert hex colors to its own representation,
|
|
5
|
+
* wrap bare channels into a complete CSS color function, and produce
|
|
6
|
+
* `color-mix()` expressions for theme variables with alpha support.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ── Private math helpers (copied from utils.js — not imported) ──────────
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert sRGB component (0-255) to linear RGB.
|
|
13
|
+
* @param {number} c - sRGB value 0-255
|
|
14
|
+
* @returns {number}
|
|
15
|
+
*/
|
|
16
|
+
function srgbToLinear(c) {
|
|
17
|
+
const s = c / 255
|
|
18
|
+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Linear sRGB → XYZ D65
|
|
22
|
+
const SRGB_TO_XYZ = [
|
|
23
|
+
[0.4123907993, 0.3575843394, 0.1804807884],
|
|
24
|
+
[0.2126390059, 0.7151686788, 0.0721923154],
|
|
25
|
+
[0.0193308187, 0.1191947798, 0.9505321522]
|
|
26
|
+
]
|
|
27
|
+
// XYZ D65 → LMS
|
|
28
|
+
const XYZ_TO_LMS = [
|
|
29
|
+
[0.8189330101, 0.3618667424, -0.1288597137],
|
|
30
|
+
[0.0329845436, 0.9293118715, 0.0361456387],
|
|
31
|
+
[0.0482003018, 0.2643662691, 0.633851707]
|
|
32
|
+
]
|
|
33
|
+
// LMS^(1/3) → OKLab
|
|
34
|
+
const LMS3_TO_OKLAB = [
|
|
35
|
+
[0.2104542553, 0.793617785, -0.0040720468],
|
|
36
|
+
[1.9779984951, -2.428592205, 0.4505937099],
|
|
37
|
+
[0.0259040371, 0.7827717662, -0.808675766]
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
/** @param {number[][]} m @param {number[]} v */
|
|
41
|
+
function matMul(m, v) {
|
|
42
|
+
return m.map((row) => row[0] * v[0] + row[1] * v[1] + row[2] * v[2])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Hex parsing helper ──────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse a hex string (#rrggbb) into [r, g, b] integers.
|
|
49
|
+
* @param {string} hex
|
|
50
|
+
* @returns {number[]}
|
|
51
|
+
*/
|
|
52
|
+
function parseHex(hex) {
|
|
53
|
+
return hex.match(/\w\w/g).map((x) => parseInt(x, 16))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Hex → HSL conversion ───────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Convert hex to HSL bare channels: "H S% L%"
|
|
60
|
+
* @param {string} hex
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
function hexToHsl(hex) {
|
|
64
|
+
const [r, g, b] = parseHex(hex)
|
|
65
|
+
const rn = r / 255
|
|
66
|
+
const gn = g / 255
|
|
67
|
+
const bn = b / 255
|
|
68
|
+
const max = Math.max(rn, gn, bn)
|
|
69
|
+
const min = Math.min(rn, gn, bn)
|
|
70
|
+
const l = (max + min) / 2
|
|
71
|
+
|
|
72
|
+
if (max === min) return `0 0% ${Math.round(l * 100)}%`
|
|
73
|
+
|
|
74
|
+
const d = max - min
|
|
75
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
|
76
|
+
|
|
77
|
+
let h = 0
|
|
78
|
+
if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6
|
|
79
|
+
else if (max === gn) h = ((bn - rn) / d + 2) / 6
|
|
80
|
+
else h = ((rn - gn) / d + 4) / 6
|
|
81
|
+
|
|
82
|
+
return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Hex → OKLCH conversion ─────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert hex to OKLCH bare channels: "L C H"
|
|
89
|
+
* @param {string} hex
|
|
90
|
+
* @returns {string}
|
|
91
|
+
*/
|
|
92
|
+
function hexToOklch(hex) {
|
|
93
|
+
const [r, g, b] = parseHex(hex)
|
|
94
|
+
const lin = [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)]
|
|
95
|
+
|
|
96
|
+
const xyz = matMul(SRGB_TO_XYZ, lin)
|
|
97
|
+
const lms = matMul(XYZ_TO_LMS, xyz)
|
|
98
|
+
const lms3 = [Math.cbrt(lms[0]), Math.cbrt(lms[1]), Math.cbrt(lms[2])]
|
|
99
|
+
const [L, a, bk] = matMul(LMS3_TO_OKLAB, lms3)
|
|
100
|
+
|
|
101
|
+
const C = Math.sqrt(a * a + bk * bk)
|
|
102
|
+
let H = (Math.atan2(bk, a) * 180) / Math.PI
|
|
103
|
+
if (H < 0) H += 360
|
|
104
|
+
|
|
105
|
+
const Lr = Math.round(L * 1000000) / 1000000
|
|
106
|
+
const Cr = Math.round(C * 1000000) / 1000000
|
|
107
|
+
const Hr = Math.round(H * 10000) / 10000
|
|
108
|
+
|
|
109
|
+
return `${Lr} ${Cr} ${Hr}`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Hex regex ───────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
const HEX_RE = /^#[0-9a-fA-F]{6}$/
|
|
115
|
+
|
|
116
|
+
// ── Base class ──────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Abstract base class for color space adapters.
|
|
120
|
+
*/
|
|
121
|
+
export class ColorSpace {
|
|
122
|
+
/** @returns {string} */
|
|
123
|
+
get name() {
|
|
124
|
+
throw new Error('Subclass must implement name')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @returns {string} CSS color-mix interpolation space */
|
|
128
|
+
get mixSpace() {
|
|
129
|
+
throw new Error('Subclass must implement mixSpace')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @returns {string} CSS function name */
|
|
133
|
+
get fn() {
|
|
134
|
+
throw new Error('Subclass must implement fn')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Convert a hex color to bare channels in this space.
|
|
139
|
+
* @param {string} hex - #rrggbb
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
fromHex(_hex) {
|
|
143
|
+
throw new Error('Subclass must implement fromHex')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Wrap any input into a complete CSS color value.
|
|
148
|
+
* - hex → convert and wrap
|
|
149
|
+
* - bare channels → wrap in fn()
|
|
150
|
+
* - already wrapped → pass through
|
|
151
|
+
* - non-string / null / undefined → pass through
|
|
152
|
+
* - named colors → pass through
|
|
153
|
+
* @param {*} value
|
|
154
|
+
* @returns {*}
|
|
155
|
+
*/
|
|
156
|
+
wrap(_value) {
|
|
157
|
+
throw new Error('Subclass must implement wrap')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Produce a color-mix expression for theme CSS variables with alpha support.
|
|
162
|
+
* @param {string} varName - CSS variable name (e.g. '--color-primary-500')
|
|
163
|
+
* @returns {string}
|
|
164
|
+
*/
|
|
165
|
+
themeColor(varName) {
|
|
166
|
+
return `color-mix(in ${this.mixSpace}, var(${varName}) calc(<alpha-value> * 100%), transparent)`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Factory method to create a ColorSpace adapter by name.
|
|
171
|
+
* @param {string} space - 'rgb', 'hsl', or 'oklch'
|
|
172
|
+
* @returns {ColorSpace}
|
|
173
|
+
*/
|
|
174
|
+
static create(space) {
|
|
175
|
+
switch (space) {
|
|
176
|
+
case 'rgb':
|
|
177
|
+
return new RgbColorSpace()
|
|
178
|
+
case 'hsl':
|
|
179
|
+
return new HslColorSpace()
|
|
180
|
+
case 'oklch':
|
|
181
|
+
return new OklchColorSpace()
|
|
182
|
+
default:
|
|
183
|
+
throw new Error(`Unknown color space: ${space}`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── RGB adapter ─────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
export class RgbColorSpace extends ColorSpace {
|
|
191
|
+
get name() {
|
|
192
|
+
return 'rgb'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
get mixSpace() {
|
|
196
|
+
return 'srgb'
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get fn() {
|
|
200
|
+
return 'rgb'
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
fromHex(hex) {
|
|
204
|
+
const [r, g, b] = parseHex(hex)
|
|
205
|
+
return `${r}, ${g}, ${b}`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
wrap(value) {
|
|
209
|
+
if (typeof value !== 'string') return value
|
|
210
|
+
|
|
211
|
+
// Already wrapped
|
|
212
|
+
if (value.startsWith('rgb(') || value.startsWith('rgba(')) return value
|
|
213
|
+
|
|
214
|
+
// Hex
|
|
215
|
+
if (HEX_RE.test(value)) return `rgb(${this.fromHex(value)})`
|
|
216
|
+
|
|
217
|
+
// Bare channels: comma-separated or space-separated digits
|
|
218
|
+
// Comma-separated: "255,0,0" or "255, 0, 0"
|
|
219
|
+
if (/^\d+\s*,\s*\d+\s*,\s*\d+$/.test(value)) {
|
|
220
|
+
const parts = value.split(',').map((s) => s.trim())
|
|
221
|
+
return `rgb(${parts.join(', ')})`
|
|
222
|
+
}
|
|
223
|
+
// Space-separated: "255 0 0"
|
|
224
|
+
if (/^\d+\s+\d+\s+\d+$/.test(value)) {
|
|
225
|
+
const parts = value.trim().split(/\s+/)
|
|
226
|
+
return `rgb(${parts.join(', ')})`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Named color or other — pass through
|
|
230
|
+
return value
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── HSL adapter ─────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
export class HslColorSpace extends ColorSpace {
|
|
237
|
+
get name() {
|
|
238
|
+
return 'hsl'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
get mixSpace() {
|
|
242
|
+
return 'srgb'
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
get fn() {
|
|
246
|
+
return 'hsl'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
fromHex(hex) {
|
|
250
|
+
return hexToHsl(hex)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
wrap(value) {
|
|
254
|
+
if (typeof value !== 'string') return value
|
|
255
|
+
|
|
256
|
+
// Already wrapped
|
|
257
|
+
if (value.startsWith('hsl(') || value.startsWith('hsla(')) return value
|
|
258
|
+
|
|
259
|
+
// Hex
|
|
260
|
+
if (HEX_RE.test(value)) return `hsl(${this.fromHex(value)})`
|
|
261
|
+
|
|
262
|
+
// Bare channels: "H S% L%" pattern (number, space, number%, space, number%)
|
|
263
|
+
if (/^\d+\s+\d+%\s+\d+%$/.test(value)) {
|
|
264
|
+
return `hsl(${value})`
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Named color or other — pass through
|
|
268
|
+
return value
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── OKLCH adapter ───────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
export class OklchColorSpace extends ColorSpace {
|
|
275
|
+
get name() {
|
|
276
|
+
return 'oklch'
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
get mixSpace() {
|
|
280
|
+
return 'oklch'
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
get fn() {
|
|
284
|
+
return 'oklch'
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
fromHex(hex) {
|
|
288
|
+
return hexToOklch(hex)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
wrap(value) {
|
|
292
|
+
if (typeof value !== 'string') return value
|
|
293
|
+
|
|
294
|
+
// Already wrapped
|
|
295
|
+
if (value.startsWith('oklch(')) return value
|
|
296
|
+
|
|
297
|
+
// Hex
|
|
298
|
+
if (HEX_RE.test(value)) return `oklch(${this.fromHex(value)})`
|
|
299
|
+
|
|
300
|
+
// Bare channels: three space-separated numbers (may contain decimals)
|
|
301
|
+
if (/^[\d.]+\s+[\d.]+\s+[\d.]+$/.test(value)) {
|
|
302
|
+
return `oklch(${value})`
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Named color or other — pass through
|
|
306
|
+
return value
|
|
307
|
+
}
|
|
308
|
+
}
|
package/src/constants.js
CHANGED
|
@@ -154,6 +154,7 @@ export const DEFAULT_KEYMAP = {
|
|
|
154
154
|
|
|
155
155
|
export const DEFAULT_THEME_MAPPING = {
|
|
156
156
|
surface: 'slate',
|
|
157
|
+
ink: 'slate',
|
|
157
158
|
primary: 'orange',
|
|
158
159
|
secondary: 'pink',
|
|
159
160
|
tertiary: 'violet',
|
|
@@ -178,6 +179,9 @@ export const TONE_MAP = {
|
|
|
178
179
|
z9: 900,
|
|
179
180
|
z10: 950
|
|
180
181
|
}
|
|
182
|
+
|
|
183
|
+
export const INVERTED_ROLES = new Set(['ink'])
|
|
184
|
+
|
|
181
185
|
/**
|
|
182
186
|
* Splits an icon name into its group and key components.
|
|
183
187
|
* @param {string} name - The icon name to split.
|
package/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from './nested.js'
|
|
|
10
10
|
export * from './string.js'
|
|
11
11
|
// skipcq: JS-E1004 - Needed for exposing all functions
|
|
12
12
|
export * from './theme.js'
|
|
13
|
+
export { ColorSpace } from './color-space.js'
|
|
13
14
|
export { FieldMapper } from './field-mapper.js'
|
|
14
15
|
export { getItemAtIndex, getIndexForItem } from './mapped-items.js'
|
|
15
16
|
export { createEmitter } from './events.js'
|
package/src/theme.ts
CHANGED
|
@@ -1,44 +1,26 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
2
|
// @ts-nocheck
|
|
3
|
-
import { DEFAULT_THEME_MAPPING, defaultColors, TONE_MAP } from './constants'
|
|
3
|
+
import { DEFAULT_THEME_MAPPING, defaultColors, TONE_MAP, INVERTED_ROLES } from './constants'
|
|
4
4
|
import { shades } from './colors/index'
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const modifiers = {
|
|
8
|
-
hsl: (value) => `hsl(${value} / <alpha-value>)`,
|
|
9
|
-
rgb: (value) => `rgb(${value} / <alpha-value>)`,
|
|
10
|
-
oklch: (value) => `oklch(${value} / <alpha-value>)`,
|
|
11
|
-
none: (value) => value
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* CSS function wrappers for each color space, used by mapVariant().
|
|
16
|
-
* rgb uses legacy rgba() comma syntax for broad compatibility.
|
|
17
|
-
* hsl/oklch use modern space-separated syntax with / for alpha.
|
|
18
|
-
*/
|
|
19
|
-
const COLOR_SPACE_WRAPPERS = {
|
|
20
|
-
rgb: (varRef) => `rgba(${varRef},<alpha-value>)`,
|
|
21
|
-
hsl: (varRef) => `hsl(${varRef} / <alpha-value>)`,
|
|
22
|
-
oklch: (varRef) => `oklch(${varRef} / <alpha-value>)`
|
|
23
|
-
}
|
|
5
|
+
import { ColorSpace } from './color-space'
|
|
24
6
|
|
|
25
7
|
/**
|
|
26
|
-
* Generate shades for a color using css
|
|
8
|
+
* Generate shades for a color using css variable and a ColorSpace adapter.
|
|
27
9
|
*
|
|
28
10
|
* @param {string} name
|
|
29
|
-
* @param {string}
|
|
30
|
-
* @returns
|
|
11
|
+
* @param {string|import('./color-space').ColorSpace} space - color space name or adapter instance
|
|
12
|
+
* @returns {Record<string|number, string>}
|
|
31
13
|
*/
|
|
32
|
-
export function shadesOf(name,
|
|
33
|
-
const
|
|
14
|
+
export function shadesOf(name, space = 'rgb') {
|
|
15
|
+
const adapter = typeof space === 'string' ? ColorSpace.create(space) : space
|
|
34
16
|
|
|
35
17
|
return shades.reduce(
|
|
36
18
|
(result, shade) => ({
|
|
37
19
|
...result,
|
|
38
|
-
[shade]:
|
|
20
|
+
[shade]: adapter.themeColor(`--color-${name}-${shade}`)
|
|
39
21
|
}),
|
|
40
22
|
{
|
|
41
|
-
DEFAULT:
|
|
23
|
+
DEFAULT: adapter.themeColor(`--color-${name}-500`)
|
|
42
24
|
}
|
|
43
25
|
)
|
|
44
26
|
}
|
|
@@ -49,14 +31,14 @@ export function shadesOf(name, modifier = 'none') {
|
|
|
49
31
|
* @param {string} variant - The name of the variant to generate rules for.
|
|
50
32
|
* @param {Object} colors - The object containing color definitions.
|
|
51
33
|
* @param {Object} mapping - An object that maps variant names to color property names.
|
|
52
|
-
* @param {import('./
|
|
34
|
+
* @param {import('./color-space').ColorSpace} adapter - ColorSpace adapter instance.
|
|
53
35
|
* @returns {import('./types').ShadeMappings} An array containing the color rules for both light and dark modes.
|
|
54
36
|
*/
|
|
55
|
-
function generateColorRules(variant, colors, mapping,
|
|
37
|
+
function generateColorRules(variant, colors, mapping, adapter) {
|
|
56
38
|
return ['DEFAULT', ...shades].flatMap((shade) => [
|
|
57
39
|
{
|
|
58
40
|
key: shade === 'DEFAULT' ? `--color-${variant}` : `--color-${variant}-${shade}`,
|
|
59
|
-
value:
|
|
41
|
+
value: adapter.wrap(colors[mapping[variant]][`${shade}`])
|
|
60
42
|
}
|
|
61
43
|
])
|
|
62
44
|
}
|
|
@@ -66,15 +48,16 @@ function generateColorRules(variant, colors, mapping, colorSpace) {
|
|
|
66
48
|
*
|
|
67
49
|
* @param {Object} [mapping=DEFAULT_THEME_MAPPING] - An object mapping variant names to color property names.
|
|
68
50
|
* @param {Object} [colors=defaultColors] - The object containing default color definitions.
|
|
69
|
-
* @param {
|
|
51
|
+
* @param {string} [colorSpace] - Color space name for CSS variable values.
|
|
70
52
|
* @returns {Array<Array>} An array containing two arrays, one for the light theme variant and another for the dark theme.
|
|
71
53
|
*/
|
|
72
54
|
export function themeRules(mapping = DEFAULT_THEME_MAPPING, colors = defaultColors, colorSpace) {
|
|
73
55
|
mapping = { ...DEFAULT_THEME_MAPPING, ...mapping }
|
|
74
56
|
colors = { ...defaultColors, ...colors }
|
|
57
|
+
const adapter = ColorSpace.create(colorSpace || 'rgb')
|
|
75
58
|
const variants = Object.keys(mapping)
|
|
76
59
|
const rules = variants
|
|
77
|
-
.flatMap((variant) => generateColorRules(variant, colors, mapping,
|
|
60
|
+
.flatMap((variant) => generateColorRules(variant, colors, mapping, adapter))
|
|
78
61
|
.reduce((acc, { key, value }) => ({ ...acc, [key]: value }), {})
|
|
79
62
|
|
|
80
63
|
return rules
|
|
@@ -163,6 +146,7 @@ export function contrastShortcuts(name) {
|
|
|
163
146
|
* If a semantic color is null, it inherits from another semantic color.
|
|
164
147
|
*/
|
|
165
148
|
const COLOR_FALLBACKS = {
|
|
149
|
+
ink: 'surface',
|
|
166
150
|
tertiary: 'primary',
|
|
167
151
|
secondary: 'primary',
|
|
168
152
|
accent: 'primary',
|
|
@@ -190,16 +174,16 @@ function resolveColors(mapping) {
|
|
|
190
174
|
export class Theme {
|
|
191
175
|
#colors
|
|
192
176
|
#mapping
|
|
193
|
-
#
|
|
177
|
+
#adapter
|
|
194
178
|
|
|
195
179
|
/**
|
|
196
180
|
*
|
|
197
|
-
* @param {import('./types.js').ColorTheme & { colorSpace?:
|
|
181
|
+
* @param {import('./types.js').ColorTheme & { colorSpace?: string }} param0
|
|
198
182
|
*/
|
|
199
183
|
constructor({ colors = defaultColors, mapping = DEFAULT_THEME_MAPPING, colorSpace = 'rgb' } = {}) {
|
|
200
184
|
this.#colors = { ...defaultColors, ...colors }
|
|
201
185
|
this.#mapping = resolveColors({ ...DEFAULT_THEME_MAPPING, ...mapping })
|
|
202
|
-
this.#
|
|
186
|
+
this.#adapter = ColorSpace.create(colorSpace)
|
|
203
187
|
}
|
|
204
188
|
|
|
205
189
|
get colors() {
|
|
@@ -217,21 +201,20 @@ export class Theme {
|
|
|
217
201
|
}
|
|
218
202
|
|
|
219
203
|
get colorSpace() {
|
|
220
|
-
return this.#
|
|
204
|
+
return this.#adapter.name
|
|
221
205
|
}
|
|
222
206
|
set colorSpace(colorSpace) {
|
|
223
|
-
this.#
|
|
207
|
+
this.#adapter = ColorSpace.create(colorSpace)
|
|
224
208
|
}
|
|
225
209
|
|
|
226
210
|
mapVariant(color, variant) {
|
|
227
|
-
const wrap = COLOR_SPACE_WRAPPERS[this.#colorSpace] || COLOR_SPACE_WRAPPERS.rgb
|
|
228
211
|
return Object.keys(color).reduce(
|
|
229
212
|
(acc, key) => ({
|
|
230
213
|
...acc,
|
|
231
214
|
[key]:
|
|
232
215
|
key === 'DEFAULT'
|
|
233
|
-
?
|
|
234
|
-
:
|
|
216
|
+
? this.#adapter.themeColor(`--color-${variant}`)
|
|
217
|
+
: this.#adapter.themeColor(`--color-${variant}-${key}`)
|
|
235
218
|
}),
|
|
236
219
|
{}
|
|
237
220
|
)
|
|
@@ -253,7 +236,7 @@ export class Theme {
|
|
|
253
236
|
const useColors = { ...defaultColors, ...this.#colors }
|
|
254
237
|
const variants = Object.keys(useMapping)
|
|
255
238
|
const rules = variants
|
|
256
|
-
.flatMap((variant) => generateColorRules(variant, useColors, useMapping, this.#
|
|
239
|
+
.flatMap((variant) => generateColorRules(variant, useColors, useMapping, this.#adapter))
|
|
257
240
|
.reduce((acc, { key, value }) => ({ ...acc, [key]: value }), {})
|
|
258
241
|
return rules
|
|
259
242
|
}
|
|
@@ -276,15 +259,17 @@ export class Theme {
|
|
|
276
259
|
const names = Object.keys(this.#mapping)
|
|
277
260
|
|
|
278
261
|
const lightLines = names.flatMap((name) =>
|
|
279
|
-
Object.entries(TONE_MAP).map(
|
|
280
|
-
(
|
|
281
|
-
|
|
262
|
+
Object.entries(TONE_MAP).map(([zone, light]) => {
|
|
263
|
+
const value = INVERTED_ROLES.has(name) ? 1000 - light : light
|
|
264
|
+
return ` --color-${name}-${zone}: var(--color-${name}-${value});`
|
|
265
|
+
})
|
|
282
266
|
)
|
|
283
267
|
|
|
284
268
|
const darkLines = names.flatMap((name) =>
|
|
285
269
|
Object.entries(TONE_MAP).map(([zone, light]) => {
|
|
286
270
|
const dark = 1000 - light
|
|
287
|
-
|
|
271
|
+
const value = INVERTED_ROLES.has(name) ? light : dark
|
|
272
|
+
return ` --color-${name}-${zone}: var(--color-${name}-${value});`
|
|
288
273
|
})
|
|
289
274
|
)
|
|
290
275
|
|
package/src/utils.js
CHANGED
|
@@ -317,86 +317,6 @@ export function oklch2hex(L, C, H) {
|
|
|
317
317
|
return `#${toHex(lin[0])}${toHex(lin[1])}${toHex(lin[2])}`
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
/**
|
|
321
|
-
* Convert hex string to HSL components: `{H} {S}% {L}%`
|
|
322
|
-
* @param {string} hex - 6-digit hex color (with or without #)
|
|
323
|
-
* @returns {string} space-separated HSL components for CSS `hsl(H S% L% / alpha)`
|
|
324
|
-
*/
|
|
325
|
-
export function hex2hsl(hex) {
|
|
326
|
-
const [r, g, b] = hex.match(/\w\w/g).map((x) => parseInt(x, 16))
|
|
327
|
-
|
|
328
|
-
const rn = r / 255
|
|
329
|
-
const gn = g / 255
|
|
330
|
-
const bn = b / 255
|
|
331
|
-
const max = Math.max(rn, gn, bn)
|
|
332
|
-
const min = Math.min(rn, gn, bn)
|
|
333
|
-
const l = (max + min) / 2
|
|
334
|
-
|
|
335
|
-
if (max === min) return `0 0% ${Math.round(l * 100)}%`
|
|
336
|
-
|
|
337
|
-
const d = max - min
|
|
338
|
-
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
|
|
339
|
-
|
|
340
|
-
let h = 0
|
|
341
|
-
if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6
|
|
342
|
-
else if (max === gn) h = ((bn - rn) / d + 2) / 6
|
|
343
|
-
else h = ((rn - gn) / d + 4) / 6
|
|
344
|
-
|
|
345
|
-
const Hr = Math.round(h * 360)
|
|
346
|
-
const Sr = Math.round(s * 100)
|
|
347
|
-
const Lr = Math.round(l * 100)
|
|
348
|
-
|
|
349
|
-
return `${Hr} ${Sr}% ${Lr}%`
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/** @typedef {'rgb' | 'hsl' | 'oklch'} ColorSpace */
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Convert a hex color to the component format for a given color space.
|
|
356
|
-
* - rgb: "r,g,b" → for `rgba(var(--x), alpha)`
|
|
357
|
-
* - hsl: "H S% L%" → for `hsl(var(--x) / alpha)`
|
|
358
|
-
* - oklch: "L C H" → for `oklch(var(--x) / alpha)`
|
|
359
|
-
*
|
|
360
|
-
* @param {string} hex - 6-digit hex color (#rrggbb)
|
|
361
|
-
* @param {ColorSpace} space
|
|
362
|
-
* @returns {string}
|
|
363
|
-
*/
|
|
364
|
-
export function hexToComponents(hex, space) {
|
|
365
|
-
switch (space) {
|
|
366
|
-
case 'hsl':
|
|
367
|
-
return hex2hsl(hex)
|
|
368
|
-
case 'oklch':
|
|
369
|
-
return hex2oklch(hex)
|
|
370
|
-
case 'rgb':
|
|
371
|
-
default:
|
|
372
|
-
return hex2rgb(hex)
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Convert a CSS color value to components for use in CSS variables.
|
|
378
|
-
*
|
|
379
|
-
* - Hex (#rrggbb): converted to space-appropriate components via hexToComponents.
|
|
380
|
-
* - oklch(...) strings: inner components extracted (e.g. "0.58 0.15 35") so the
|
|
381
|
-
* value is compatible with the oklch(var(--x) / alpha) wrapper pattern.
|
|
382
|
-
* - All other formats: returned as-is (no opacity-modifier support).
|
|
383
|
-
*
|
|
384
|
-
* @param {unknown} value
|
|
385
|
-
* @param {ColorSpace} [space]
|
|
386
|
-
* @returns {unknown}
|
|
387
|
-
*/
|
|
388
|
-
export function colorToRgb(value, space) {
|
|
389
|
-
if (typeof value !== 'string') return value
|
|
390
|
-
if (/^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
391
|
-
return space ? hexToComponents(value, space) : hex2rgb(value)
|
|
392
|
-
}
|
|
393
|
-
// oklch(...) — extract inner components so the var can be used in oklch(var(--x) / alpha)
|
|
394
|
-
const oklchMatch = value.match(/^oklch\(\s*([^)]+?)\s*\)$/)
|
|
395
|
-
if (oklchMatch) return oklchMatch[1]
|
|
396
|
-
return value
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
|
|
400
320
|
/**
|
|
401
321
|
* Detects whether an icon value is a CSS class (i-*) or literal text (kanji, emoji, etc.)
|
|
402
322
|
* @param {string | null | undefined} icon
|