@ksmv/ui-checks 0.21.1 → 0.22.1
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/CHANGELOG.md +12 -0
- package/README.md +3 -1
- package/dist/color.d.ts +2 -1
- package/dist/color.js +29 -1
- package/dist/contrast-contract.json +126 -2
- package/dist/rules/default.js +1 -1
- package/dist/rules/noLiteralColor.d.ts +3 -1
- package/dist/rules/noLiteralColor.js +14 -14
- package/dist/rules/tokenContrast.js +39 -11
- package/dist/theme-contract.json +98 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.22.1 - 2026-10-17
|
|
4
|
+
|
|
5
|
+
- **Contratos de tema 3.4.0 e de contraste 12.0.0** empacotados (#394). O de tema ganha o derivado opcional `--ui-surface-inverse-end`, a ponta do degradê da superfície inversa; o de contraste ganha 20 pares: o texto apagado sobre a superfície inversa (`inverse-muted-text`) e cada par de um derivado `-on-inverse` sobre a superfície medido também sobre a ponta (contexto `-end`). Nenhum par sai. Um tema que não declara nenhum dos dois tokens passa como antes.
|
|
6
|
+
- **A regra `tokens/contrast` mede um par na ponta de um degradê ao longo dele:** um token `<início>-end`, quando o início também é um token, é a ponta de um degradê, e o par é medido em 17 pontos, em sRGB e em Oklab (os espaços em que o navegador interpola um degradê que não declara espaço), com o alfa pré-multiplicado, ficando com o pior. Um texto que passa nas duas pontas pode reprovar no meio.
|
|
7
|
+
- **Quem usa `computedEvidence` precisa capturar a evidência de novo:** os dois contratos mudaram.
|
|
8
|
+
|
|
9
|
+
## 0.22.0 - 2026-10-17
|
|
10
|
+
|
|
11
|
+
- **Contratos de tema 3.3.0 e de contraste 11.0.0** empacotados. O de tema ganha 89 derivados opcionais e 5 tokens de forma; o de contraste ganha 104 pares. Nenhum par sai. Os pares do pressionado contra as superfícies e os da linha com tom são obrigações novas, e um tema com o primário claro demais, ou com um fundo de tom escuro, reprova. Atualize o `@ksmv/ui-react` junto (ver o CHANGELOG dele).
|
|
12
|
+
- **A regra `css/no-literal-color` aceita o padrão literal do contrato**, só como fallback do próprio token: `var(--ui-x, <cor>)` passa quando a cor é, texto por texto, o padrão que o contrato dá à `--ui-x`. Qualquer outra cor literal continua recusada.
|
|
13
|
+
- **Quem usa `computedEvidence` precisa capturar a evidência de novo:** os dois contratos mudaram.
|
|
14
|
+
|
|
3
15
|
## 0.21.1 - 2026-10-15
|
|
4
16
|
|
|
5
17
|
- Sem mudança no verificador: a versão acompanha a do `@ksmv/ui-react` 0.21.1. Os contratos continuam tema `3.2.0` e contraste `10.0.0`, e a evidência calculada da 0.21.0 continua valendo.
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @ksmv/ui-checks
|
|
2
2
|
|
|
3
|
-
Verificações de qualidade para o contrato semântico `--ui-*` de `@ksmv/ui-react`: completude dos temas, contraste dos pares do contrato (ações, estados, campos, controles e, desde o contrato de contraste `3.0.0`, o texto base sobre as superfícies e, desde o `4.0.0`, a cor de link e, desde o `5.0.0`, a borda das ações e, desde o `6.0.0`, as variantes de ação da 0.17.0 e o botão de texto e, desde o `7.0.0`, os botões da casa da 0.18.1 e o hover por tom do botão de texto e, desde o `7.1.0`, o texto de hover dos botões suaves e do contorno de aviso e, desde o `9.0.0`, o texto, o limite de controle, o link, o botão de texto e o fantasma sobre a superfície inversa, a paleta categórica, a descrição do diálogo, o giro contra a superfície, o aviso e o rótulo de seção, o item de menu e o cartão de rádio, o cabeçalho da tabela e o grupo de alternância, e o selo cheio, a bolinha sobre a superfície inversa e a barra e, desde o `10.0.0`, o fundo do botão de ícone contornado, a cor da ajuda e a caixa de seleção), prefixo dos tokens, cores literais fora dos arquivos de tema e evidência calculada vinculada às fontes.
|
|
3
|
+
Verificações de qualidade para o contrato semântico `--ui-*` de `@ksmv/ui-react`: completude dos temas, contraste dos pares do contrato (ações, estados, campos, controles e, desde o contrato de contraste `3.0.0`, o texto base sobre as superfícies e, desde o `4.0.0`, a cor de link e, desde o `5.0.0`, a borda das ações e, desde o `6.0.0`, as variantes de ação da 0.17.0 e o botão de texto e, desde o `7.0.0`, os botões da casa da 0.18.1 e o hover por tom do botão de texto e, desde o `7.1.0`, o texto de hover dos botões suaves e do contorno de aviso e, desde o `9.0.0`, o texto, o limite de controle, o link, o botão de texto e o fantasma sobre a superfície inversa, a paleta categórica, a descrição do diálogo, o giro contra a superfície, o aviso e o rótulo de seção, o item de menu e o cartão de rádio, o cabeçalho da tabela e o grupo de alternância, e o selo cheio, a bolinha sobre a superfície inversa e a barra e, desde o `10.0.0`, o fundo do botão de ícone contornado, a cor da ajuda e a caixa de seleção e, desde o `11.0.0`, o botão pressionado contra as superfícies, a linha da lista com tom, o selo com marca à frente, o cabeçalho de página e a métrica, os passos e a navegação lateral, a conversa e a mídia, a escolha de horário e de dia, a grade de calendário e o quadro e, desde o `12.0.0`, o texto apagado sobre a superfície inversa e cada par da superfície inversa medido também sobre a ponta do degradê), prefixo dos tokens, cores literais fora dos arquivos de tema e evidência calculada vinculada às fontes.
|
|
4
|
+
|
|
5
|
+
A regra `css/no-literal-color` recusa cor literal fora dos temas configurados. Desde a 0.22.0, ela aceita uma exceção: o fallback de `var(--ui-x, <cor>)`, quando a cor é, texto por texto, o padrão literal que o contrato de tema dá à própria `--ui-x` (como `var(--ui-media-overlay-bg, rgb(0 0 0 / 70%))`). Quem escreve esse fallback implementa o contrato. Outra cor, ainda que equivalente, uma cor fora do `var()` ou o fallback de um token sem padrão literal continuam reprovados.
|
|
4
6
|
|
|
5
7
|
## Antes de depender deste pacote
|
|
6
8
|
|
package/dist/color.d.ts
CHANGED
|
@@ -14,9 +14,10 @@ export declare function parsedColor(value: string): {
|
|
|
14
14
|
mode: "rgb";
|
|
15
15
|
} | undefined;
|
|
16
16
|
export declare function containsUnsupportedColorFunction(value: string): boolean;
|
|
17
|
-
export declare function containsLiteralColor(value: string): boolean;
|
|
17
|
+
export declare function containsLiteralColor(value: string, contractDefaults?: Readonly<Record<string, string>>): boolean;
|
|
18
18
|
export declare function referencedTokens(value: string): string[];
|
|
19
19
|
export declare function unresolvedTokenReferences(value: string, tokens: ReadonlyMap<string, string>, stack?: ReadonlySet<string>): string[];
|
|
20
20
|
export declare function resolveColor(value: string | undefined, tokens: ReadonlyMap<string, string>, stack?: ReadonlySet<string>): ColorResolution;
|
|
21
21
|
export declare function composite(foreground: RgbColor, background: RgbColor): RgbColor;
|
|
22
|
+
export declare function mix(start: RgbColor, end: RgbColor, at: number): RgbColor[];
|
|
22
23
|
export declare function contrastRatio(foreground: RgbColor, background: RgbColor): number | undefined;
|
package/dist/color.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { converter, parse } from "culori";
|
|
2
2
|
import valueParser from "postcss-value-parser";
|
|
3
3
|
const toRgb = converter("rgb");
|
|
4
|
+
const toOklab = converter("oklab");
|
|
4
5
|
const unsupportedFunctions = new Set(["color-mix", "light-dark"]);
|
|
5
6
|
const literalColorFunctions = new Set([
|
|
6
7
|
"color",
|
|
@@ -87,9 +88,21 @@ export function containsUnsupportedColorFunction(value) {
|
|
|
87
88
|
});
|
|
88
89
|
return unsupported;
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
// A literal color in a value. The fallback of `var(--ui-x, <literal>)` is not
|
|
92
|
+
// one when the theme contract gives --ui-x exactly that literal as its
|
|
93
|
+
// default: the stylesheet implements the contract (#276). Each var is judged
|
|
94
|
+
// on its own, and the text must be the contract's, not an equal color.
|
|
95
|
+
export function containsLiteralColor(value, contractDefaults = {}) {
|
|
91
96
|
let literal = false;
|
|
92
97
|
valueParser(value).walk((node) => {
|
|
98
|
+
if (node.type === "function" && node.value.toLowerCase() === "var") {
|
|
99
|
+
const { fallback, name } = splitVarArguments(node.nodes);
|
|
100
|
+
const variable = name.trim();
|
|
101
|
+
if (fallback !== undefined && Object.hasOwn(contractDefaults, variable) && fallback.trim() === contractDefaults[variable]) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
93
106
|
if (node.type === "function" && node.value.toLowerCase() !== "var") {
|
|
94
107
|
if (literalColorFunctions.has(node.value.toLowerCase()) ||
|
|
95
108
|
parsedColor(valueParser.stringify(node))) {
|
|
@@ -183,6 +196,21 @@ export function composite(foreground, background) {
|
|
|
183
196
|
r: foreground.r * alpha + background.r * (1 - alpha),
|
|
184
197
|
};
|
|
185
198
|
}
|
|
199
|
+
// A point of a gradient between two colors. The browser interpolates legacy
|
|
200
|
+
// colors (hex, rgb(), hsl()) in sRGB and the others, as oklch(), in Oklab
|
|
201
|
+
// (CSS Color 4, §12.1); the space is chosen by the syntax, so the check takes
|
|
202
|
+
// both points. Both interpolate with premultiplied alpha (§12.3): a channel
|
|
203
|
+
// is weighted by its color's alpha, so a transparent end lends no hue.
|
|
204
|
+
export function mix(start, end, at) {
|
|
205
|
+
const [startAlpha, endAlpha] = [start.alpha ?? 1, end.alpha ?? 1];
|
|
206
|
+
const alpha = startAlpha + (endAlpha - startAlpha) * at;
|
|
207
|
+
const between = (from, to) => alpha === 0 ? 0 : (from * startAlpha + (to * endAlpha - from * startAlpha) * at) / alpha;
|
|
208
|
+
const srgb = { alpha, b: between(start.b, end.b), g: between(start.g, end.g), mode: "rgb", r: between(start.r, end.r) };
|
|
209
|
+
const [from, to] = [toOklab(start), toOklab(end)];
|
|
210
|
+
const clamp = (value) => Math.min(1, Math.max(0, value));
|
|
211
|
+
const oklab = toRgb({ a: between(from.a, to.a), alpha, b: between(from.b, to.b), l: between(from.l, to.l), mode: "oklab" });
|
|
212
|
+
return [srgb, { alpha, b: clamp(oklab.b), g: clamp(oklab.g), mode: "rgb", r: clamp(oklab.r) }];
|
|
213
|
+
}
|
|
186
214
|
function luminance(color) {
|
|
187
215
|
const channel = (value) => value <= 0.04045
|
|
188
216
|
? value / 12.92
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contractVersion": "
|
|
2
|
+
"contractVersion": "12.0.0",
|
|
3
3
|
"schemaVersion": 2,
|
|
4
4
|
"pairs": [
|
|
5
5
|
{ "foreground": "--ui-action-primary-text", "background": "--ui-action-primary-bg", "minimum": 4.5, "context": "normal-text" },
|
|
@@ -213,6 +213,130 @@
|
|
|
213
213
|
{ "foreground": "--ui-field-description", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "field-description-subtle" },
|
|
214
214
|
{ "foreground": "--ui-checkbox-border", "background": "--ui-checkbox-bg", "over": "--ui-surface", "minimum": 3, "context": "checkbox-boundary" },
|
|
215
215
|
{ "foreground": "--ui-checkbox-border", "background": "--ui-surface", "minimum": 3, "context": "checkbox-boundary-surface" },
|
|
216
|
-
{ "foreground": "--ui-checkbox-mark", "background": "--ui-control-checked-bg", "minimum": 3, "context": "checkbox-mark" }
|
|
216
|
+
{ "foreground": "--ui-checkbox-mark", "background": "--ui-control-checked-bg", "minimum": 3, "context": "checkbox-mark" },
|
|
217
|
+
{ "foreground": "--ui-button-pressed-text", "background": "--ui-button-pressed-bg", "minimum": 4.5, "context": "button-pressed-text" },
|
|
218
|
+
{ "foreground": "--ui-button-pressed-text", "background": "--ui-button-pressed-bg-hover", "minimum": 4.5, "context": "button-pressed-text-hover" },
|
|
219
|
+
{ "foreground": "--ui-button-pressed-bg", "background": "--ui-surface", "minimum": 3, "context": "button-pressed-boundary" },
|
|
220
|
+
{ "foreground": "--ui-button-pressed-bg-hover", "background": "--ui-surface", "minimum": 3, "context": "button-pressed-boundary-hover" },
|
|
221
|
+
{ "foreground": "--ui-button-pressed-text-on-inverse", "background": "--ui-button-pressed-bg-on-inverse", "minimum": 4.5, "context": "button-pressed-text-on-inverse" },
|
|
222
|
+
{ "foreground": "--ui-button-pressed-text-on-inverse", "background": "--ui-button-pressed-bg-hover-on-inverse", "minimum": 4.5, "context": "button-pressed-text-hover-on-inverse" },
|
|
223
|
+
{ "foreground": "--ui-button-pressed-bg-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "button-pressed-boundary-on-inverse" },
|
|
224
|
+
{ "foreground": "--ui-button-pressed-bg-hover-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "button-pressed-boundary-hover-on-inverse" },
|
|
225
|
+
{ "foreground": "--ui-button-pressed-bg", "background": "--ui-surface-raised", "minimum": 3, "context": "button-pressed-boundary-raised" },
|
|
226
|
+
{ "foreground": "--ui-button-pressed-bg-hover", "background": "--ui-surface-raised", "minimum": 3, "context": "button-pressed-boundary-hover-raised" },
|
|
227
|
+
{ "foreground": "--ui-button-pressed-bg", "background": "--ui-surface-subtle", "minimum": 3, "context": "button-pressed-boundary-subtle" },
|
|
228
|
+
{ "foreground": "--ui-button-pressed-bg-hover", "background": "--ui-surface-subtle", "minimum": 3, "context": "button-pressed-boundary-hover-subtle" },
|
|
229
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-success-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-title-success" },
|
|
230
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-success-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-description-success" },
|
|
231
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-warning-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-title-warning" },
|
|
232
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-warning-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-description-warning" },
|
|
233
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-danger-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-title-danger" },
|
|
234
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-danger-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-description-danger" },
|
|
235
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-info-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-title-info" },
|
|
236
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-info-bg", "over": "--ui-surface", "minimum": 4.5, "context": "list-item-description-info" },
|
|
237
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-success-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-title-success-raised" },
|
|
238
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-success-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-description-success-raised" },
|
|
239
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-warning-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-title-warning-raised" },
|
|
240
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-warning-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-description-warning-raised" },
|
|
241
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-danger-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-title-danger-raised" },
|
|
242
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-danger-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-description-danger-raised" },
|
|
243
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-list-item-info-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-title-info-raised" },
|
|
244
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-list-item-info-bg", "over": "--ui-surface-raised", "minimum": 4.5, "context": "list-item-description-info-raised" },
|
|
245
|
+
{ "foreground": "--ui-badge-leading-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "badge-leading-text" },
|
|
246
|
+
{ "foreground": "--ui-page-header-eyebrow", "background": "--ui-surface", "minimum": 4.5, "context": "page-header-eyebrow" },
|
|
247
|
+
{ "foreground": "--ui-page-header-eyebrow", "background": "--ui-surface-raised", "minimum": 4.5, "context": "page-header-eyebrow-raised" },
|
|
248
|
+
{ "foreground": "--ui-page-header-eyebrow", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "page-header-eyebrow-subtle" },
|
|
249
|
+
{ "foreground": "--ui-stat-label", "background": "--ui-surface", "minimum": 4.5, "context": "stat-label" },
|
|
250
|
+
{ "foreground": "--ui-stat-label", "background": "--ui-surface-raised", "minimum": 4.5, "context": "stat-label-raised" },
|
|
251
|
+
{ "foreground": "--ui-stat-label", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "stat-label-subtle" },
|
|
252
|
+
{ "foreground": "--ui-steps-current-text", "background": "--ui-steps-current", "minimum": 4.5, "context": "steps-current-text" },
|
|
253
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-steps-current-bg", "over": "--ui-surface", "minimum": 4.5, "context": "steps-current-title" },
|
|
254
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-steps-current-bg", "over": "--ui-surface", "minimum": 4.5, "context": "steps-current-description" },
|
|
255
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-steps-complete-bg", "over": "--ui-surface", "minimum": 4.5, "context": "steps-complete-title" },
|
|
256
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-steps-complete-bg", "over": "--ui-surface", "minimum": 4.5, "context": "steps-complete-description" },
|
|
257
|
+
{ "foreground": "--ui-steps-track-empty", "background": "--ui-surface", "minimum": 3, "context": "steps-track-empty" },
|
|
258
|
+
{ "foreground": "--ui-side-nav-count-text", "background": "--ui-side-nav-count-bg", "over": "--ui-surface", "minimum": 4.5, "context": "side-nav-count-text" },
|
|
259
|
+
{ "foreground": "--ui-side-nav-count-text-on-inverse", "background": "--ui-side-nav-count-bg-on-inverse", "over": "--ui-surface-inverse", "minimum": 4.5, "context": "side-nav-count-text-on-inverse" },
|
|
260
|
+
{ "foreground": "--ui-steps-current", "background": "--ui-steps-current-bg", "over": "--ui-surface", "minimum": 3, "context": "steps-current-boundary" },
|
|
261
|
+
{ "foreground": "--ui-steps-current", "background": "--ui-surface", "minimum": 3, "context": "steps-current-border" },
|
|
262
|
+
{ "foreground": "--ui-steps-current", "background": "--ui-surface", "minimum": 3, "context": "steps-complete-mark" },
|
|
263
|
+
{ "foreground": "--ui-steps-track-filled", "background": "--ui-surface", "minimum": 3, "context": "steps-track-filled" },
|
|
264
|
+
{ "foreground": "--ui-side-nav-count-dot", "background": "--ui-surface", "minimum": 3, "context": "side-nav-count-dot" },
|
|
265
|
+
{ "foreground": "--ui-message-incoming-text", "background": "--ui-message-incoming-bg", "over": "--ui-surface", "minimum": 4.5, "context": "message-incoming-text" },
|
|
266
|
+
{ "foreground": "--ui-message-incoming-meta", "background": "--ui-message-incoming-bg", "over": "--ui-surface", "minimum": 4.5, "context": "message-incoming-meta" },
|
|
267
|
+
{ "foreground": "--ui-message-incoming-quote-text", "background": "--ui-message-incoming-quote-bg", "over": "--ui-message-incoming-bg", "minimum": 4.5, "context": "message-incoming-quote" },
|
|
268
|
+
{ "foreground": "--ui-message-assistant-text", "background": "--ui-message-assistant-bg", "over": "--ui-surface", "minimum": 4.5, "context": "message-assistant-text" },
|
|
269
|
+
{ "foreground": "--ui-message-assistant-meta", "background": "--ui-message-assistant-bg", "over": "--ui-surface", "minimum": 4.5, "context": "message-assistant-meta" },
|
|
270
|
+
{ "foreground": "--ui-message-assistant-quote-text", "background": "--ui-message-assistant-quote-bg", "over": "--ui-message-assistant-bg", "minimum": 4.5, "context": "message-assistant-quote" },
|
|
271
|
+
{ "foreground": "--ui-message-outgoing-text", "background": "--ui-message-outgoing-bg", "over": "--ui-surface", "minimum": 4.5, "context": "message-outgoing-text" },
|
|
272
|
+
{ "foreground": "--ui-message-outgoing-meta", "background": "--ui-message-outgoing-bg", "over": "--ui-surface", "minimum": 4.5, "context": "message-outgoing-meta" },
|
|
273
|
+
{ "foreground": "--ui-message-outgoing-quote-text", "background": "--ui-message-outgoing-quote-bg", "over": "--ui-message-outgoing-bg", "minimum": 4.5, "context": "message-outgoing-quote" },
|
|
274
|
+
{ "foreground": "--ui-system-message-accent-text", "background": "--ui-system-message-accent-bg", "over": "--ui-surface", "minimum": 4.5, "context": "system-message-accent-title" },
|
|
275
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-system-message-accent-bg", "over": "--ui-surface", "minimum": 4.5, "context": "system-message-accent-body" },
|
|
276
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-system-message-accent-bg", "over": "--ui-surface", "minimum": 4.5, "context": "system-message-accent-time" },
|
|
277
|
+
{ "foreground": "--ui-system-message-danger-text", "background": "--ui-system-message-danger-bg", "over": "--ui-surface", "minimum": 4.5, "context": "system-message-danger-title" },
|
|
278
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-system-message-danger-bg", "over": "--ui-surface", "minimum": 4.5, "context": "system-message-danger-body" },
|
|
279
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-system-message-danger-bg", "over": "--ui-surface", "minimum": 4.5, "context": "system-message-danger-time" },
|
|
280
|
+
{ "foreground": "--ui-media-overlay-text", "background": "--ui-media-overlay-bg", "over": "--ui-surface", "minimum": 4.5, "context": "media-overlay-text" },
|
|
281
|
+
{ "foreground": "--ui-media-overlay-text", "background": "--ui-media-control-bg", "over": "--ui-media-viewer-base", "minimum": 3, "context": "media-control-icon" },
|
|
282
|
+
{ "foreground": "--ui-media-overlay-text", "background": "--ui-media-control-bg-hover", "over": "--ui-media-viewer-base", "minimum": 3, "context": "media-control-icon-hover" },
|
|
283
|
+
{ "foreground": "--ui-media-overlay-text", "background": "--ui-media-caption-bg", "over": "--ui-media-viewer-base", "minimum": 4.5, "context": "media-caption" },
|
|
284
|
+
{ "foreground": "--ui-media-strip-current", "background": "--ui-surface", "minimum": 3, "context": "media-strip-current" },
|
|
285
|
+
{ "foreground": "--ui-time-slot-available-text", "background": "--ui-time-slot-available-bg", "over": "--ui-surface", "minimum": 4.5, "context": "time-slot-available-text" },
|
|
286
|
+
{ "foreground": "--ui-time-slot-available-text-hover", "background": "--ui-time-slot-available-bg-hover", "over": "--ui-surface", "minimum": 4.5, "context": "time-slot-available-text-hover" },
|
|
287
|
+
{ "foreground": "--ui-time-slot-available-border", "background": "--ui-surface", "minimum": 3, "context": "time-slot-available-boundary" },
|
|
288
|
+
{ "foreground": "--ui-time-slot-busy-text", "background": "--ui-time-slot-busy-bg", "over": "--ui-surface", "minimum": 4.5, "context": "time-slot-busy-text" },
|
|
289
|
+
{ "foreground": "--ui-time-slot-busy-border", "background": "--ui-surface", "minimum": 3, "context": "time-slot-busy-boundary" },
|
|
290
|
+
{ "foreground": "--ui-time-slot-busy-selected-text", "background": "--ui-time-slot-busy-selected-bg", "over": "--ui-surface", "minimum": 4.5, "context": "time-slot-busy-selected-text" },
|
|
291
|
+
{ "foreground": "--ui-time-slot-busy-selected-bg", "background": "--ui-surface", "minimum": 3, "context": "time-slot-busy-selected-boundary" },
|
|
292
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "time-slot-outside-text" },
|
|
293
|
+
{ "foreground": "--ui-day-today-text", "background": "--ui-surface", "minimum": 4.5, "context": "day-today-text" },
|
|
294
|
+
{ "foreground": "--ui-day-today-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "day-today-text-hover" },
|
|
295
|
+
{ "foreground": "--ui-text-secondary", "background": "--ui-day-selected-bg", "over": "--ui-surface", "minimum": 4.5, "context": "day-selected-label" },
|
|
296
|
+
{ "foreground": "--ui-day-selected-border", "background": "--ui-day-selected-bg", "over": "--ui-surface", "minimum": 3, "context": "day-selected-boundary" },
|
|
297
|
+
{ "foreground": "--ui-day-selected-border", "background": "--ui-surface", "minimum": 3, "context": "day-selected-border" },
|
|
298
|
+
{ "foreground": "--ui-button-pressed-bg", "background": "--ui-day-selected-bg", "over": "--ui-surface", "minimum": 3, "context": "day-selected-circle" },
|
|
299
|
+
{ "foreground": "--ui-day-detail-available", "background": "--ui-surface", "minimum": 4.5, "context": "day-detail-available" },
|
|
300
|
+
{ "foreground": "--ui-day-detail-busy", "background": "--ui-surface", "minimum": 4.5, "context": "day-detail-busy" },
|
|
301
|
+
{ "foreground": "--ui-day-detail-available", "background": "--ui-day-selected-bg", "over": "--ui-surface", "minimum": 4.5, "context": "day-detail-available-selected" },
|
|
302
|
+
{ "foreground": "--ui-day-detail-busy", "background": "--ui-day-selected-bg", "over": "--ui-surface", "minimum": 4.5, "context": "day-detail-busy-selected" },
|
|
303
|
+
{ "foreground": "--ui-day-detail-available", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "day-detail-available-hover" },
|
|
304
|
+
{ "foreground": "--ui-day-detail-busy", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "day-detail-busy-hover" },
|
|
305
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-1-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-1" },
|
|
306
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-2-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-2" },
|
|
307
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-3-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-3" },
|
|
308
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-4-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-4" },
|
|
309
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-5-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-5" },
|
|
310
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-6-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-6" },
|
|
311
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-7-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-7" },
|
|
312
|
+
{ "foreground": "--ui-text-primary", "background": "--ui-category-8-bg", "over": "--ui-surface", "minimum": 4.5, "context": "calendar-event-title-8" },
|
|
313
|
+
{ "foreground": "--ui-calendar-selected-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "calendar-selected-text" },
|
|
314
|
+
{ "foreground": "--ui-calendar-free-text", "background": "--ui-surface", "minimum": 4.5, "context": "calendar-free-text" },
|
|
315
|
+
{ "foreground": "--ui-calendar-free-border", "background": "--ui-surface", "minimum": 3, "context": "calendar-free-boundary" },
|
|
316
|
+
{ "foreground": "--ui-board-count-text", "background": "--ui-board-count-bg", "over": "--ui-surface", "minimum": 4.5, "context": "board-count-text" },
|
|
317
|
+
{ "foreground": "--ui-board-card-border-hover", "background": "--ui-surface-subtle", "minimum": 3, "context": "board-card-border-hover" },
|
|
318
|
+
{ "foreground": "--ui-board-drop-border", "background": "--ui-surface-subtle", "minimum": 3, "context": "board-drop-border" },
|
|
319
|
+
{ "foreground": "--ui-board-drop-border", "background": "--ui-board-drop-bg", "over": "--ui-surface-subtle", "minimum": 3, "context": "board-drop-line" },
|
|
320
|
+
{ "foreground": "--ui-border-control", "background": "--ui-surface-subtle", "minimum": 3, "context": "board-placeholder-boundary" },
|
|
321
|
+
{ "foreground": "--ui-text-muted-on-inverse", "background": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-muted-text" },
|
|
322
|
+
{ "foreground": "--ui-text-primary-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-body-text-end" },
|
|
323
|
+
{ "foreground": "--ui-text-secondary-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-secondary-text-end" },
|
|
324
|
+
{ "foreground": "--ui-border-control-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "inverse-control-boundary-end" },
|
|
325
|
+
{ "foreground": "--ui-link-text-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-link-text-end" },
|
|
326
|
+
{ "foreground": "--ui-text-button-text-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-text-button-text-end" },
|
|
327
|
+
{ "foreground": "--ui-action-ghost-text-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-ghost-text-end" },
|
|
328
|
+
{ "foreground": "--ui-action-ghost-text-hover-on-inverse", "background": "--ui-action-ghost-bg-hover-on-inverse", "over": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-ghost-text-hover-end" },
|
|
329
|
+
{ "foreground": "--ui-action-ghost-text-hover-on-inverse", "background": "--ui-action-ghost-bg-active-on-inverse", "over": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-ghost-text-active-end" },
|
|
330
|
+
{ "foreground": "--ui-icon-button-ghost-text-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "inverse-icon-button-ghost-icon-end" },
|
|
331
|
+
{ "foreground": "--ui-icon-button-ghost-text-on-inverse", "background": "--ui-action-ghost-bg-hover-on-inverse", "over": "--ui-surface-inverse-end", "minimum": 3, "context": "inverse-icon-button-ghost-icon-hover-end" },
|
|
332
|
+
{ "foreground": "--ui-icon-button-ghost-text-on-inverse", "background": "--ui-action-ghost-bg-active-on-inverse", "over": "--ui-surface-inverse-end", "minimum": 3, "context": "inverse-icon-button-ghost-icon-active-end" },
|
|
333
|
+
{ "foreground": "--ui-status-success-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "status-indicator-success-on-inverse-end" },
|
|
334
|
+
{ "foreground": "--ui-status-warning-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "status-indicator-warning-on-inverse-end" },
|
|
335
|
+
{ "foreground": "--ui-status-danger-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "status-indicator-danger-on-inverse-end" },
|
|
336
|
+
{ "foreground": "--ui-status-info-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "status-indicator-info-on-inverse-end" },
|
|
337
|
+
{ "foreground": "--ui-button-pressed-bg-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "button-pressed-boundary-on-inverse-end" },
|
|
338
|
+
{ "foreground": "--ui-button-pressed-bg-hover-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 3, "context": "button-pressed-boundary-hover-on-inverse-end" },
|
|
339
|
+
{ "foreground": "--ui-side-nav-count-text-on-inverse", "background": "--ui-side-nav-count-bg-on-inverse", "over": "--ui-surface-inverse-end", "minimum": 4.5, "context": "side-nav-count-text-on-inverse-end" },
|
|
340
|
+
{ "foreground": "--ui-text-muted-on-inverse", "background": "--ui-surface-inverse-end", "minimum": 4.5, "context": "inverse-muted-text-end" }
|
|
217
341
|
]
|
|
218
342
|
}
|
package/dist/rules/default.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function createDefaultRules({ components, computed, contract, manif
|
|
|
10
10
|
manifest ?? loadContrastManifest(),
|
|
11
11
|
]);
|
|
12
12
|
return [
|
|
13
|
-
createNoLiteralColorRule({ themes }),
|
|
13
|
+
createNoLiteralColorRule({ contractDefaults: resolvedContract.defaults, themes }),
|
|
14
14
|
createTokenPrefixRule(),
|
|
15
15
|
createTokenCompleteRule({ computed, contract: resolvedContract, themes }),
|
|
16
16
|
createThemeShapeRule({ contract: resolvedContract, themes }),
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ThemeConfig } from "../config.js";
|
|
2
2
|
import type { CheckRule } from "../scan.js";
|
|
3
3
|
export interface NoLiteralColorOptions {
|
|
4
|
+
/** The theme contract's literal defaults: `var(--ui-x, <its default>)` is the contract, not a literal. */
|
|
5
|
+
contractDefaults?: Readonly<Record<string, string>>;
|
|
4
6
|
themes: ThemeConfig[];
|
|
5
7
|
}
|
|
6
|
-
export declare function createNoLiteralColorRule({ themes, }: NoLiteralColorOptions): CheckRule;
|
|
8
|
+
export declare function createNoLiteralColorRule({ contractDefaults, themes, }: NoLiteralColorOptions): CheckRule;
|
|
@@ -26,12 +26,12 @@ function jsxStringValue(node) {
|
|
|
26
26
|
? node.expression.text
|
|
27
27
|
: undefined;
|
|
28
28
|
}
|
|
29
|
-
function classContainsLiteralColor(value) {
|
|
29
|
+
function classContainsLiteralColor(value, contractDefaults) {
|
|
30
30
|
return [...value.matchAll(/\[([^\]]+)\]/g)].some((match) => {
|
|
31
31
|
const candidate = match[1] ?? "";
|
|
32
32
|
const separator = candidate.indexOf(":");
|
|
33
33
|
if (separator <= 0) {
|
|
34
|
-
return containsLiteralColor(candidate);
|
|
34
|
+
return containsLiteralColor(candidate, contractDefaults);
|
|
35
35
|
}
|
|
36
36
|
const property = candidate.slice(0, separator);
|
|
37
37
|
const propertyValue = candidate.slice(separator + 1);
|
|
@@ -40,27 +40,27 @@ function classContainsLiteralColor(value) {
|
|
|
40
40
|
const utility = currentClass.split(":").at(-1) ?? "";
|
|
41
41
|
if (utility.endsWith("-")) {
|
|
42
42
|
if (property === "color" || property === "image") {
|
|
43
|
-
return containsLiteralColor(propertyValue);
|
|
43
|
+
return containsLiteralColor(propertyValue, contractDefaults);
|
|
44
44
|
}
|
|
45
|
-
return (/^(?:accent|bg|border|caret|decoration|divide|fill|from|outline|placeholder|ring|shadow|stroke|text|to|via)-$/.test(utility) && containsLiteralColor(propertyValue));
|
|
45
|
+
return (/^(?:accent|bg|border|caret|decoration|divide|fill|from|outline|placeholder|ring|shadow|stroke|text|to|via)-$/.test(utility) && containsLiteralColor(propertyValue, contractDefaults));
|
|
46
46
|
}
|
|
47
|
-
return (isColorBearingProperty(property) && containsLiteralColor(propertyValue));
|
|
47
|
+
return (isColorBearingProperty(property) && containsLiteralColor(propertyValue, contractDefaults));
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
export function createNoLiteralColorRule({ themes, }) {
|
|
50
|
+
export function createNoLiteralColorRule({ contractDefaults = {}, themes, }) {
|
|
51
51
|
return {
|
|
52
52
|
name: "css/no-literal-color",
|
|
53
53
|
check(file) {
|
|
54
54
|
const result = emptyResult();
|
|
55
55
|
file.cssRoot?.walkDecls((declaration) => {
|
|
56
56
|
if (isColorBearingProperty(declaration.prop) &&
|
|
57
|
-
containsLiteralColor(declaration.value) &&
|
|
57
|
+
containsLiteralColor(declaration.value, contractDefaults) &&
|
|
58
58
|
!isAllowedThemeDeclaration(file, declaration, themes)) {
|
|
59
59
|
result.violations.push({
|
|
60
60
|
anchor: cssDeclarationAnchor(declaration),
|
|
61
61
|
column: declaration.source?.start?.column ?? 1,
|
|
62
62
|
line: declaration.source?.start?.line ?? 1,
|
|
63
|
-
message: "Literal colors are allowed only in configured themes.",
|
|
63
|
+
message: "Literal colors are allowed only in configured themes, or as the theme contract's own default of the token they fall back from.",
|
|
64
64
|
nodeKind: "css-declaration",
|
|
65
65
|
path: file.path,
|
|
66
66
|
rule: "css/no-literal-color",
|
|
@@ -77,20 +77,20 @@ export function createNoLiteralColorRule({ themes, }) {
|
|
|
77
77
|
const isDirectColor = isColorBearingProperty(node.name.text);
|
|
78
78
|
const isLiteralClass = node.name.text === "className" &&
|
|
79
79
|
value !== undefined &&
|
|
80
|
-
classContainsLiteralColor(value);
|
|
80
|
+
classContainsLiteralColor(value, contractDefaults);
|
|
81
81
|
if (value !== undefined &&
|
|
82
|
-
((isDirectColor && containsLiteralColor(value)) || isLiteralClass)) {
|
|
82
|
+
((isDirectColor && containsLiteralColor(value, contractDefaults)) || isLiteralClass)) {
|
|
83
83
|
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
84
84
|
// For className, only the classes that carry the literal color
|
|
85
85
|
// identify the finding; the other classes may change freely.
|
|
86
86
|
const anchor = isLiteralClass && !isDirectColor
|
|
87
|
-
? `className: ${value.split(/\s+/).filter(classContainsLiteralColor).join(" ")}`
|
|
87
|
+
? `className: ${value.split(/\s+/).filter((name) => classContainsLiteralColor(name, contractDefaults)).join(" ")}`
|
|
88
88
|
: tsxNodeAnchor(node, sourceFile);
|
|
89
89
|
result.violations.push({
|
|
90
90
|
anchor,
|
|
91
91
|
column: position.character + 1,
|
|
92
92
|
line: position.line + 1,
|
|
93
|
-
message: "Literal colors are allowed only in configured themes.",
|
|
93
|
+
message: "Literal colors are allowed only in configured themes, or as the theme contract's own default of the token they fall back from.",
|
|
94
94
|
nodeKind: "tsx-color-attribute",
|
|
95
95
|
path: file.path,
|
|
96
96
|
rule: "css/no-literal-color",
|
|
@@ -112,13 +112,13 @@ export function createNoLiteralColorRule({ themes, }) {
|
|
|
112
112
|
const name = propertyName(property.name);
|
|
113
113
|
if (name !== undefined &&
|
|
114
114
|
isColorBearingProperty(name) &&
|
|
115
|
-
containsLiteralColor(property.initializer.text)) {
|
|
115
|
+
containsLiteralColor(property.initializer.text, contractDefaults)) {
|
|
116
116
|
const position = sourceFile.getLineAndCharacterOfPosition(property.initializer.getStart(sourceFile));
|
|
117
117
|
result.violations.push({
|
|
118
118
|
anchor: tsxNodeAnchor(property, sourceFile),
|
|
119
119
|
column: position.character + 1,
|
|
120
120
|
line: position.line + 1,
|
|
121
|
-
message: "Literal colors are allowed only in configured themes.",
|
|
121
|
+
message: "Literal colors are allowed only in configured themes, or as the theme contract's own default of the token they fall back from.",
|
|
122
122
|
nodeKind: "tsx-style-property",
|
|
123
123
|
path: file.path,
|
|
124
124
|
rule: "css/no-literal-color",
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import { isComputedPropertyName, isIdentifier, isJsxAttribute, isJsxExpression, isObjectLiteralExpression, isPropertyAssignment, isJsxSpreadAttribute, isStringLiteralLikeNode, } from "typescript/unstable/ast";
|
|
2
|
-
import { composite, contrastRatio, isColorBearingProperty, resolveColor, } from "../color.js";
|
|
2
|
+
import { composite, contrastRatio, isColorBearingProperty, mix, resolveColor, } from "../color.js";
|
|
3
3
|
import { parseContrastManifest, } from "../contracts.js";
|
|
4
4
|
import { createThemeCollector } from "./theme.js";
|
|
5
|
+
// The end of a gradient (#394): a token named <start>-end, when the start is
|
|
6
|
+
// a token too, paints a gradient from the start to itself. A pair on it is
|
|
7
|
+
// measured along the whole gradient, and not only at the end: between two
|
|
8
|
+
// colors that pass, the mix can fall below the minimum, as when the text's
|
|
9
|
+
// luminance lies between the two ends. Each point is taken in sRGB and in
|
|
10
|
+
// Oklab, the two spaces a browser may interpolate in.
|
|
11
|
+
const gradientSteps = 16;
|
|
12
|
+
function resolveAlong(token, tokens) {
|
|
13
|
+
const end = resolveColor(tokens.get(token), tokens);
|
|
14
|
+
const startName = token.endsWith("-end") ? token.slice(0, -"-end".length) : undefined;
|
|
15
|
+
if (!startName || !tokens.has(startName))
|
|
16
|
+
return [end];
|
|
17
|
+
const start = resolveColor(tokens.get(startName), tokens);
|
|
18
|
+
if (start.status !== "resolved" || end.status !== "resolved")
|
|
19
|
+
return [{ status: "uncovered" }];
|
|
20
|
+
return Array.from({ length: gradientSteps + 1 }, (_, step) => mix(start.color, end.color, step / gradientSteps))
|
|
21
|
+
.flat()
|
|
22
|
+
.map((color) => ({ color, status: "resolved" }));
|
|
23
|
+
}
|
|
5
24
|
export async function loadContrastManifest(url) {
|
|
6
25
|
const { readFile } = await import("node:fs/promises");
|
|
7
26
|
const packaged = url ?? new URL("../contrast-contract.json", import.meta.url);
|
|
@@ -144,17 +163,25 @@ export function createTokenContrastRule({ computed = {}, defaults = {}, derived
|
|
|
144
163
|
tokens.set(token, value);
|
|
145
164
|
}
|
|
146
165
|
for (const pair of manifest.pairs) {
|
|
147
|
-
let background = resolveColor(tokens.get(pair.background), tokens);
|
|
148
166
|
const foreground = resolveColor(tokens.get(pair.foreground), tokens);
|
|
149
|
-
//
|
|
150
|
-
// the pair
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
167
|
+
// Each point of the background, a gradient's included, and of the
|
|
168
|
+
// surface under it; the pair is as good as its worst point.
|
|
169
|
+
const backgrounds = [];
|
|
170
|
+
for (const painted of resolveAlong(pair.background, tokens)) {
|
|
171
|
+
// A translucent background is measured as painted over the surface
|
|
172
|
+
// the pair names (schema 2); without one it stays uncovered below.
|
|
173
|
+
if (pair.over && painted.status === "resolved" && (painted.color.alpha ?? 1) < 1) {
|
|
174
|
+
for (const over of resolveAlong(pair.over, tokens)) {
|
|
175
|
+
backgrounds.push(over.status === "resolved" && (over.color.alpha ?? 1) === 1
|
|
176
|
+
? { color: composite(painted.color, over.color), status: "resolved" }
|
|
177
|
+
: { status: "uncovered" });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
backgrounds.push(painted);
|
|
182
|
+
}
|
|
156
183
|
}
|
|
157
|
-
if (background.status === "uncovered" ||
|
|
184
|
+
if (backgrounds.some((background) => background.status === "uncovered") ||
|
|
158
185
|
foreground.status === "uncovered") {
|
|
159
186
|
result.uncovered.push({
|
|
160
187
|
...location,
|
|
@@ -165,7 +192,8 @@ export function createTokenContrastRule({ computed = {}, defaults = {}, derived
|
|
|
165
192
|
});
|
|
166
193
|
continue;
|
|
167
194
|
}
|
|
168
|
-
const
|
|
195
|
+
const ratios = backgrounds.map((background) => background.status === "resolved" ? contrastRatio(foreground.color, background.color) : undefined);
|
|
196
|
+
const ratio = ratios.includes(undefined) ? undefined : Math.min(...ratios);
|
|
169
197
|
if (ratio === undefined) {
|
|
170
198
|
result.uncovered.push({
|
|
171
199
|
...location,
|
package/dist/theme-contract.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contractVersion": "3.
|
|
2
|
+
"contractVersion": "3.4.0",
|
|
3
3
|
"schemaVersion": 2,
|
|
4
4
|
"prefix": "--ui-",
|
|
5
5
|
"required": [
|
|
@@ -231,7 +231,97 @@
|
|
|
231
231
|
"--ui-field-description": { "kind": "token", "token": "--ui-text-secondary" },
|
|
232
232
|
"--ui-checkbox-border": { "kind": "token", "token": "--ui-border-control" },
|
|
233
233
|
"--ui-checkbox-bg": { "kind": "token", "token": "--ui-surface" },
|
|
234
|
-
"--ui-checkbox-mark": { "kind": "token", "token": "--ui-control-checked-fg" }
|
|
234
|
+
"--ui-checkbox-mark": { "kind": "token", "token": "--ui-control-checked-fg" },
|
|
235
|
+
"--ui-button-pressed-bg": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
236
|
+
"--ui-button-pressed-bg-hover": { "kind": "token", "token": "--ui-action-primary-bg-hover" },
|
|
237
|
+
"--ui-button-pressed-text": { "kind": "token", "token": "--ui-action-primary-text" },
|
|
238
|
+
"--ui-button-pressed-bg-on-inverse": { "kind": "token", "token": "--ui-surface" },
|
|
239
|
+
"--ui-button-pressed-bg-hover-on-inverse": { "kind": "token", "token": "--ui-surface-subtle" },
|
|
240
|
+
"--ui-button-pressed-text-on-inverse": { "kind": "token", "token": "--ui-text-primary" },
|
|
241
|
+
"--ui-list-item-success-bg": { "kind": "token", "token": "--ui-status-success-bg" },
|
|
242
|
+
"--ui-list-item-success-border": { "kind": "token", "token": "--ui-status-success" },
|
|
243
|
+
"--ui-list-item-warning-bg": { "kind": "token", "token": "--ui-status-warning-bg" },
|
|
244
|
+
"--ui-list-item-warning-border": { "kind": "token", "token": "--ui-status-warning" },
|
|
245
|
+
"--ui-list-item-danger-bg": { "kind": "token", "token": "--ui-status-danger-bg" },
|
|
246
|
+
"--ui-list-item-danger-border": { "kind": "token", "token": "--ui-status-danger" },
|
|
247
|
+
"--ui-list-item-info-bg": { "kind": "token", "token": "--ui-status-info-bg" },
|
|
248
|
+
"--ui-list-item-info-border": { "kind": "token", "token": "--ui-status-info" },
|
|
249
|
+
"--ui-badge-leading-text": { "kind": "token", "token": "--ui-text-secondary" },
|
|
250
|
+
"--ui-page-header-eyebrow": { "kind": "token", "token": "--ui-text-secondary" },
|
|
251
|
+
"--ui-stat-label": { "kind": "token", "token": "--ui-text-secondary" },
|
|
252
|
+
"--ui-steps-current": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
253
|
+
"--ui-steps-current-text": { "kind": "token", "token": "--ui-action-primary-text" },
|
|
254
|
+
"--ui-steps-current-bg": { "kind": "token", "token": "--ui-surface" },
|
|
255
|
+
"--ui-steps-current-ring": { "kind": "token", "token": "--ui-border" },
|
|
256
|
+
"--ui-steps-complete-bg": { "kind": "token", "token": "--ui-surface-subtle" },
|
|
257
|
+
"--ui-steps-track-filled": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
258
|
+
"--ui-steps-track-empty": { "kind": "token", "token": "--ui-border-control" },
|
|
259
|
+
"--ui-side-nav-count-bg": { "kind": "token", "token": "--ui-surface-subtle" },
|
|
260
|
+
"--ui-side-nav-count-text": { "kind": "token", "token": "--ui-text-secondary" },
|
|
261
|
+
"--ui-side-nav-count-bg-on-inverse": { "kind": "literal", "value": "transparent" },
|
|
262
|
+
"--ui-side-nav-count-text-on-inverse": { "kind": "token", "token": "--ui-text-secondary-on-inverse" },
|
|
263
|
+
"--ui-side-nav-count-dot": { "kind": "token", "token": "--ui-status-info" },
|
|
264
|
+
"--ui-message-incoming-bg": { "kind": "token", "token": "--ui-surface" },
|
|
265
|
+
"--ui-message-incoming-border": { "kind": "token", "token": "--ui-border" },
|
|
266
|
+
"--ui-message-incoming-text": { "kind": "token", "token": "--ui-text-primary" },
|
|
267
|
+
"--ui-message-incoming-meta": { "kind": "token", "token": "--ui-text-secondary" },
|
|
268
|
+
"--ui-message-incoming-quote-bg": { "kind": "token", "token": "--ui-surface-subtle" },
|
|
269
|
+
"--ui-message-incoming-quote-bar": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
270
|
+
"--ui-message-incoming-quote-text": { "kind": "token", "token": "--ui-text-secondary" },
|
|
271
|
+
"--ui-message-assistant-bg": { "kind": "token", "token": "--ui-action-soft-accent-bg" },
|
|
272
|
+
"--ui-message-assistant-border": { "kind": "token", "token": "--ui-border" },
|
|
273
|
+
"--ui-message-assistant-text": { "kind": "token", "token": "--ui-text-primary" },
|
|
274
|
+
"--ui-message-assistant-meta": { "kind": "token", "token": "--ui-text-secondary" },
|
|
275
|
+
"--ui-message-assistant-quote-bg": { "kind": "token", "token": "--ui-surface-subtle" },
|
|
276
|
+
"--ui-message-assistant-quote-bar": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
277
|
+
"--ui-message-assistant-quote-text": { "kind": "token", "token": "--ui-text-secondary" },
|
|
278
|
+
"--ui-message-outgoing-bg": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
279
|
+
"--ui-message-outgoing-border": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
280
|
+
"--ui-message-outgoing-text": { "kind": "token", "token": "--ui-action-primary-text" },
|
|
281
|
+
"--ui-message-outgoing-meta": { "kind": "token", "token": "--ui-action-primary-text" },
|
|
282
|
+
"--ui-message-outgoing-quote-bg": { "kind": "literal", "value": "rgb(0 0 0 / 20%)" },
|
|
283
|
+
"--ui-message-outgoing-quote-bar": { "kind": "token", "token": "--ui-action-primary-text" },
|
|
284
|
+
"--ui-message-outgoing-quote-text": { "kind": "token", "token": "--ui-action-primary-text" },
|
|
285
|
+
"--ui-system-message-accent-bg": { "kind": "token", "token": "--ui-action-soft-accent-bg" },
|
|
286
|
+
"--ui-system-message-accent-border": { "kind": "token", "token": "--ui-border" },
|
|
287
|
+
"--ui-system-message-accent-text": { "kind": "token", "token": "--ui-text-primary" },
|
|
288
|
+
"--ui-system-message-danger-bg": { "kind": "token", "token": "--ui-status-danger-bg" },
|
|
289
|
+
"--ui-system-message-danger-border": { "kind": "token", "token": "--ui-status-danger" },
|
|
290
|
+
"--ui-system-message-danger-text": { "kind": "token", "token": "--ui-status-danger-text" },
|
|
291
|
+
"--ui-media-overlay-bg": { "kind": "literal", "value": "rgb(0 0 0 / 70%)" },
|
|
292
|
+
"--ui-media-overlay-text": { "kind": "literal", "value": "#ffffff" },
|
|
293
|
+
"--ui-media-control-bg": { "kind": "literal", "value": "rgb(255 255 255 / 20%)" },
|
|
294
|
+
"--ui-media-control-bg-hover": { "kind": "literal", "value": "rgb(255 255 255 / 30%)" },
|
|
295
|
+
"--ui-media-caption-bg": { "kind": "literal", "value": "rgb(0 0 0 / 50%)" },
|
|
296
|
+
"--ui-media-viewer-backdrop": { "kind": "literal", "value": "rgb(0 0 0 / 85%)" },
|
|
297
|
+
"--ui-media-viewer-base": { "kind": "literal", "value": "#262626" },
|
|
298
|
+
"--ui-media-strip-current": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
299
|
+
"--ui-time-slot-available-bg": { "kind": "token", "token": "--ui-action-soft-accent-bg" },
|
|
300
|
+
"--ui-time-slot-available-border": { "kind": "token", "token": "--ui-action-soft-accent-border" },
|
|
301
|
+
"--ui-time-slot-available-text": { "kind": "token", "token": "--ui-action-soft-accent-text" },
|
|
302
|
+
"--ui-time-slot-available-bg-hover": { "kind": "token", "token": "--ui-action-soft-accent-bg-hover" },
|
|
303
|
+
"--ui-time-slot-available-text-hover": { "kind": "token", "token": "--ui-action-soft-accent-text-hover" },
|
|
304
|
+
"--ui-time-slot-busy-bg": { "kind": "token", "token": "--ui-status-warning-bg" },
|
|
305
|
+
"--ui-time-slot-busy-border": { "kind": "token", "token": "--ui-status-warning" },
|
|
306
|
+
"--ui-time-slot-busy-text": { "kind": "token", "token": "--ui-status-warning-text" },
|
|
307
|
+
"--ui-time-slot-busy-selected-bg": { "kind": "token", "token": "--ui-action-warning-bg" },
|
|
308
|
+
"--ui-time-slot-busy-selected-text": { "kind": "token", "token": "--ui-action-warning-text" },
|
|
309
|
+
"--ui-time-slot-ring": { "kind": "token", "token": "--ui-border" },
|
|
310
|
+
"--ui-day-today-text": { "kind": "token", "token": "--ui-action-soft-accent-text" },
|
|
311
|
+
"--ui-day-selected-bg": { "kind": "token", "token": "--ui-action-soft-accent-bg" },
|
|
312
|
+
"--ui-day-selected-border": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
313
|
+
"--ui-day-detail-available": { "kind": "token", "token": "--ui-text-secondary" },
|
|
314
|
+
"--ui-day-detail-busy": { "kind": "token", "token": "--ui-text-secondary" },
|
|
315
|
+
"--ui-calendar-selected-text": { "kind": "token", "token": "--ui-text-primary" },
|
|
316
|
+
"--ui-calendar-conflict": { "kind": "token", "token": "--ui-status-danger" },
|
|
317
|
+
"--ui-calendar-free-border": { "kind": "token", "token": "--ui-action-soft-accent-border" },
|
|
318
|
+
"--ui-calendar-free-text": { "kind": "token", "token": "--ui-action-soft-accent-text" },
|
|
319
|
+
"--ui-board-count-bg": { "kind": "token", "token": "--ui-surface-subtle" },
|
|
320
|
+
"--ui-board-count-text": { "kind": "token", "token": "--ui-text-primary" },
|
|
321
|
+
"--ui-board-card-border-hover": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
322
|
+
"--ui-board-drop-bg": { "kind": "token", "token": "--ui-action-soft-accent-bg" },
|
|
323
|
+
"--ui-board-drop-border": { "kind": "token", "token": "--ui-action-primary-bg" },
|
|
324
|
+
"--ui-surface-inverse-end": { "kind": "token", "token": "--ui-surface-inverse" }
|
|
235
325
|
},
|
|
236
326
|
"shape": [
|
|
237
327
|
"--ui-font-body",
|
|
@@ -276,7 +366,12 @@
|
|
|
276
366
|
"--ui-font-weight-medium",
|
|
277
367
|
"--ui-font-weight-bold",
|
|
278
368
|
"--ui-alert-radius",
|
|
279
|
-
"--ui-alert-padding"
|
|
369
|
+
"--ui-alert-padding",
|
|
370
|
+
"--ui-control-height-xs",
|
|
371
|
+
"--ui-radius-xs",
|
|
372
|
+
"--ui-list-item-radius",
|
|
373
|
+
"--ui-font-size-2xl",
|
|
374
|
+
"--ui-font-size-3xl"
|
|
280
375
|
],
|
|
281
376
|
"brandComposition": [
|
|
282
377
|
"--ui-brand-primary",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ksmv/ui-checks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "Static and computed quality checks for semantic UI token contracts.",
|
|
5
5
|
"keywords": ["accessibility", "design-tokens", "quality", "static-analysis"],
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": { "@types/node": "26.6.2", "vitest": "5.0.1" },
|
|
29
29
|
"engines": { "node": ">=22.14" },
|
|
30
|
-
"repository": { "type": "git", "url": "https://github.com/
|
|
30
|
+
"repository": { "type": "git", "url": "https://github.com/katosmv/ui.git", "directory": "packages/checks" },
|
|
31
31
|
"homepage": "https://www.npmjs.com/package/@ksmv/ui-checks",
|
|
32
32
|
"bugs": {},
|
|
33
33
|
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }
|