| 1 |
/* global Color, jQuery */ |
| 2 |
(function( $, undef ){ |
| 3 |
var _html, nonGradientIE, gradientType, vendorPrefixes, _css, Iris, UA, isIE, IEVersion; |
| 4 |
|
| 5 |
_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>'; |
| 6 |
// Even IE9 dosen't support gradients. Elaborate sigh. |
| 7 |
UA = navigator.userAgent.toLowerCase(); |
| 8 |
isIE = navigator.appName === 'Microsoft Internet Explorer'; |
| 9 |
IEVersion = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0; |
| 10 |
nonGradientIE = ( isIE && IEVersion < 10 ); |
| 11 |
gradientType = false; |
| 12 |
// we don't bother with an unprefixed version, as it has a different syntax |
| 13 |
vendorPrefixes = [ '-moz-', '-webkit-', '-o-', '-ms-' ]; |
| 14 |
// This is manually copied from iris.min.css until I can figure out how to do it without |
| 15 |
_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;top:none;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}'; |
| 16 |
// Bail for IE <= 7 |
| 17 |
if ( nonGradientIE && IEVersion <= 7 ) { |
| 18 |
$.fn.iris = $.noop; |
| 19 |
$.support.iris = false; |
| 20 |
return; |
| 21 |
} |
| 22 |
|
| 23 |
$.support.iris = true; |
| 24 |
|
| 25 |
function testGradientType() { |
| 26 |
var el, base; |
| 27 |
|
| 28 |
if ( nonGradientIE ) { |
| 29 |
gradientType = 'filter'; |
| 30 |
} |
| 31 |
else { |
| 32 |
el = $( '<div id="iris-gradtest" />' ); |
| 33 |
base = 'linear-gradient(top,#fff,#000)'; |
| 34 |
$.each( vendorPrefixes, function( i, val ){ |
| 35 |
el.css( 'backgroundImage', val + base ); |
| 36 |
if ( el.css( 'backgroundImage').match( 'gradient' ) ) { |
| 37 |
gradientType = i; |
| 38 |
return false; |
| 39 |
} |
| 40 |
}); |
| 41 |
// check for legacy webkit gradient syntax |
| 42 |
if ( gradientType === false ) { |
| 43 |
el.css( 'background', '-webkit-gradient(linear,0% 0%,0% 100%,from(#fff),to(#000))' ); |
| 44 |
if ( el.css( 'backgroundImage').match( 'gradient' ) ) { |
| 45 |
gradientType = 'webkit'; |
| 46 |
} |
| 47 |
} |
| 48 |
el.remove(); |
| 49 |
} |
| 50 |
|
| 51 |
} |
| 52 |
|
| 53 |
/** |
| 54 |
* Only for CSS3 gradients. oldIE will use a separate function. |
| 55 |
* |
| 56 |
* Accepts as many color stops as necessary from 2nd arg on, or 2nd |
| 57 |
* arg can be an array of color stops |
| 58 |
* |
| 59 |
* @param {string} origin Gradient origin - top or left, defaults to left. |
| 60 |
* @return {string} Appropriate CSS3 gradient string for use in |
| 61 |
*/ |
| 62 |
function createGradient( origin, stops ) { |
| 63 |
origin = ( origin === 'top' ) ? 'top' : 'left'; |
| 64 |
stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); |
| 65 |
if ( gradientType === 'webkit' ) { |
| 66 |
return legacyWebkitGradient( origin, stops ); |
| 67 |
} else { |
| 68 |
return vendorPrefixes[ gradientType ] + 'linear-gradient(' + origin + ', ' + stops.join(', ') + ')'; |
| 69 |
} |
| 70 |
} |
| 71 |
|
| 72 |
/** |
| 73 |
* Stupid gradients for a stupid browser. |
| 74 |
*/ |
| 75 |
function stupidIEGradient( origin, stops ) { |
| 76 |
var type, self, lastIndex, filter, startPosProp, endPosProp, dimensionProp, template, html; |
| 77 |
|
| 78 |
origin = ( origin === 'top' ) ? 'top' : 'left'; |
| 79 |
stops = $.isArray( stops ) ? stops : Array.prototype.slice.call( arguments, 1 ); |
| 80 |
// 8 hex: AARRGGBB |
| 81 |
// GradientType: 0 vertical, 1 horizontal |
| 82 |
type = ( origin === 'top' ) ? 0 : 1; |
| 83 |
self = $( this ); |
| 84 |
lastIndex = stops.length - 1; |
| 85 |
filter = 'filter'; |
| 86 |
startPosProp = ( type === 1 ) ? 'left' : 'top'; |
| 87 |
endPosProp = ( type === 1 ) ? 'right' : 'bottom'; |
| 88 |
dimensionProp = ( type === 1 ) ? 'height' : 'width'; |
| 89 |
template = '<div class="iris-ie-gradient-shim" style="position:absolute;' + dimensionProp + ':100%;' + startPosProp + ':%start%;' + endPosProp + ':%end%;' + filter + ':%filter%;" data-color:"%color%"></div>'; |
| 90 |
html = ''; |
| 91 |
// need a positioning context |
| 92 |
if ( self.css('position') === 'static' ) { |
| 93 |
self.css( {position: 'relative' } ); |
| 94 |
} |
| 95 |
|
| 96 |
stops = fillColorStops( stops ); |
| 97 |
$.each(stops, function( i, startColor ) { |
| 98 |
var endColor, endStop, filterVal; |
| 99 |
|
| 100 |
// we want two at a time. if we're on the last pair, bail. |
| 101 |
if ( i === lastIndex ) { |
| 102 |
return false; |
| 103 |
} |
| 104 |
|
| 105 |
endColor = stops[ i + 1 ]; |
| 106 |
//if our pairs are at the same color stop, moving along. |
| 107 |
if ( startColor.stop === endColor.stop ) { |
| 108 |
return; |
| 109 |
} |
| 110 |
|
| 111 |
endStop = 100 - parseFloat( endColor.stop ) + '%'; |
| 112 |
startColor.octoHex = new Color( startColor.color ).toIEOctoHex(); |
| 113 |
endColor.octoHex = new Color( endColor.color ).toIEOctoHex(); |
| 114 |
|
| 115 |
filterVal = 'progid:DXImageTransform.Microsoft.Gradient(GradientType=' + type + ', StartColorStr=\'' + startColor.octoHex + '\', EndColorStr=\'' + endColor.octoHex + '\')'; |
| 116 |
html += template.replace( '%start%', startColor.stop ).replace( '%end%', endStop ).replace( '%filter%', filterVal ); |
| 117 |
}); |
| 118 |
self.find( '.iris-ie-gradient-shim' ).remove(); |
| 119 |
$( html ).prependTo( self ); |
| 120 |
} |
| 121 |
|
| 122 |
function legacyWebkitGradient( origin, colorList ) { |
| 123 |
var stops = []; |
| 124 |
origin = ( origin === 'top' ) ? '0% 0%,0% 100%,' : '0% 100%,100% 100%,'; |
| 125 |
colorList = fillColorStops( colorList ); |
| 126 |
$.each( colorList, function( i, val ){ |
| 127 |
stops.push( 'color-stop(' + ( parseFloat( val.stop ) / 100 ) + ', ' + val.color + ')' ); |
| 128 |
}); |
| 129 |
return '-webkit-gradient(linear,' + origin + stops.join(',') + ')'; |
| 130 |
} |
| 131 |
|
| 132 |
function fillColorStops( colorList ) { |
| 133 |
var colors = [], |
| 134 |
percs = [], |
| 135 |
newColorList = [], |
| 136 |
lastIndex = colorList.length - 1; |
| 137 |
|
| 138 |
$.each( colorList, function( index, val ) { |
| 139 |
var color = val, |
| 140 |
perc = false, |
| 141 |
match = val.match( /1?[0-9]{1,2}%$/ ); |
| 142 |
|
| 143 |
if ( match ) { |
| 144 |
color = val.replace( /\s?1?[0-9]{1,2}%$/, '' ); |
| 145 |
perc = match.shift(); |
| 146 |
} |
| 147 |
colors.push( color ); |
| 148 |
percs.push( perc ); |
| 149 |
}); |
| 150 |
|
| 151 |
// back fill first and last |
| 152 |
if ( percs[0] === false ) { |
| 153 |
percs[0] = '0%'; |
| 154 |
} |
| 155 |
|
| 156 |
if ( percs[lastIndex] === false ) { |
| 157 |
percs[lastIndex] = '100%'; |
| 158 |
} |
| 159 |
|
| 160 |
percs = backFillColorStops( percs ); |
| 161 |
|
| 162 |
$.each( percs, function( i ){ |
| 163 |
newColorList[i] = { color: colors[i], stop: percs[i] }; |
| 164 |
}); |
| 165 |
return newColorList; |
| 166 |
} |
| 167 |
|
| 168 |
function backFillColorStops( stops ) { |
| 169 |
var first = 0, |
| 170 |
last = stops.length - 1, |
| 171 |
i = 0, |
| 172 |
foundFirst = false, |
| 173 |
incr, |
| 174 |
steps, |
| 175 |
step, |
| 176 |
firstVal; |
| 177 |
|
| 178 |
if ( stops.length <= 2 || $.inArray( false, stops ) < 0 ) { |
| 179 |
return stops; |
| 180 |
} |
| 181 |
while ( i < stops.length - 1 ) { |
| 182 |
if ( ! foundFirst && stops[i] === false ) { |
| 183 |
first = i - 1; |
| 184 |
foundFirst = true; |
| 185 |
} else if ( foundFirst && stops[i] !== false ) { |
| 186 |
last = i; |
| 187 |
i = stops.length; |
| 188 |
} |
| 189 |
i++; |
| 190 |
} |
| 191 |
steps = last - first; |
| 192 |
firstVal = parseInt( stops[first].replace('%'), 10 ); |
| 193 |
incr = ( parseFloat( stops[last].replace('%') ) - firstVal ) / steps; |
| 194 |
i = first + 1; |
| 195 |
step = 1; |
| 196 |
while ( i < last ) { |
| 197 |
stops[i] = ( firstVal + ( step * incr ) ) + '%'; |
| 198 |
step++; |
| 199 |
i++; |
| 200 |
} |
| 201 |
return backFillColorStops( stops ); |
| 202 |
} |
| 203 |
|
| 204 |
$.fn.gradient = function() { |
| 205 |
var args = arguments; |
| 206 |
return this.each( function() { |
| 207 |
// this'll be oldishIE |
| 208 |
if ( nonGradientIE ) { |
| 209 |
stupidIEGradient.apply( this, args ); |
| 210 |
} else { |
| 211 |
// new hotness |
| 212 |
$( this ).css( 'backgroundImage', createGradient.apply( this, args ) ); |
| 213 |
} |
| 214 |
}); |
| 215 |
}; |
| 216 |
|
| 217 |
$.fn.raninbowGradient = function( origin, args ) { |
| 218 |
var opts, template, i, steps; |
| 219 |
|
| 220 |
origin = origin || 'top'; |
| 221 |
opts = $.extend( {}, { s: 100, l: 50 }, args ); |
| 222 |
template = 'hsl(%h%,' + opts.s + '%,' + opts.l + '%)'; |
| 223 |
i = 0; |
| 224 |
steps = []; |
| 225 |
while ( i <= 360 ) { |
| 226 |
steps.push( template.replace('%h%', i) ); |
| 227 |
i += 30; |
| 228 |
} |
| 229 |
return this.each(function() { |
| 230 |
$(this).gradient( origin, steps ); |
| 231 |
}); |
| 232 |
}; |
| 233 |
|
| 234 |
// the colorpicker widget def. |
| 235 |
Iris = { |
| 236 |
options: { |
| 237 |
color: false, |
| 238 |
mode: 'hsl', |
| 239 |
controls: { |
| 240 |
horiz: 's', // horizontal defaults to saturation |
| 241 |
vert: 'l', // vertical defaults to lightness |
| 242 |
strip: 'h' // right strip defaults to hue |
| 243 |
}, |
| 244 |
hide: true, // hide the color picker by default |
| 245 |
border: true, // draw a border around the collection of UI elements |
| 246 |
target: false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input. |
| 247 |
width: 200, // the width of the collection of UI elements |
| 248 |
palettes: false // show a palette of basic colors beneath the square. |
| 249 |
}, |
| 250 |
_color: '', |
| 251 |
_palettes: [ '#000', '#fff', '#d33', '#d93', '#ee2', '#81d742', '#1e73be', '#8224e3' ], |
| 252 |
_inited: false, |
| 253 |
_defaultHSLControls: { |
| 254 |
horiz: 's', |
| 255 |
vert: 'l', |
| 256 |
strip: 'h' |
| 257 |
}, |
| 258 |
_defaultHSVControls: { |
| 259 |
horiz: 'h', |
| 260 |
vert: 'v', |
| 261 |
strip: 's' |
| 262 |
}, |
| 263 |
_scale: { |
| 264 |
h: 360, |
| 265 |
s: 100, |
| 266 |
l: 100, |
| 267 |
v: 100 |
| 268 |
}, |
| 269 |
_create: function() { |
| 270 |
var self = this, |
| 271 |
el = self.element, |
| 272 |
color = self.options.color || el.val(); |
| 273 |
|
| 274 |
if ( gradientType === false ) { |
| 275 |
testGradientType(); |
| 276 |
} |
| 277 |
|
| 278 |
if ( el.is( 'input' ) ) { |
| 279 |
if ( self.options.target ) { |
| 280 |
self.picker = $( _html ).appendTo( self.options.target ); |
| 281 |
} else { |
| 282 |
self.picker = $( _html ).insertAfter( el ); |
| 283 |
} |
| 284 |
|
| 285 |
self._addInputListeners( el ); |
| 286 |
} else { |
| 287 |
el.append( _html ); |
| 288 |
self.picker = el.find( '.iris-picker' ); |
| 289 |
} |
| 290 |
|
| 291 |
// Browsers / Versions |
| 292 |
// Feature detection doesn't work for these, and $.browser is deprecated |
| 293 |
if ( isIE ) { |
| 294 |
if ( IEVersion === 9 ) { |
| 295 |
self.picker.addClass( 'iris-ie-9' ); |
| 296 |
} else if ( IEVersion <= 8 ) { |
| 297 |
self.picker.addClass( 'iris-ie-lt9' ); |
| 298 |
} |
| 299 |
} else if ( UA.indexOf('compatible') < 0 && UA.indexOf('khtml') < 0 && UA.match( /mozilla/ ) ) { |
| 300 |
self.picker.addClass( 'iris-mozilla' ); |
| 301 |
} |
| 302 |
|
| 303 |
if ( self.options.palettes ) { |
| 304 |
self._addPalettes(); |
| 305 |
} |
| 306 |
|
| 307 |
self._color = new Color( color ).setHSpace( self.options.mode ); |
| 308 |
self.options.color = self._color.toString(); |
| 309 |
|
| 310 |
// prep 'em for re-use |
| 311 |
self.controls = { |
| 312 |
square: self.picker.find( '.iris-square' ), |
| 313 |
squareDrag: self.picker.find( '.iris-square-value' ), |
| 314 |
horiz: self.picker.find( '.iris-square-horiz' ), |
| 315 |
vert: self.picker.find( '.iris-square-vert' ), |
| 316 |
strip: self.picker.find( '.iris-strip' ), |
| 317 |
stripSlider: self.picker.find( '.iris-strip .iris-slider-offset' ) |
| 318 |
}; |
| 319 |
|
| 320 |
// small sanity check - if we chose hsv, change default controls away from hsl |
| 321 |
if ( self.options.mode === 'hsv' && self._has('l', self.options.controls) ) { |
| 322 |
self.options.controls = self._defaultHSVControls; |
| 323 |
} else if ( self.options.mode === 'hsl' && self._has('v', self.options.controls) ) { |
| 324 |
self.options.controls = self._defaultHSLControls; |
| 325 |
} |
| 326 |
|
| 327 |
// store it. HSL gets squirrely |
| 328 |
self.hue = self._color.h(); |
| 329 |
|
| 330 |
if ( self.options.hide ) { |
| 331 |
self.picker.hide(); |
| 332 |
} |
| 333 |
|
| 334 |
if ( self.options.border ) { |
| 335 |
self.picker.addClass( 'iris-border' ); |
| 336 |
} |
| 337 |
|
| 338 |
self._initControls(); |
| 339 |
self.active = 'external'; |
| 340 |
self._dimensions(); |
| 341 |
self._change(); |
| 342 |
}, |
| 343 |
_has: function(needle, haystack) { |
| 344 |
var ret = false; |
| 345 |
$.each(haystack, function(i,v){ |
| 346 |
if ( needle === v ) { |
| 347 |
ret = true; |
| 348 |
// exit the loop |
| 349 |
return false; |
| 350 |
} |
| 351 |
}); |
| 352 |
return ret; |
| 353 |
}, |
| 354 |
_addPalettes: function () { |
| 355 |
var container = $( '<div class="iris-palette-container" />' ), |
| 356 |
palette = $( '<a class="iris-palette" tabindex="0" />' ), |
| 357 |
colors = $.isArray( this.options.palettes ) ? this.options.palettes : this._palettes; |
| 358 |
|
| 359 |
// do we have an existing container? Empty and reuse it. |
| 360 |
if ( this.picker.find( '.iris-palette-container' ).length ) { |
| 361 |
container = this.picker.find( '.iris-palette-container' ).detach().html( '' ); |
| 362 |
} |
| 363 |
|
| 364 |
$.each(colors, function(index, val) { |
| 365 |
palette.clone().data( 'color', val ) |
| 366 |
.css( 'backgroundColor', val ).appendTo( container ) |
| 367 |
.height( 10 ).width( 10 ); |
| 368 |
}); |
| 369 |
|
| 370 |
this.picker.append(container); |
| 371 |
}, |
| 372 |
_paint: function() { |
| 373 |
var self = this; |
| 374 |
self._paintDimension( 'top', 'strip' ); |
| 375 |
self._paintDimension( 'top', 'vert' ); |
| 376 |
self._paintDimension( 'left', 'horiz' ); |
| 377 |
}, |
| 378 |
_paintDimension: function( origin, control ) { |
| 379 |
var self = this, |
| 380 |
c = self._color, |
| 381 |
mode = self.options.mode, |
| 382 |
color = self._getHSpaceColor(), |
| 383 |
target = self.controls[ control ], |
| 384 |
controlOpts = self.options.controls, |
| 385 |
stops; |
| 386 |
|
| 387 |
// don't paint the active control |
| 388 |
if ( control === self.active || ( self.active === 'square' && control !== 'strip' ) ) { |
| 389 |
return; |
| 390 |
} |
| 391 |
|
| 392 |
switch ( controlOpts[ control ] ) { |
| 393 |
case 'h': |
| 394 |
if ( mode === 'hsv' ) { |
| 395 |
color = c.clone(); |
| 396 |
switch ( control ) { |
| 397 |
case 'horiz': |
| 398 |
color[controlOpts.vert](100); |
| 399 |
break; |
| 400 |
case 'vert': |
| 401 |
color[controlOpts.horiz](100); |
| 402 |
break; |
| 403 |
case 'strip': |
| 404 |
color.setHSpace('hsl'); |
| 405 |
break; |
| 406 |
} |
| 407 |
stops = color.toHsl(); |
| 408 |
} else { |
| 409 |
if ( control === 'strip' ) { |
| 410 |
stops = { s: color.s, l: color.l }; |
| 411 |
} else { |
| 412 |
stops = { s: 100, l: color.l }; |
| 413 |
} |
| 414 |
} |
| 415 |
|
| 416 |
target.raninbowGradient( origin, stops ); |
| 417 |
break; |
| 418 |
case 's': |
| 419 |
if ( mode === 'hsv' ) { |
| 420 |
if ( control === 'vert' ) { |
| 421 |
stops = [ c.clone().a(0).s(0).toCSS('rgba'), c.clone().a(1).s(0).toCSS('rgba') ]; |
| 422 |
} else if ( control === 'strip' ) { |
| 423 |
stops = [ c.clone().s(100).toCSS('hsl'), c.clone().s(0).toCSS('hsl') ]; |
| 424 |
} else if ( control === 'horiz' ) { |
| 425 |
stops = [ '#fff', 'hsl(' + color.h + ',100%,50%)' ]; |
| 426 |
} |
| 427 |
} else { // implicit mode === 'hsl' |
| 428 |
if ( control === 'vert' && self.options.controls.horiz === 'h' ) { |
| 429 |
stops = ['hsla(0, 0%, ' + color.l + '%, 0)', 'hsla(0, 0%, ' + color.l + '%, 1)']; |
| 430 |
} else { |
| 431 |
stops = ['hsl('+ color.h +',0%,50%)', 'hsl(' + color.h + ',100%,50%)']; |
| 432 |
} |
| 433 |
} |
| 434 |
|
| 435 |
|
| 436 |
target.gradient( origin, stops ); |
| 437 |
break; |
| 438 |
case 'l': |
| 439 |
if ( control === 'strip' ) { |
| 440 |
stops = ['hsl(' + color.h + ',100%,100%)', 'hsl(' + color.h + ', ' + color.s + '%,50%)', 'hsl('+ color.h +',100%,0%)']; |
| 441 |
} else { |
| 442 |
stops = ['#fff', 'rgba(255,255,255,0) 50%', 'rgba(0,0,0,0) 50%', 'rgba(0,0,0,1)']; |
| 443 |
} |
| 444 |
target.gradient( origin, stops ); |
| 445 |
break; |
| 446 |
case 'v': |
| 447 |
if ( control === 'strip' ) { |
| 448 |
stops = [ c.clone().v(100).toCSS(), c.clone().v(0).toCSS() ]; |
| 449 |
} else { |
| 450 |
stops = ['rgba(0,0,0,0)', '#000']; |
| 451 |
} |
| 452 |
target.gradient( origin, stops ); |
| 453 |
break; |
| 454 |
default: |
| 455 |
break; |
| 456 |
} |
| 457 |
}, |
| 458 |
|
| 459 |
_getHSpaceColor: function() { |
| 460 |
return ( this.options.mode === 'hsv' ) ? this._color.toHsv() : this._color.toHsl(); |
| 461 |
}, |
| 462 |
|
| 463 |
_dimensions: function( reset ) { |
| 464 |
// whatever size |
| 465 |
var self = this, |
| 466 |
opts = self.options, |
| 467 |
controls = self.controls, |
| 468 |
square = controls.square, |
| 469 |
strip = self.picker.find( '.iris-strip' ), |
| 470 |
squareWidth = '77.5%', |
| 471 |
stripWidth = '12%', |
| 472 |
totalPadding = 20, |
| 473 |
innerWidth = opts.border ? opts.width - totalPadding : opts.width, |
| 474 |
controlsHeight, |
| 475 |
paletteCount = $.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length, |
| 476 |
paletteMargin, paletteWidth, paletteContainerWidth; |
| 477 |
|
| 478 |
if ( reset ) { |
| 479 |
square.css( 'width', '' ); |
| 480 |
strip.css( 'width', '' ); |
| 481 |
self.picker.css( {width: '', height: ''} ); |
| 482 |
} |
| 483 |
|
| 484 |
squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 ); |
| 485 |
stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 ); |
| 486 |
controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth; |
| 487 |
|
| 488 |
square.width( squareWidth ).height( squareWidth ); |
| 489 |
strip.height( squareWidth ).width( stripWidth ); |
| 490 |
self.picker.css( { width: opts.width, height: controlsHeight } ); |
| 491 |
|
| 492 |
if ( ! opts.palettes ) { |
| 493 |
return self.picker.css( 'paddingBottom', '' ); |
| 494 |
} |
| 495 |
|
| 496 |
// single margin at 2% |
| 497 |
paletteMargin = squareWidth * 2 / 100; |
| 498 |
paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin ); |
| 499 |
paletteWidth = paletteContainerWidth / paletteCount; |
| 500 |
self.picker.find('.iris-palette').each( function( i ) { |
| 501 |
var margin = i === 0 ? 0 : paletteMargin; |
| 502 |
$( this ).css({ |
| 503 |
width: paletteWidth, |
| 504 |
height: paletteWidth, |
| 505 |
marginLeft: margin |
| 506 |
}); |
| 507 |
}); |
| 508 |
self.picker.css( 'paddingBottom', paletteWidth + paletteMargin ); |
| 509 |
strip.height( paletteWidth + paletteMargin + squareWidth ); |
| 510 |
}, |
| 511 |
|
| 512 |
_addInputListeners: function( input ) { |
| 513 |
var self = this, |
| 514 |
debounceTimeout = 100, |
| 515 |
callback = function( event ){ |
| 516 |
var color = new Color( input.val() ), |
| 517 |
val = input.val().replace( /^#/, '' ); |
| 518 |
|
| 519 |
input.removeClass( 'iris-error' ); |
| 520 |
// we gave a bad color |
| 521 |
if ( color.error ) { |
| 522 |
// don't error on an empty input - we want those allowed |
| 523 |
if ( val !== '' ) { |
| 524 |
input.addClass( 'iris-error' ); |
| 525 |
} |
| 526 |
} else { |
| 527 |
if ( color.toString() !== self._color.toString() ) { |
| 528 |
// let's not do this on keyup for hex shortcodes |
| 529 |
if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) { |
| 530 |
self._setOption( 'color', color.toString() ); |
| 531 |
} |
| 532 |
} |
| 533 |
} |
| 534 |
}; |
| 535 |
|
| 536 |
input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); |
| 537 |
|
| 538 |
// If we initialized hidden, show on first focus. The rest is up to you. |
| 539 |
if ( self.options.hide ) { |
| 540 |
input.one( 'focus', function() { |
| 541 |
self.show(); |
| 542 |
}); |
| 543 |
} |
| 544 |
}, |
| 545 |
|
| 546 |
_initControls: function() { |
| 547 |
var self = this, |
| 548 |
controls = self.controls, |
| 549 |
square = controls.square, |
| 550 |
controlOpts = self.options.controls, |
| 551 |
stripScale = self._scale[controlOpts.strip]; |
| 552 |
|
| 553 |
controls.stripSlider.slider({ |
| 554 |
orientation: 'vertical', |
| 555 |
max: stripScale, |
| 556 |
slide: function( event, ui ) { |
| 557 |
self.active = 'strip'; |
| 558 |
// "reverse" for hue. |
| 559 |
if ( controlOpts.strip === 'h' ) { |
| 560 |
ui.value = stripScale - ui.value; |
| 561 |
} |
| 562 |
|
| 563 |
self._color[controlOpts.strip]( ui.value ); |
| 564 |
self._change.apply( self, arguments ); |
| 565 |
} |
| 566 |
}); |
| 567 |
|
| 568 |
controls.squareDrag.draggable({ |
| 569 |
containment: 'parent', |
| 570 |
zIndex: 1000, |
| 571 |
cursor: 'move', |
| 572 |
drag: function( event, ui ) { |
| 573 |
self._squareDrag( event, ui ); |
| 574 |
}, |
| 575 |
start: function() { |
| 576 |
square.addClass( 'iris-dragging' ); |
| 577 |
$(this).addClass( 'ui-state-focus' ); |
| 578 |
}, |
| 579 |
stop: function() { |
| 580 |
square.removeClass( 'iris-dragging' ); |
| 581 |
$(this).removeClass( 'ui-state-focus' ); |
| 582 |
} |
| 583 |
}).on( 'mousedown mouseup', function( event ) { |
| 584 |
var focusClass = 'ui-state-focus'; |
| 585 |
event.preventDefault(); |
| 586 |
if (event.type === 'mousedown' ) { |
| 587 |
self.picker.find( '.' + focusClass ).removeClass( focusClass ).blur(); |
| 588 |
$(this).addClass( focusClass ).focus(); |
| 589 |
} else { |
| 590 |
$(this).removeClass( focusClass ); |
| 591 |
} |
| 592 |
}).on( 'keydown', function( event ) { |
| 593 |
var container = controls.square, |
| 594 |
draggable = controls.squareDrag, |
| 595 |
position = draggable.position(), |
| 596 |
distance = self.options.width / 100; // Distance in pixels the draggable should be moved: 1 "stop" |
| 597 |
|
| 598 |
// make alt key go "10" |
| 599 |
if ( event.altKey ) { |
| 600 |
distance *= 10; |
| 601 |
} |
| 602 |
|
| 603 |
// Reposition if one of the directional keys is pressed |
| 604 |
switch ( event.keyCode ) { |
| 605 |
case 37: position.left -= distance; break; // Left |
| 606 |
case 38: position.top -= distance; break; // Up |
| 607 |
case 39: position.left += distance; break; // Right |
| 608 |
case 40: position.top += distance; break; // Down |
| 609 |
default: return true; // Exit and bubble |
| 610 |
} |
| 611 |
|
| 612 |
// Keep draggable within container |
| 613 |
position.left = Math.max( 0, Math.min( position.left, container.width() ) ); |
| 614 |
position.top = Math.max( 0, Math.min( position.top, container.height() ) ); |
| 615 |
|
| 616 |
draggable.css(position); |
| 617 |
self._squareDrag( event, { position: position }); |
| 618 |
event.preventDefault(); |
| 619 |
}); |
| 620 |
|
| 621 |
// allow clicking on the square to move there and keep dragging |
| 622 |
square.mousedown( function( event ) { |
| 623 |
var squareOffset, pos; |
| 624 |
// only left click |
| 625 |
if ( event.which !== 1 ) { |
| 626 |
return; |
| 627 |
} |
| 628 |
|
| 629 |
// prevent bubbling from the handle: no infinite loops |
| 630 |
if ( ! $( event.target ).is( 'div' ) ) { |
| 631 |
return; |
| 632 |
} |
| 633 |
|
| 634 |
squareOffset = self.controls.square.offset(); |
| 635 |
pos = { |
| 636 |
top: event.pageY - squareOffset.top, |
| 637 |
left: event.pageX - squareOffset.left |
| 638 |
}; |
| 639 |
event.preventDefault(); |
| 640 |
self._squareDrag( event, { position: pos } ); |
| 641 |
event.target = self.controls.squareDrag.get(0); |
| 642 |
self.controls.squareDrag.css( pos ).trigger( event ); |
| 643 |
}); |
| 644 |
|
| 645 |
// palettes |
| 646 |
if ( self.options.palettes ) { |
| 647 |
self._paletteListeners(); |
| 648 |
} |
| 649 |
}, |
| 650 |
|
| 651 |
_paletteListeners: function() { |
| 652 |
var self = this; |
| 653 |
self.picker.find('.iris-palette-container').on('click.palette', '.iris-palette', function() { |
| 654 |
self._color.fromCSS( $(this).data('color') ); |
| 655 |
self.active = 'external'; |
| 656 |
self._change(); |
| 657 |
}).on( 'keydown.palette', '.iris-palette', function( event ) { |
| 658 |
if ( ! ( event.keyCode === 13 || event.keyCode === 32 ) ) { |
| 659 |
return true; |
| 660 |
} |
| 661 |
event.stopPropagation(); |
| 662 |
$( this ).click(); |
| 663 |
}); |
| 664 |
}, |
| 665 |
|
| 666 |
_squareDrag: function( event, ui ) { |
| 667 |
var self = this, |
| 668 |
controlOpts = self.options.controls, |
| 669 |
dimensions = self._squareDimensions(), |
| 670 |
vertVal = Math.round( ( dimensions.h - ui.position.top ) / dimensions.h * self._scale[controlOpts.vert] ), |
| 671 |
horizVal = self._scale[controlOpts.horiz] - Math.round( ( dimensions.w - ui.position.left ) / dimensions.w * self._scale[controlOpts.horiz] ); |
| 672 |
|
| 673 |
self._color[controlOpts.horiz]( horizVal )[controlOpts.vert]( vertVal ); |
| 674 |
|
| 675 |
self.active = 'square'; |
| 676 |
self._change.apply( self, arguments ); |
| 677 |
}, |
| 678 |
|
| 679 |
_setOption: function( key, value ) { |
| 680 |
var self = this, |
| 681 |
oldValue = self.options[key], |
| 682 |
doDimensions = false, |
| 683 |
hexLessColor, |
| 684 |
newColor, |
| 685 |
method; |
| 686 |
|
| 687 |
// ensure the new value is set. We can reset to oldValue if some check wasn't met. |
| 688 |
self.options[key] = value; |
| 689 |
|
| 690 |
switch(key) { |
| 691 |
case 'color': |
| 692 |
// cast to string in case we have a number |
| 693 |
value = '' + value; |
| 694 |
hexLessColor = value.replace( /^#/, '' ); |
| 695 |
newColor = new Color( value ).setHSpace( self.options.mode ); |
| 696 |
if ( newColor.error ) { |
| 697 |
self.options[key] = oldValue; |
| 698 |
} else { |
| 699 |
self._color = newColor; |
| 700 |
self.options.color = self.options[key] = self._color.toString(); |
| 701 |
self.active = 'external'; |
| 702 |
self._change(); |
| 703 |
} |
| 704 |
break; |
| 705 |
case 'palettes': |
| 706 |
doDimensions = true; |
| 707 |
|
| 708 |
if ( value ) { |
| 709 |
self._addPalettes(); |
| 710 |
} else { |
| 711 |
self.picker.find('.iris-palette-container').remove(); |
| 712 |
} |
| 713 |
|
| 714 |
// do we need to add events? |
| 715 |
if ( ! oldValue ) { |
| 716 |
self._paletteListeners(); |
| 717 |
} |
| 718 |
break; |
| 719 |
case 'width': |
| 720 |
doDimensions = true; |
| 721 |
break; |
| 722 |
case 'border': |
| 723 |
doDimensions = true; |
| 724 |
method = value ? 'addClass' : 'removeClass'; |
| 725 |
self.picker[method]('iris-border'); |
| 726 |
break; |
| 727 |
case 'mode': |
| 728 |
case 'controls': |
| 729 |
// if nothing's changed, let's bail, since this causes re-rendering the whole widget |
| 730 |
if ( oldValue === value ) { |
| 731 |
return; |
| 732 |
} |
| 733 |
|
| 734 |
// we're using these poorly named variables because they're already scoped. |
| 735 |
// method is the element that Iris was called on. oldValue will be the options |
| 736 |
method = self.element; |
| 737 |
oldValue = self.options; |
| 738 |
oldValue.hide = ! self.picker.is( ':visible' ); |
| 739 |
self.destroy(); |
| 740 |
self.picker.remove(); |
| 741 |
return $(self.element).iris(oldValue); |
| 742 |
} |
| 743 |
|
| 744 |
// Do we need to recalc dimensions? |
| 745 |
if ( doDimensions ) { |
| 746 |
self._dimensions(true); |
| 747 |
} |
| 748 |
}, |
| 749 |
|
| 750 |
_squareDimensions: function( forceRefresh ) { |
| 751 |
var square = this.controls.square, |
| 752 |
dimensions, |
| 753 |
control; |
| 754 |
|
| 755 |
if ( forceRefresh !== undef && square.data('dimensions') ) { |
| 756 |
return square.data('dimensions'); |
| 757 |
} |
| 758 |
|
| 759 |
control = this.controls.squareDrag; |
| 760 |
dimensions = { |
| 761 |
w: square.width(), |
| 762 |
h: square.height() |
| 763 |
}; |
| 764 |
square.data( 'dimensions', dimensions ); |
| 765 |
return dimensions; |
| 766 |
}, |
| 767 |
|
| 768 |
_isNonHueControl: function( active, type ) { |
| 769 |
if ( active === 'square' && this.options.controls.strip === 'h' ) { |
| 770 |
return true; |
| 771 |
} else if ( type === 'external' || ( type === 'h' && active === 'strip' ) ) { |
| 772 |
return false; |
| 773 |
} |
| 774 |
|
| 775 |
return true; |
| 776 |
}, |
| 777 |
|
| 778 |
_change: function() { |
| 779 |
var self = this, |
| 780 |
controls = self.controls, |
| 781 |
color = self._getHSpaceColor(), |
| 782 |
actions = [ 'square', 'strip' ], |
| 783 |
controlOpts = self.options.controls, |
| 784 |
type = controlOpts[self.active] || 'external', |
| 785 |
oldHue = self.hue; |
| 786 |
|
| 787 |
if ( self.active === 'strip' ) { |
| 788 |
// take no action on any of the square sliders if we adjusted the strip |
| 789 |
actions = []; |
| 790 |
} else if ( self.active !== 'external' ) { |
| 791 |
// for non-strip, non-external, strip should never change |
| 792 |
actions.pop(); // conveniently the last item |
| 793 |
} |
| 794 |
|
| 795 |
$.each( actions, function(index, item) { |
| 796 |
var value, dimensions, cssObj; |
| 797 |
if ( item !== self.active ) { |
| 798 |
switch ( item ) { |
| 799 |
case 'strip': |
| 800 |
// reverse for hue |
| 801 |
value = ( controlOpts.strip === 'h' ) ? self._scale[controlOpts.strip] - color[controlOpts.strip] : color[controlOpts.strip]; |
| 802 |
controls.stripSlider.slider( 'value', value ); |
| 803 |
break; |
| 804 |
case 'square': |
| 805 |
dimensions = self._squareDimensions(); |
| 806 |
cssObj = { |
| 807 |
left: color[controlOpts.horiz] / self._scale[controlOpts.horiz] * dimensions.w, |
| 808 |
top: dimensions.h - ( color[controlOpts.vert] / self._scale[controlOpts.vert] * dimensions.h ) |
| 809 |
}; |
| 810 |
|
| 811 |
self.controls.squareDrag.css( cssObj ); |
| 812 |
break; |
| 813 |
} |
| 814 |
} |
| 815 |
}); |
| 816 |
|
| 817 |
// Ensure that we don't change hue if we triggered a hue reset |
| 818 |
if ( color.h !== oldHue && self._isNonHueControl( self.active, type ) ) { |
| 819 |
self._color.h(oldHue); |
| 820 |
} |
| 821 |
|
| 822 |
// store hue for repeating above check next time |
| 823 |
self.hue = self._color.h(); |
| 824 |
|
| 825 |
self.options.color = self._color.toString(); |
| 826 |
|
| 827 |
// only run after the first time |
| 828 |
if ( self._inited ) { |
| 829 |
self._trigger( 'change', { type: self.active }, { color: self._color } ); |
| 830 |
} |
| 831 |
|
| 832 |
if ( self.element.is( ':input' ) && ! self._color.error ) { |
| 833 |
self.element.removeClass( 'iris-error' ); |
| 834 |
if ( self.element.val() !== self._color.toString() ) { |
| 835 |
self.element.val( self._color.toString() ); |
| 836 |
} |
| 837 |
} |
| 838 |
|
| 839 |
self._paint(); |
| 840 |
self._inited = true; |
| 841 |
self.active = false; |
| 842 |
}, |
| 843 |
// taken from underscore.js _.debounce method |
| 844 |
_debounce: function( func, wait, immediate ) { |
| 845 |
var timeout, result; |
| 846 |
return function() { |
| 847 |
var context = this, |
| 848 |
args = arguments, |
| 849 |
later, |
| 850 |
callNow; |
| 851 |
|
| 852 |
later = function() { |
| 853 |
timeout = null; |
| 854 |
if ( ! immediate) { |
| 855 |
result = func.apply( context, args ); |
| 856 |
} |
| 857 |
}; |
| 858 |
|
| 859 |
callNow = immediate && !timeout; |
| 860 |
clearTimeout( timeout ); |
| 861 |
timeout = setTimeout( later, wait ); |
| 862 |
if ( callNow ) { |
| 863 |
result = func.apply( context, args ); |
| 864 |
} |
| 865 |
return result; |
| 866 |
}; |
| 867 |
}, |
| 868 |
show: function() { |
| 869 |
this.picker.show(); |
| 870 |
}, |
| 871 |
hide: function() { |
| 872 |
this.picker.hide(); |
| 873 |
}, |
| 874 |
toggle: function() { |
| 875 |
this.picker.toggle(); |
| 876 |
}, |
| 877 |
color: function(newColor) { |
| 878 |
if ( newColor === true ) { |
| 879 |
return this._color.clone(); |
| 880 |
} else if ( newColor === undef ) { |
| 881 |
return this._color.toString(); |
| 882 |
} |
| 883 |
this.option('color', newColor); |
| 884 |
} |
| 885 |
}; |
| 886 |
// initialize the widget |
| 887 |
$.widget( 'a8c.iris', Iris ); |
| 888 |
// add CSS |
| 889 |
$( '<style id="iris-css">' + _css + '</style>' ).appendTo( 'head' ); |
| 890 |
|
| 891 |
}( jQuery )); |