From c37ef5b7b906bf0c757b94d2aae4947857b527f7 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Thu, 9 Dec 2021 10:52:21 -0500 Subject: [PATCH] (enh) improve default theme accessibility (#3402) * first pass at accessibility report in checkTheme * resolve CSS color names when necessary * (enh) default Dark theme is WCAG AAA wrt contrast * (enh) improve accessibility of default theme --- CHANGES.md | 9 ++++- package-lock.json | 55 ++++++++++++++++++++++++++++- package.json | 4 ++- src/styles/dark.css | 4 +-- src/styles/default.css | 10 +++--- tools/checkTheme.js | 78 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 262bc2100a..fc60632dd1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,11 @@ -## Version 11.3.2 (most likely) +## Version 11.4 (most likely) + +Themes: + +- `Default` is now much closer WCAG AA (contrast) (#3402) [Josh Goebel] +- `Dark` now meets WCAG AA (contrast) (#3402) [Josh Goebel] + +These changes should be for the better and should not be super noticeable but if you're super picky about your colors you may want to intervene here or copy over the older themes from 11.3 or prior. Grammars: diff --git a/package-lock.json b/package-lock.json index 877b12bf68..55b90dec9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "colors": "^1.1.2", "commander": "8.2", "css": "^3.0.0", + "css-color-names": "^1.0.1", "deep-freeze-es6": "^1.4.1", "del": "^6.0.0", "dependency-resolver": "^2.0.1", @@ -38,7 +39,8 @@ "should": "^13.2.3", "terser": "^5.7.0", "tiny-worker": "^2.3.0", - "typescript": "^4.4.4" + "typescript": "^4.4.4", + "wcag-contrast": "^3.0.0" }, "engines": { "node": ">=12.0.0" @@ -1042,6 +1044,15 @@ "source-map-resolve": "^0.6.0" } }, + "node_modules/css-color-names": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", + "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -3358,6 +3369,15 @@ "node": ">=8" } }, + "node_modules/relative-luminance": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/relative-luminance/-/relative-luminance-2.0.1.tgz", + "integrity": "sha512-wFuITNthJilFPwkK7gNJcULxXBcfFZvZORsvdvxeOdO44wCeZnuQkf3nFFzOR/dpJNxYsdRZJLsepWbyKhnMww==", + "dev": true, + "dependencies": { + "esm": "^3.0.84" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4075,6 +4095,15 @@ "node": ">=12" } }, + "node_modules/wcag-contrast": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wcag-contrast/-/wcag-contrast-3.0.0.tgz", + "integrity": "sha512-RWbpg/S7FOXDCwqC2oFhN/vh8dHzj0OS6dpyOSDHyQFSmqmR+lAUStV/ziTT1GzDqL9wol+nZQB4vCi5yEak+w==", + "dev": true, + "dependencies": { + "relative-luminance": "^2.0.0" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -5111,6 +5140,12 @@ "source-map-resolve": "^0.6.0" } }, + "css-color-names": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-1.0.1.tgz", + "integrity": "sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==", + "dev": true + }, "cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", @@ -6839,6 +6874,15 @@ "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "dev": true }, + "relative-luminance": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/relative-luminance/-/relative-luminance-2.0.1.tgz", + "integrity": "sha512-wFuITNthJilFPwkK7gNJcULxXBcfFZvZORsvdvxeOdO44wCeZnuQkf3nFFzOR/dpJNxYsdRZJLsepWbyKhnMww==", + "dev": true, + "requires": { + "esm": "^3.0.84" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7399,6 +7443,15 @@ "xml-name-validator": "^4.0.0" } }, + "wcag-contrast": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wcag-contrast/-/wcag-contrast-3.0.0.tgz", + "integrity": "sha512-RWbpg/S7FOXDCwqC2oFhN/vh8dHzj0OS6dpyOSDHyQFSmqmR+lAUStV/ziTT1GzDqL9wol+nZQB4vCi5yEak+w==", + "dev": true, + "requires": { + "relative-luminance": "^2.0.0" + } + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/package.json b/package.json index a6376bd740..985b47fdd6 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "colors": "^1.1.2", "commander": "8.2", "css": "^3.0.0", + "css-color-names": "^1.0.1", "deep-freeze-es6": "^1.4.1", "del": "^6.0.0", "dependency-resolver": "^2.0.1", @@ -84,6 +85,7 @@ "should": "^13.2.3", "terser": "^5.7.0", "tiny-worker": "^2.3.0", - "typescript": "^4.4.4" + "typescript": "^4.4.4", + "wcag-contrast": "^3.0.0" } } diff --git a/src/styles/dark.css b/src/styles/dark.css index 34708897f3..052dbc94f1 100644 --- a/src/styles/dark.css +++ b/src/styles/dark.css @@ -6,7 +6,7 @@ Dark style from softwaremaniacs.org (c) Ivan Sagalaev Math.round(x*100)/100; + +class CSSRule { + constructor(rule, body) { + this.rule = rule; + if (rule.declarations) { + this.bg = rule.declarations.find(x => x.property =="background")?.value; + this.fg = rule.declarations.find(x => x.property =="color")?.value; + + if (this.bg) { + this.bg = csscolors[this.bg] || this.bg; + } + if (this.fg) { + this.fg = csscolors[this.fg] || this.fg; + } + + // inherit from body if we're missing fg or bg + if (this.hasColor) { + if (!this.bg) this.bg = body.background; + if (!this.fg) this.fg = body.foreground; + } + } + } + get background() { + return this.bg + } + + get foreground() { + return this.fg + } + get hasColor() { + if (!this.rule.declarations) return false; + return this.fg || this.bg; + } + toString() { + return ` ${this.foreground} on ${this.background}` + } + + contrastRatio() { + if (!this.foreground) return "unknown (no fg)" + if (!this.background) return "unknown (no bg)" + return round2(wcagContrast.hex(this.foreground, this.background)); + } +} + +function contrast_report(rules) { + console.log("Accessibility Report".yellow); + + var hljs = rules.find (x => x.selectors && x.selectors.includes(".hljs")); + var body = new CSSRule(hljs); + const table = new Table({ + chars: {'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''}, + head: ['ratio', 'selector', 'fg', 'bg'], + colWidths: [7, 40, 10, 10], + style: { + head: ['grey'] + } + }); + + rules.forEach(rule => { + var color = new CSSRule(rule, body); + if (!color.hasColor) return; + table.push([ + color.contrastRatio(), + rule.selectors, + color.foreground, + color.background + ]) + // console.log(r.selectors[0], color.contrastRatio(), color.toString()); + }) + console.log(table.toString()) +} + function validate(data) { const rules = data.stylesheet.rules; @@ -195,6 +271,8 @@ function validate(data) { check_group(CODE, rules); check_group(OTHER, rules); check_group(HIGH_FIDELITY, rules); + + contrast_report(rules); } process.argv.shift();