PluginProbe
PilotPress / stable
PilotPress vstable
trunk 1.9.2 1.9.3 1.9.5 2.0.7 stable
pilotpress / js / iris.js

iris.js in PilotPress stable, at js/iris.js

891 lines 29.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 ));