mirror of
https://github.com/YunoHost-Apps/dokuwiki_ynh.git
synced 2024-09-03 18:26:20 +02:00
1488 lines
42 KiB
JavaScript
1488 lines
42 KiB
JavaScript
/*! Iris Color Picker - v1.0.7 - 2014-11-28
|
|
* https://github.com/Automattic/Iris
|
|
* Copyright (c) 2014 Matt Wiebe; Licensed GPLv2 */
|
|
(function( $, undef ){
|
|
var _html, nonGradientIE, gradientType, vendorPrefixes, _css, Iris, UA, isIE, IEVersion;
|
|
|
|
_html = '<div class="iris-picker"><div class="iris-picker-inner"><div class="iris-square"><a class="iris-square-value" href="#"><span class="iris-square-handle ui-slider-handle"></span></a><div class="iris-square-inner iris-square-horiz"></div><div class="iris-square-inner iris-square-vert"></div></div><div class="iris-slider iris-strip"><div class="iris-slider-offset"></div></div></div></div>';
|
|
_css = '.iris-picker{display:block;position:relative}.iris-picker,.iris-picker *{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input+.iris-picker{margin-top:4px}.iris-error{background-color:#ffafaf}.iris-border{border-radius:3px;border:1px solid #aaa;width:200px;background-color:#fff}.iris-picker-inner{position:absolute;top:0;right:0;left:0;bottom:0}.iris-border .iris-picker-inner{top:10px;right:10px;left:10px;bottom:10px}.iris-picker .iris-square-inner{position:absolute;left:0;right:0;top:0;bottom:0}.iris-picker .iris-square,.iris-picker .iris-slider,.iris-picker .iris-square-inner,.iris-picker .iris-palette{border-radius:3px;box-shadow:inset 0 0 5px rgba(0,0,0,.4);height:100%;width:12.5%;float:left;margin-right:5%}.iris-picker .iris-square{width:76%;margin-right:10%;position:relative}.iris-picker .iris-square-inner{width:auto;margin:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-square-inner,.iris-ie-9 .iris-palette{box-shadow:none;border-radius:0}.iris-ie-9 .iris-square,.iris-ie-9 .iris-slider,.iris-ie-9 .iris-palette{outline:1px solid rgba(0,0,0,.1)}.iris-ie-lt9 .iris-square,.iris-ie-lt9 .iris-slider,.iris-ie-lt9 .iris-square-inner,.iris-ie-lt9 .iris-palette{outline:1px solid #aaa}.iris-ie-lt9 .iris-square .ui-slider-handle{outline:1px solid #aaa;background-color:#fff;-ms-filter:"alpha(Opacity=30)"}.iris-ie-lt9 .iris-square .iris-square-handle{background:0;border:3px solid #fff;-ms-filter:"alpha(Opacity=50)"}.iris-picker .iris-strip{margin-right:0;position:relative}.iris-picker .iris-strip .ui-slider-handle{position:absolute;background:0;margin:0;right:-3px;left:-3px;border:4px solid #aaa;border-width:4px 3px;width:auto;height:6px;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.2);opacity:.9;z-index:5;cursor:ns-resize}.iris-strip .ui-slider-handle:before{content:" ";position:absolute;left:-2px;right:-2px;top:-3px;bottom:-3px;border:2px solid #fff;border-radius:3px}.iris-picker .iris-slider-offset{position:absolute;top:11px;left:0;right:0;bottom:-3px;width:auto;height:auto;background:transparent;border:0;border-radius:0}.iris-picker .iris-square-handle{background:transparent;border:5px solid #aaa;border-radius:50%;border-color:rgba(128,128,128,.5);box-shadow:none;width:12px;height:12px;position:absolute;left:-10px;top:-10px;cursor:move;opacity:1;z-index:10}.iris-picker .ui-state-focus .iris-square-handle{opacity:.8}.iris-picker .iris-square-handle:hover{border-color:#999}.iris-picker .iris-square-value:focus .iris-square-handle{box-shadow:0 0 2px rgba(0,0,0,.75);opacity:.8}.iris-picker .iris-square-handle:hover::after{border-color:#fff}.iris-picker .iris-square-handle::after{position:absolute;bottom:-4px;right:-4px;left:-4px;top:-4px;border:3px solid #f9f9f9;border-color:rgba(255,255,255,.8);border-radius:50%;content:" "}.iris-picker .iris-square-value{width:8px;height:8px;position:absolute}.iris-ie-lt9 .iris-square-value,.iris-mozilla .iris-square-value{width:1px;height:1px}.iris-palette-container{position:absolute;bottom:0;left:0;margin:0;padding:0}.iris-border .iris-palette-container{left:10px;bottom:10px}.iris-picker .iris-palette{margin:0;cursor:pointer}.iris-square-handle,.ui-slider-handle{border:0;outline:0}';
|
|
|
|
// Even IE9 dosen't support gradients. Elaborate sigh.
|
|
UA = navigator.userAgent.toLowerCase();
|
|
isIE = navigator.appName === 'Microsoft Internet Explorer';
|
|
IEVersion = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0;
|
|
nonGradientIE = ( isIE && IEVersion < 10 );
|
|
gradientType = false;
|
|
|
|
// we don't bother with an unprefixed version, as it has a different syntax
|
|
vendorPrefixes = [ '-moz-', '-webkit-', '-o-', '-ms-' ];
|
|
|
|
// Bail for IE <= 7
|
|
if ( nonGradientIE && IEVersion <= 7 ) {
|
|
$.fn.iris = $.noop;
|
|
$.support.iris = false;
|
|
return;
|
|
}
|
|
|
|
$.support.iris = true;
|
|
|
|
function testGradientType() {
|
|
var el, base,
|
|
bgImageString = 'backgroundImage';
|
|
|
|
if ( nonGradientIE ) {
|
|
gradientType = 'filter';
|
|
}
|
|
else {
|
|
el = $( '<div id="iris-gradtest" />' );
|
|
base = 'linear-gradient(top,#fff,#000)';
|
|
$.each( vendorPrefixes, function( i, val ){
|
|
el.css( bgImageString, val + base );
|
|
if ( el.css( bgImageString ).match( 'gradient' ) ) {
|
|
gradientType = i;
|
|
return false;
|
|
}
|
|
});
|
|
// check for legacy webkit gradient syntax
|
|
if ( gradientType === false ) {
|
|
el.css( 'background', '-webkit-gradient(linear,0% 0%,0% 100%,from(#fff),to(#000))' );
|
|
if ( el.css( bgImageString ).match( 'gradient' ) ) {
|
|
gradientType = 'webkit';
|
|
}
|
|
}
|
|
el.remove();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Only for CSS3 gradients. oldIE will use a separate function.
|
|
*
|
|
* Accepts as many color stops as necessary from 2nd arg on, or 2nd
|
|
* arg can be an array of color stops
|
|
*
|
|
* @param {string} origin Gradient origin - top or left, defaults to left.
|
|
* @return {string} Appropriate CSS3 gradient string for use in
|
|
*/
|
|
function createGradient( origin, stops ) {
|
|
origin = ( origin === 'top' ) ? 'top' : 'left';
|
|
stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 );
|
|
if ( gradientType === 'webkit' ) {
|
|
return legacyWebkitGradient( origin, stops );
|
|
} else {
|
|
return vendorPrefixes[ gradientType ] + 'linear-gradient(' + origin + ', ' + stops.join(', ') + ')';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stupid gradients for a stupid browser.
|
|
*/
|
|
function stupidIEGradient( origin, stops ) {
|
|
var type, self, lastIndex, filter, startPosProp, endPosProp, dimensionProp, template, html;
|
|
|
|
origin = ( origin === 'top' ) ? 'top' : 'left';
|
|
stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 );
|
|
// 8 hex: AARRGGBB
|
|
// GradientType: 0 vertical, 1 horizontal
|
|
type = ( origin === 'top' ) ? 0 : 1;
|
|
self = $( this );
|
|
lastIndex = stops.length - 1;
|
|
filter = 'filter';
|
|
startPosProp = ( type === 1 ) ? 'left' : 'top';
|
|
endPosProp = ( type === 1 ) ? 'right' : 'bottom';
|
|
dimensionProp = ( type === 1 ) ? 'height' : 'width';
|
|
template = '<div class="iris-ie-gradient-shim" style="position:absolute;' + dimensionProp + ':100%;' + startPosProp + ':%start%;' + endPosProp + ':%end%;' + filter + ':%filter%;" data-color:"%color%"></div>';
|
|
html = '';
|
|
// need a positioning context
|
|
if ( self.css('position') === 'static' ) {
|
|
self.css( {position: 'relative' } );
|
|
}
|
|
|
|
stops = fillColorStops( stops );
|
|
$.each(stops, function( i, startColor ) {
|
|
var endColor, endStop, filterVal;
|
|
|
|
// we want two at a time. if we're on the last pair, bail.
|
|
if ( i === lastIndex ) {
|
|
return false;
|
|
}
|
|
|
|
endColor = stops[ i + 1 ];
|
|
//if our pairs are at the same color stop, moving along.
|
|
if ( startColor.stop === endColor.stop ) {
|
|
return;
|
|
}
|
|
|
|
endStop = 100 - parseFloat( endColor.stop ) + '%';
|
|
startColor.octoHex = new Color( startColor.color ).toIEOctoHex();
|
|
endColor.octoHex = new Color( endColor.color ).toIEOctoHex();
|
|
|
|
filterVal = 'progid:DXImageTransform.Microsoft.Gradient(GradientType=' + type + ', StartColorStr=\'' + startColor.octoHex + '\', EndColorStr=\'' + endColor.octoHex + '\')';
|
|
html += template.replace( '%start%', startColor.stop ).replace( '%end%', endStop ).replace( '%filter%', filterVal );
|
|
});
|
|
self.find( '.iris-ie-gradient-shim' ).remove();
|
|
$( html ).prependTo( self );
|
|
}
|
|
|
|
function legacyWebkitGradient( origin, colorList ) {
|
|
var stops = [];
|
|
origin = ( origin === 'top' ) ? '0% 0%,0% 100%,' : '0% 100%,100% 100%,';
|
|
colorList = fillColorStops( colorList );
|
|
$.each( colorList, function( i, val ){
|
|
stops.push( 'color-stop(' + ( parseFloat( val.stop ) / 100 ) + ', ' + val.color + ')' );
|
|
});
|
|
return '-webkit-gradient(linear,' + origin + stops.join(',') + ')';
|
|
}
|
|
|
|
function fillColorStops( colorList ) {
|
|
var colors = [],
|
|
percs = [],
|
|
newColorList = [],
|
|
lastIndex = colorList.length - 1;
|
|
|
|
$.each( colorList, function( index, val ) {
|
|
var color = val,
|
|
perc = false,
|
|
match = val.match( /1?[0-9]{1,2}%$/ );
|
|
|
|
if ( match ) {
|
|
color = val.replace( /\s?1?[0-9]{1,2}%$/, '' );
|
|
perc = match.shift();
|
|
}
|
|
colors.push( color );
|
|
percs.push( perc );
|
|
});
|
|
|
|
// back fill first and last
|
|
if ( percs[0] === false ) {
|
|
percs[0] = '0%';
|
|
}
|
|
|
|
if ( percs[lastIndex] === false ) {
|
|
percs[lastIndex] = '100%';
|
|
}
|
|
|
|
percs = backFillColorStops( percs );
|
|
|
|
$.each( percs, function( i ){
|
|
newColorList[i] = { color: colors[i], stop: percs[i] };
|
|
});
|
|
return newColorList;
|
|
}
|
|
|
|
function backFillColorStops( stops ) {
|
|
var first = 0,
|
|
last = stops.length - 1,
|
|
i = 0,
|
|
foundFirst = false,
|
|
incr,
|
|
steps,
|
|
step,
|
|
firstVal;
|
|
|
|
if ( stops.length <= 2 || $.inArray( false, stops ) < 0 ) {
|
|
return stops;
|
|
}
|
|
while ( i < stops.length - 1 ) {
|
|
if ( ! foundFirst && stops[i] === false ) {
|
|
first = i - 1;
|
|
foundFirst = true;
|
|
} else if ( foundFirst && stops[i] !== false ) {
|
|
last = i;
|
|
i = stops.length;
|
|
}
|
|
i++;
|
|
}
|
|
steps = last - first;
|
|
firstVal = parseInt( stops[first].replace('%'), 10 );
|
|
incr = ( parseFloat( stops[last].replace('%') ) - firstVal ) / steps;
|
|
i = first + 1;
|
|
step = 1;
|
|
while ( i < last ) {
|
|
stops[i] = ( firstVal + ( step * incr ) ) + '%';
|
|
step++;
|
|
i++;
|
|
}
|
|
return backFillColorStops( stops );
|
|
}
|
|
|
|
$.fn.gradient = function() {
|
|
var args = arguments;
|
|
return this.each( function() {
|
|
// this'll be oldishIE
|
|
if ( nonGradientIE ) {
|
|
stupidIEGradient.apply( this, args );
|
|
} else {
|
|
// new hotness
|
|
$( this ).css( 'backgroundImage', createGradient.apply( this, args ) );
|
|
}
|
|
});
|
|
};
|
|
|
|
$.fn.raninbowGradient = function( origin, args ) {
|
|
var opts, template, i, steps;
|
|
|
|
origin = origin || 'top';
|
|
opts = $.extend( {}, { s: 100, l: 50 }, args );
|
|
template = 'hsl(%h%,' + opts.s + '%,' + opts.l + '%)';
|
|
i = 0;
|
|
steps = [];
|
|
while ( i <= 360 ) {
|
|
steps.push( template.replace('%h%', i) );
|
|
i += 30;
|
|
}
|
|
return this.each(function() {
|
|
$(this).gradient( origin, steps );
|
|
});
|
|
};
|
|
|
|
// the colorpicker widget def.
|
|
Iris = {
|
|
options: {
|
|
color: false,
|
|
mode: 'hsl',
|
|
controls: {
|
|
horiz: 's', // horizontal defaults to saturation
|
|
vert: 'l', // vertical defaults to lightness
|
|
strip: 'h' // right strip defaults to hue
|
|
},
|
|
hide: true, // hide the color picker by default
|
|
border: true, // draw a border around the collection of UI elements
|
|
target: false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input.
|
|
width: 200, // the width of the collection of UI elements
|
|
palettes: false // show a palette of basic colors beneath the square.
|
|
},
|
|
_color: '',
|
|
_palettes: [ '#000', '#fff', '#d33', '#d93', '#ee2', '#81d742', '#1e73be', '#8224e3' ],
|
|
_inited: false,
|
|
_defaultHSLControls: {
|
|
horiz: 's',
|
|
vert: 'l',
|
|
strip: 'h'
|
|
},
|
|
_defaultHSVControls: {
|
|
horiz: 'h',
|
|
vert: 'v',
|
|
strip: 's'
|
|
},
|
|
_scale: {
|
|
h: 360,
|
|
s: 100,
|
|
l: 100,
|
|
v: 100
|
|
},
|
|
_create: function() {
|
|
var self = this,
|
|
el = self.element,
|
|
color = self.options.color || el.val();
|
|
|
|
if ( gradientType === false ) {
|
|
testGradientType();
|
|
}
|
|
|
|
if ( el.is( 'input' ) ) {
|
|
if ( self.options.target ) {
|
|
self.picker = $( _html ).appendTo( self.options.target );
|
|
} else {
|
|
self.picker = $( _html ).insertAfter( el );
|
|
}
|
|
|
|
self._addInputListeners( el );
|
|
} else {
|
|
el.append( _html );
|
|
self.picker = el.find( '.iris-picker' );
|
|
}
|
|
|
|
// Browsers / Versions
|
|
// Feature detection doesn't work for these, and $.browser is deprecated
|
|
if ( isIE ) {
|
|
if ( IEVersion === 9 ) {
|
|
self.picker.addClass( 'iris-ie-9' );
|
|
} else if ( IEVersion <= 8 ) {
|
|
self.picker.addClass( 'iris-ie-lt9' );
|
|
}
|
|
} else if ( UA.indexOf('compatible') < 0 && UA.indexOf('khtml') < 0 && UA.match( /mozilla/ ) ) {
|
|
self.picker.addClass( 'iris-mozilla' );
|
|
}
|
|
|
|
if ( self.options.palettes ) {
|
|
self._addPalettes();
|
|
}
|
|
|
|
self._color = new Color( color ).setHSpace( self.options.mode );
|
|
self.options.color = self._color.toString();
|
|
|
|
// prep 'em for re-use
|
|
self.controls = {
|
|
square: self.picker.find( '.iris-square' ),
|
|
squareDrag: self.picker.find( '.iris-square-value' ),
|
|
horiz: self.picker.find( '.iris-square-horiz' ),
|
|
vert: self.picker.find( '.iris-square-vert' ),
|
|
strip: self.picker.find( '.iris-strip' ),
|
|
stripSlider: self.picker.find( '.iris-strip .iris-slider-offset' )
|
|
};
|
|
|
|
// small sanity check - if we chose hsv, change default controls away from hsl
|
|
if ( self.options.mode === 'hsv' && self._has('l', self.options.controls) ) {
|
|
self.options.controls = self._defaultHSVControls;
|
|
} else if ( self.options.mode === 'hsl' && self._has('v', self.options.controls) ) {
|
|
self.options.controls = self._defaultHSLControls;
|
|
}
|
|
|
|
// store it. HSL gets squirrely
|
|
self.hue = self._color.h();
|
|
|
|
if ( self.options.hide ) {
|
|
self.picker.hide();
|
|
}
|
|
|
|
if ( self.options.border ) {
|
|
self.picker.addClass( 'iris-border' );
|
|
}
|
|
|
|
self._initControls();
|
|
self.active = 'external';
|
|
self._dimensions();
|
|
self._change();
|
|
},
|
|
_has: function(needle, haystack) {
|
|
var ret = false;
|
|
$.each(haystack, function(i,v){
|
|
if ( needle === v ) {
|
|
ret = true;
|
|
// exit the loop
|
|
return false;
|
|
}
|
|
});
|
|
return ret;
|
|
},
|
|
_addPalettes: function () {
|
|
var container = $( '<div class="iris-palette-container" />' ),
|
|
palette = $( '<a class="iris-palette" tabindex="0" />' ),
|
|
colors = $.isArray( this.options.palettes ) ? this.options.palettes : this._palettes;
|
|
|
|
// do we have an existing container? Empty and reuse it.
|
|
if ( this.picker.find( '.iris-palette-container' ).length ) {
|
|
container = this.picker.find( '.iris-palette-container' ).detach().html( '' );
|
|
}
|
|
|
|
$.each(colors, function(index, val) {
|
|
palette.clone().data( 'color', val )
|
|
.css( 'backgroundColor', val ).appendTo( container )
|
|
.height( 10 ).width( 10 );
|
|
});
|
|
|
|
this.picker.append(container);
|
|
},
|
|
_paint: function() {
|
|
var self = this;
|
|
self._paintDimension( 'top', 'strip' );
|
|
self._paintDimension( 'top', 'vert' );
|
|
self._paintDimension( 'left', 'horiz' );
|
|
},
|
|
_paintDimension: function( origin, control ) {
|
|
var self = this,
|
|
c = self._color,
|
|
mode = self.options.mode,
|
|
color = self._getHSpaceColor(),
|
|
target = self.controls[ control ],
|
|
controlOpts = self.options.controls,
|
|
stops;
|
|
|
|
// don't paint the active control
|
|
if ( control === self.active || ( self.active === 'square' && control !== 'strip' ) ) {
|
|
return;
|
|
}
|
|
|
|
switch ( controlOpts[ control ] ) {
|
|
case 'h':
|
|
if ( mode === 'hsv' ) {
|
|
color = c.clone();
|
|
switch ( control ) {
|
|
case 'horiz':
|
|
color[controlOpts.vert](100);
|
|
break;
|
|
case 'vert':
|
|
color[controlOpts.horiz](100);
|
|
break;
|
|
case 'strip':
|
|
color.setHSpace('hsl');
|
|
break;
|
|
}
|
|
stops = color.toHsl();
|
|
} else {
|
|
if ( control === 'strip' ) {
|
|
stops = { s: color.s, l: color.l };
|
|
} else {
|
|
stops = { s: 100, l: color.l };
|
|
}
|
|
}
|
|
|
|
target.raninbowGradient( origin, stops );
|
|
break;
|
|
case 's':
|
|
if ( mode === 'hsv' ) {
|
|
if ( control === 'vert' ) {
|
|
stops = [ c.clone().a(0).s(0).toCSS('rgba'), c.clone().a(1).s(0).toCSS('rgba') ];
|
|
} else if ( control === 'strip' ) {
|
|
stops = [ c.clone().s(100).toCSS('hsl'), c.clone().s(0).toCSS('hsl') ];
|
|
} else if ( control === 'horiz' ) {
|
|
stops = [ '#fff', 'hsl(' + color.h + ',100%,50%)' ];
|
|
}
|
|
} else { // implicit mode === 'hsl'
|
|
if ( control === 'vert' && self.options.controls.horiz === 'h' ) {
|
|
stops = ['hsla(0, 0%, ' + color.l + '%, 0)', 'hsla(0, 0%, ' + color.l + '%, 1)'];
|
|
} else {
|
|
stops = ['hsl('+ color.h +',0%,50%)', 'hsl(' + color.h + ',100%,50%)'];
|
|
}
|
|
}
|
|
|
|
|
|
target.gradient( origin, stops );
|
|
break;
|
|
case 'l':
|
|
if ( control === 'strip' ) {
|
|
stops = ['hsl(' + color.h + ',100%,100%)', 'hsl(' + color.h + ', ' + color.s + '%,50%)', 'hsl('+ color.h +',100%,0%)'];
|
|
} else {
|
|
stops = ['#fff', 'rgba(255,255,255,0) 50%', 'rgba(0,0,0,0) 50%', 'rgba(0,0,0,1)'];
|
|
}
|
|
target.gradient( origin, stops );
|
|
break;
|
|
case 'v':
|
|
if ( control === 'strip' ) {
|
|
stops = [ c.clone().v(100).toCSS(), c.clone().v(0).toCSS() ];
|
|
} else {
|
|
stops = ['rgba(0,0,0,0)', '#000'];
|
|
}
|
|
target.gradient( origin, stops );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
|
|
_getHSpaceColor: function() {
|
|
return ( this.options.mode === 'hsv' ) ? this._color.toHsv() : this._color.toHsl();
|
|
},
|
|
|
|
_dimensions: function( reset ) {
|
|
// whatever size
|
|
var self = this,
|
|
opts = self.options,
|
|
controls = self.controls,
|
|
square = controls.square,
|
|
strip = self.picker.find( '.iris-strip' ),
|
|
squareWidth = '77.5%',
|
|
stripWidth = '12%',
|
|
totalPadding = 20,
|
|
innerWidth = opts.border ? opts.width - totalPadding : opts.width,
|
|
controlsHeight,
|
|
paletteCount = $.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length,
|
|
paletteMargin, paletteWidth, paletteContainerWidth;
|
|
|
|
if ( reset ) {
|
|
square.css( 'width', '' );
|
|
strip.css( 'width', '' );
|
|
self.picker.css( {width: '', height: ''} );
|
|
}
|
|
|
|
squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 );
|
|
stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 );
|
|
controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth;
|
|
|
|
square.width( squareWidth ).height( squareWidth );
|
|
strip.height( squareWidth ).width( stripWidth );
|
|
self.picker.css( { width: opts.width, height: controlsHeight } );
|
|
|
|
if ( ! opts.palettes ) {
|
|
return self.picker.css( 'paddingBottom', '' );
|
|
}
|
|
|
|
// single margin at 2%
|
|
paletteMargin = squareWidth * 2 / 100;
|
|
paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin );
|
|
paletteWidth = paletteContainerWidth / paletteCount;
|
|
self.picker.find('.iris-palette').each( function( i ) {
|
|
var margin = i === 0 ? 0 : paletteMargin;
|
|
$( this ).css({
|
|
width: paletteWidth,
|
|
height: paletteWidth,
|
|
marginLeft: margin
|
|
});
|
|
});
|
|
self.picker.css( 'paddingBottom', paletteWidth + paletteMargin );
|
|
strip.height( paletteWidth + paletteMargin + squareWidth );
|
|
},
|
|
|
|
_addInputListeners: function( input ) {
|
|
var self = this,
|
|
debounceTimeout = 100,
|
|
callback = function( event ){
|
|
var color = new Color( input.val() ),
|
|
val = input.val().replace( /^#/, '' );
|
|
|
|
input.removeClass( 'iris-error' );
|
|
// we gave a bad color
|
|
if ( color.error ) {
|
|
// don't error on an empty input - we want those allowed
|
|
if ( val !== '' ) {
|
|
input.addClass( 'iris-error' );
|
|
}
|
|
} else {
|
|
if ( color.toString() !== self._color.toString() ) {
|
|
// let's not do this on keyup for hex shortcodes
|
|
if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) {
|
|
self._setOption( 'color', color.toString() );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) );
|
|
|
|
// If we initialized hidden, show on first focus. The rest is up to you.
|
|
if ( self.options.hide ) {
|
|
input.one( 'focus', function() {
|
|
self.show();
|
|
});
|
|
}
|
|
},
|
|
|
|
_initControls: function() {
|
|
var self = this,
|
|
controls = self.controls,
|
|
square = controls.square,
|
|
controlOpts = self.options.controls,
|
|
stripScale = self._scale[controlOpts.strip];
|
|
|
|
controls.stripSlider.slider({
|
|
orientation: 'vertical',
|
|
max: stripScale,
|
|
slide: function( event, ui ) {
|
|
self.active = 'strip';
|
|
// "reverse" for hue.
|
|
if ( controlOpts.strip === 'h' ) {
|
|
ui.value = stripScale - ui.value;
|
|
}
|
|
|
|
self._color[controlOpts.strip]( ui.value );
|
|
self._change.apply( self, arguments );
|
|
}
|
|
});
|
|
|
|
controls.squareDrag.draggable({
|
|
containment: controls.square.find( '.iris-square-inner' ),
|
|
zIndex: 1000,
|
|
cursor: 'move',
|
|
drag: function( event, ui ) {
|
|
self._squareDrag( event, ui );
|
|
},
|
|
start: function() {
|
|
square.addClass( 'iris-dragging' );
|
|
$(this).addClass( 'ui-state-focus' );
|
|
},
|
|
stop: function() {
|
|
square.removeClass( 'iris-dragging' );
|
|
$(this).removeClass( 'ui-state-focus' );
|
|
}
|
|
}).on( 'mousedown mouseup', function( event ) {
|
|
var focusClass = 'ui-state-focus';
|
|
event.preventDefault();
|
|
if (event.type === 'mousedown' ) {
|
|
self.picker.find( '.' + focusClass ).removeClass( focusClass ).blur();
|
|
$(this).addClass( focusClass ).focus();
|
|
} else {
|
|
$(this).removeClass( focusClass );
|
|
}
|
|
}).on( 'keydown', function( event ) {
|
|
var container = controls.square,
|
|
draggable = controls.squareDrag,
|
|
position = draggable.position(),
|
|
distance = self.options.width / 100; // Distance in pixels the draggable should be moved: 1 "stop"
|
|
|
|
// make alt key go "10"
|
|
if ( event.altKey ) {
|
|
distance *= 10;
|
|
}
|
|
|
|
// Reposition if one of the directional keys is pressed
|
|
switch ( event.keyCode ) {
|
|
case 37: position.left -= distance; break; // Left
|
|
case 38: position.top -= distance; break; // Up
|
|
case 39: position.left += distance; break; // Right
|
|
case 40: position.top += distance; break; // Down
|
|
default: return true; // Exit and bubble
|
|
}
|
|
|
|
// Keep draggable within container
|
|
position.left = Math.max( 0, Math.min( position.left, container.width() ) );
|
|
position.top = Math.max( 0, Math.min( position.top, container.height() ) );
|
|
|
|
draggable.css(position);
|
|
self._squareDrag( event, { position: position });
|
|
event.preventDefault();
|
|
});
|
|
|
|
// allow clicking on the square to move there and keep dragging
|
|
square.mousedown( function( event ) {
|
|
var squareOffset, pos;
|
|
// only left click
|
|
if ( event.which !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
// prevent bubbling from the handle: no infinite loops
|
|
if ( ! $( event.target ).is( 'div' ) ) {
|
|
return;
|
|
}
|
|
|
|
squareOffset = self.controls.square.offset();
|
|
pos = {
|
|
top: event.pageY - squareOffset.top,
|
|
left: event.pageX - squareOffset.left
|
|
};
|
|
event.preventDefault();
|
|
self._squareDrag( event, { position: pos } );
|
|
event.target = self.controls.squareDrag.get(0);
|
|
self.controls.squareDrag.css( pos ).trigger( event );
|
|
});
|
|
|
|
// palettes
|
|
if ( self.options.palettes ) {
|
|
self._paletteListeners();
|
|
}
|
|
},
|
|
|
|
_paletteListeners: function() {
|
|
var self = this;
|
|
self.picker.find('.iris-palette-container').on('click.palette', '.iris-palette', function() {
|
|
self._color.fromCSS( $(this).data('color') );
|
|
self.active = 'external';
|
|
self._change();
|
|
}).on( 'keydown.palette', '.iris-palette', function( event ) {
|
|
if ( ! ( event.keyCode === 13 || event.keyCode === 32 ) ) {
|
|
return true;
|
|
}
|
|
event.stopPropagation();
|
|
$( this ).click();
|
|
});
|
|
},
|
|
|
|
_squareDrag: function( event, ui ) {
|
|
var self = this,
|
|
controlOpts = self.options.controls,
|
|
dimensions = self._squareDimensions(),
|
|
vertVal = Math.round( ( dimensions.h - ui.position.top ) / dimensions.h * self._scale[controlOpts.vert] ),
|
|
horizVal = self._scale[controlOpts.horiz] - Math.round( ( dimensions.w - ui.position.left ) / dimensions.w * self._scale[controlOpts.horiz] );
|
|
|
|
self._color[controlOpts.horiz]( horizVal )[controlOpts.vert]( vertVal );
|
|
|
|
self.active = 'square';
|
|
self._change.apply( self, arguments );
|
|
},
|
|
|
|
_setOption: function( key, value ) {
|
|
var self = this,
|
|
oldValue = self.options[key],
|
|
doDimensions = false,
|
|
hexLessColor,
|
|
newColor,
|
|
method;
|
|
|
|
// ensure the new value is set. We can reset to oldValue if some check wasn't met.
|
|
self.options[key] = value;
|
|
|
|
switch(key) {
|
|
case 'color':
|
|
// cast to string in case we have a number
|
|
value = '' + value;
|
|
hexLessColor = value.replace( /^#/, '' );
|
|
newColor = new Color( value ).setHSpace( self.options.mode );
|
|
if ( newColor.error ) {
|
|
self.options[key] = oldValue;
|
|
} else {
|
|
self._color = newColor;
|
|
self.options.color = self.options[key] = self._color.toString();
|
|
self.active = 'external';
|
|
self._change();
|
|
}
|
|
break;
|
|
case 'palettes':
|
|
doDimensions = true;
|
|
|
|
if ( value ) {
|
|
self._addPalettes();
|
|
} else {
|
|
self.picker.find('.iris-palette-container').remove();
|
|
}
|
|
|
|
// do we need to add events?
|
|
if ( ! oldValue ) {
|
|
self._paletteListeners();
|
|
}
|
|
break;
|
|
case 'width':
|
|
doDimensions = true;
|
|
break;
|
|
case 'border':
|
|
doDimensions = true;
|
|
method = value ? 'addClass' : 'removeClass';
|
|
self.picker[method]('iris-border');
|
|
break;
|
|
case 'mode':
|
|
case 'controls':
|
|
// if nothing's changed, let's bail, since this causes re-rendering the whole widget
|
|
if ( oldValue === value ) {
|
|
return;
|
|
}
|
|
|
|
// we're using these poorly named variables because they're already scoped.
|
|
// method is the element that Iris was called on. oldValue will be the options
|
|
method = self.element;
|
|
oldValue = self.options;
|
|
oldValue.hide = ! self.picker.is( ':visible' );
|
|
self.destroy();
|
|
self.picker.remove();
|
|
return $(self.element).iris(oldValue);
|
|
}
|
|
|
|
// Do we need to recalc dimensions?
|
|
if ( doDimensions ) {
|
|
self._dimensions(true);
|
|
}
|
|
},
|
|
|
|
_squareDimensions: function( forceRefresh ) {
|
|
var square = this.controls.square,
|
|
dimensions,
|
|
control;
|
|
|
|
if ( forceRefresh !== undef && square.data('dimensions') ) {
|
|
return square.data('dimensions');
|
|
}
|
|
|
|
control = this.controls.squareDrag;
|
|
dimensions = {
|
|
w: square.width(),
|
|
h: square.height()
|
|
};
|
|
square.data( 'dimensions', dimensions );
|
|
return dimensions;
|
|
},
|
|
|
|
_isNonHueControl: function( active, type ) {
|
|
if ( active === 'square' && this.options.controls.strip === 'h' ) {
|
|
return true;
|
|
} else if ( type === 'external' || ( type === 'h' && active === 'strip' ) ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
_change: function() {
|
|
var self = this,
|
|
controls = self.controls,
|
|
color = self._getHSpaceColor(),
|
|
actions = [ 'square', 'strip' ],
|
|
controlOpts = self.options.controls,
|
|
type = controlOpts[self.active] || 'external',
|
|
oldHue = self.hue;
|
|
|
|
if ( self.active === 'strip' ) {
|
|
// take no action on any of the square sliders if we adjusted the strip
|
|
actions = [];
|
|
} else if ( self.active !== 'external' ) {
|
|
// for non-strip, non-external, strip should never change
|
|
actions.pop(); // conveniently the last item
|
|
}
|
|
|
|
$.each( actions, function(index, item) {
|
|
var value, dimensions, cssObj;
|
|
if ( item !== self.active ) {
|
|
switch ( item ) {
|
|
case 'strip':
|
|
// reverse for hue
|
|
value = ( controlOpts.strip === 'h' ) ? self._scale[controlOpts.strip] - color[controlOpts.strip] : color[controlOpts.strip];
|
|
controls.stripSlider.slider( 'value', value );
|
|
break;
|
|
case 'square':
|
|
dimensions = self._squareDimensions();
|
|
cssObj = {
|
|
left: color[controlOpts.horiz] / self._scale[controlOpts.horiz] * dimensions.w,
|
|
top: dimensions.h - ( color[controlOpts.vert] / self._scale[controlOpts.vert] * dimensions.h )
|
|
};
|
|
|
|
self.controls.squareDrag.css( cssObj );
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Ensure that we don't change hue if we triggered a hue reset
|
|
if ( color.h !== oldHue && self._isNonHueControl( self.active, type ) ) {
|
|
self._color.h(oldHue);
|
|
}
|
|
|
|
// store hue for repeating above check next time
|
|
self.hue = self._color.h();
|
|
|
|
self.options.color = self._color.toString();
|
|
|
|
// only run after the first time
|
|
if ( self._inited ) {
|
|
self._trigger( 'change', { type: self.active }, { color: self._color } );
|
|
}
|
|
|
|
if ( self.element.is( ':input' ) && ! self._color.error ) {
|
|
self.element.removeClass( 'iris-error' );
|
|
if ( self.element.val() !== self._color.toString() ) {
|
|
self.element.val( self._color.toString() );
|
|
}
|
|
}
|
|
|
|
self._paint();
|
|
self._inited = true;
|
|
self.active = false;
|
|
},
|
|
// taken from underscore.js _.debounce method
|
|
_debounce: function( func, wait, immediate ) {
|
|
var timeout, result;
|
|
return function() {
|
|
var context = this,
|
|
args = arguments,
|
|
later,
|
|
callNow;
|
|
|
|
later = function() {
|
|
timeout = null;
|
|
if ( ! immediate) {
|
|
result = func.apply( context, args );
|
|
}
|
|
};
|
|
|
|
callNow = immediate && !timeout;
|
|
clearTimeout( timeout );
|
|
timeout = setTimeout( later, wait );
|
|
if ( callNow ) {
|
|
result = func.apply( context, args );
|
|
}
|
|
return result;
|
|
};
|
|
},
|
|
show: function() {
|
|
this.picker.show();
|
|
},
|
|
hide: function() {
|
|
this.picker.hide();
|
|
},
|
|
toggle: function() {
|
|
this.picker.toggle();
|
|
},
|
|
color: function(newColor) {
|
|
if ( newColor === true ) {
|
|
return this._color.clone();
|
|
} else if ( newColor === undef ) {
|
|
return this._color.toString();
|
|
}
|
|
this.option('color', newColor);
|
|
}
|
|
};
|
|
// initialize the widget
|
|
$.widget( 'a8c.iris', Iris );
|
|
// add CSS
|
|
$( '<style id="iris-css">' + _css + '</style>' ).appendTo( 'head' );
|
|
|
|
}( jQuery ));
|
|
/*! Color.js - v0.9.11 - 2013-08-09
|
|
* https://github.com/Automattic/Color.js
|
|
* Copyright (c) 2013 Matt Wiebe; Licensed GPLv2 */
|
|
(function(global, undef) {
|
|
|
|
var Color = function( color, type ) {
|
|
if ( ! ( this instanceof Color ) )
|
|
return new Color( color, type );
|
|
|
|
return this._init( color, type );
|
|
};
|
|
|
|
Color.fn = Color.prototype = {
|
|
_color: 0,
|
|
_alpha: 1,
|
|
error: false,
|
|
// for preserving hue/sat in fromHsl().toHsl() flows
|
|
_hsl: { h: 0, s: 0, l: 0 },
|
|
// for preserving hue/sat in fromHsv().toHsv() flows
|
|
_hsv: { h: 0, s: 0, v: 0 },
|
|
// for setting hsl or hsv space - needed for .h() & .s() functions to function properly
|
|
_hSpace: 'hsl',
|
|
_init: function( color ) {
|
|
var func = 'noop';
|
|
switch ( typeof color ) {
|
|
case 'object':
|
|
// alpha?
|
|
if ( color.a !== undef )
|
|
this.a( color.a );
|
|
func = ( color.r !== undef ) ? 'fromRgb' :
|
|
( color.l !== undef ) ? 'fromHsl' :
|
|
( color.v !== undef ) ? 'fromHsv' : func;
|
|
return this[func]( color );
|
|
case 'string':
|
|
return this.fromCSS( color );
|
|
case 'number':
|
|
return this.fromInt( parseInt( color, 10 ) );
|
|
}
|
|
return this;
|
|
},
|
|
|
|
_error: function() {
|
|
this.error = true;
|
|
return this;
|
|
},
|
|
|
|
clone: function() {
|
|
var newColor = new Color( this.toInt() ),
|
|
copy = ['_alpha', '_hSpace', '_hsl', '_hsv', 'error'];
|
|
for ( var i = copy.length - 1; i >= 0; i-- ) {
|
|
newColor[ copy[i] ] = this[ copy[i] ];
|
|
}
|
|
return newColor;
|
|
},
|
|
|
|
setHSpace: function( space ) {
|
|
this._hSpace = ( space === 'hsv' ) ? space : 'hsl';
|
|
return this;
|
|
},
|
|
|
|
noop: function() {
|
|
return this;
|
|
},
|
|
|
|
fromCSS: function( color ) {
|
|
var list,
|
|
leadingRE = /^(rgb|hs(l|v))a?\(/;
|
|
this.error = false;
|
|
|
|
// whitespace and semicolon trim
|
|
color = color.replace(/^\s+/, '').replace(/\s+$/, '').replace(/;$/, '');
|
|
|
|
if ( color.match(leadingRE) && color.match(/\)$/) ) {
|
|
list = color.replace(/(\s|%)/g, '').replace(leadingRE, '').replace(/,?\);?$/, '').split(',');
|
|
|
|
if ( list.length < 3 )
|
|
return this._error();
|
|
|
|
if ( list.length === 4 ) {
|
|
this.a( parseFloat( list.pop() ) );
|
|
// error state has been set to true in .a() if we passed NaN
|
|
if ( this.error )
|
|
return this;
|
|
}
|
|
|
|
for (var i = list.length - 1; i >= 0; i--) {
|
|
list[i] = parseInt(list[i], 10);
|
|
if ( isNaN( list[i] ) )
|
|
return this._error();
|
|
}
|
|
|
|
if ( color.match(/^rgb/) ) {
|
|
return this.fromRgb( {
|
|
r: list[0],
|
|
g: list[1],
|
|
b: list[2]
|
|
} );
|
|
} else if ( color.match(/^hsv/) ) {
|
|
return this.fromHsv( {
|
|
h: list[0],
|
|
s: list[1],
|
|
v: list[2]
|
|
} );
|
|
} else {
|
|
return this.fromHsl( {
|
|
h: list[0],
|
|
s: list[1],
|
|
l: list[2]
|
|
} );
|
|
}
|
|
} else {
|
|
// must be hex amirite?
|
|
return this.fromHex( color );
|
|
}
|
|
},
|
|
|
|
fromRgb: function( rgb, preserve ) {
|
|
if ( typeof rgb !== 'object' || rgb.r === undef || rgb.g === undef || rgb.b === undef )
|
|
return this._error();
|
|
|
|
this.error = false;
|
|
return this.fromInt( parseInt( ( rgb.r << 16 ) + ( rgb.g << 8 ) + rgb.b, 10 ), preserve );
|
|
},
|
|
|
|
fromHex: function( color ) {
|
|
color = color.replace(/^#/, '').replace(/^0x/, '');
|
|
if ( color.length === 3 ) {
|
|
color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
|
|
}
|
|
|
|
// rough error checking - this is where things go squirrely the most
|
|
this.error = ! /^[0-9A-F]{6}$/i.test( color );
|
|
return this.fromInt( parseInt( color, 16 ) );
|
|
},
|
|
|
|
fromHsl: function( hsl ) {
|
|
var r, g, b, q, p, h, s, l;
|
|
|
|
if ( typeof hsl !== 'object' || hsl.h === undef || hsl.s === undef || hsl.l === undef )
|
|
return this._error();
|
|
|
|
this._hsl = hsl; // store it
|
|
this._hSpace = 'hsl'; // implicit
|
|
h = hsl.h / 360; s = hsl.s / 100; l = hsl.l / 100;
|
|
if ( s === 0 ) {
|
|
r = g = b = l; // achromatic
|
|
}
|
|
else {
|
|
q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s;
|
|
p = 2 * l - q;
|
|
r = this.hue2rgb( p, q, h + 1/3 );
|
|
g = this.hue2rgb( p, q, h );
|
|
b = this.hue2rgb( p, q, h - 1/3 );
|
|
}
|
|
return this.fromRgb( {
|
|
r: r * 255,
|
|
g: g * 255,
|
|
b: b * 255
|
|
}, true ); // true preserves hue/sat
|
|
},
|
|
|
|
fromHsv: function( hsv ) {
|
|
var h, s, v, r, g, b, i, f, p, q, t;
|
|
if ( typeof hsv !== 'object' || hsv.h === undef || hsv.s === undef || hsv.v === undef )
|
|
return this._error();
|
|
|
|
this._hsv = hsv; // store it
|
|
this._hSpace = 'hsv'; // implicit
|
|
|
|
h = hsv.h / 360; s = hsv.s / 100; v = hsv.v / 100;
|
|
i = Math.floor( h * 6 );
|
|
f = h * 6 - i;
|
|
p = v * ( 1 - s );
|
|
q = v * ( 1 - f * s );
|
|
t = v * ( 1 - ( 1 - f ) * s );
|
|
|
|
switch( i % 6 ) {
|
|
case 0:
|
|
r = v; g = t; b = p;
|
|
break;
|
|
case 1:
|
|
r = q; g = v; b = p;
|
|
break;
|
|
case 2:
|
|
r = p; g = v; b = t;
|
|
break;
|
|
case 3:
|
|
r = p; g = q; b = v;
|
|
break;
|
|
case 4:
|
|
r = t; g = p; b = v;
|
|
break;
|
|
case 5:
|
|
r = v; g = p; b = q;
|
|
break;
|
|
}
|
|
|
|
return this.fromRgb( {
|
|
r: r * 255,
|
|
g: g * 255,
|
|
b: b * 255
|
|
}, true ); // true preserves hue/sat
|
|
|
|
},
|
|
// everything comes down to fromInt
|
|
fromInt: function( color, preserve ) {
|
|
this._color = parseInt( color, 10 );
|
|
|
|
if ( isNaN( this._color ) )
|
|
this._color = 0;
|
|
|
|
// let's coerce things
|
|
if ( this._color > 16777215 )
|
|
this._color = 16777215;
|
|
else if ( this._color < 0 )
|
|
this._color = 0;
|
|
|
|
// let's not do weird things
|
|
if ( preserve === undef ) {
|
|
this._hsv.h = this._hsv.s = this._hsl.h = this._hsl.s = 0;
|
|
}
|
|
// EVENT GOES HERE
|
|
return this;
|
|
},
|
|
|
|
hue2rgb: function( p, q, t ) {
|
|
if ( t < 0 ) {
|
|
t += 1;
|
|
}
|
|
if ( t > 1 ) {
|
|
t -= 1;
|
|
}
|
|
if ( t < 1/6 ) {
|
|
return p + ( q - p ) * 6 * t;
|
|
}
|
|
if ( t < 1/2 ) {
|
|
return q;
|
|
}
|
|
if ( t < 2/3 ) {
|
|
return p + ( q - p ) * ( 2/3 - t ) * 6;
|
|
}
|
|
return p;
|
|
},
|
|
|
|
toString: function() {
|
|
var hex = parseInt( this._color, 10 ).toString( 16 );
|
|
if ( this.error )
|
|
return '';
|
|
// maybe left pad it
|
|
if ( hex.length < 6 ) {
|
|
for (var i = 6 - hex.length - 1; i >= 0; i--) {
|
|
hex = '0' + hex;
|
|
}
|
|
}
|
|
return '#' + hex;
|
|
},
|
|
|
|
toCSS: function( type, alpha ) {
|
|
type = type || 'hex';
|
|
alpha = parseFloat( alpha || this._alpha );
|
|
switch ( type ) {
|
|
case 'rgb':
|
|
case 'rgba':
|
|
var rgb = this.toRgb();
|
|
if ( alpha < 1 ) {
|
|
return "rgba( " + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + alpha + " )";
|
|
}
|
|
else {
|
|
return "rgb( " + rgb.r + ", " + rgb.g + ", " + rgb.b + " )";
|
|
}
|
|
break;
|
|
case 'hsl':
|
|
case 'hsla':
|
|
var hsl = this.toHsl();
|
|
if ( alpha < 1 ) {
|
|
return "hsla( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "%, " + alpha + " )";
|
|
}
|
|
else {
|
|
return "hsl( " + hsl.h + ", " + hsl.s + "%, " + hsl.l + "% )";
|
|
}
|
|
break;
|
|
default:
|
|
return this.toString();
|
|
}
|
|
},
|
|
|
|
toRgb: function() {
|
|
return {
|
|
r: 255 & ( this._color >> 16 ),
|
|
g: 255 & ( this._color >> 8 ),
|
|
b: 255 & ( this._color )
|
|
};
|
|
},
|
|
|
|
toHsl: function() {
|
|
var rgb = this.toRgb();
|
|
var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255;
|
|
var max = Math.max( r, g, b ), min = Math.min( r, g, b );
|
|
var h, s, l = ( max + min ) / 2;
|
|
|
|
if ( max === min ) {
|
|
h = s = 0; // achromatic
|
|
} else {
|
|
var d = max - min;
|
|
s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min );
|
|
switch ( max ) {
|
|
case r: h = ( g - b ) / d + ( g < b ? 6 : 0 );
|
|
break;
|
|
case g: h = ( b - r ) / d + 2;
|
|
break;
|
|
case b: h = ( r - g ) / d + 4;
|
|
break;
|
|
}
|
|
h /= 6;
|
|
}
|
|
|
|
// maintain hue & sat if we've been manipulating things in the HSL space.
|
|
h = Math.round( h * 360 );
|
|
if ( h === 0 && this._hsl.h !== h ) {
|
|
h = this._hsl.h;
|
|
}
|
|
s = Math.round( s * 100 );
|
|
if ( s === 0 && this._hsl.s ) {
|
|
s = this._hsl.s;
|
|
}
|
|
|
|
return {
|
|
h: h,
|
|
s: s,
|
|
l: Math.round( l * 100 )
|
|
};
|
|
|
|
},
|
|
|
|
toHsv: function() {
|
|
var rgb = this.toRgb();
|
|
var r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255;
|
|
var max = Math.max( r, g, b ), min = Math.min( r, g, b );
|
|
var h, s, v = max;
|
|
var d = max - min;
|
|
s = max === 0 ? 0 : d / max;
|
|
|
|
if ( max === min ) {
|
|
h = s = 0; // achromatic
|
|
} else {
|
|
switch( max ){
|
|
case r:
|
|
h = ( g - b ) / d + ( g < b ? 6 : 0 );
|
|
break;
|
|
case g:
|
|
h = ( b - r ) / d + 2;
|
|
break;
|
|
case b:
|
|
h = ( r - g ) / d + 4;
|
|
break;
|
|
}
|
|
h /= 6;
|
|
}
|
|
|
|
// maintain hue & sat if we've been manipulating things in the HSV space.
|
|
h = Math.round( h * 360 );
|
|
if ( h === 0 && this._hsv.h !== h ) {
|
|
h = this._hsv.h;
|
|
}
|
|
s = Math.round( s * 100 );
|
|
if ( s === 0 && this._hsv.s ) {
|
|
s = this._hsv.s;
|
|
}
|
|
|
|
return {
|
|
h: h,
|
|
s: s,
|
|
v: Math.round( v * 100 )
|
|
};
|
|
},
|
|
|
|
toInt: function() {
|
|
return this._color;
|
|
},
|
|
|
|
toIEOctoHex: function() {
|
|
// AARRBBGG
|
|
var hex = this.toString();
|
|
var AA = parseInt( 255 * this._alpha, 10 ).toString(16);
|
|
if ( AA.length === 1 ) {
|
|
AA = '0' + AA;
|
|
}
|
|
return '#' + AA + hex.replace(/^#/, '' );
|
|
},
|
|
|
|
toLuminosity: function() {
|
|
var rgb = this.toRgb();
|
|
return 0.2126 * Math.pow( rgb.r / 255, 2.2 ) + 0.7152 * Math.pow( rgb.g / 255, 2.2 ) + 0.0722 * Math.pow( rgb.b / 255, 2.2);
|
|
},
|
|
|
|
getDistanceLuminosityFrom: function( color ) {
|
|
if ( ! ( color instanceof Color ) ) {
|
|
throw 'getDistanceLuminosityFrom requires a Color object';
|
|
}
|
|
var lum1 = this.toLuminosity();
|
|
var lum2 = color.toLuminosity();
|
|
if ( lum1 > lum2 ) {
|
|
return ( lum1 + 0.05 ) / ( lum2 + 0.05 );
|
|
}
|
|
else {
|
|
return ( lum2 + 0.05 ) / ( lum1 + 0.05 );
|
|
}
|
|
},
|
|
|
|
getMaxContrastColor: function() {
|
|
var lum = this.toLuminosity();
|
|
var hex = ( lum >= 0.5 ) ? '000000' : 'ffffff';
|
|
return new Color( hex );
|
|
},
|
|
|
|
getReadableContrastingColor: function( bgColor, minContrast ) {
|
|
if ( ! bgColor instanceof Color ) {
|
|
return this;
|
|
}
|
|
|
|
// you shouldn't use less than 5, but you might want to.
|
|
var targetContrast = ( minContrast === undef ) ? 5 : minContrast;
|
|
// working things
|
|
var contrast = bgColor.getDistanceLuminosityFrom( this );
|
|
var maxContrastColor = bgColor.getMaxContrastColor();
|
|
var maxContrast = maxContrastColor.getDistanceLuminosityFrom( bgColor );
|
|
|
|
// if current max contrast is less than the target contrast, we had wishful thinking.
|
|
// still, go max
|
|
if ( maxContrast <= targetContrast ) {
|
|
return maxContrastColor;
|
|
}
|
|
// or, we might already have sufficient contrast
|
|
else if ( contrast >= targetContrast ) {
|
|
return this;
|
|
}
|
|
|
|
var incr = ( 0 === maxContrastColor.toInt() ) ? -1 : 1;
|
|
while ( contrast < targetContrast ) {
|
|
this.l( incr, true ); // 2nd arg turns this into an incrementer
|
|
contrast = this.getDistanceLuminosityFrom( bgColor );
|
|
// infininite loop prevention: you never know.
|
|
if ( this._color === 0 || this._color === 16777215 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
a: function( val ) {
|
|
if ( val === undef )
|
|
return this._alpha;
|
|
|
|
var a = parseFloat( val );
|
|
|
|
if ( isNaN( a ) )
|
|
return this._error();
|
|
|
|
this._alpha = a;
|
|
return this;
|
|
},
|
|
|
|
// TRANSFORMS
|
|
|
|
darken: function( amount ) {
|
|
amount = amount || 5;
|
|
return this.l( - amount, true );
|
|
},
|
|
|
|
lighten: function( amount ) {
|
|
amount = amount || 5;
|
|
return this.l( amount, true );
|
|
},
|
|
|
|
saturate: function( amount ) {
|
|
amount = amount || 15;
|
|
return this.s( amount, true );
|
|
},
|
|
|
|
desaturate: function( amount ) {
|
|
amount = amount || 15;
|
|
return this.s( - amount, true );
|
|
},
|
|
|
|
toGrayscale: function() {
|
|
return this.setHSpace('hsl').s( 0 );
|
|
},
|
|
|
|
getComplement: function() {
|
|
return this.h( 180, true );
|
|
},
|
|
|
|
getSplitComplement: function( step ) {
|
|
step = step || 1;
|
|
var incr = 180 + ( step * 30 );
|
|
return this.h( incr, true );
|
|
},
|
|
|
|
getAnalog: function( step ) {
|
|
step = step || 1;
|
|
var incr = step * 30;
|
|
return this.h( incr, true );
|
|
},
|
|
|
|
getTetrad: function( step ) {
|
|
step = step || 1;
|
|
var incr = step * 60;
|
|
return this.h( incr, true );
|
|
},
|
|
|
|
getTriad: function( step ) {
|
|
step = step || 1;
|
|
var incr = step * 120;
|
|
return this.h( incr, true );
|
|
},
|
|
|
|
_partial: function( key ) {
|
|
var prop = shortProps[key];
|
|
return function( val, incr ) {
|
|
var color = this._spaceFunc('to', prop.space);
|
|
|
|
// GETTER
|
|
if ( val === undef )
|
|
return color[key];
|
|
|
|
// INCREMENT
|
|
if ( incr === true )
|
|
val = color[key] + val;
|
|
|
|
// MOD & RANGE
|
|
if ( prop.mod )
|
|
val = val % prop.mod;
|
|
if ( prop.range )
|
|
val = ( val < prop.range[0] ) ? prop.range[0] : ( val > prop.range[1] ) ? prop.range[1] : val;
|
|
|
|
// NEW VALUE
|
|
color[key] = val;
|
|
|
|
return this._spaceFunc('from', prop.space, color);
|
|
};
|
|
},
|
|
|
|
_spaceFunc: function( dir, s, val ) {
|
|
var space = s || this._hSpace,
|
|
funcName = dir + space.charAt(0).toUpperCase() + space.substr(1);
|
|
return this[funcName](val);
|
|
}
|
|
};
|
|
|
|
var shortProps = {
|
|
h: {
|
|
mod: 360
|
|
},
|
|
s: {
|
|
range: [0,100]
|
|
},
|
|
l: {
|
|
space: 'hsl',
|
|
range: [0,100]
|
|
},
|
|
v: {
|
|
space: 'hsv',
|
|
range: [0,100]
|
|
},
|
|
r: {
|
|
space: 'rgb',
|
|
range: [0,255]
|
|
},
|
|
g: {
|
|
space: 'rgb',
|
|
range: [0,255]
|
|
},
|
|
b: {
|
|
space: 'rgb',
|
|
range: [0,255]
|
|
}
|
|
};
|
|
|
|
for ( var key in shortProps ) {
|
|
if ( shortProps.hasOwnProperty( key ) )
|
|
Color.fn[key] = Color.fn._partial(key);
|
|
}
|
|
|
|
// play nicely with Node + browser
|
|
if ( typeof exports === 'object' )
|
|
module.exports = Color;
|
|
else
|
|
global.Color = Color;
|
|
|
|
}(this));
|