@wordpress/theme 0.1.0 → 0.2.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 +69 -9
- package/bin/generate-default-ramps/index.ts +49 -0
- package/bin/generate-primitive-tokens/index.ts +14 -9
- package/bin/terrazzo-plugin-ds-tokens-docs/index.ts +5 -24
- package/bin/terrazzo-plugin-figma-ds-token-manager/index.ts +11 -4
- package/bin/terrazzo-plugin-inline-alias-values/index.ts +84 -0
- package/bin/terrazzo-plugin-known-wpds-css-variables/index.ts +19 -39
- package/build/color-ramps/index.js +21 -39
- package/build/color-ramps/index.js.map +3 -3
- package/build/color-ramps/lib/color-utils.js +39 -0
- package/build/color-ramps/lib/color-utils.js.map +7 -0
- package/build/color-ramps/lib/constants.js +20 -27
- package/build/color-ramps/lib/constants.js.map +3 -3
- package/build/color-ramps/lib/default-ramps.js +220 -0
- package/build/color-ramps/lib/default-ramps.js.map +7 -0
- package/build/color-ramps/lib/find-color-with-constraints.js +60 -91
- package/build/color-ramps/lib/find-color-with-constraints.js.map +3 -3
- package/build/color-ramps/lib/index.js +77 -119
- package/build/color-ramps/lib/index.js.map +3 -3
- package/build/color-ramps/lib/ramp-configs.js +15 -14
- package/build/color-ramps/lib/ramp-configs.js.map +2 -2
- package/build/color-ramps/lib/register-color-spaces.js +7 -0
- package/build/color-ramps/lib/register-color-spaces.js.map +7 -0
- package/build/color-ramps/lib/taper-chroma.js +35 -27
- package/build/color-ramps/lib/taper-chroma.js.map +3 -3
- package/build/color-ramps/lib/types.js +2 -1
- package/build/color-ramps/lib/types.js.map +1 -1
- package/build/color-ramps/lib/utils.js +75 -11
- package/build/color-ramps/lib/utils.js.map +2 -2
- package/build/context.js +3 -2
- package/build/context.js.map +1 -1
- package/build/index.js +2 -1
- package/build/index.js.map +1 -1
- package/build/lock-unlock.js +3 -2
- package/build/lock-unlock.js.map +1 -1
- package/build/prebuilt/js/design-tokens.js +19 -11
- package/build/prebuilt/js/design-tokens.js.map +2 -2
- package/build/prebuilt/json/figma.json +165 -783
- package/build/prebuilt/ts/color-tokens.js +137 -0
- package/build/prebuilt/ts/color-tokens.js.map +7 -0
- package/build/private-apis.js +3 -2
- package/build/private-apis.js.map +1 -1
- package/build/theme-provider.js +19 -17
- package/build/theme-provider.js.map +4 -4
- package/build/token-id.js +30 -0
- package/build/token-id.js.map +7 -0
- package/build/types/css-modules.d.js +0 -1
- package/build/types.js +2 -1
- package/build/types.js.map +1 -1
- package/build/use-theme-provider-styles.js +67 -62
- package/build/use-theme-provider-styles.js.map +3 -3
- package/build-module/color-ramps/index.js +20 -28
- package/build-module/color-ramps/index.js.map +2 -2
- package/build-module/color-ramps/lib/color-utils.js +19 -0
- package/build-module/color-ramps/lib/color-utils.js.map +7 -0
- package/build-module/color-ramps/lib/constants.js +14 -11
- package/build-module/color-ramps/lib/constants.js.map +2 -2
- package/build-module/color-ramps/lib/default-ramps.js +196 -0
- package/build-module/color-ramps/lib/default-ramps.js.map +7 -0
- package/build-module/color-ramps/lib/find-color-with-constraints.js +61 -87
- package/build-module/color-ramps/lib/find-color-with-constraints.js.map +2 -2
- package/build-module/color-ramps/lib/index.js +85 -109
- package/build-module/color-ramps/lib/index.js.map +3 -3
- package/build-module/color-ramps/lib/ramp-configs.js +14 -13
- package/build-module/color-ramps/lib/ramp-configs.js.map +2 -2
- package/build-module/color-ramps/lib/register-color-spaces.js +7 -0
- package/build-module/color-ramps/lib/register-color-spaces.js.map +7 -0
- package/build-module/color-ramps/lib/taper-chroma.js +40 -16
- package/build-module/color-ramps/lib/taper-chroma.js.map +2 -2
- package/build-module/color-ramps/lib/utils.js +70 -6
- package/build-module/color-ramps/lib/utils.js.map +2 -2
- package/build-module/context.js +2 -1
- package/build-module/context.js.map +1 -1
- package/build-module/index.js +1 -0
- package/build-module/index.js.map +1 -1
- package/build-module/lock-unlock.js +2 -1
- package/build-module/lock-unlock.js.map +1 -1
- package/build-module/prebuilt/js/design-tokens.js +18 -10
- package/build-module/prebuilt/js/design-tokens.js.map +2 -2
- package/build-module/prebuilt/json/figma.json +165 -783
- package/build-module/prebuilt/ts/color-tokens.js +117 -0
- package/build-module/prebuilt/ts/color-tokens.js.map +7 -0
- package/build-module/private-apis.js +2 -1
- package/build-module/private-apis.js.map +1 -1
- package/build-module/theme-provider.js +18 -6
- package/build-module/theme-provider.js.map +3 -3
- package/build-module/token-id.js +6 -0
- package/build-module/token-id.js.map +7 -0
- package/build-module/use-theme-provider-styles.js +69 -57
- package/build-module/use-theme-provider-styles.js.map +2 -2
- package/build-types/color-ramps/index.d.ts +9 -16
- package/build-types/color-ramps/index.d.ts.map +1 -1
- package/build-types/color-ramps/lib/color-utils.d.ts +22 -0
- package/build-types/color-ramps/lib/color-utils.d.ts.map +1 -0
- package/build-types/color-ramps/lib/constants.d.ts +7 -9
- package/build-types/color-ramps/lib/constants.d.ts.map +1 -1
- package/build-types/color-ramps/lib/default-ramps.d.ts +7 -0
- package/build-types/color-ramps/lib/default-ramps.d.ts.map +1 -0
- package/build-types/color-ramps/lib/find-color-with-constraints.d.ts +8 -7
- package/build-types/color-ramps/lib/find-color-with-constraints.d.ts.map +1 -1
- package/build-types/color-ramps/lib/index.d.ts +5 -2
- package/build-types/color-ramps/lib/index.d.ts.map +1 -1
- package/build-types/color-ramps/lib/register-color-spaces.d.ts +2 -0
- package/build-types/color-ramps/lib/register-color-spaces.d.ts.map +1 -0
- package/build-types/color-ramps/lib/taper-chroma.d.ts +7 -3
- package/build-types/color-ramps/lib/taper-chroma.d.ts.map +1 -1
- package/build-types/color-ramps/lib/utils.d.ts +28 -5
- package/build-types/color-ramps/lib/utils.d.ts.map +1 -1
- package/build-types/color-ramps/stories/index.story.d.ts.map +1 -1
- package/build-types/prebuilt/ts/color-tokens.d.ts +7 -0
- package/build-types/prebuilt/ts/color-tokens.d.ts.map +1 -0
- package/build-types/stories/index.story.d.ts.map +1 -1
- package/build-types/theme-provider.d.ts.map +1 -1
- package/build-types/token-id.d.ts +9 -0
- package/build-types/token-id.d.ts.map +1 -0
- package/build-types/use-theme-provider-styles.d.ts +4 -0
- package/build-types/use-theme-provider-styles.d.ts.map +1 -1
- package/docs/ds-tokens.md +22 -156
- package/package.json +19 -9
- package/src/color-ramps/index.ts +24 -41
- package/src/color-ramps/lib/color-utils.ts +34 -0
- package/src/color-ramps/lib/constants.ts +13 -9
- package/src/color-ramps/lib/default-ramps.ts +200 -0
- package/src/color-ramps/lib/find-color-with-constraints.ts +83 -116
- package/src/color-ramps/lib/index.ts +107 -145
- package/src/color-ramps/lib/ramp-configs.ts +3 -3
- package/src/color-ramps/lib/register-color-spaces.ts +13 -0
- package/src/color-ramps/lib/taper-chroma.ts +47 -19
- package/src/color-ramps/lib/utils.ts +117 -14
- package/src/color-ramps/stories/index.story.tsx +16 -22
- package/src/color-ramps/test/__snapshots__/index.test.ts.snap +45722 -376
- package/src/color-ramps/test/index.test.ts +68 -29
- package/src/prebuilt/css/design-tokens.css +88 -355
- package/src/prebuilt/js/design-tokens.js +17 -10
- package/src/prebuilt/json/figma.json +165 -783
- package/src/prebuilt/ts/color-tokens.ts +117 -0
- package/src/stories/index.story.tsx +4 -18
- package/src/test/token-id.test.ts +12 -0
- package/src/token-id.ts +9 -0
- package/src/use-theme-provider-styles.ts +67 -60
- package/terrazzo.config.ts +15 -12
- package/tokens/color.json +221 -69
- package/tokens/dimension.json +75 -0
- package/tsconfig.bin.json +13 -0
- package/tsconfig.bin.tsbuildinfo +1 -0
- package/tsconfig.json +6 -4
- package/tsconfig.src.json +9 -0
- package/tsconfig.src.tsbuildinfo +1 -0
- package/bin/build-tokens.js +0 -83
- package/build/color-ramps/lib/cache-utils.js +0 -57
- package/build/color-ramps/lib/cache-utils.js.map +0 -7
- package/build/prebuilt/ts/design-tokens.js +0 -354
- package/build/prebuilt/ts/design-tokens.js.map +0 -7
- package/build/style.module.css.js +0 -2
- package/build-module/color-ramps/lib/cache-utils.js +0 -31
- package/build-module/color-ramps/lib/cache-utils.js.map +0 -7
- package/build-module/prebuilt/ts/design-tokens.js +0 -334
- package/build-module/prebuilt/ts/design-tokens.js.map +0 -7
- package/build-module/style.module.css.js +0 -1
- package/build-style/style.css +0 -3
- package/build-types/color-ramps/lib/cache-utils.d.ts +0 -22
- package/build-types/color-ramps/lib/cache-utils.d.ts.map +0 -1
- package/build-types/prebuilt/ts/design-tokens.d.ts +0 -7
- package/build-types/prebuilt/ts/design-tokens.d.ts.map +0 -1
- package/src/color-ramps/lib/cache-utils.ts +0 -56
- package/src/prebuilt/ts/design-tokens.ts +0 -335
- package/tokens/spacing.json +0 -45
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { get, OKLCH, type ColorTypes } from 'colorjs.io/fn';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
LIGHTNESS_EPSILON,
|
|
14
|
-
MAX_BISECTION_ITERATIONS,
|
|
15
|
-
} from './constants';
|
|
16
|
-
import { getCachedContrast } from './cache-utils';
|
|
9
|
+
import './register-color-spaces';
|
|
10
|
+
import { clampToGamut, solveWithBisect } from './utils';
|
|
11
|
+
import { WHITE, BLACK, CONTRAST_EPSILON } from './constants';
|
|
12
|
+
import { getContrast } from './color-utils';
|
|
17
13
|
import { type TaperChromaOptions, taperChroma } from './taper-chroma';
|
|
18
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Difference of contrast values that grows linearly with the Y luminance.
|
|
17
|
+
* We get more precise linear interpolations when we use this.
|
|
18
|
+
* @param c1 First contrast.
|
|
19
|
+
* @param c2 Second contrast.
|
|
20
|
+
* @return Difference of logarithms.
|
|
21
|
+
*/
|
|
22
|
+
function cdiff( c1: number, c2: number ) {
|
|
23
|
+
return Math.log( c1 / c2 );
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
27
|
* Solve for L such that:
|
|
21
28
|
* - the L applied to the seed meets the contrast target against the reference
|
|
@@ -27,115 +34,98 @@ import { type TaperChromaOptions, taperChroma } from './taper-chroma';
|
|
|
27
34
|
* @param target
|
|
28
35
|
* @param direction
|
|
29
36
|
* @param options
|
|
30
|
-
* @param options.strict
|
|
31
|
-
* @param options.debug
|
|
32
37
|
* @param options.lightnessConstraint
|
|
33
38
|
* @param options.lightnessConstraint.type
|
|
34
39
|
* @param options.lightnessConstraint.value
|
|
35
40
|
* @param options.taperChromaOptions
|
|
36
41
|
*/
|
|
37
42
|
export function findColorMeetingRequirements(
|
|
38
|
-
reference:
|
|
39
|
-
seed:
|
|
43
|
+
reference: ColorTypes,
|
|
44
|
+
seed: ColorTypes,
|
|
40
45
|
target: number,
|
|
41
46
|
direction: 'lighter' | 'darker',
|
|
42
47
|
{
|
|
43
48
|
lightnessConstraint,
|
|
44
49
|
taperChromaOptions,
|
|
45
|
-
strict = true,
|
|
46
|
-
debug = false,
|
|
47
50
|
}: {
|
|
48
51
|
lightnessConstraint?: {
|
|
49
52
|
type: 'force' | 'onlyIfSucceeds';
|
|
50
53
|
value: number;
|
|
51
54
|
};
|
|
52
55
|
taperChromaOptions?: TaperChromaOptions;
|
|
53
|
-
strict?: boolean;
|
|
54
|
-
debug?: boolean;
|
|
55
56
|
} = {}
|
|
56
|
-
): { color:
|
|
57
|
+
): { color: ColorTypes; reached: boolean; achieved: number; deficit?: number } {
|
|
57
58
|
// A target of 1 means same color.
|
|
58
59
|
// A target lower than 1 doesn't make sense.
|
|
59
60
|
if ( target <= 1 ) {
|
|
60
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
color: reference,
|
|
63
|
+
reached: true,
|
|
64
|
+
achieved: 1,
|
|
65
|
+
};
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// min/max L values.
|
|
67
|
-
let newL = lightnessConstraint.value;
|
|
68
|
-
let newC = seed.oklch.c;
|
|
68
|
+
function getColorForL( l: number ): ColorTypes {
|
|
69
|
+
let newL = l;
|
|
70
|
+
let newC = get( seed, [ OKLCH, 'c' ] );
|
|
69
71
|
|
|
70
72
|
if ( taperChromaOptions ) {
|
|
71
|
-
(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
const tapered = taperChroma( seed, newL, taperChromaOptions );
|
|
74
|
+
// taperChroma returns either { l, c } or a ColorObject
|
|
75
|
+
if ( 'l' in tapered && 'c' in tapered ) {
|
|
76
|
+
newL = tapered.l;
|
|
77
|
+
newC = tapered.c;
|
|
78
|
+
} else {
|
|
79
|
+
// It's already a ColorObject, return it directly
|
|
80
|
+
return tapered;
|
|
81
|
+
}
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
return clampToGamut( {
|
|
85
|
+
spaceId: 'oklch',
|
|
86
|
+
coords: [ newL, newC, get( seed, [ OKLCH, 'h' ] ) ],
|
|
87
|
+
} );
|
|
88
|
+
}
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
// Set the boundary based on the direction.
|
|
91
|
+
const mostContrastingL = direction === 'lighter' ? 1 : 0;
|
|
92
|
+
const mostContrastingColor = direction === 'lighter' ? WHITE : BLACK;
|
|
93
|
+
const highestContrast = getContrast( reference, mostContrastingColor );
|
|
94
|
+
|
|
95
|
+
if ( lightnessConstraint ) {
|
|
96
|
+
// Apply a specific L value.
|
|
97
|
+
// Useful when pinning a step to a specific lightness, of to specify
|
|
98
|
+
// min/max L values.
|
|
99
|
+
const colorWithExactL = getColorForL( lightnessConstraint.value );
|
|
100
|
+
const exactLContrast = getContrast( reference, colorWithExactL );
|
|
101
|
+
const exactLContrastMeetsTarget =
|
|
102
|
+
cdiff( exactLContrast, target ) >= -CONTRAST_EPSILON;
|
|
91
103
|
|
|
92
104
|
// If the L constraint is of "force" type, apply it even when it doesn't
|
|
93
105
|
// meet the contrast target.
|
|
94
106
|
if (
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
exactLContrastMeetsTarget ||
|
|
108
|
+
lightnessConstraint.type === 'force'
|
|
97
109
|
) {
|
|
98
110
|
return {
|
|
99
111
|
color: colorWithExactL,
|
|
100
|
-
reached:
|
|
112
|
+
reached: exactLContrastMeetsTarget,
|
|
101
113
|
achieved: exactLContrast,
|
|
114
|
+
deficit: exactLContrastMeetsTarget
|
|
115
|
+
? cdiff( exactLContrast, highestContrast )
|
|
116
|
+
: cdiff( target, exactLContrast ),
|
|
102
117
|
};
|
|
103
118
|
}
|
|
104
119
|
}
|
|
105
120
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const highestPossibleContrast = getCachedContrast(
|
|
110
|
-
reference,
|
|
111
|
-
mostContrastingColor
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
// If even the most contrasting color can't reach the target,
|
|
115
|
-
// the target is unreachable.
|
|
116
|
-
if ( highestPossibleContrast < target ) {
|
|
117
|
-
if ( strict ) {
|
|
118
|
-
throw new Error(
|
|
119
|
-
`Contrast target ${ target.toFixed(
|
|
120
|
-
2
|
|
121
|
-
) }:1 unreachable in ${ direction } direction against ${ mostContrastingColor.toString() }` +
|
|
122
|
-
`(boundary achieves ${ highestPossibleContrast.toFixed(
|
|
123
|
-
3
|
|
124
|
-
) }:1).`
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if ( debug ) {
|
|
129
|
-
// eslint-disable-next-line no-console
|
|
130
|
-
console.log(
|
|
131
|
-
'Did not succeeded because it reached the limit',
|
|
132
|
-
mostContrastingL
|
|
133
|
-
);
|
|
134
|
-
}
|
|
121
|
+
// If even the most contrasting color can't reach the target, the target is unreachable.
|
|
122
|
+
// On the other hand, if the contrast is very close to the target, we consider it reached.
|
|
123
|
+
if ( cdiff( highestContrast, target ) <= CONTRAST_EPSILON ) {
|
|
135
124
|
return {
|
|
136
125
|
color: mostContrastingColor,
|
|
137
|
-
reached:
|
|
138
|
-
achieved:
|
|
126
|
+
reached: cdiff( highestContrast, target ) >= -CONTRAST_EPSILON,
|
|
127
|
+
achieved: highestContrast,
|
|
128
|
+
deficit: cdiff( target, highestContrast ),
|
|
139
129
|
};
|
|
140
130
|
}
|
|
141
131
|
|
|
@@ -143,48 +133,25 @@ export function findColorMeetingRequirements(
|
|
|
143
133
|
// Originally this was seed.oklch.l — although it's an assumption that works
|
|
144
134
|
// only when we know for sure the direction of the search.
|
|
145
135
|
// TODO: can we bring this back to seed.oklch.l ?
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
let newC = seed.oklch.c;
|
|
160
|
-
|
|
161
|
-
if ( taperChromaOptions ) {
|
|
162
|
-
( { l: newL, c: newC } = taperChroma(
|
|
163
|
-
seed,
|
|
164
|
-
newL,
|
|
165
|
-
taperChromaOptions
|
|
166
|
-
) );
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const newColor = clampToGamut(
|
|
170
|
-
new Color( 'oklch', [ newL, newC, seed.oklch.h ] )
|
|
171
|
-
);
|
|
172
|
-
const newContrast = getCachedContrast( reference, newColor );
|
|
173
|
-
|
|
174
|
-
if ( newContrast >= target ) {
|
|
175
|
-
betterL = newL;
|
|
176
|
-
// Only update the resulting color when the target is met, this ensuring
|
|
177
|
-
// at the end of the search the target is always met.
|
|
178
|
-
bestContrastFound = newContrast;
|
|
179
|
-
resultingColor = newColor;
|
|
180
|
-
} else {
|
|
181
|
-
worseL = newL;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
136
|
+
const lowerL = get( reference, [ OKLCH, 'l' ] );
|
|
137
|
+
const lowerContrast = cdiff( 1, target );
|
|
138
|
+
const upperL = mostContrastingL;
|
|
139
|
+
const upperContrast = cdiff( highestContrast, target );
|
|
140
|
+
|
|
141
|
+
const bestColor = solveWithBisect(
|
|
142
|
+
getColorForL,
|
|
143
|
+
( c: ColorTypes ) => cdiff( getContrast( reference, c ), target ),
|
|
144
|
+
lowerL,
|
|
145
|
+
lowerContrast,
|
|
146
|
+
upperL,
|
|
147
|
+
upperContrast
|
|
148
|
+
);
|
|
184
149
|
|
|
185
150
|
return {
|
|
186
|
-
color:
|
|
151
|
+
color: bestColor,
|
|
187
152
|
reached: true,
|
|
188
|
-
achieved:
|
|
153
|
+
achieved: target,
|
|
154
|
+
// Negative number that specifies how much room we have.
|
|
155
|
+
deficit: cdiff( target, highestContrast ),
|
|
189
156
|
};
|
|
190
157
|
}
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
clone,
|
|
6
|
+
get,
|
|
7
|
+
OKLCH,
|
|
8
|
+
parse,
|
|
9
|
+
set,
|
|
10
|
+
type ColorTypes,
|
|
11
|
+
type PlainColorObject,
|
|
12
|
+
} from 'colorjs.io/fn';
|
|
5
13
|
|
|
6
14
|
/**
|
|
7
15
|
* Internal dependencies
|
|
8
16
|
*/
|
|
9
|
-
import
|
|
17
|
+
import './register-color-spaces';
|
|
18
|
+
import { getContrast, getColorString } from './color-utils';
|
|
10
19
|
import { findColorMeetingRequirements } from './find-color-with-constraints';
|
|
11
20
|
import {
|
|
12
21
|
clampToGamut,
|
|
13
22
|
sortByDependency,
|
|
14
23
|
computeBetterFgColorDirection,
|
|
15
24
|
adjustContrastTarget,
|
|
25
|
+
stepsForStep,
|
|
26
|
+
solveWithBisect,
|
|
16
27
|
} from './utils';
|
|
17
28
|
|
|
18
29
|
import type {
|
|
@@ -22,7 +33,7 @@ import type {
|
|
|
22
33
|
RampConfig,
|
|
23
34
|
RampResult,
|
|
24
35
|
} from './types';
|
|
25
|
-
import {
|
|
36
|
+
import { CONTRAST_EPSILON } from './constants';
|
|
26
37
|
|
|
27
38
|
/**
|
|
28
39
|
* Calculate a complete color ramp based on the provided configuration.
|
|
@@ -36,7 +47,6 @@ import { LIGHTNESS_EPSILON, MAX_BISECTION_ITERATIONS } from './constants';
|
|
|
36
47
|
* @param params.pinLightness - Optional lightness override for a given step
|
|
37
48
|
* @param params.pinLightness.stepName
|
|
38
49
|
* @param params.pinLightness.value
|
|
39
|
-
* @param params.debug
|
|
40
50
|
* @return Object containing ramp results and satisfaction status
|
|
41
51
|
*/
|
|
42
52
|
function calculateRamp( {
|
|
@@ -46,9 +56,8 @@ function calculateRamp( {
|
|
|
46
56
|
mainDir,
|
|
47
57
|
oppDir,
|
|
48
58
|
pinLightness,
|
|
49
|
-
debug = false,
|
|
50
59
|
}: {
|
|
51
|
-
seed:
|
|
60
|
+
seed: ColorTypes;
|
|
52
61
|
sortedSteps: ( keyof Ramp )[];
|
|
53
62
|
config: RampConfig;
|
|
54
63
|
mainDir: RampDirection;
|
|
@@ -57,19 +66,18 @@ function calculateRamp( {
|
|
|
57
66
|
stepName: keyof Ramp;
|
|
58
67
|
value: number;
|
|
59
68
|
};
|
|
60
|
-
debug?: boolean;
|
|
61
69
|
} ) {
|
|
62
70
|
const rampResults = {} as Record<
|
|
63
71
|
keyof Ramp,
|
|
64
72
|
{ color: string; warning: boolean }
|
|
65
73
|
>;
|
|
66
|
-
let
|
|
67
|
-
let
|
|
68
|
-
let
|
|
74
|
+
let maxDeficit = -Infinity;
|
|
75
|
+
let maxDeficitDirection: RampDirection = 'lighter';
|
|
76
|
+
let maxDeficitStep;
|
|
69
77
|
|
|
70
78
|
// Keep track of the calculated colors, as they are going to be useful
|
|
71
79
|
// when other colors reference them.
|
|
72
|
-
const calculatedColors = new Map< keyof Ramp | 'seed',
|
|
80
|
+
const calculatedColors = new Map< keyof Ramp | 'seed', ColorTypes >();
|
|
73
81
|
calculatedColors.set( 'seed', seed );
|
|
74
82
|
|
|
75
83
|
for ( const stepName of sortedSteps ) {
|
|
@@ -79,8 +87,8 @@ function calculateRamp( {
|
|
|
79
87
|
taperChromaOptions,
|
|
80
88
|
sameAsIfPossible,
|
|
81
89
|
} = config[ stepName ];
|
|
82
|
-
const referenceColor = calculatedColors.get( contrast.reference );
|
|
83
90
|
|
|
91
|
+
const referenceColor = calculatedColors.get( contrast.reference );
|
|
84
92
|
if ( ! referenceColor ) {
|
|
85
93
|
throw new Error(
|
|
86
94
|
`Reference color for step ${ stepName } not found: ${ contrast.reference }`
|
|
@@ -90,28 +98,32 @@ function calculateRamp( {
|
|
|
90
98
|
// Check if we can reuse color from the `sameAsIfPossible` config option
|
|
91
99
|
if ( sameAsIfPossible ) {
|
|
92
100
|
const candidateColor = calculatedColors.get( sameAsIfPossible );
|
|
93
|
-
if ( candidateColor ) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
candidateColor
|
|
101
|
+
if ( ! candidateColor ) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Same-as color for step ${ stepName } not found: ${ sameAsIfPossible }`
|
|
97
104
|
);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const candidateContrast = getContrast(
|
|
108
|
+
referenceColor,
|
|
109
|
+
candidateColor
|
|
110
|
+
);
|
|
111
|
+
const adjustedTarget = adjustContrastTarget( contrast.target );
|
|
112
|
+
// If the candidate meets the contrast requirement, use it
|
|
113
|
+
if ( candidateContrast >= adjustedTarget ) {
|
|
114
|
+
// Store the reused color
|
|
115
|
+
calculatedColors.set( stepName, candidateColor );
|
|
116
|
+
rampResults[ stepName ] = {
|
|
117
|
+
color: getColorString( candidateColor ),
|
|
118
|
+
warning: false,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
continue; // Skip to next step
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
function computeDirection(
|
|
114
|
-
color:
|
|
126
|
+
color: ColorTypes,
|
|
115
127
|
followDirection: FollowDirection
|
|
116
128
|
): RampDirection {
|
|
117
129
|
if ( followDirection === 'main' ) {
|
|
@@ -160,33 +172,21 @@ function calculateRamp( {
|
|
|
160
172
|
adjustedTarget,
|
|
161
173
|
computedDir,
|
|
162
174
|
{
|
|
163
|
-
strict: false,
|
|
164
175
|
lightnessConstraint,
|
|
165
176
|
taperChromaOptions,
|
|
166
|
-
debug,
|
|
167
177
|
}
|
|
168
178
|
);
|
|
169
179
|
|
|
170
180
|
// When the target contrast is not met, take note of it and use
|
|
171
181
|
// that information to guide the ramp calculation bisection.
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// If seed has low contrast vs reference, adjusting seed has high impact
|
|
181
|
-
// If seed has high contrast vs reference, adjusting seed has low impact
|
|
182
|
-
const impactWeight = 1 / getCachedContrast( seed, referenceColor );
|
|
183
|
-
const weightedDeficit = deficitVsTarget * impactWeight;
|
|
184
|
-
|
|
185
|
-
// Track the most impactful failure for seed optimization
|
|
186
|
-
if ( weightedDeficit > MAX_WEIGHTED_DEFICIT ) {
|
|
187
|
-
MAX_WEIGHTED_DEFICIT = weightedDeficit;
|
|
188
|
-
UNSATISFIED_DIRECTION = computedDir;
|
|
189
|
-
}
|
|
182
|
+
if (
|
|
183
|
+
! contrast.ignoreWhenAdjustingSeed &&
|
|
184
|
+
searchResults.deficit &&
|
|
185
|
+
searchResults.deficit > maxDeficit
|
|
186
|
+
) {
|
|
187
|
+
maxDeficit = searchResults.deficit;
|
|
188
|
+
maxDeficitDirection = computedDir;
|
|
189
|
+
maxDeficitStep = stepName;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// Store calculated color for future dependencies
|
|
@@ -199,11 +199,11 @@ function calculateRamp( {
|
|
|
199
199
|
! contrast.ignoreWhenAdjustingSeed && ! searchResults.reached,
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
|
-
|
|
203
202
|
return {
|
|
204
203
|
rampResults,
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
maxDeficit,
|
|
205
|
+
maxDeficitDirection,
|
|
206
|
+
maxDeficitStep,
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
209
|
|
|
@@ -213,7 +213,6 @@ export function buildRamp(
|
|
|
213
213
|
{
|
|
214
214
|
mainDirection,
|
|
215
215
|
pinLightness,
|
|
216
|
-
debug = false,
|
|
217
216
|
rescaleToFitContrastTargets = true,
|
|
218
217
|
}: {
|
|
219
218
|
mainDirection?: RampDirection;
|
|
@@ -222,12 +221,11 @@ export function buildRamp(
|
|
|
222
221
|
value: number;
|
|
223
222
|
};
|
|
224
223
|
rescaleToFitContrastTargets?: boolean;
|
|
225
|
-
debug?: boolean;
|
|
226
224
|
} = {}
|
|
227
225
|
): RampResult {
|
|
228
|
-
let seed:
|
|
226
|
+
let seed: PlainColorObject;
|
|
229
227
|
try {
|
|
230
|
-
seed = clampToGamut(
|
|
228
|
+
seed = clampToGamut( parse( seedArg ) );
|
|
231
229
|
} catch ( error ) {
|
|
232
230
|
throw new Error(
|
|
233
231
|
`Invalid seed color "${ seedArg }": ${
|
|
@@ -252,118 +250,82 @@ export function buildRamp(
|
|
|
252
250
|
const sortedSteps = sortByDependency( config );
|
|
253
251
|
|
|
254
252
|
// Calculate the ramp with the initial seed.
|
|
255
|
-
const {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
UNSATISFIED_DIRECTION,
|
|
259
|
-
} = calculateRamp( {
|
|
260
|
-
seed,
|
|
261
|
-
sortedSteps,
|
|
262
|
-
config,
|
|
263
|
-
mainDir,
|
|
264
|
-
oppDir,
|
|
265
|
-
pinLightness,
|
|
266
|
-
debug,
|
|
267
|
-
} );
|
|
268
|
-
const toReturn = {
|
|
269
|
-
ramp: rampResults,
|
|
270
|
-
direction: mainDir,
|
|
271
|
-
} as RampResult;
|
|
272
|
-
|
|
273
|
-
if ( debug ) {
|
|
274
|
-
// eslint-disable-next-line no-console
|
|
275
|
-
console.log( `First run`, {
|
|
276
|
-
SATISFIED_ALL_CONTRAST_REQUIREMENTS,
|
|
277
|
-
UNSATISFIED_DIRECTION,
|
|
278
|
-
seed: seed.toString(),
|
|
253
|
+
const { rampResults, maxDeficit, maxDeficitDirection, maxDeficitStep } =
|
|
254
|
+
calculateRamp( {
|
|
255
|
+
seed,
|
|
279
256
|
sortedSteps,
|
|
280
257
|
config,
|
|
281
258
|
mainDir,
|
|
282
259
|
oppDir,
|
|
283
260
|
pinLightness,
|
|
284
261
|
} );
|
|
285
|
-
}
|
|
286
262
|
|
|
287
|
-
|
|
288
|
-
! SATISFIED_ALL_CONTRAST_REQUIREMENTS &&
|
|
289
|
-
rescaleToFitContrastTargets
|
|
290
|
-
) {
|
|
291
|
-
let worseSeedL = seed.oklch.l;
|
|
292
|
-
// For a scale with the "lighter" direction, the contrast can be improved
|
|
293
|
-
// by darkening the seed. For "darker" direction, by lightening the seed.
|
|
294
|
-
let betterSeedL = UNSATISFIED_DIRECTION === 'lighter' ? 0 : 1;
|
|
295
|
-
|
|
296
|
-
// Binary search: try a new seed and recompute the whole ramp
|
|
297
|
-
// (TODO: try a smarter approach?)
|
|
298
|
-
for (
|
|
299
|
-
let i = 0;
|
|
300
|
-
i < MAX_BISECTION_ITERATIONS &&
|
|
301
|
-
Math.abs( betterSeedL - worseSeedL ) > LIGHTNESS_EPSILON;
|
|
302
|
-
i++
|
|
303
|
-
) {
|
|
304
|
-
const newSeed = clampToGamut(
|
|
305
|
-
seed.clone().set( {
|
|
306
|
-
l: ( worseSeedL + betterSeedL ) / 2,
|
|
307
|
-
} )
|
|
308
|
-
);
|
|
263
|
+
let bestRamp = rampResults;
|
|
309
264
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
console.log( `Iteration ${ i }`, {
|
|
313
|
-
worseSeedL,
|
|
314
|
-
newSeedL: ( worseSeedL + betterSeedL ) / 2,
|
|
315
|
-
betterSeedL,
|
|
316
|
-
} );
|
|
317
|
-
}
|
|
265
|
+
if ( maxDeficit > CONTRAST_EPSILON && rescaleToFitContrastTargets ) {
|
|
266
|
+
const iterSteps = stepsForStep( maxDeficitStep!, config );
|
|
318
267
|
|
|
268
|
+
function getSeedForL( l: number ): ColorTypes {
|
|
269
|
+
return clampToGamut( set( clone( seed ), [ OKLCH, 'l' ], l ) );
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function getDeficitForSeed( s: ColorTypes ): number {
|
|
319
273
|
const iterationResults = calculateRamp( {
|
|
320
|
-
seed:
|
|
321
|
-
sortedSteps,
|
|
274
|
+
seed: s,
|
|
275
|
+
sortedSteps: iterSteps,
|
|
322
276
|
config,
|
|
323
277
|
mainDir,
|
|
324
278
|
oppDir,
|
|
325
279
|
pinLightness,
|
|
326
|
-
debug,
|
|
327
280
|
} );
|
|
328
281
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
betterSeedL = newSeed.oklch.l;
|
|
337
|
-
} else {
|
|
338
|
-
// Failing constraint is in same direction as main ramp direction
|
|
339
|
-
// We haven't moved far enough in mainDir, continue searching
|
|
340
|
-
worseSeedL = newSeed.oklch.l;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if ( debug ) {
|
|
344
|
-
// eslint-disable-next-line no-console
|
|
345
|
-
console.log( `Retry #${ i }`, {
|
|
346
|
-
SATISFIED_ALL_CONTRAST_REQUIREMENTS,
|
|
347
|
-
UNSATISFIED_DIRECTION,
|
|
348
|
-
seed: newSeed.toString(),
|
|
349
|
-
sortedSteps,
|
|
350
|
-
config,
|
|
351
|
-
mainDir,
|
|
352
|
-
oppDir,
|
|
353
|
-
pinLightness,
|
|
354
|
-
} );
|
|
355
|
-
}
|
|
282
|
+
// If the constraints start failing in the opposite direction to the original
|
|
283
|
+
// iteration's direction, that means we've moved too far away from the target.
|
|
284
|
+
// Don't use the `maxDeficit` value because it's not related to our search,
|
|
285
|
+
// and might even be positive, which would confuse the bisection algorithm.
|
|
286
|
+
return iterationResults.maxDeficitDirection === maxDeficitDirection
|
|
287
|
+
? iterationResults.maxDeficit
|
|
288
|
+
: -maxDeficit;
|
|
356
289
|
}
|
|
290
|
+
|
|
291
|
+
// For a scale with the "lighter" direction, the contrast can be improved
|
|
292
|
+
// by darkening the seed. For "darker" direction, by lightening the seed.
|
|
293
|
+
const lowerSeedL = maxDeficitDirection === 'lighter' ? 0 : 1;
|
|
294
|
+
const lowerDeficit = -maxDeficit;
|
|
295
|
+
const upperSeedL = get( seed, [ OKLCH, 'l' ] );
|
|
296
|
+
const upperDeficit = maxDeficit;
|
|
297
|
+
|
|
298
|
+
const bestSeed = solveWithBisect(
|
|
299
|
+
getSeedForL,
|
|
300
|
+
getDeficitForSeed,
|
|
301
|
+
lowerSeedL,
|
|
302
|
+
lowerDeficit,
|
|
303
|
+
upperSeedL,
|
|
304
|
+
upperDeficit
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Calculate the final ramp with adjusted seed.
|
|
308
|
+
bestRamp = calculateRamp( {
|
|
309
|
+
seed: bestSeed,
|
|
310
|
+
sortedSteps,
|
|
311
|
+
config,
|
|
312
|
+
mainDir,
|
|
313
|
+
oppDir,
|
|
314
|
+
pinLightness,
|
|
315
|
+
} ).rampResults;
|
|
357
316
|
}
|
|
358
317
|
|
|
359
318
|
// Swap surface1 and surface3 for darker ramps to maintain visual elevation hierarchy.
|
|
360
319
|
// This ensures surface1 appears "behind" surface2, and surface3 appears "in front",
|
|
361
320
|
// regardless of the ramp's main direction.
|
|
362
321
|
if ( mainDir === 'darker' ) {
|
|
363
|
-
const tmpSurface1 =
|
|
364
|
-
|
|
365
|
-
|
|
322
|
+
const tmpSurface1 = bestRamp.surface1;
|
|
323
|
+
bestRamp.surface1 = bestRamp.surface3;
|
|
324
|
+
bestRamp.surface3 = tmpSurface1;
|
|
366
325
|
}
|
|
367
326
|
|
|
368
|
-
return
|
|
327
|
+
return {
|
|
328
|
+
ramp: bestRamp,
|
|
329
|
+
direction: mainDir,
|
|
330
|
+
};
|
|
369
331
|
}
|
|
@@ -59,7 +59,7 @@ export const BG_RAMP_CONFIG: RampConfig = {
|
|
|
59
59
|
contrast: {
|
|
60
60
|
reference: 'surface2',
|
|
61
61
|
followDirection: 'opposite',
|
|
62
|
-
target: 1.
|
|
62
|
+
target: 1.06,
|
|
63
63
|
ignoreWhenAdjustingSeed: true,
|
|
64
64
|
},
|
|
65
65
|
taperChromaOptions: BG_SURFACE_TAPER_CHROMA,
|
|
@@ -75,7 +75,7 @@ export const BG_RAMP_CONFIG: RampConfig = {
|
|
|
75
75
|
contrast: {
|
|
76
76
|
reference: 'surface2',
|
|
77
77
|
followDirection: 'main',
|
|
78
|
-
target: 1.
|
|
78
|
+
target: 1.06,
|
|
79
79
|
},
|
|
80
80
|
taperChromaOptions: BG_SURFACE_TAPER_CHROMA,
|
|
81
81
|
},
|
|
@@ -83,7 +83,7 @@ export const BG_RAMP_CONFIG: RampConfig = {
|
|
|
83
83
|
contrast: {
|
|
84
84
|
reference: 'surface2',
|
|
85
85
|
followDirection: 'main',
|
|
86
|
-
target: 1.
|
|
86
|
+
target: 1.12,
|
|
87
87
|
},
|
|
88
88
|
taperChromaOptions: BG_SURFACE_TAPER_CHROMA,
|
|
89
89
|
},
|