You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
6.3 KiB
206 lines
6.3 KiB
/* global wp, _validateWCAGColorContrastExports */ |
|
/* exported validateWCAGColorContrast */ |
|
let validateWCAGColorContrast = ( function ( $, api, exports ) { |
|
let self = { validate_color_contrast: [] }; |
|
if ( exports ) { |
|
$.extend( self, exports ); |
|
} |
|
/** |
|
* Add contrast validation to a control if it is entitled (is a valid color control). |
|
* |
|
* @param {wp.customize.Control} setting - Control. |
|
* @param {wp.customize.Value} setting.validationMessage - Validation message. |
|
* @return {boolean} Whether validation was added. |
|
*/ |
|
self.addWCAGColorContrastValidation = function ( setting ) { |
|
let initialValidate; |
|
|
|
if ( ! self.isColorControl( setting ) ) { |
|
return false; |
|
} |
|
initialValidate = setting.validate; |
|
|
|
/** |
|
* Wrap the setting's validate() method to do validation on the value to be sent to the server. |
|
* |
|
* @param {mixed} value - New value being assigned to the setting. |
|
* @returns {*} |
|
*/ |
|
setting.validate = function ( value ) { |
|
let setting = this, title, validationError; |
|
let current_color = value; |
|
let current_id = this.id; |
|
|
|
let all_color_controls = _.union( _.flatten( _.values( self.validate_color_contrast ) ) ); |
|
|
|
// remove other (old) notifications |
|
_.each( _.without( all_color_controls, current_id ), function ( other_color_control_id ) { |
|
let other_control = api.control.instance( other_color_control_id ); |
|
notice = other_control.container.find( '.notice' ); |
|
notice.hide(); |
|
} ); |
|
|
|
// find other color controls and check contrast with current color control |
|
let other_color_controls = self.validate_color_contrast[ current_id ]; |
|
|
|
_.each( other_color_controls, function ( other_color_control_id ) { |
|
let other_control = api.control.instance( other_color_control_id ); |
|
let other_color = other_control.container.find( '.color-picker-hex' ).val(); |
|
let name = $( '#customize-control-' + other_color_control_id + ' .customize-control-title' ).text(); |
|
let contrast = self.hex( current_color, other_color ); |
|
let score = self.score( contrast ); |
|
|
|
// contrast >= 7 ? "AAA" : contrast >= 4.5 ? "AA" : "" |
|
if ( contrast < 4.5 ) { |
|
setting.notifications.remove( other_color_control_id ); |
|
validationWarning = new api.Notification( other_color_control_id, { message: self.sprintf( 'WCAG conflict with "%s"<br/>contrast: %s', name, contrast ), type: 'warning' } ); |
|
setting.notifications.add( validationWarning.code, validationWarning ); |
|
// console.log( color_control_id + ' ' + color + ' ' + contrast + ' ' + score ); |
|
} else { |
|
setting.notifications.remove( other_color_control_id ); |
|
} |
|
} ); |
|
|
|
return value; |
|
}; |
|
|
|
return true; |
|
}; |
|
|
|
/** |
|
* Return whether the setting is entitled (i.e. if it is a title or has a title). |
|
* |
|
* @param {wp.customize.Setting} setting - Setting. |
|
* @returns {boolean} |
|
*/ |
|
self.isColorControl = function ( setting ) { |
|
return _.findKey( self.validate_color_contrast, function ( key, value ) { |
|
return value == setting.id; |
|
} ); |
|
}; |
|
|
|
api.bind( 'add', function ( setting ) { |
|
self.addWCAGColorContrastValidation( setting ); |
|
} ); |
|
|
|
self.sprintf = function ( format ) { |
|
for ( let i=1; i < arguments.length; i++ ) { |
|
format = format.replace( /%s/, arguments[i] ); |
|
} |
|
return format; |
|
}; |
|
|
|
/** |
|
* Methods used to calculate WCAG Color Contrast |
|
*/ |
|
|
|
// from https://github.com/sindresorhus/hex-rgb |
|
self.hexRgb = function ( hex ) { |
|
if ( typeof hex !== 'string' ) { |
|
throw new TypeError( 'Expected a string' ); |
|
} |
|
|
|
hex = hex.replace( /^#/, '' ); |
|
|
|
if ( hex.length === 3 ) { |
|
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; |
|
} |
|
|
|
let num = parseInt( hex, 16 ); |
|
|
|
return [ num >> 16, num >> 8 & 255, num & 255 ]; |
|
}; |
|
|
|
// from https://github.com/tmcw/relative-luminance |
|
// # Relative luminance |
|
// |
|
// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef |
|
// https://en.wikipedia.org/wiki/Luminance_(relative) |
|
// https://en.wikipedia.org/wiki/Luminosity_function |
|
// https://en.wikipedia.org/wiki/Rec._709#Luma_coefficients |
|
|
|
// red, green, and blue coefficients |
|
let rc = 0.2126, |
|
gc = 0.7152, |
|
bc = 0.0722, |
|
// low-gamma adjust coefficient |
|
lowc = 1 / 12.92; |
|
|
|
self.adjustGamma = function ( g ) { |
|
return Math.pow( ( g + 0.055 ) / 1.055, 2.4 ); |
|
}; |
|
|
|
/** |
|
* Given a 3-element array of R, G, B varying from 0 to 255, return the luminance |
|
* as a number from 0 to 1. |
|
* @param {Array<number>} rgb 3-element array of a color |
|
* @returns {number} luminance, between 0 and 1 |
|
* @example |
|
* var luminance = require('relative-luminance'); |
|
* var black_lum = luminance([0, 0, 0]); // 0 |
|
*/ |
|
self.relativeLuminance = function ( rgb ) { |
|
let rsrgb = rgb[0] / 255; |
|
let gsrgb = rgb[1] / 255; |
|
let bsrgb = rgb[2] / 255; |
|
|
|
let r = rsrgb <= 0.03928 ? rsrgb * lowc : self.adjustGamma( rsrgb ), |
|
g = gsrgb <= 0.03928 ? gsrgb * lowc : self.adjustGamma( gsrgb ), |
|
b = bsrgb <= 0.03928 ? bsrgb * lowc : self.adjustGamma( bsrgb ); |
|
|
|
return r * rc + g * gc + b * bc; |
|
}; |
|
|
|
// from https://github.com/tmcw/wcag-contrast |
|
/** |
|
* Get the contrast ratio between two relative luminance values |
|
* @param {number} a luminance value |
|
* @param {number} b luminance value |
|
* @returns {number} contrast ratio |
|
* @example |
|
* luminance(1, 1); // = 1 |
|
*/ |
|
self.luminance = function ( a, b ) { |
|
let l1 = Math.max( a, b ); |
|
let l2 = Math.min( a, b ); |
|
return ( l1 + 0.05 ) / ( l2 + 0.05 ); |
|
}; |
|
|
|
/** |
|
* Get a score for the contrast between two colors as rgb triplets |
|
* @param {array} a |
|
* @param {array} b |
|
* @returns {number} contrast ratio |
|
* @example |
|
* rgb([0, 0, 0], [255, 255, 255]); // = 21 |
|
*/ |
|
self.rgb = function ( a, b ) { |
|
return self.luminance( self.relativeLuminance( a ), self.relativeLuminance( b ) ); |
|
}; |
|
|
|
/** |
|
* Get a score for the contrast between two colors as hex strings |
|
* @param {string} a hex value |
|
* @param {string} b hex value |
|
* @returns {number} contrast ratio |
|
* @example |
|
* hex('#000', '#fff'); // = 21 |
|
*/ |
|
self.hex = function ( a, b ) { |
|
return self.rgb( self.hexRgb( a ), self.hexRgb( b ) ); |
|
}; |
|
|
|
/** |
|
* Get a textual score from a numeric contrast value |
|
* @param {number} contrast |
|
* @returns {string} score |
|
* @example |
|
* score(10); // = 'AAA' |
|
*/ |
|
self.score = function ( contrast ) { |
|
return contrast >= 7 ? 'AAA' : contrast >= 4.5 ? 'AA' : ''; |
|
}; |
|
|
|
return self; |
|
|
|
}( jQuery, wp.customize, _validateWCAGColorContrastExports ) );
|
|
|