@wordpress/components
Version:
UI components for WordPress.
215 lines (200 loc) • 6.85 kB
JavaScript
/**
* Parts of this source were derived and modified from react-color,
* released under the MIT license.
*
* https://github.com/casesandberg/react-color/
*
* Copyright (c) 2015 Case Sandberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* External dependencies
*/
import { each } from 'lodash';
import tinycolor from 'tinycolor2';
/**
* Given a hex color, get all other color properties (rgb, alpha, etc).
*
* @param {Object|string} data A hex color string or an object with a hex property
* @param {string} oldHue A reference to the hue of the previous color, otherwise dragging the saturation to zero will reset the current hue to zero as well. See https://github.com/casesandberg/react-color/issues/29#issuecomment-132686909.
* @return {Object} An object of different color representations.
*/
export function colorToState( data = {}, oldHue = false ) {
const color = data.hex ? tinycolor( data.hex ) : tinycolor( data );
const hsl = color.toHsl();
hsl.h = Math.round( hsl.h );
hsl.s = Math.round( hsl.s * 100 );
hsl.l = Math.round( hsl.l * 100 );
const hsv = color.toHsv();
hsv.h = Math.round( hsv.h );
hsv.s = Math.round( hsv.s * 100 );
hsv.v = Math.round( hsv.v * 100 );
const rgb = color.toRgb();
const hex = color.toHex();
if ( hsl.s === 0 ) {
hsl.h = oldHue || 0;
hsv.h = oldHue || 0;
}
const transparent = hex === '000000' && rgb.a === 0;
return {
color,
hex: transparent ? 'transparent' : `#${ hex }`,
hsl,
hsv,
oldHue: data.h || oldHue || hsl.h,
rgb,
source: data.source,
};
}
/**
* Get the top/left offsets of a point in a container, also returns the container width/height.
*
* @param {Event} e Mouse or touch event with a location coordinate.
* @param {HTMLElement} container The container div, returned point is relative to this container.
* @return {Object} An object of the offset positions & container size.
*/
function getPointOffset( e, container ) {
e.preventDefault();
const {
left: containerLeft,
top: containerTop,
width,
height,
} = container.getBoundingClientRect();
const x = typeof e.pageX === 'number' ? e.pageX : e.touches[ 0 ].pageX;
const y = typeof e.pageY === 'number' ? e.pageY : e.touches[ 0 ].pageY;
let left = x - ( containerLeft + window.pageXOffset );
let top = y - ( containerTop + window.pageYOffset );
if ( left < 0 ) {
left = 0;
} else if ( left > width ) {
left = width;
} else if ( top < 0 ) {
top = 0;
} else if ( top > height ) {
top = height;
}
return { top, left, width, height };
}
/**
* Check if a string is a valid hex color code.
*
* @param {string} hex A possible hex color.
* @return {boolean} True if the color is a valid hex color.
*/
export function isValidHex( hex ) {
// disable hex4 and hex8
const lh = String( hex ).charAt( 0 ) === '#' ? 1 : 0;
return (
hex.length !== 4 + lh &&
hex.length < 7 + lh &&
tinycolor( hex ).isValid()
);
}
/**
* Check an object for any valid color properties.
*
* @param {Object} data A possible object representing a color.
* @return {Object|boolean} If a valid representation of color, returns the data object. Otherwise returns false.
*/
export function simpleCheckForValidColor( data ) {
const keysToCheck = [ 'r', 'g', 'b', 'a', 'h', 's', 'l', 'v' ];
let checked = 0;
let passed = 0;
each( keysToCheck, ( letter ) => {
if ( data[ letter ] ) {
checked += 1;
if ( ! isNaN( data[ letter ] ) ) {
passed += 1;
}
}
} );
return checked === passed ? data : false;
}
/**
* Calculate the current alpha based on a mouse or touch event
*
* @param {Event} e A mouse or touch event on the alpha bar.
* @param {Object} props The current component props
* @param {HTMLElement} container The container div for the alpha bar graph.
* @return {Object|null} If the alpha value has changed, returns a new color object.
*/
export function calculateAlphaChange( e, props, container ) {
const { left, width } = getPointOffset( e, container );
const a = left < 0 ? 0 : Math.round( ( left * 100 ) / width ) / 100;
if ( props.hsl.a !== a ) {
return {
h: props.hsl.h,
s: props.hsl.s,
l: props.hsl.l,
a,
source: 'rgb',
};
}
return null;
}
/**
* Calculate the current hue based on a mouse or touch event
*
* @param {Event} e A mouse or touch event on the hue bar.
* @param {Object} props The current component props
* @param {HTMLElement} container The container div for the hue bar graph.
* @return {Object|null} If the hue value has changed, returns a new color object.
*/
export function calculateHueChange( e, props, container ) {
const { left, width } = getPointOffset( e, container );
const percent = ( left * 100 ) / width;
const h = left >= width ? 359 : ( 360 * percent ) / 100;
if ( props.hsl.h !== h ) {
return {
h,
s: props.hsl.s,
l: props.hsl.l,
a: props.hsl.a,
source: 'rgb',
};
}
return null;
}
/**
* Calculate the current saturation & brightness based on a mouse or touch event
*
* @param {Event} e A mouse or touch event on the saturation graph.
* @param {Object} props The current component props
* @param {HTMLElement} container The container div for the 2D saturation graph.
* @return {Object} Returns a new color object.
*/
export function calculateSaturationChange( e, props, container ) {
const { top, left, width, height } = getPointOffset( e, container );
const saturation = left < 0 ? 0 : ( left * 100 ) / width;
let bright = top >= height ? 0 : -( ( top * 100 ) / height ) + 100;
// `v` values less than 1 are considered in the [0,1] range, causing unexpected behavior at the bottom
// of the chart. To fix this, we assume any value less than 1 should be 0 brightness.
if ( bright < 1 ) {
bright = 0;
}
return {
h: props.hsl.h,
s: saturation,
v: bright,
a: props.hsl.a,
source: 'rgb',
};
}