@wordpress/theme 0.1.1-next.2f1c7c01b.0 → 0.2.1-next.16d95556a.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 +5 -1
- package/bin/generate-primitive-tokens/index.ts +1 -1
- 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/index.js +5 -5
- package/build/color-ramps/index.js.map +2 -2
- 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 +154 -154
- package/build/color-ramps/lib/default-ramps.js.map +2 -2
- 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 +72 -64
- 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/types.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 +15 -27
- package/build/use-theme-provider-styles.js.map +3 -3
- package/build-module/color-ramps/index.js +5 -5
- package/build-module/color-ramps/index.js.map +2 -2
- 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 +154 -154
- package/build-module/color-ramps/lib/default-ramps.js.map +2 -2
- 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 +76 -66
- 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 +15 -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/types.d.ts +2 -4
- package/build-types/color-ramps/lib/types.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/color-ramps/stories/ramp-table.d.ts +2 -4
- package/build-types/color-ramps/stories/ramp-table.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/index.ts +5 -5
- package/src/color-ramps/lib/constants.ts +7 -5
- package/src/color-ramps/lib/default-ramps.ts +154 -154
- package/src/color-ramps/lib/find-color-with-constraints.ts +53 -77
- package/src/color-ramps/lib/index.ts +100 -100
- package/src/color-ramps/lib/ramp-configs.ts +3 -3
- package/src/color-ramps/lib/types.ts +2 -7
- package/src/color-ramps/lib/utils.ts +109 -5
- package/src/color-ramps/stories/index.story.tsx +4 -1
- package/src/color-ramps/stories/ramp-table.tsx +15 -26
- package/src/color-ramps/test/__snapshots__/index.test.ts.snap +16891 -1059
- package/src/color-ramps/test/index.test.ts +43 -16
- 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 +17 -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.
|
|
@@ -65,13 +67,11 @@ function calculateRamp( {
|
|
|
65
67
|
value: number;
|
|
66
68
|
};
|
|
67
69
|
} ) {
|
|
68
|
-
const rampResults = {} as Record<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let
|
|
73
|
-
let UNSATISFIED_DIRECTION: RampDirection = 'lighter';
|
|
74
|
-
let MAX_WEIGHTED_DEFICIT = 0;
|
|
70
|
+
const rampResults = {} as Record< keyof Ramp, string >;
|
|
71
|
+
let warnings: string[] | undefined;
|
|
72
|
+
let maxDeficit = -Infinity;
|
|
73
|
+
let maxDeficitDirection: RampDirection = 'lighter';
|
|
74
|
+
let maxDeficitStep;
|
|
75
75
|
|
|
76
76
|
// Keep track of the calculated colors, as they are going to be useful
|
|
77
77
|
// when other colors reference them.
|
|
@@ -85,8 +85,8 @@ function calculateRamp( {
|
|
|
85
85
|
taperChromaOptions,
|
|
86
86
|
sameAsIfPossible,
|
|
87
87
|
} = config[ stepName ];
|
|
88
|
-
const referenceColor = calculatedColors.get( contrast.reference );
|
|
89
88
|
|
|
89
|
+
const referenceColor = calculatedColors.get( contrast.reference );
|
|
90
90
|
if ( ! referenceColor ) {
|
|
91
91
|
throw new Error(
|
|
92
92
|
`Reference color for step ${ stepName } not found: ${ contrast.reference }`
|
|
@@ -96,23 +96,24 @@ function calculateRamp( {
|
|
|
96
96
|
// Check if we can reuse color from the `sameAsIfPossible` config option
|
|
97
97
|
if ( sameAsIfPossible ) {
|
|
98
98
|
const candidateColor = calculatedColors.get( sameAsIfPossible );
|
|
99
|
-
if ( candidateColor ) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
candidateColor
|
|
99
|
+
if ( ! candidateColor ) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Same-as color for step ${ stepName } not found: ${ sameAsIfPossible }`
|
|
103
102
|
);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const candidateContrast = getContrast(
|
|
106
|
+
referenceColor,
|
|
107
|
+
candidateColor
|
|
108
|
+
);
|
|
109
|
+
const adjustedTarget = adjustContrastTarget( contrast.target );
|
|
110
|
+
// If the candidate meets the contrast requirement, use it
|
|
111
|
+
if ( candidateContrast >= adjustedTarget ) {
|
|
112
|
+
// Store the reused color
|
|
113
|
+
calculatedColors.set( stepName, candidateColor );
|
|
114
|
+
rampResults[ stepName ] = getColorString( candidateColor );
|
|
115
|
+
|
|
116
|
+
continue; // Skip to next step
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -166,7 +167,6 @@ function calculateRamp( {
|
|
|
166
167
|
adjustedTarget,
|
|
167
168
|
computedDir,
|
|
168
169
|
{
|
|
169
|
-
strict: false,
|
|
170
170
|
lightnessConstraint,
|
|
171
171
|
taperChromaOptions,
|
|
172
172
|
}
|
|
@@ -174,41 +174,34 @@ function calculateRamp( {
|
|
|
174
174
|
|
|
175
175
|
// When the target contrast is not met, take note of it and use
|
|
176
176
|
// 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
|
-
}
|
|
177
|
+
if (
|
|
178
|
+
! contrast.ignoreWhenAdjustingSeed &&
|
|
179
|
+
searchResults.deficit &&
|
|
180
|
+
searchResults.deficit > maxDeficit
|
|
181
|
+
) {
|
|
182
|
+
maxDeficit = searchResults.deficit;
|
|
183
|
+
maxDeficitDirection = computedDir;
|
|
184
|
+
maxDeficitStep = stepName;
|
|
195
185
|
}
|
|
196
186
|
|
|
197
187
|
// Store calculated color for future dependencies
|
|
198
188
|
calculatedColors.set( stepName, searchResults.color );
|
|
199
189
|
|
|
200
190
|
// Add to results
|
|
201
|
-
rampResults[ stepName ] =
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
191
|
+
rampResults[ stepName ] = getColorString( searchResults.color );
|
|
192
|
+
|
|
193
|
+
if ( ! searchResults.reached && ! contrast.ignoreWhenAdjustingSeed ) {
|
|
194
|
+
warnings ??= [];
|
|
195
|
+
warnings.push( stepName );
|
|
196
|
+
}
|
|
206
197
|
}
|
|
207
198
|
|
|
208
199
|
return {
|
|
209
200
|
rampResults,
|
|
210
|
-
|
|
211
|
-
|
|
201
|
+
warnings,
|
|
202
|
+
maxDeficit,
|
|
203
|
+
maxDeficitDirection,
|
|
204
|
+
maxDeficitStep,
|
|
212
205
|
};
|
|
213
206
|
}
|
|
214
207
|
|
|
@@ -257,8 +250,10 @@ export function buildRamp(
|
|
|
257
250
|
// Calculate the ramp with the initial seed.
|
|
258
251
|
const {
|
|
259
252
|
rampResults,
|
|
260
|
-
|
|
261
|
-
|
|
253
|
+
warnings,
|
|
254
|
+
maxDeficit,
|
|
255
|
+
maxDeficitDirection,
|
|
256
|
+
maxDeficitStep,
|
|
262
257
|
} = calculateRamp( {
|
|
263
258
|
seed,
|
|
264
259
|
sortedSteps,
|
|
@@ -267,69 +262,74 @@ export function buildRamp(
|
|
|
267
262
|
oppDir,
|
|
268
263
|
pinLightness,
|
|
269
264
|
} );
|
|
270
|
-
const toReturn = {
|
|
271
|
-
ramp: rampResults,
|
|
272
|
-
direction: mainDir,
|
|
273
|
-
} as RampResult;
|
|
274
265
|
|
|
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
|
-
);
|
|
266
|
+
let bestRamp = rampResults;
|
|
299
267
|
|
|
268
|
+
if ( maxDeficit > CONTRAST_EPSILON && rescaleToFitContrastTargets ) {
|
|
269
|
+
const iterSteps = stepsForStep( maxDeficitStep!, config );
|
|
270
|
+
|
|
271
|
+
function getSeedForL( l: number ): ColorTypes {
|
|
272
|
+
return clampToGamut( set( clone( seed ), [ OKLCH, 'l' ], l ) );
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function getDeficitForSeed( s: ColorTypes ): number {
|
|
300
276
|
const iterationResults = calculateRamp( {
|
|
301
|
-
seed:
|
|
302
|
-
sortedSteps,
|
|
277
|
+
seed: s,
|
|
278
|
+
sortedSteps: iterSteps,
|
|
303
279
|
config,
|
|
304
280
|
mainDir,
|
|
305
281
|
oppDir,
|
|
306
282
|
pinLightness,
|
|
307
283
|
} );
|
|
308
284
|
|
|
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
|
-
}
|
|
285
|
+
// If the constraints start failing in the opposite direction to the original
|
|
286
|
+
// iteration's direction, that means we've moved too far away from the target.
|
|
287
|
+
// Don't use the `maxDeficit` value because it's not related to our search,
|
|
288
|
+
// and might even be positive, which would confuse the bisection algorithm.
|
|
289
|
+
return iterationResults.maxDeficitDirection === maxDeficitDirection
|
|
290
|
+
? iterationResults.maxDeficit
|
|
291
|
+
: -maxDeficit;
|
|
322
292
|
}
|
|
293
|
+
|
|
294
|
+
// For a scale with the "lighter" direction, the contrast can be improved
|
|
295
|
+
// by darkening the seed. For "darker" direction, by lightening the seed.
|
|
296
|
+
const lowerSeedL = maxDeficitDirection === 'lighter' ? 0 : 1;
|
|
297
|
+
const lowerDeficit = -maxDeficit;
|
|
298
|
+
const upperSeedL = get( seed, [ OKLCH, 'l' ] );
|
|
299
|
+
const upperDeficit = maxDeficit;
|
|
300
|
+
|
|
301
|
+
const bestSeed = solveWithBisect(
|
|
302
|
+
getSeedForL,
|
|
303
|
+
getDeficitForSeed,
|
|
304
|
+
lowerSeedL,
|
|
305
|
+
lowerDeficit,
|
|
306
|
+
upperSeedL,
|
|
307
|
+
upperDeficit
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Calculate the final ramp with adjusted seed.
|
|
311
|
+
bestRamp = calculateRamp( {
|
|
312
|
+
seed: bestSeed,
|
|
313
|
+
sortedSteps,
|
|
314
|
+
config,
|
|
315
|
+
mainDir,
|
|
316
|
+
oppDir,
|
|
317
|
+
pinLightness,
|
|
318
|
+
} ).rampResults;
|
|
323
319
|
}
|
|
324
320
|
|
|
325
321
|
// Swap surface1 and surface3 for darker ramps to maintain visual elevation hierarchy.
|
|
326
322
|
// This ensures surface1 appears "behind" surface2, and surface3 appears "in front",
|
|
327
323
|
// regardless of the ramp's main direction.
|
|
328
324
|
if ( mainDir === 'darker' ) {
|
|
329
|
-
const tmpSurface1 =
|
|
330
|
-
|
|
331
|
-
|
|
325
|
+
const tmpSurface1 = bestRamp.surface1;
|
|
326
|
+
bestRamp.surface1 = bestRamp.surface3;
|
|
327
|
+
bestRamp.surface3 = tmpSurface1;
|
|
332
328
|
}
|
|
333
329
|
|
|
334
|
-
return
|
|
330
|
+
return {
|
|
331
|
+
ramp: bestRamp,
|
|
332
|
+
warnings,
|
|
333
|
+
direction: mainDir,
|
|
334
|
+
};
|
|
335
335
|
}
|
|
@@ -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
|
},
|
|
@@ -79,12 +79,7 @@ export type RampStepConfig = {
|
|
|
79
79
|
export type RampConfig = Record< keyof Ramp, RampStepConfig >;
|
|
80
80
|
|
|
81
81
|
export type RampResult = {
|
|
82
|
-
ramp: Record<
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
color: string;
|
|
86
|
-
warning: boolean;
|
|
87
|
-
}
|
|
88
|
-
>;
|
|
82
|
+
ramp: Record< keyof Ramp, string >;
|
|
83
|
+
warnings?: string[];
|
|
89
84
|
direction: RampDirection;
|
|
90
85
|
};
|
|
@@ -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
|
+
}
|