@wordpress/theme 0.1.1-next.2f1c7c01b.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/bin/terrazzo-plugin-ds-tokens-docs/index.ts +5 -24
- 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/lib/constants.js +4 -4
- package/build/color-ramps/lib/constants.js.map +2 -2
- package/build/color-ramps/lib/default-ramps.js +82 -82
- package/build/color-ramps/lib/default-ramps.js.map +1 -1
- package/build/color-ramps/lib/find-color-with-constraints.js +36 -53
- package/build/color-ramps/lib/find-color-with-constraints.js.map +2 -2
- package/build/color-ramps/lib/index.js +64 -63
- package/build/color-ramps/lib/index.js.map +2 -2
- package/build/color-ramps/lib/ramp-configs.js +3 -3
- package/build/color-ramps/lib/ramp-configs.js.map +1 -1
- package/build/color-ramps/lib/utils.js +63 -2
- package/build/color-ramps/lib/utils.js.map +2 -2
- package/build/prebuilt/js/design-tokens.js +5 -10
- package/build/prebuilt/js/design-tokens.js.map +2 -2
- package/build/prebuilt/json/figma.json +105 -905
- package/build/prebuilt/ts/color-tokens.js +137 -0
- package/build/prebuilt/ts/color-tokens.js.map +7 -0
- package/build/token-id.js +30 -0
- package/build/token-id.js.map +7 -0
- package/build/use-theme-provider-styles.js +18 -27
- package/build/use-theme-provider-styles.js.map +3 -3
- package/build-module/color-ramps/lib/constants.js +3 -3
- package/build-module/color-ramps/lib/constants.js.map +2 -2
- package/build-module/color-ramps/lib/default-ramps.js +82 -82
- package/build-module/color-ramps/lib/default-ramps.js.map +1 -1
- package/build-module/color-ramps/lib/find-color-with-constraints.js +38 -60
- package/build-module/color-ramps/lib/find-color-with-constraints.js.map +2 -2
- package/build-module/color-ramps/lib/index.js +68 -65
- package/build-module/color-ramps/lib/index.js.map +2 -2
- package/build-module/color-ramps/lib/ramp-configs.js +3 -3
- package/build-module/color-ramps/lib/ramp-configs.js.map +1 -1
- package/build-module/color-ramps/lib/utils.js +63 -2
- package/build-module/color-ramps/lib/utils.js.map +2 -2
- package/build-module/prebuilt/js/design-tokens.js +5 -10
- package/build-module/prebuilt/js/design-tokens.js.map +2 -2
- package/build-module/prebuilt/json/figma.json +105 -905
- package/build-module/prebuilt/ts/color-tokens.js +117 -0
- package/build-module/prebuilt/ts/color-tokens.js.map +7 -0
- 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 +18 -27
- package/build-module/use-theme-provider-styles.js.map +2 -2
- package/build-types/color-ramps/lib/constants.d.ts +2 -2
- package/build-types/color-ramps/lib/constants.d.ts.map +1 -1
- package/build-types/color-ramps/lib/find-color-with-constraints.d.ts +2 -3
- package/build-types/color-ramps/lib/find-color-with-constraints.d.ts.map +1 -1
- package/build-types/color-ramps/lib/index.d.ts.map +1 -1
- package/build-types/color-ramps/lib/utils.d.ts +21 -2
- 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.map +1 -1
- package/docs/ds-tokens.md +10 -178
- package/package.json +4 -4
- package/src/color-ramps/lib/constants.ts +7 -5
- package/src/color-ramps/lib/default-ramps.ts +82 -82
- package/src/color-ramps/lib/find-color-with-constraints.ts +53 -77
- package/src/color-ramps/lib/index.ts +98 -102
- package/src/color-ramps/lib/ramp-configs.ts +3 -3
- package/src/color-ramps/lib/utils.ts +109 -5
- package/src/color-ramps/test/__snapshots__/index.test.ts.snap +45706 -360
- package/src/color-ramps/test/index.test.ts +41 -14
- package/src/prebuilt/css/design-tokens.css +88 -413
- package/src/prebuilt/js/design-tokens.js +5 -10
- package/src/prebuilt/json/figma.json +105 -905
- 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 +20 -35
- package/terrazzo.config.ts +15 -12
- package/tokens/color.json +82 -82
- package/tokens/dimension.json +75 -0
- package/tsconfig.bin.tsbuildinfo +1 -1
- package/tsconfig.src.tsbuildinfo +1 -1
- package/build/prebuilt/ts/design-tokens.js +0 -391
- package/build/prebuilt/ts/design-tokens.js.map +0 -7
- package/build-module/prebuilt/ts/design-tokens.js +0 -371
- package/build-module/prebuilt/ts/design-tokens.js.map +0 -7
- 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/prebuilt/ts/design-tokens.ts +0 -371
- package/tokens/spacing.json +0 -45
|
@@ -7,16 +7,22 @@ import { get, OKLCH, type ColorTypes } from 'colorjs.io/fn';
|
|
|
7
7
|
* Internal dependencies
|
|
8
8
|
*/
|
|
9
9
|
import './register-color-spaces';
|
|
10
|
-
import { clampToGamut } from './utils';
|
|
11
|
-
import {
|
|
12
|
-
WHITE,
|
|
13
|
-
BLACK,
|
|
14
|
-
LIGHTNESS_EPSILON,
|
|
15
|
-
MAX_BISECTION_ITERATIONS,
|
|
16
|
-
} from './constants';
|
|
10
|
+
import { clampToGamut, solveWithBisect } from './utils';
|
|
11
|
+
import { WHITE, BLACK, CONTRAST_EPSILON } from './constants';
|
|
17
12
|
import { getContrast } from './color-utils';
|
|
18
13
|
import { type TaperChromaOptions, taperChroma } from './taper-chroma';
|
|
19
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
|
+
|
|
20
26
|
/**
|
|
21
27
|
* Solve for L such that:
|
|
22
28
|
* - the L applied to the seed meets the contrast target against the reference
|
|
@@ -28,7 +34,6 @@ import { type TaperChromaOptions, taperChroma } from './taper-chroma';
|
|
|
28
34
|
* @param target
|
|
29
35
|
* @param direction
|
|
30
36
|
* @param options
|
|
31
|
-
* @param options.strict
|
|
32
37
|
* @param options.lightnessConstraint
|
|
33
38
|
* @param options.lightnessConstraint.type
|
|
34
39
|
* @param options.lightnessConstraint.value
|
|
@@ -42,20 +47,22 @@ export function findColorMeetingRequirements(
|
|
|
42
47
|
{
|
|
43
48
|
lightnessConstraint,
|
|
44
49
|
taperChromaOptions,
|
|
45
|
-
strict = true,
|
|
46
50
|
}: {
|
|
47
51
|
lightnessConstraint?: {
|
|
48
52
|
type: 'force' | 'onlyIfSucceeds';
|
|
49
53
|
value: number;
|
|
50
54
|
};
|
|
51
55
|
taperChromaOptions?: TaperChromaOptions;
|
|
52
|
-
strict?: boolean;
|
|
53
56
|
} = {}
|
|
54
|
-
): { color: ColorTypes; reached: boolean; achieved: number } {
|
|
57
|
+
): { color: ColorTypes; reached: boolean; achieved: number; deficit?: number } {
|
|
55
58
|
// A target of 1 means same color.
|
|
56
59
|
// A target lower than 1 doesn't make sense.
|
|
57
60
|
if ( target <= 1 ) {
|
|
58
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
color: reference,
|
|
63
|
+
reached: true,
|
|
64
|
+
achieved: 1,
|
|
65
|
+
};
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
function getColorForL( l: number ): ColorTypes {
|
|
@@ -80,48 +87,45 @@ export function findColorMeetingRequirements(
|
|
|
80
87
|
} );
|
|
81
88
|
}
|
|
82
89
|
|
|
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
|
+
|
|
83
95
|
if ( lightnessConstraint ) {
|
|
84
96
|
// Apply a specific L value.
|
|
85
97
|
// Useful when pinning a step to a specific lightness, of to specify
|
|
86
98
|
// min/max L values.
|
|
87
99
|
const colorWithExactL = getColorForL( lightnessConstraint.value );
|
|
88
100
|
const exactLContrast = getContrast( reference, colorWithExactL );
|
|
101
|
+
const exactLContrastMeetsTarget =
|
|
102
|
+
cdiff( exactLContrast, target ) >= -CONTRAST_EPSILON;
|
|
89
103
|
|
|
90
104
|
// If the L constraint is of "force" type, apply it even when it doesn't
|
|
91
105
|
// meet the contrast target.
|
|
92
106
|
if (
|
|
93
|
-
|
|
94
|
-
|
|
107
|
+
exactLContrastMeetsTarget ||
|
|
108
|
+
lightnessConstraint.type === 'force'
|
|
95
109
|
) {
|
|
96
110
|
return {
|
|
97
111
|
color: colorWithExactL,
|
|
98
|
-
reached:
|
|
112
|
+
reached: exactLContrastMeetsTarget,
|
|
99
113
|
achieved: exactLContrast,
|
|
114
|
+
deficit: exactLContrastMeetsTarget
|
|
115
|
+
? cdiff( exactLContrast, highestContrast )
|
|
116
|
+
: cdiff( target, exactLContrast ),
|
|
100
117
|
};
|
|
101
118
|
}
|
|
102
119
|
}
|
|
103
120
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const highestContrast = getContrast( reference, mostContrastingColor );
|
|
108
|
-
|
|
109
|
-
// If even the most contrasting color can't reach the target,
|
|
110
|
-
// the target is unreachable.
|
|
111
|
-
if ( highestContrast < target ) {
|
|
112
|
-
if ( strict ) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`Contrast target ${ target.toFixed(
|
|
115
|
-
2
|
|
116
|
-
) }:1 unreachable in ${ direction } direction` +
|
|
117
|
-
`(boundary achieves ${ highestContrast.toFixed( 3 ) }:1).`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
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 ) {
|
|
121
124
|
return {
|
|
122
125
|
color: mostContrastingColor,
|
|
123
|
-
reached:
|
|
126
|
+
reached: cdiff( highestContrast, target ) >= -CONTRAST_EPSILON,
|
|
124
127
|
achieved: highestContrast,
|
|
128
|
+
deficit: cdiff( target, highestContrast ),
|
|
125
129
|
};
|
|
126
130
|
}
|
|
127
131
|
|
|
@@ -129,53 +133,25 @@ export function findColorMeetingRequirements(
|
|
|
129
133
|
// Originally this was seed.oklch.l — although it's an assumption that works
|
|
130
134
|
// only when we know for sure the direction of the search.
|
|
131
135
|
// TODO: can we bring this back to seed.oklch.l ?
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
( worseL * ( betterContrast - target ) -
|
|
146
|
-
betterL * ( worseContrast - target ) ) /
|
|
147
|
-
( betterContrast - worseContrast );
|
|
148
|
-
|
|
149
|
-
bestColor = getColorForL( newL );
|
|
150
|
-
bestContrast = getContrast( reference, bestColor );
|
|
151
|
-
|
|
152
|
-
if ( Math.abs( bestContrast - target ) <= LIGHTNESS_EPSILON ) {
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Update one of the boundary L values, using the Illinois method.
|
|
157
|
-
if ( bestContrast >= target ) {
|
|
158
|
-
betterL = newL;
|
|
159
|
-
betterContrast = bestContrast;
|
|
160
|
-
if ( replacedBetter ) {
|
|
161
|
-
worseContrast = ( worseContrast + target ) / 2;
|
|
162
|
-
}
|
|
163
|
-
replacedBetter = true;
|
|
164
|
-
replacedWorse = false;
|
|
165
|
-
} else {
|
|
166
|
-
worseL = newL;
|
|
167
|
-
worseContrast = bestContrast;
|
|
168
|
-
if ( replacedWorse ) {
|
|
169
|
-
betterContrast = ( betterContrast + target ) / 2;
|
|
170
|
-
}
|
|
171
|
-
replacedWorse = true;
|
|
172
|
-
replacedBetter = false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
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
|
+
);
|
|
175
149
|
|
|
176
150
|
return {
|
|
177
151
|
color: bestColor,
|
|
178
152
|
reached: true,
|
|
179
|
-
achieved:
|
|
153
|
+
achieved: target,
|
|
154
|
+
// Negative number that specifies how much room we have.
|
|
155
|
+
deficit: cdiff( target, highestContrast ),
|
|
180
156
|
};
|
|
181
157
|
}
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
sortByDependency,
|
|
23
23
|
computeBetterFgColorDirection,
|
|
24
24
|
adjustContrastTarget,
|
|
25
|
+
stepsForStep,
|
|
26
|
+
solveWithBisect,
|
|
25
27
|
} from './utils';
|
|
26
28
|
|
|
27
29
|
import type {
|
|
@@ -31,7 +33,7 @@ import type {
|
|
|
31
33
|
RampConfig,
|
|
32
34
|
RampResult,
|
|
33
35
|
} from './types';
|
|
34
|
-
import {
|
|
36
|
+
import { CONTRAST_EPSILON } from './constants';
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
39
|
* Calculate a complete color ramp based on the provided configuration.
|
|
@@ -69,9 +71,9 @@ function calculateRamp( {
|
|
|
69
71
|
keyof Ramp,
|
|
70
72
|
{ color: string; warning: boolean }
|
|
71
73
|
>;
|
|
72
|
-
let
|
|
73
|
-
let
|
|
74
|
-
let
|
|
74
|
+
let maxDeficit = -Infinity;
|
|
75
|
+
let maxDeficitDirection: RampDirection = 'lighter';
|
|
76
|
+
let maxDeficitStep;
|
|
75
77
|
|
|
76
78
|
// Keep track of the calculated colors, as they are going to be useful
|
|
77
79
|
// when other colors reference them.
|
|
@@ -85,8 +87,8 @@ function calculateRamp( {
|
|
|
85
87
|
taperChromaOptions,
|
|
86
88
|
sameAsIfPossible,
|
|
87
89
|
} = config[ stepName ];
|
|
88
|
-
const referenceColor = calculatedColors.get( contrast.reference );
|
|
89
90
|
|
|
91
|
+
const referenceColor = calculatedColors.get( contrast.reference );
|
|
90
92
|
if ( ! referenceColor ) {
|
|
91
93
|
throw new Error(
|
|
92
94
|
`Reference color for step ${ stepName } not found: ${ contrast.reference }`
|
|
@@ -96,23 +98,27 @@ function calculateRamp( {
|
|
|
96
98
|
// Check if we can reuse color from the `sameAsIfPossible` config option
|
|
97
99
|
if ( sameAsIfPossible ) {
|
|
98
100
|
const candidateColor = calculatedColors.get( sameAsIfPossible );
|
|
99
|
-
if ( candidateColor ) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
candidateColor
|
|
101
|
+
if ( ! candidateColor ) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Same-as color for step ${ stepName } not found: ${ sameAsIfPossible }`
|
|
103
104
|
);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
|
|
@@ -166,7 +172,6 @@ function calculateRamp( {
|
|
|
166
172
|
adjustedTarget,
|
|
167
173
|
computedDir,
|
|
168
174
|
{
|
|
169
|
-
strict: false,
|
|
170
175
|
lightnessConstraint,
|
|
171
176
|
taperChromaOptions,
|
|
172
177
|
}
|
|
@@ -174,24 +179,14 @@ function calculateRamp( {
|
|
|
174
179
|
|
|
175
180
|
// When the target contrast is not met, take note of it and use
|
|
176
181
|
// that information to guide the ramp calculation bisection.
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// If seed has low contrast vs reference, adjusting seed has high impact
|
|
186
|
-
// If seed has high contrast vs reference, adjusting seed has low impact
|
|
187
|
-
const impactWeight = 1 / getContrast( seed, referenceColor );
|
|
188
|
-
const weightedDeficit = deficitVsTarget * impactWeight;
|
|
189
|
-
|
|
190
|
-
// Track the most impactful failure for seed optimization
|
|
191
|
-
if ( weightedDeficit > MAX_WEIGHTED_DEFICIT ) {
|
|
192
|
-
MAX_WEIGHTED_DEFICIT = weightedDeficit;
|
|
193
|
-
UNSATISFIED_DIRECTION = computedDir;
|
|
194
|
-
}
|
|
182
|
+
if (
|
|
183
|
+
! contrast.ignoreWhenAdjustingSeed &&
|
|
184
|
+
searchResults.deficit &&
|
|
185
|
+
searchResults.deficit > maxDeficit
|
|
186
|
+
) {
|
|
187
|
+
maxDeficit = searchResults.deficit;
|
|
188
|
+
maxDeficitDirection = computedDir;
|
|
189
|
+
maxDeficitStep = stepName;
|
|
195
190
|
}
|
|
196
191
|
|
|
197
192
|
// Store calculated color for future dependencies
|
|
@@ -204,11 +199,11 @@ function calculateRamp( {
|
|
|
204
199
|
! contrast.ignoreWhenAdjustingSeed && ! searchResults.reached,
|
|
205
200
|
};
|
|
206
201
|
}
|
|
207
|
-
|
|
208
202
|
return {
|
|
209
203
|
rampResults,
|
|
210
|
-
|
|
211
|
-
|
|
204
|
+
maxDeficit,
|
|
205
|
+
maxDeficitDirection,
|
|
206
|
+
maxDeficitStep,
|
|
212
207
|
};
|
|
213
208
|
}
|
|
214
209
|
|
|
@@ -255,81 +250,82 @@ export function buildRamp(
|
|
|
255
250
|
const sortedSteps = sortByDependency( config );
|
|
256
251
|
|
|
257
252
|
// Calculate the ramp with the initial seed.
|
|
258
|
-
const {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
oppDir,
|
|
268
|
-
pinLightness,
|
|
269
|
-
} );
|
|
270
|
-
const toReturn = {
|
|
271
|
-
ramp: rampResults,
|
|
272
|
-
direction: mainDir,
|
|
273
|
-
} as RampResult;
|
|
253
|
+
const { rampResults, maxDeficit, maxDeficitDirection, maxDeficitStep } =
|
|
254
|
+
calculateRamp( {
|
|
255
|
+
seed,
|
|
256
|
+
sortedSteps,
|
|
257
|
+
config,
|
|
258
|
+
mainDir,
|
|
259
|
+
oppDir,
|
|
260
|
+
pinLightness,
|
|
261
|
+
} );
|
|
274
262
|
|
|
275
|
-
|
|
276
|
-
! SATISFIED_ALL_CONTRAST_REQUIREMENTS &&
|
|
277
|
-
rescaleToFitContrastTargets
|
|
278
|
-
) {
|
|
279
|
-
let worseSeedL = get( seed, [ OKLCH, 'l' ] );
|
|
280
|
-
// For a scale with the "lighter" direction, the contrast can be improved
|
|
281
|
-
// by darkening the seed. For "darker" direction, by lightening the seed.
|
|
282
|
-
let betterSeedL = UNSATISFIED_DIRECTION === 'lighter' ? 0 : 1;
|
|
283
|
-
|
|
284
|
-
// Binary search: try a new seed and recompute the whole ramp
|
|
285
|
-
// (TODO: try a smarter approach?)
|
|
286
|
-
for (
|
|
287
|
-
let i = 0;
|
|
288
|
-
i < MAX_BISECTION_ITERATIONS &&
|
|
289
|
-
Math.abs( betterSeedL - worseSeedL ) > LIGHTNESS_EPSILON;
|
|
290
|
-
i++
|
|
291
|
-
) {
|
|
292
|
-
const newSeed = clampToGamut(
|
|
293
|
-
set(
|
|
294
|
-
clone( seed ),
|
|
295
|
-
[ OKLCH, 'l' ],
|
|
296
|
-
( worseSeedL + betterSeedL ) / 2
|
|
297
|
-
)
|
|
298
|
-
);
|
|
263
|
+
let bestRamp = rampResults;
|
|
299
264
|
|
|
265
|
+
if ( maxDeficit > CONTRAST_EPSILON && rescaleToFitContrastTargets ) {
|
|
266
|
+
const iterSteps = stepsForStep( maxDeficitStep!, config );
|
|
267
|
+
|
|
268
|
+
function getSeedForL( l: number ): ColorTypes {
|
|
269
|
+
return clampToGamut( set( clone( seed ), [ OKLCH, 'l' ], l ) );
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function getDeficitForSeed( s: ColorTypes ): number {
|
|
300
273
|
const iterationResults = calculateRamp( {
|
|
301
|
-
seed:
|
|
302
|
-
sortedSteps,
|
|
274
|
+
seed: s,
|
|
275
|
+
sortedSteps: iterSteps,
|
|
303
276
|
config,
|
|
304
277
|
mainDir,
|
|
305
278
|
oppDir,
|
|
306
279
|
pinLightness,
|
|
307
280
|
} );
|
|
308
281
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
betterSeedL = get( newSeed, [ OKLCH, 'l' ] );
|
|
317
|
-
} else {
|
|
318
|
-
// Failing constraint is in same direction as main ramp direction
|
|
319
|
-
// We haven't moved far enough in mainDir, continue searching
|
|
320
|
-
worseSeedL = get( newSeed, [ OKLCH, 'l' ] );
|
|
321
|
-
}
|
|
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;
|
|
322
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;
|
|
323
316
|
}
|
|
324
317
|
|
|
325
318
|
// Swap surface1 and surface3 for darker ramps to maintain visual elevation hierarchy.
|
|
326
319
|
// This ensures surface1 appears "behind" surface2, and surface3 appears "in front",
|
|
327
320
|
// regardless of the ramp's main direction.
|
|
328
321
|
if ( mainDir === 'darker' ) {
|
|
329
|
-
const tmpSurface1 =
|
|
330
|
-
|
|
331
|
-
|
|
322
|
+
const tmpSurface1 = bestRamp.surface1;
|
|
323
|
+
bestRamp.surface1 = bestRamp.surface3;
|
|
324
|
+
bestRamp.surface3 = tmpSurface1;
|
|
332
325
|
}
|
|
333
326
|
|
|
334
|
-
return
|
|
327
|
+
return {
|
|
328
|
+
ramp: bestRamp,
|
|
329
|
+
direction: mainDir,
|
|
330
|
+
};
|
|
335
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
|
},
|
|
@@ -13,8 +13,10 @@ import {
|
|
|
13
13
|
UNIVERSAL_CONTRAST_TOPUP,
|
|
14
14
|
WHITE_TEXT_CONTRAST_MARGIN,
|
|
15
15
|
ACCENT_SCALE_BASE_LIGHTNESS_THRESHOLDS,
|
|
16
|
+
MAX_BISECTION_ITERATIONS,
|
|
17
|
+
CONTRAST_EPSILON,
|
|
16
18
|
} from './constants';
|
|
17
|
-
import type { Ramp,
|
|
19
|
+
import type { Ramp, RampConfig, RampDirection } from './types';
|
|
18
20
|
import { getContrast } from './color-utils';
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -28,7 +30,7 @@ export const clampToGamut = ( c: ColorTypes ) =>
|
|
|
28
30
|
* Build a dependency graph from the steps configuration
|
|
29
31
|
* @param config - The steps configuration object
|
|
30
32
|
*/
|
|
31
|
-
function buildDependencyGraph( config:
|
|
33
|
+
function buildDependencyGraph( config: RampConfig ): {
|
|
32
34
|
dependencies: Map< keyof Ramp, ( keyof Ramp | 'seed' )[] >;
|
|
33
35
|
dependents: Map< keyof Ramp | 'seed', ( keyof Ramp )[] >;
|
|
34
36
|
} {
|
|
@@ -66,9 +68,7 @@ function buildDependencyGraph( config: Record< keyof Ramp, RampStepConfig > ): {
|
|
|
66
68
|
* Topologically sort steps based on their dependencies
|
|
67
69
|
* @param config - The steps configuration object
|
|
68
70
|
*/
|
|
69
|
-
export function sortByDependency(
|
|
70
|
-
config: Record< keyof Ramp, RampStepConfig >
|
|
71
|
-
): ( keyof Ramp )[] {
|
|
71
|
+
export function sortByDependency( config: RampConfig ): ( keyof Ramp )[] {
|
|
72
72
|
const { dependents } = buildDependencyGraph( config );
|
|
73
73
|
const result: ( keyof Ramp )[] = [];
|
|
74
74
|
const visited = new Set< keyof Ramp | 'seed' >();
|
|
@@ -108,6 +108,37 @@ export function sortByDependency(
|
|
|
108
108
|
|
|
109
109
|
return result;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Return minimal set of steps that are needed to calculate `stepName` from the seed.
|
|
113
|
+
* @param stepName Name of the step.
|
|
114
|
+
* @param config Configuration of the ramp.
|
|
115
|
+
* @return Array of steps that `stepName` depends on.
|
|
116
|
+
*/
|
|
117
|
+
export function stepsForStep(
|
|
118
|
+
stepName: keyof Ramp,
|
|
119
|
+
config: RampConfig
|
|
120
|
+
): ( keyof Ramp )[] {
|
|
121
|
+
const result = new Set< keyof Ramp >();
|
|
122
|
+
function visit( step: keyof Ramp | 'seed' ) {
|
|
123
|
+
if ( step === 'seed' || result.has( step ) ) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const stepConfig = config[ step ];
|
|
128
|
+
if ( ! stepConfig ) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
visit( stepConfig.contrast.reference );
|
|
133
|
+
if ( stepConfig.sameAsIfPossible ) {
|
|
134
|
+
visit( stepConfig.sameAsIfPossible );
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
result.add( step );
|
|
138
|
+
}
|
|
139
|
+
visit( stepName );
|
|
140
|
+
return Array.from( result );
|
|
141
|
+
}
|
|
111
142
|
|
|
112
143
|
/**
|
|
113
144
|
* Finds out whether a lighter or a darker foreground color achieves a better
|
|
@@ -158,3 +189,76 @@ export function clampAccentScaleReferenceLightness(
|
|
|
158
189
|
const thresholds = ACCENT_SCALE_BASE_LIGHTNESS_THRESHOLDS[ direction ];
|
|
159
190
|
return Math.max( thresholds.min, Math.min( thresholds.max, rawLightness ) );
|
|
160
191
|
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Find the value of of `L` (luminance) that produces a `C` (color) that has a
|
|
195
|
+
* `value` (contrast delta) equal to zero.
|
|
196
|
+
* @param calculateC Calculate `C` from a given `L`.
|
|
197
|
+
* @param calculateValue Calculate value (delta) for a given `C`.
|
|
198
|
+
* @param initLowerL Initial lower value of `L`.
|
|
199
|
+
* @param initLowerValue Initial lower delta (negative).
|
|
200
|
+
* @param initUpperL Initial upper value of `L`.
|
|
201
|
+
* @param initUpperValue Initial upper delta (positive).
|
|
202
|
+
* @return Resulting value of type `C`.
|
|
203
|
+
*/
|
|
204
|
+
export function solveWithBisect< C >(
|
|
205
|
+
calculateC: ( l: number ) => C,
|
|
206
|
+
calculateValue: ( t: C ) => number,
|
|
207
|
+
initLowerL: number,
|
|
208
|
+
initLowerValue: number,
|
|
209
|
+
initUpperL: number,
|
|
210
|
+
initUpperValue: number
|
|
211
|
+
): C {
|
|
212
|
+
let lowerL = initLowerL;
|
|
213
|
+
let lowerValue = initLowerValue;
|
|
214
|
+
let lowerReplaced = false;
|
|
215
|
+
|
|
216
|
+
let upperL = initUpperL;
|
|
217
|
+
let upperValue = initUpperValue;
|
|
218
|
+
let upperReplaced = false;
|
|
219
|
+
|
|
220
|
+
let bestC: C;
|
|
221
|
+
let bestValue: number;
|
|
222
|
+
let iterations = 0;
|
|
223
|
+
|
|
224
|
+
while ( true ) {
|
|
225
|
+
iterations++;
|
|
226
|
+
|
|
227
|
+
// Linear interpolation: find the point where a line would cross the zero axis.
|
|
228
|
+
const newL =
|
|
229
|
+
( lowerL * upperValue - upperL * lowerValue ) /
|
|
230
|
+
( upperValue - lowerValue );
|
|
231
|
+
|
|
232
|
+
bestC = calculateC( newL );
|
|
233
|
+
bestValue = calculateValue( bestC );
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
Math.abs( bestValue ) <= CONTRAST_EPSILON ||
|
|
237
|
+
iterations >= MAX_BISECTION_ITERATIONS
|
|
238
|
+
) {
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Update the lower/upper bracket values. When only one side is repeatedly updated,
|
|
243
|
+
// apply so-called "Illinois trick" for faster convergence: halve the opposite value.
|
|
244
|
+
if ( bestValue <= 0 ) {
|
|
245
|
+
lowerL = newL;
|
|
246
|
+
lowerValue = bestValue;
|
|
247
|
+
if ( lowerReplaced ) {
|
|
248
|
+
upperValue /= 2;
|
|
249
|
+
}
|
|
250
|
+
lowerReplaced = true;
|
|
251
|
+
upperReplaced = false;
|
|
252
|
+
} else {
|
|
253
|
+
upperL = newL;
|
|
254
|
+
upperValue = bestValue;
|
|
255
|
+
if ( upperReplaced ) {
|
|
256
|
+
lowerValue /= 2;
|
|
257
|
+
}
|
|
258
|
+
upperReplaced = true;
|
|
259
|
+
lowerReplaced = false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return bestC;
|
|
264
|
+
}
|