1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | @use 'sass:color';
|
24 | @use 'sass:list';
|
25 | @use 'sass:map';
|
26 | @use 'sass:math';
|
27 | @use 'sass:meta';
|
28 | @use 'sass:string';
|
29 | @use './custom-properties';
|
30 | @use './keys';
|
31 |
|
32 | @function _linear-channel-value($channel-value) {
|
33 | $normalized-channel-value: math.div($channel-value, 255);
|
34 | @if $normalized-channel-value < 0.03928 {
|
35 | @return math.div($normalized-channel-value, 12.92);
|
36 | }
|
37 |
|
38 | @return math.pow(math.div($normalized-channel-value + 0.055, 1.055), 2.4);
|
39 | }
|
40 |
|
41 |
|
42 |
|
43 | @function luminance($color) {
|
44 | $red: _linear-channel-value(color.red($color));
|
45 | $green: _linear-channel-value(color.green($color));
|
46 | $blue: _linear-channel-value(color.blue($color));
|
47 |
|
48 | @return 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue;
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 | @function contrast($back, $front) {
|
54 | $backLum: luminance($back) + 0.05;
|
55 | $foreLum: luminance($front) + 0.05;
|
56 |
|
57 | @return math.div(math.max($backLum, $foreLum), math.min($backLum, $foreLum));
|
58 | }
|
59 |
|
60 |
|
61 | @function tone($color) {
|
62 | @if $color == 'dark' or $color == 'light' {
|
63 | @return $color;
|
64 | }
|
65 |
|
66 | @if meta.type-of($color) != 'color' {
|
67 | @warn '#{$color} is not a color. Falling back to "dark" tone.';
|
68 | @return 'dark';
|
69 | }
|
70 |
|
71 | $minimumContrast: 3.1;
|
72 |
|
73 | $lightContrast: contrast($color, white);
|
74 | $darkContrast: contrast($color, rgba(black, 0.87));
|
75 |
|
76 | @if ($lightContrast < $minimumContrast) and ($darkContrast > $lightContrast) {
|
77 | @return 'light';
|
78 | } @else {
|
79 | @return 'dark';
|
80 | }
|
81 | }
|
82 |
|
83 |
|
84 |
|
85 | @function contrast-tone($color) {
|
86 | @return if(tone($color) == 'dark', 'light', 'dark');
|
87 | }
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | @function color-hash($color) {
|
100 | @if custom-properties.is-custom-prop($color) {
|
101 | $color-value: custom-properties.get-fallback($color);
|
102 |
|
103 | @if (custom-properties.is-custom-prop-string($color-value)) {
|
104 | $varEndIndex: if(
|
105 | string.index($color-value, ', '),
|
106 | string.index($color-value, ', ') - 1,
|
107 | -2
|
108 | );
|
109 | @return string.slice($color-value, 5, $varEndIndex);
|
110 | }
|
111 |
|
112 | @if (meta.type-of($color-value) == 'color') {
|
113 | @return _get-hex-string($color-value);
|
114 | }
|
115 |
|
116 | @return custom-properties.get-varname($color);
|
117 | }
|
118 |
|
119 | @if meta.type-of($color) == 'string' {
|
120 | @return $color;
|
121 | }
|
122 |
|
123 | @return _get-hex-string($color);
|
124 | }
|
125 |
|
126 | @function _get-hex-string($color) {
|
127 | @return string.slice(color.ie-hex-str($color), 2);
|
128 | }
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | $primary: #6200ee !default;
|
137 | $on-primary: if(contrast-tone($primary) == 'dark', #000, #fff) !default;
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | $accent: #018786 !default;
|
143 | $secondary: $accent !default;
|
144 | $on-secondary: if(contrast-tone($secondary) == 'dark', #000, #fff) !default;
|
145 | $background: #fff !default;
|
146 |
|
147 | $surface: #fff !default;
|
148 | $on-surface: if(contrast-tone($surface) == 'dark', #000, #fff) !default;
|
149 |
|
150 | $error: #b00020 !default;
|
151 | $on-error: if(contrast-tone($error) == 'dark', #000, #fff) !default;
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | $text-colors: (
|
158 | dark: (
|
159 | primary: rgba(black, 0.87),
|
160 | secondary: rgba(black, 0.54),
|
161 | hint: rgba(black, 0.38),
|
162 | disabled: rgba(black, 0.38),
|
163 | icon: rgba(black, 0.38),
|
164 | ),
|
165 | light: (
|
166 | primary: white,
|
167 | secondary: rgba(white, 0.7),
|
168 | hint: rgba(white, 0.5),
|
169 | disabled: rgba(white, 0.5),
|
170 | icon: rgba(white, 0.5),
|
171 | ),
|
172 | ) !default;
|
173 |
|
174 | $text-emphasis: (
|
175 | high: 0.87,
|
176 | medium: 0.6,
|
177 | disabled: 0.38,
|
178 | ) !default;
|
179 |
|
180 | @function ink-color-for-fill_($text-style, $fill-color) {
|
181 | $contrast-tone: contrast-tone($fill-color);
|
182 |
|
183 | @return map.get(map.get($text-colors, $contrast-tone), $text-style);
|
184 | }
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | $property-values: (
|
193 | primary: $primary,
|
194 | secondary: $secondary,
|
195 | background: $background,
|
196 | surface: $surface,
|
197 | error: $error,
|
198 | on-primary: $on-primary,
|
199 | on-secondary: $on-secondary,
|
200 | on-surface: $on-surface,
|
201 | on-error: $on-error,
|
202 | text-primary-on-background: ink-color-for-fill_(primary, $background),
|
203 | text-secondary-on-background: ink-color-for-fill_(secondary, $background),
|
204 | text-hint-on-background: ink-color-for-fill_(hint, $background),
|
205 | text-disabled-on-background: ink-color-for-fill_(disabled, $background),
|
206 | text-icon-on-background: ink-color-for-fill_(icon, $background),
|
207 | text-primary-on-light: ink-color-for-fill_(primary, light),
|
208 | text-secondary-on-light: ink-color-for-fill_(secondary, light),
|
209 | text-hint-on-light: ink-color-for-fill_(hint, light),
|
210 | text-disabled-on-light: ink-color-for-fill_(disabled, light),
|
211 | text-icon-on-light: ink-color-for-fill_(icon, light),
|
212 | text-primary-on-dark: ink-color-for-fill_(primary, dark),
|
213 | text-secondary-on-dark: ink-color-for-fill_(secondary, dark),
|
214 | text-hint-on-dark: ink-color-for-fill_(hint, dark),
|
215 | text-disabled-on-dark: ink-color-for-fill_(disabled, dark),
|
216 | text-icon-on-dark: ink-color-for-fill_(icon, dark),
|
217 | ) !default;
|
218 |
|
219 | @include keys.set-values(
|
220 | $property-values,
|
221 | $options: (custom-property-prefix: theme)
|
222 | );
|
223 |
|
224 |
|
225 |
|
226 | $_property-values-copy: $property-values;
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 | @function deprecated-get-global-theme-key-value-if-changed($key) {
|
240 |
|
241 |
|
242 | $current-global-value: map.get($property-values, $key);
|
243 | $configured-global-value: map.get($_property-values-copy, $key);
|
244 | @if $current-global-value != $configured-global-value {
|
245 |
|
246 |
|
247 | @return (value: $current-global-value, changed: true);
|
248 | }
|
249 |
|
250 | @return (changed: false);
|
251 | }
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 | @function prop-value($style) {
|
269 | @if custom-properties.is-custom-prop($style) {
|
270 | @return custom-properties.get-fallback($style);
|
271 | }
|
272 |
|
273 | @if is-valid-theme-prop-value_($style) {
|
274 | @return $style;
|
275 | }
|
276 |
|
277 | @if is-theme-key($style) {
|
278 |
|
279 |
|
280 | $result: deprecated-get-global-theme-key-value-if-changed($style);
|
281 | @if map.get($result, changed) {
|
282 | @return map.get($result, value);
|
283 | }
|
284 | }
|
285 |
|
286 | @return keys.resolve($style);
|
287 | }
|
288 |
|
289 |
|
290 | @function accessible-ink-color($fill-color, $text-style: primary) {
|
291 | $fill-color-value: prop-value($fill-color);
|
292 | $color-map-for-tone: map.get($text-colors, contrast-tone($fill-color-value));
|
293 |
|
294 | @if not map.has-key($color-map-for-tone, $text-style) {
|
295 | @error "Invalid $text-style: '#{$text-style}'. Choose one of: #{map.keys($color-map-for-tone)}";
|
296 | }
|
297 |
|
298 | @return map.get($color-map-for-tone, $text-style);
|
299 | }
|
300 |
|
301 |
|
302 | @function is-valid-theme-prop-value_($style) {
|
303 | @return meta.type-of($style) == 'color' or $style == 'currentColor' or
|
304 | str_slice($style, 1, 4) == 'var(' or $style == 'inherit' or $style ==
|
305 | 'transparent' or
|
306 | // NOTE: `GrayText` is deprecated, but is the only feasible way to convey the
|
307 | // correct high-contrast mode colors in alignment with Windows system colors.
|
308 | $style == 'GrayText';
|
309 | }
|
310 |
|
311 | @function text-emphasis($emphasis) {
|
312 | @return map.get($text-emphasis, $emphasis);
|
313 | }
|
314 |
|
315 | @function is-theme-key($style) {
|
316 | @return map.has-key($property-values, $style);
|
317 | }
|
318 |
|
319 | @function get-theme-keys() {
|
320 | @return map.keys($property-values);
|
321 | }
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 | @function get-custom-property($color) {
|
337 | $is-tokens-custom-prop: meta.type-of($color) == 'string' and
|
338 | string.index($color, '--') != null;
|
339 | @if custom-properties.is-custom-prop($color) or
|
340 | $is-tokens-custom-prop or
|
341 | is-valid-theme-prop-value_($color)
|
342 | {
|
343 | @return $color;
|
344 | } @else if is-theme-key($color) {
|
345 | $custom-prop: keys.create-custom-property($color);
|
346 |
|
347 |
|
348 |
|
349 | $result: deprecated-get-global-theme-key-value-if-changed($color);
|
350 | @if map.get($result, changed) {
|
351 | $custom-prop: custom-properties.set-fallback(
|
352 | $custom-prop,
|
353 | map.get($result, value)
|
354 | );
|
355 | }
|
356 |
|
357 | @return $custom-prop;
|
358 | } @else {
|
359 | @error "Invalid theme property: '#{$color}'. Choose one of: #{get-theme-keys()}";
|
360 | }
|
361 | }
|