Skip to content

Commit

Permalink
Merge branch 'develop' into blendmode-hue-sat-color-lum
Browse files Browse the repository at this point in the history
  • Loading branch information
straker committed Mar 12, 2024
2 parents e000030 + 9e70199 commit 6ea3b48
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 179 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module.exports = {
root: true,
extends: ['prettier'],
parserOptions: {
ecmaVersion: 2021
ecmaVersion: 2023
},
env: {
node: true,
Expand Down
2 changes: 1 addition & 1 deletion doc/rule-descriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
| [aria-command-name](https://dequeuniversity.com/rules/axe/4.8/aria-command-name?application=RuleDescription) | Ensures every ARIA button, link and menuitem has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [97a4e1](https://act-rules.github.io/rules/97a4e1) |
| [aria-conditional-attr](https://dequeuniversity.com/rules/axe/4.8/aria-conditional-attr?application=RuleDescription) | Ensures ARIA attributes are used as described in the specification of the element's role | Serious | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [5c01ea](https://act-rules.github.io/rules/5c01ea) |
| [aria-deprecated-role](https://dequeuniversity.com/rules/axe/4.8/aria-deprecated-role?application=RuleDescription) | Ensures elements do not use deprecated roles | Minor | cat.aria, wcag2a, wcag412, EN-301-549, EN-9.4.1.2 | failure | [674b10](https://act-rules.github.io/rules/674b10) |
| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.8/aria-hidden-body?application=RuleDescription) | Ensures aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag131, wcag412, EN-301-549, EN-9.4.1.2 | failure | |
| [aria-hidden-body](https://dequeuniversity.com/rules/axe/4.8/aria-hidden-body?application=RuleDescription) | Ensures aria-hidden="true" is not present on the document body. | Critical | cat.aria, wcag2a, wcag131, wcag412, EN-301-549, EN-9.1.3.1, EN-9.4.1.2 | failure | |
| [aria-hidden-focus](https://dequeuniversity.com/rules/axe/4.8/aria-hidden-focus?application=RuleDescription) | Ensures aria-hidden elements are not focusable nor contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, TTv5, TT6.a, EN-301-549, EN-9.4.1.2 | failure, needs review | [6cfa84](https://act-rules.github.io/rules/6cfa84) |
| [aria-input-field-name](https://dequeuniversity.com/rules/axe/4.8/aria-input-field-name?application=RuleDescription) | Ensures every ARIA input field has an accessible name | Serious | cat.aria, wcag2a, wcag412, TTv5, TT5.c, EN-301-549, EN-9.4.1.2, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) |
| [aria-meter-name](https://dequeuniversity.com/rules/axe/4.8/aria-meter-name?application=RuleDescription) | Ensures every ARIA meter node has an accessible name | Serious | cat.aria, wcag2a, wcag111, EN-301-549, EN-9.1.1.1 | failure, needs review | |
Expand Down
252 changes: 183 additions & 69 deletions lib/commons/color/color.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Colorjs } from '../../core/imports';

const hexRegex = /^#[0-9a-f]{3,8}$/i;
const hslRegex = /hsl\(\s*([\d.]+)(rad|turn)/;

/**
* @class Color
Expand All @@ -12,7 +11,26 @@ const hslRegex = /hsl\(\s*([\d.]+)(rad|turn)/;
* @param {number} alpha
*/
export default class Color {
// color channel values typically in the range of 0-1 (can go below or above)
#r;
#g;
#b;
// color component values resolved to the sRGB color space (0-255)
#red;
#green;
#blue;

constructor(red, green, blue, alpha = 1) {
if (red instanceof Color) {
// preserve out of gamut values
const { r, g, b } = red;
this.r = r;
this.g = g;
this.b = b;
this.alpha = red.alpha;
return;
}

/** @type {number} */
this.red = red;

Expand All @@ -26,6 +44,60 @@ export default class Color {
this.alpha = alpha;
}

get r() {
return this.#r;
}

set r(value) {
this.#r = value;
this.#red = Math.round(clamp(value, 0, 1) * 255);
}

get g() {
return this.#g;
}

set g(value) {
this.#g = value;
this.#green = Math.round(clamp(value, 0, 1) * 255);
}

get b() {
return this.#b;
}

set b(value) {
this.#b = value;
this.#blue = Math.round(clamp(value, 0, 1) * 255);
}

get red() {
return this.#red;
}

set red(value) {
this.#r = value / 255;
this.#red = clamp(value, 0, 255);
}

get green() {
return this.#green;
}

set green(value) {
this.#g = value / 255;
this.#green = clamp(value, 0, 255);
}

get blue() {
return this.#blue;
}

set blue(value) {
this.#b = value / 255;
this.#blue = clamp(value, 0, 255);
}

/**
* Provide the hex string value for the color
* @method toHexString
Expand All @@ -34,9 +106,9 @@ export default class Color {
* @return {string}
*/
toHexString() {
var redString = Math.round(this.red).toString(16);
var greenString = Math.round(this.green).toString(16);
var blueString = Math.round(this.blue).toString(16);
const redString = Math.round(this.red).toString(16);
const greenString = Math.round(this.green).toString(16);
const blueString = Math.round(this.blue).toString(16);
return (
'#' +
(this.red > 15.5 ? redString : '0' + redString) +
Expand All @@ -57,28 +129,12 @@ export default class Color {
* @instance
*/
parseString(colorString) {
// Colorjs currently does not support rad or turn angle values
// @see https://github.com/LeaVerou/color.js/issues/311
colorString = colorString.replace(hslRegex, (match, angle, unit) => {
const value = angle + unit;

switch (unit) {
case 'rad':
return match.replace(value, radToDeg(angle));
case 'turn':
return match.replace(value, turnToDeg(angle));
}
});

try {
// srgb values are between 0 and 1
const color = new Colorjs(colorString).to('srgb');
// when converting from one color space to srgb
// the values of rgb may be above 1 so we need to clamp them
// we also need to round the final value as rgb values don't have decimals
this.red = Math.round(clamp(color.r, 0, 1) * 255);
this.green = Math.round(clamp(color.g, 0, 1) * 255);
this.blue = Math.round(clamp(color.b, 0, 1) * 255);
this.r = color.r;
this.g = color.g;
this.b = color.b;
// color.alpha is a Number object so convert it to a number
this.alpha = +color.alpha;
} catch (err) {
Expand Down Expand Up @@ -137,80 +193,138 @@ export default class Color {
* @return {number} The luminance value, ranges from 0 to 1
*/
getRelativeLuminance() {
var rSRGB = this.red / 255;
var gSRGB = this.green / 255;
var bSRGB = this.blue / 255;
const { r: rSRGB, g: gSRGB, b: bSRGB } = this;

var r =
const r =
rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow((rSRGB + 0.055) / 1.055, 2.4);
var g =
const g =
gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow((gSRGB + 0.055) / 1.055, 2.4);
var b =
const b =
bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow((bSRGB + 0.055) / 1.055, 2.4);

return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

/**
* Add the value to a color and return a new instance of the Color. The resulting color values are not clamped to valid values of the color space (must be done separately).
* @method add
* Add a value to the color channels
* @private
* @param {number} value The value to add
* @return {Color} A new color instance
*/
#add(value) {
const C = new Color(this);
C.r += value;
C.g += value;
C.b += value;
return C;
}

/**
* Get the luminosity of a color
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable
* @method getLuminosity
* @memberof axe.commons.color.Color
* @instance
* @return {Color}
* @return {number} The luminosity of the color
*/
add(value) {
return new Color(
this.red + value,
this.green + value,
this.blue + value,
this.alpha
);
getLuminosity() {
return 0.3 * this.r + 0.59 * this.g + 0.11 * this.b;
}

/**
* Divide a color by the value and return a new instance of the Color
* @method divide
* Set the luminosity of a color
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable
* @method setLuminosity
* @memberof axe.commons.color.Color
* @instance
* @return {Color}
* @param {number} L The luminosity
* @return {Color} A new color instance
*/
divide(value) {
return new Color(
this.red / value,
this.green / value,
this.blue / value,
this.alpha
);
setLuminosity(L) {
const d = L - this.getLuminosity();
return this.#add(d).clip();
}

/**
* Multiply a color by the value and return a new instance of the Color. The resulting color values are not clamped to valid values of the color space (must be done separately).
* @method multiply
* Get the saturation of a color
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable
* @method getSaturation
* @memberof axe.commons.color.Color
* @instance
* @return {Color}
* @return {number} The saturation of the color
*/
multiply(value) {
return new Color(
this.red * value,
this.green * value,
this.blue * value,
this.alpha
);
getSaturation() {
return Math.max(this.r, this.g, this.b) - Math.min(this.r, this.g, this.b);
}

/**
* Set the saturation of a color
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable
* @method setSaturation
* @memberof axe.commons.color.Color
* @instance
* @param {number} s The saturation
* @return {Color} A new color instance
*/
setSaturation(s) {
const C = new Color(this);
const colorEntires = [
{ name: 'r', value: C.r },
{ name: 'g', value: C.g },
{ name: 'b', value: C.b }
];

// find the min, mid, and max values of the color components
const [Cmin, Cmid, Cmax] = colorEntires.sort((a, b) => {
return a.value - b.value;
});

if (Cmax.value > Cmin.value) {
Cmid.value = ((Cmid.value - Cmin.value) * s) / (Cmax.value - Cmin.value);
Cmax.value = s;
} else {
Cmid.value = Cmax.value = 0;
}

Cmin.value = 0;

C[Cmax.name] = Cmax.value;
C[Cmin.name] = Cmin.value;
C[Cmid.name] = Cmid.value;
return C;
}

/**
* Clip the color between RGB 0-1 accounting for the luminosity of the color. Color must be normalized before calling.
* using algorithm from https://www.w3.org/TR/compositing-1/#blendingnonseparable
* @method clip
* @memberof axe.commons.color.Color
* @instance
* @return {Color} A new color instance clipped between 0-1
*/
clip() {
const C = new Color(this);
const L = C.getLuminosity();
const n = Math.min(C.r, C.g, C.b);
const x = Math.max(C.r, C.g, C.b);

if (n < 0) {
C.r = L + ((C.r - L) * L) / (L - n);
C.g = L + ((C.g - L) * L) / (L - n);
C.b = L + ((C.b - L) * L) / (L - n);
}

if (x > 1) {
C.r = L + ((C.r - L) * (1 - L)) / (x - L);
C.g = L + ((C.g - L) * (1 - L)) / (x - L);
C.b = L + ((C.b - L) * (1 - L)) / (x - L);
}

return C;
}
}

// clamp a value between two numbers (inclusive)
function clamp(value, min, max) {
return Math.min(Math.max(min, value), max);
}

// convert radians to degrees
function radToDeg(rad) {
return (rad * 180) / Math.PI;
}

// convert turn to degrees
function turnToDeg(turn) {
return turn * 360;
}
8 changes: 7 additions & 1 deletion lib/commons/dom/get-visible-child-text-rects.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,14 @@ const getVisibleChildTextRects = memoize(
* @see https://github.com/dequelabs/axe-core/issues/2178
* @see https://github.com/dequelabs/axe-core/issues/2483
* @see https://github.com/dequelabs/axe-core/issues/2681
*
* also need to resize the nodeRect to fit within the bounds of any overflow: hidden ancestors.
*
* @see https://github.com/dequelabs/axe-core/issues/4253
*/
return clientRects.length ? clientRects : [nodeRect];
return clientRects.length
? clientRects
: filterHiddenRects([nodeRect], overflowHiddenNodes);
}
);
export default getVisibleChildTextRects;
Expand Down
Loading

0 comments on commit 6ea3b48

Please sign in to comment.