|
1 // Spectrum Colorpicker v1.0.9 |
|
2 // https://github.com/bgrins/spectrum |
|
3 // Author: Brian Grinstead |
|
4 // License: MIT |
|
5 |
|
6 (function (window, $, undefined) { |
|
7 var defaultOpts = { |
|
8 |
|
9 // Callbacks |
|
10 beforeShow: noop, |
|
11 move: noop, |
|
12 change: noop, |
|
13 show: noop, |
|
14 hide: noop, |
|
15 |
|
16 // Options |
|
17 color: false, |
|
18 flat: false, |
|
19 showInput: false, |
|
20 showButtons: true, |
|
21 clickoutFiresChange: false, |
|
22 showInitial: false, |
|
23 showPalette: false, |
|
24 showPaletteOnly: false, |
|
25 showSelectionPalette: true, |
|
26 localStorageKey: false, |
|
27 maxSelectionSize: 7, |
|
28 cancelText: "cancel", |
|
29 chooseText: "choose", |
|
30 preferredFormat: false, |
|
31 className: "", |
|
32 showAlpha: false, |
|
33 theme: "sp-light", |
|
34 palette: ['fff', '000'], |
|
35 selectionPalette: [], |
|
36 disabled: false |
|
37 }, |
|
38 spectrums = [], |
|
39 IE = !!/msie/i.exec( window.navigator.userAgent ), |
|
40 rgbaSupport = (function() { |
|
41 function contains( str, substr ) { |
|
42 return !!~('' + str).indexOf(substr); |
|
43 } |
|
44 |
|
45 var elem = document.createElement('div'); |
|
46 var style = elem.style; |
|
47 style.cssText = 'background-color:rgba(0,0,0,.5)'; |
|
48 return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); |
|
49 })(), |
|
50 replaceInput = [ |
|
51 "<div class='sp-replacer'>", |
|
52 "<div class='sp-preview'><div class='sp-preview-inner'></div></div>", |
|
53 "<div class='sp-dd'>▼</div>", |
|
54 "</div>" |
|
55 ].join(''), |
|
56 markup = (function () { |
|
57 |
|
58 // IE does not support gradients with multiple stops, so we need to simulate |
|
59 // that for the rainbow slider with 8 divs that each have a single gradient |
|
60 var gradientFix = ""; |
|
61 if (IE) { |
|
62 for (var i = 1; i <= 6; i++) { |
|
63 gradientFix += "<div class='sp-" + i + "'></div>"; |
|
64 } |
|
65 } |
|
66 |
|
67 return [ |
|
68 "<div class='sp-container sp-hidden'>", |
|
69 "<div class='sp-palette-container'>", |
|
70 "<div class='sp-palette sp-thumb sp-cf'></div>", |
|
71 "</div>", |
|
72 "<div class='sp-picker-container'>", |
|
73 "<div class='sp-top sp-cf'>", |
|
74 "<div class='sp-fill'></div>", |
|
75 "<div class='sp-top-inner'>", |
|
76 "<div class='sp-color'>", |
|
77 "<div class='sp-sat'>", |
|
78 "<div class='sp-val'>", |
|
79 "<div class='sp-dragger'></div>", |
|
80 "</div>", |
|
81 "</div>", |
|
82 "</div>", |
|
83 "<div class='sp-hue'>", |
|
84 "<div class='sp-slider'></div>", |
|
85 gradientFix, |
|
86 "</div>", |
|
87 "</div>", |
|
88 "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>", |
|
89 "</div>", |
|
90 "<div class='sp-input-container sp-cf'>", |
|
91 "<input class='sp-input' type='text' spellcheck='false' />", |
|
92 "</div>", |
|
93 "<div class='sp-initial sp-thumb sp-cf'></div>", |
|
94 "<div class='sp-button-container sp-cf'>", |
|
95 "<a class='sp-cancel' href='#'></a>", |
|
96 "<button class='sp-choose'></button>", |
|
97 "</div>", |
|
98 "</div>", |
|
99 "</div>" |
|
100 ].join(""); |
|
101 })(); |
|
102 |
|
103 function paletteTemplate (p, color, className) { |
|
104 var html = []; |
|
105 for (var i = 0; i < p.length; i++) { |
|
106 var tiny = tinycolor(p[i]); |
|
107 var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light"; |
|
108 c += (tinycolor.equals(color, p[i])) ? " sp-thumb-active" : ""; |
|
109 |
|
110 var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter(); |
|
111 html.push('<span title="' + tiny.toRgbString() + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>'); |
|
112 } |
|
113 return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>"; |
|
114 } |
|
115 |
|
116 function hideAll() { |
|
117 for (var i = 0; i < spectrums.length; i++) { |
|
118 if (spectrums[i]) { |
|
119 spectrums[i].hide(); |
|
120 } |
|
121 } |
|
122 } |
|
123 |
|
124 function instanceOptions(o, callbackContext) { |
|
125 var opts = $.extend({}, defaultOpts, o); |
|
126 opts.callbacks = { |
|
127 'move': bind(opts.move, callbackContext), |
|
128 'change': bind(opts.change, callbackContext), |
|
129 'show': bind(opts.show, callbackContext), |
|
130 'hide': bind(opts.hide, callbackContext), |
|
131 'beforeShow': bind(opts.beforeShow, callbackContext) |
|
132 }; |
|
133 |
|
134 return opts; |
|
135 } |
|
136 |
|
137 function spectrum(element, o) { |
|
138 |
|
139 var opts = instanceOptions(o, element), |
|
140 flat = opts.flat, |
|
141 showSelectionPalette = opts.showSelectionPalette, |
|
142 localStorageKey = opts.localStorageKey, |
|
143 theme = opts.theme, |
|
144 callbacks = opts.callbacks, |
|
145 resize = throttle(reflow, 10), |
|
146 visible = false, |
|
147 dragWidth = 0, |
|
148 dragHeight = 0, |
|
149 dragHelperHeight = 0, |
|
150 slideHeight = 0, |
|
151 slideWidth = 0, |
|
152 alphaWidth = 0, |
|
153 alphaSlideHelperWidth = 0, |
|
154 slideHelperHeight = 0, |
|
155 currentHue = 0, |
|
156 currentSaturation = 0, |
|
157 currentValue = 0, |
|
158 currentAlpha = 1, |
|
159 palette = opts.palette.slice(0), |
|
160 paletteArray = $.isArray(palette[0]) ? palette : [palette], |
|
161 selectionPalette = opts.selectionPalette.slice(0), |
|
162 maxSelectionSize = opts.maxSelectionSize, |
|
163 draggingClass = "sp-dragging"; |
|
164 |
|
165 var doc = element.ownerDocument, |
|
166 body = doc.body, |
|
167 boundElement = $(element), |
|
168 disabled = false, |
|
169 container = $(markup, doc).addClass(theme), |
|
170 dragger = container.find(".sp-color"), |
|
171 dragHelper = container.find(".sp-dragger"), |
|
172 slider = container.find(".sp-hue"), |
|
173 slideHelper = container.find(".sp-slider"), |
|
174 alphaSliderInner = container.find(".sp-alpha-inner"), |
|
175 alphaSlider = container.find(".sp-alpha"), |
|
176 alphaSlideHelper = container.find(".sp-alpha-handle"), |
|
177 textInput = container.find(".sp-input"), |
|
178 paletteContainer = container.find(".sp-palette"), |
|
179 initialColorContainer = container.find(".sp-initial"), |
|
180 cancelButton = container.find(".sp-cancel"), |
|
181 chooseButton = container.find(".sp-choose"), |
|
182 isInput = boundElement.is("input"), |
|
183 shouldReplace = isInput && !flat, |
|
184 replacer = (shouldReplace) ? $(replaceInput).addClass(theme) : $([]), |
|
185 offsetElement = (shouldReplace) ? replacer : boundElement, |
|
186 previewElement = replacer.find(".sp-preview-inner"), |
|
187 initialColor = opts.color || (isInput && boundElement.val()), |
|
188 colorOnShow = false, |
|
189 preferredFormat = opts.preferredFormat, |
|
190 currentPreferredFormat = preferredFormat, |
|
191 clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange; |
|
192 |
|
193 |
|
194 function applyOptions() { |
|
195 |
|
196 container.toggleClass("sp-flat", flat); |
|
197 container.toggleClass("sp-input-disabled", !opts.showInput); |
|
198 container.toggleClass("sp-alpha-enabled", opts.showAlpha); |
|
199 container.toggleClass("sp-buttons-disabled", !opts.showButtons || flat); |
|
200 container.toggleClass("sp-palette-disabled", !opts.showPalette); |
|
201 container.toggleClass("sp-palette-only", opts.showPaletteOnly); |
|
202 container.toggleClass("sp-initial-disabled", !opts.showInitial); |
|
203 container.addClass(opts.className); |
|
204 |
|
205 reflow(); |
|
206 } |
|
207 |
|
208 function initialize() { |
|
209 |
|
210 if (IE) { |
|
211 container.find("*:not(input)").attr("unselectable", "on"); |
|
212 } |
|
213 |
|
214 applyOptions(); |
|
215 |
|
216 if (shouldReplace) { |
|
217 boundElement.hide().after(replacer); |
|
218 } |
|
219 |
|
220 if (flat) { |
|
221 boundElement.after(container).hide(); |
|
222 } |
|
223 else { |
|
224 $(body).append(container); |
|
225 } |
|
226 |
|
227 if (localStorageKey && window.localStorage) { |
|
228 |
|
229 // Migrate old palettes over to new format. May want to remove this eventually. |
|
230 try { |
|
231 var oldPalette = window.localStorage[localStorageKey].split(",#"); |
|
232 if (oldPalette.length > 1) { |
|
233 delete window.localStorage[localStorageKey]; |
|
234 $.each(oldPalette, function(i, c) { |
|
235 addColorToSelectionPalette(c); |
|
236 }); |
|
237 } |
|
238 } |
|
239 catch(e) { } |
|
240 |
|
241 try { |
|
242 selectionPalette = window.localStorage[localStorageKey].split(";"); |
|
243 } |
|
244 catch (e) { } |
|
245 } |
|
246 |
|
247 offsetElement.bind("click.spectrum touchstart.spectrum", function (e) { |
|
248 if (!disabled) { |
|
249 toggle(); |
|
250 } |
|
251 |
|
252 e.stopPropagation(); |
|
253 |
|
254 if (!$(e.target).is("input")) { |
|
255 e.preventDefault(); |
|
256 } |
|
257 }); |
|
258 |
|
259 if(boundElement.is(":disabled") || (opts.disabled === true)) { |
|
260 disable(); |
|
261 } |
|
262 |
|
263 // Prevent clicks from bubbling up to document. This would cause it to be hidden. |
|
264 container.click(stopPropagation); |
|
265 |
|
266 // Handle user typed input |
|
267 textInput.change(setFromTextInput); |
|
268 textInput.bind("paste", function () { |
|
269 setTimeout(setFromTextInput, 1); |
|
270 }); |
|
271 textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } }); |
|
272 |
|
273 cancelButton.text(opts.cancelText); |
|
274 cancelButton.bind("click.spectrum", function (e) { |
|
275 e.stopPropagation(); |
|
276 e.preventDefault(); |
|
277 hide("cancel"); |
|
278 }); |
|
279 |
|
280 chooseButton.text(opts.chooseText); |
|
281 chooseButton.bind("click.spectrum", function (e) { |
|
282 e.stopPropagation(); |
|
283 e.preventDefault(); |
|
284 |
|
285 if (isValid()) { |
|
286 updateOriginalInput(true); |
|
287 hide(); |
|
288 } |
|
289 }); |
|
290 |
|
291 draggable(alphaSlider, function (dragX, dragY, e) { |
|
292 currentAlpha = (dragX / alphaWidth); |
|
293 if (e.shiftKey) { |
|
294 currentAlpha = Math.round(currentAlpha * 10) / 10; |
|
295 } |
|
296 |
|
297 move(); |
|
298 }); |
|
299 |
|
300 draggable(slider, function (dragX, dragY) { |
|
301 currentHue = parseFloat(dragY / slideHeight); |
|
302 move(); |
|
303 }, dragStart, dragStop); |
|
304 |
|
305 draggable(dragger, function (dragX, dragY) { |
|
306 currentSaturation = parseFloat(dragX / dragWidth); |
|
307 currentValue = parseFloat((dragHeight - dragY) / dragHeight); |
|
308 move(); |
|
309 }, dragStart, dragStop); |
|
310 |
|
311 if (!!initialColor) { |
|
312 set(initialColor); |
|
313 |
|
314 // In case color was black - update the preview UI and set the format |
|
315 // since the set function will not run (default color is black). |
|
316 updateUI(); |
|
317 currentPreferredFormat = preferredFormat || tinycolor(initialColor).format; |
|
318 |
|
319 addColorToSelectionPalette(initialColor); |
|
320 } |
|
321 else { |
|
322 updateUI(); |
|
323 } |
|
324 |
|
325 if (flat) { |
|
326 show(); |
|
327 } |
|
328 |
|
329 function palletElementClick(e) { |
|
330 if (e.data && e.data.ignore) { |
|
331 set($(this).data("color")); |
|
332 move(); |
|
333 } |
|
334 else { |
|
335 set($(this).data("color")); |
|
336 updateOriginalInput(true); |
|
337 move(); |
|
338 hide(); |
|
339 } |
|
340 |
|
341 return false; |
|
342 } |
|
343 |
|
344 var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; |
|
345 paletteContainer.delegate(".sp-thumb-el", paletteEvent, palletElementClick); |
|
346 initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, palletElementClick); |
|
347 } |
|
348 |
|
349 function addColorToSelectionPalette(color) { |
|
350 if (showSelectionPalette) { |
|
351 var colorRgb = tinycolor(color).toRgbString(); |
|
352 if ($.inArray(colorRgb, selectionPalette) === -1) { |
|
353 selectionPalette.push(colorRgb); |
|
354 while(selectionPalette.length > maxSelectionSize) { |
|
355 selectionPalette.shift(); |
|
356 } |
|
357 } |
|
358 |
|
359 if (localStorageKey && window.localStorage) { |
|
360 try { |
|
361 window.localStorage[localStorageKey] = selectionPalette.join(";"); |
|
362 } |
|
363 catch(e) { } |
|
364 } |
|
365 } |
|
366 } |
|
367 |
|
368 function getUniqueSelectionPalette() { |
|
369 var unique = []; |
|
370 var p = selectionPalette; |
|
371 var paletteLookup = {}; |
|
372 var rgb; |
|
373 |
|
374 if (opts.showPalette) { |
|
375 |
|
376 for (var i = 0; i < paletteArray.length; i++) { |
|
377 for (var j = 0; j < paletteArray[i].length; j++) { |
|
378 rgb = tinycolor(paletteArray[i][j]).toRgbString(); |
|
379 paletteLookup[rgb] = true; |
|
380 } |
|
381 } |
|
382 |
|
383 for (i = 0; i < p.length; i++) { |
|
384 rgb = tinycolor(p[i]).toRgbString(); |
|
385 |
|
386 if (!paletteLookup.hasOwnProperty(rgb)) { |
|
387 unique.push(p[i]); |
|
388 paletteLookup[rgb] = true; |
|
389 } |
|
390 } |
|
391 } |
|
392 |
|
393 return unique.reverse().slice(0, opts.maxSelectionSize); |
|
394 } |
|
395 |
|
396 function drawPalette() { |
|
397 |
|
398 var currentColor = get(); |
|
399 |
|
400 var html = $.map(paletteArray, function (palette, i) { |
|
401 return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i); |
|
402 }); |
|
403 |
|
404 if (selectionPalette) { |
|
405 html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection")); |
|
406 } |
|
407 |
|
408 paletteContainer.html(html.join("")); |
|
409 } |
|
410 |
|
411 function drawInitial() { |
|
412 if (opts.showInitial) { |
|
413 var initial = colorOnShow; |
|
414 var current = get(); |
|
415 initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial")); |
|
416 } |
|
417 } |
|
418 |
|
419 function dragStart() { |
|
420 if (dragHeight === 0 || dragWidth === 0 || slideHeight === 0) { |
|
421 reflow(); |
|
422 } |
|
423 container.addClass(draggingClass); |
|
424 } |
|
425 |
|
426 function dragStop() { |
|
427 container.removeClass(draggingClass); |
|
428 } |
|
429 |
|
430 function setFromTextInput() { |
|
431 var tiny = tinycolor(textInput.val()); |
|
432 if (tiny.ok) { |
|
433 set(tiny); |
|
434 } |
|
435 else { |
|
436 textInput.addClass("sp-validation-error"); |
|
437 } |
|
438 } |
|
439 |
|
440 function toggle() { |
|
441 if (visible) { |
|
442 hide(); |
|
443 } |
|
444 else { |
|
445 show(); |
|
446 } |
|
447 } |
|
448 |
|
449 function show() { |
|
450 var event = $.Event('beforeShow.spectrum'); |
|
451 |
|
452 if (visible) { |
|
453 reflow(); |
|
454 return; |
|
455 } |
|
456 |
|
457 boundElement.trigger(event, [ get() ]); |
|
458 |
|
459 if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) { |
|
460 return; |
|
461 } |
|
462 |
|
463 hideAll(); |
|
464 visible = true; |
|
465 |
|
466 $(doc).bind("click.spectrum", hide); |
|
467 $(window).bind("resize.spectrum", resize); |
|
468 replacer.addClass("sp-active"); |
|
469 container.removeClass("sp-hidden"); |
|
470 |
|
471 if (opts.showPalette) { |
|
472 drawPalette(); |
|
473 } |
|
474 reflow(); |
|
475 updateUI(); |
|
476 |
|
477 colorOnShow = get(); |
|
478 |
|
479 drawInitial(); |
|
480 callbacks.show(colorOnShow); |
|
481 boundElement.trigger('show.spectrum', [ colorOnShow ]); |
|
482 } |
|
483 |
|
484 function hide(e) { |
|
485 |
|
486 // Return on right click |
|
487 if (e && e.type == "click" && e.button == 2) { return; } |
|
488 |
|
489 // Return if hiding is unnecessary |
|
490 if (!visible || flat) { return; } |
|
491 visible = false; |
|
492 |
|
493 $(doc).unbind("click.spectrum", hide); |
|
494 $(window).unbind("resize.spectrum", resize); |
|
495 |
|
496 replacer.removeClass("sp-active"); |
|
497 container.addClass("sp-hidden"); |
|
498 |
|
499 var colorHasChanged = !tinycolor.equals(get(), colorOnShow); |
|
500 |
|
501 if (colorHasChanged) { |
|
502 if (clickoutFiresChange && e !== "cancel") { |
|
503 updateOriginalInput(true); |
|
504 } |
|
505 else { |
|
506 revert(); |
|
507 } |
|
508 } |
|
509 |
|
510 callbacks.hide(get()); |
|
511 boundElement.trigger('hide.spectrum', [ get() ]); |
|
512 } |
|
513 |
|
514 function revert() { |
|
515 set(colorOnShow, true); |
|
516 } |
|
517 |
|
518 function set(color, ignoreFormatChange) { |
|
519 if (tinycolor.equals(color, get())) { |
|
520 return; |
|
521 } |
|
522 |
|
523 var newColor = tinycolor(color); |
|
524 var newHsv = newColor.toHsv(); |
|
525 |
|
526 currentHue = newHsv.h; |
|
527 currentSaturation = newHsv.s; |
|
528 currentValue = newHsv.v; |
|
529 currentAlpha = newHsv.a; |
|
530 |
|
531 updateUI(); |
|
532 |
|
533 if (newColor.ok && !ignoreFormatChange) { |
|
534 currentPreferredFormat = preferredFormat || newColor.format; |
|
535 } |
|
536 } |
|
537 |
|
538 function get() { |
|
539 return tinycolor.fromRatio({ h: currentHue, s: currentSaturation, v: currentValue, a: Math.round(currentAlpha * 100) / 100 }); |
|
540 } |
|
541 |
|
542 function isValid() { |
|
543 return !textInput.hasClass("sp-validation-error"); |
|
544 } |
|
545 |
|
546 function move() { |
|
547 updateUI(); |
|
548 |
|
549 callbacks.move(get()); |
|
550 boundElement.trigger('move.spectrum', [ get() ]); |
|
551 } |
|
552 |
|
553 function updateUI() { |
|
554 |
|
555 textInput.removeClass("sp-validation-error"); |
|
556 |
|
557 updateHelperLocations(); |
|
558 |
|
559 // Update dragger background color (gradients take care of saturation and value). |
|
560 var flatColor = tinycolor({ h: currentHue, s: "1.0", v: "1.0" }); |
|
561 dragger.css("background-color", flatColor.toHexString()); |
|
562 |
|
563 // Get a format that alpha will be included in (hex and names ignore alpha) |
|
564 var format = currentPreferredFormat; |
|
565 if (currentAlpha < 1) { |
|
566 if (format === "hex" || format === "name") { |
|
567 format = "rgb"; |
|
568 } |
|
569 } |
|
570 |
|
571 var realColor = get(), |
|
572 realHex = realColor.toHexString(), |
|
573 realRgb = realColor.toRgbString(); |
|
574 |
|
575 |
|
576 // Update the replaced elements background color (with actual selected color) |
|
577 if (rgbaSupport || realColor.alpha === 1) { |
|
578 previewElement.css("background-color", realRgb); |
|
579 } |
|
580 else { |
|
581 previewElement.css("background-color", "transparent"); |
|
582 previewElement.css("filter", realColor.toFilter()); |
|
583 } |
|
584 |
|
585 if (opts.showAlpha) { |
|
586 var rgb = realColor.toRgb(); |
|
587 rgb.a = 0; |
|
588 var realAlpha = tinycolor(rgb).toRgbString(); |
|
589 var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")"; |
|
590 |
|
591 if (IE) { |
|
592 alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex)); |
|
593 } |
|
594 else { |
|
595 alphaSliderInner.css("background", "-webkit-" + gradient); |
|
596 alphaSliderInner.css("background", "-moz-" + gradient); |
|
597 alphaSliderInner.css("background", "-ms-" + gradient); |
|
598 alphaSliderInner.css("background", gradient); |
|
599 } |
|
600 } |
|
601 |
|
602 |
|
603 // Update the text entry input as it changes happen |
|
604 if (opts.showInput) { |
|
605 if (currentAlpha < 1) { |
|
606 if (format === "hex" || format === "name") { |
|
607 format = "rgb"; |
|
608 } |
|
609 } |
|
610 textInput.val(realColor.toString(format)); |
|
611 } |
|
612 |
|
613 if (opts.showPalette) { |
|
614 drawPalette(); |
|
615 } |
|
616 |
|
617 drawInitial(); |
|
618 } |
|
619 |
|
620 function updateHelperLocations() { |
|
621 var s = currentSaturation; |
|
622 var v = currentValue; |
|
623 |
|
624 // Where to show the little circle in that displays your current selected color |
|
625 var dragX = s * dragWidth; |
|
626 var dragY = dragHeight - (v * dragHeight); |
|
627 dragX = Math.max( |
|
628 -dragHelperHeight, |
|
629 Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight) |
|
630 ); |
|
631 dragY = Math.max( |
|
632 -dragHelperHeight, |
|
633 Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight) |
|
634 ); |
|
635 dragHelper.css({ |
|
636 "top": dragY, |
|
637 "left": dragX |
|
638 }); |
|
639 |
|
640 var alphaX = currentAlpha * alphaWidth; |
|
641 alphaSlideHelper.css({ |
|
642 "left": alphaX - (alphaSlideHelperWidth / 2) |
|
643 }); |
|
644 |
|
645 // Where to show the bar that displays your current selected hue |
|
646 var slideY = (currentHue) * slideHeight; |
|
647 slideHelper.css({ |
|
648 "top": slideY - slideHelperHeight |
|
649 }); |
|
650 } |
|
651 |
|
652 function updateOriginalInput(fireCallback) { |
|
653 var color = get(); |
|
654 |
|
655 if (isInput) { |
|
656 boundElement.val(color.toString(currentPreferredFormat)).change(); |
|
657 } |
|
658 |
|
659 var hasChanged = !tinycolor.equals(color, colorOnShow); |
|
660 colorOnShow = color; |
|
661 |
|
662 // Update the selection palette with the current color |
|
663 addColorToSelectionPalette(color); |
|
664 if (fireCallback && hasChanged) { |
|
665 callbacks.change(color); |
|
666 boundElement.trigger('change.spectrum', [ color ]); |
|
667 } |
|
668 } |
|
669 |
|
670 function reflow() { |
|
671 dragWidth = dragger.width(); |
|
672 dragHeight = dragger.height(); |
|
673 dragHelperHeight = dragHelper.height(); |
|
674 slideWidth = slider.width(); |
|
675 slideHeight = slider.height(); |
|
676 slideHelperHeight = slideHelper.height(); |
|
677 alphaWidth = alphaSlider.width(); |
|
678 alphaSlideHelperWidth = alphaSlideHelper.width(); |
|
679 |
|
680 if (!flat) { |
|
681 container.offset(getOffset(container, offsetElement)); |
|
682 } |
|
683 |
|
684 updateHelperLocations(); |
|
685 } |
|
686 |
|
687 function destroy() { |
|
688 boundElement.show(); |
|
689 offsetElement.unbind("click.spectrum touchstart.spectrum"); |
|
690 container.remove(); |
|
691 replacer.remove(); |
|
692 spectrums[spect.id] = null; |
|
693 } |
|
694 |
|
695 function option(optionName, optionValue) { |
|
696 if (optionName === undefined) { |
|
697 return $.extend({}, opts); |
|
698 } |
|
699 if (optionValue === undefined) { |
|
700 return opts[optionName]; |
|
701 } |
|
702 |
|
703 opts[optionName] = optionValue; |
|
704 applyOptions(); |
|
705 } |
|
706 |
|
707 function enable() { |
|
708 disabled = false; |
|
709 boundElement.attr("disabled", false); |
|
710 offsetElement.removeClass("sp-disabled"); |
|
711 } |
|
712 |
|
713 function disable() { |
|
714 hide(); |
|
715 disabled = true; |
|
716 boundElement.attr("disabled", true); |
|
717 offsetElement.addClass("sp-disabled"); |
|
718 } |
|
719 |
|
720 initialize(); |
|
721 |
|
722 var spect = { |
|
723 show: show, |
|
724 hide: hide, |
|
725 toggle: toggle, |
|
726 reflow: reflow, |
|
727 option: option, |
|
728 enable: enable, |
|
729 disable: disable, |
|
730 set: function (c) { |
|
731 set(c); |
|
732 updateOriginalInput(); |
|
733 }, |
|
734 get: get, |
|
735 destroy: destroy, |
|
736 container: container |
|
737 }; |
|
738 |
|
739 spect.id = spectrums.push(spect) - 1; |
|
740 |
|
741 return spect; |
|
742 } |
|
743 |
|
744 /** |
|
745 * checkOffset - get the offset below/above and left/right element depending on screen position |
|
746 * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js |
|
747 */ |
|
748 function getOffset(picker, input) { |
|
749 var extraY = 0; |
|
750 var dpWidth = picker.outerWidth(); |
|
751 var dpHeight = picker.outerHeight(); |
|
752 var inputHeight = input.outerHeight(); |
|
753 var doc = picker[0].ownerDocument; |
|
754 var docElem = doc.documentElement; |
|
755 var viewWidth = docElem.clientWidth + $(doc).scrollLeft(); |
|
756 var viewHeight = docElem.clientHeight + $(doc).scrollTop(); |
|
757 var offset = input.offset(); |
|
758 offset.top += inputHeight; |
|
759 |
|
760 offset.left -= |
|
761 Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? |
|
762 Math.abs(offset.left + dpWidth - viewWidth) : 0); |
|
763 |
|
764 offset.top -= |
|
765 Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? |
|
766 Math.abs(dpHeight + inputHeight - extraY) : extraY)); |
|
767 |
|
768 return offset; |
|
769 } |
|
770 |
|
771 /** |
|
772 * noop - do nothing |
|
773 */ |
|
774 function noop() { |
|
775 |
|
776 } |
|
777 |
|
778 /** |
|
779 * stopPropagation - makes the code only doing this a little easier to read in line |
|
780 */ |
|
781 function stopPropagation(e) { |
|
782 e.stopPropagation(); |
|
783 } |
|
784 |
|
785 /** |
|
786 * Create a function bound to a given object |
|
787 * Thanks to underscore.js |
|
788 */ |
|
789 function bind(func, obj) { |
|
790 var slice = Array.prototype.slice; |
|
791 var args = slice.call(arguments, 2); |
|
792 return function () { |
|
793 return func.apply(obj, args.concat(slice.call(arguments))); |
|
794 }; |
|
795 } |
|
796 |
|
797 /** |
|
798 * Lightweight drag helper. Handles containment within the element, so that |
|
799 * when dragging, the x is within [0,element.width] and y is within [0,element.height] |
|
800 */ |
|
801 function draggable(element, onmove, onstart, onstop) { |
|
802 onmove = onmove || function () { }; |
|
803 onstart = onstart || function () { }; |
|
804 onstop = onstop || function () { }; |
|
805 var doc = element.ownerDocument || document; |
|
806 var dragging = false; |
|
807 var offset = {}; |
|
808 var maxHeight = 0; |
|
809 var maxWidth = 0; |
|
810 var hasTouch = ('ontouchstart' in window); |
|
811 |
|
812 var duringDragEvents = {}; |
|
813 duringDragEvents["selectstart"] = prevent; |
|
814 duringDragEvents["dragstart"] = prevent; |
|
815 duringDragEvents[(hasTouch ? "touchmove" : "mousemove")] = move; |
|
816 duringDragEvents[(hasTouch ? "touchend" : "mouseup")] = stop; |
|
817 |
|
818 function prevent(e) { |
|
819 if (e.stopPropagation) { |
|
820 e.stopPropagation(); |
|
821 } |
|
822 if (e.preventDefault) { |
|
823 e.preventDefault(); |
|
824 } |
|
825 e.returnValue = false; |
|
826 } |
|
827 |
|
828 function move(e) { |
|
829 if (dragging) { |
|
830 // Mouseup happened outside of window |
|
831 if (IE && document.documentMode < 9 && !e.button) { |
|
832 return stop(); |
|
833 } |
|
834 |
|
835 var touches = e.originalEvent.touches; |
|
836 var pageX = touches ? touches[0].pageX : e.pageX; |
|
837 var pageY = touches ? touches[0].pageY : e.pageY; |
|
838 |
|
839 var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); |
|
840 var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); |
|
841 |
|
842 if (hasTouch) { |
|
843 // Stop scrolling in iOS |
|
844 prevent(e); |
|
845 } |
|
846 |
|
847 onmove.apply(element, [dragX, dragY, e]); |
|
848 } |
|
849 } |
|
850 function start(e) { |
|
851 var rightclick = (e.which) ? (e.which == 3) : (e.button == 2); |
|
852 var touches = e.originalEvent.touches; |
|
853 |
|
854 if (!rightclick && !dragging) { |
|
855 if (onstart.apply(element, arguments) !== false) { |
|
856 dragging = true; |
|
857 maxHeight = $(element).height(); |
|
858 maxWidth = $(element).width(); |
|
859 offset = $(element).offset(); |
|
860 |
|
861 $(doc).bind(duringDragEvents); |
|
862 $(doc.body).addClass("sp-dragging"); |
|
863 |
|
864 if (!hasTouch) { |
|
865 move(e); |
|
866 } |
|
867 |
|
868 prevent(e); |
|
869 } |
|
870 } |
|
871 } |
|
872 function stop() { |
|
873 if (dragging) { |
|
874 $(doc).unbind(duringDragEvents); |
|
875 $(doc.body).removeClass("sp-dragging"); |
|
876 onstop.apply(element, arguments); |
|
877 } |
|
878 dragging = false; |
|
879 } |
|
880 |
|
881 $(element).bind(hasTouch ? "touchstart" : "mousedown", start); |
|
882 } |
|
883 |
|
884 function throttle(func, wait, debounce) { |
|
885 var timeout; |
|
886 return function () { |
|
887 var context = this, args = arguments; |
|
888 var throttler = function () { |
|
889 timeout = null; |
|
890 func.apply(context, args); |
|
891 }; |
|
892 if (debounce) clearTimeout(timeout); |
|
893 if (debounce || !timeout) timeout = setTimeout(throttler, wait); |
|
894 }; |
|
895 } |
|
896 |
|
897 |
|
898 /** |
|
899 * Define a jQuery plugin |
|
900 */ |
|
901 var dataID = "spectrum.id"; |
|
902 $.fn.spectrum = function (opts, extra) { |
|
903 |
|
904 if (typeof opts == "string") { |
|
905 |
|
906 var returnValue = this; |
|
907 var args = Array.prototype.slice.call( arguments, 1 ); |
|
908 |
|
909 this.each(function () { |
|
910 var spect = spectrums[$(this).data(dataID)]; |
|
911 if (spect) { |
|
912 |
|
913 var method = spect[opts]; |
|
914 if (!method) { |
|
915 throw new Error( "Spectrum: no such method: '" + opts + "'" ); |
|
916 } |
|
917 |
|
918 if (opts == "get") { |
|
919 returnValue = spect.get(); |
|
920 } |
|
921 else if (opts == "container") { |
|
922 returnValue = spect.container; |
|
923 } |
|
924 else if (opts == "option") { |
|
925 returnValue = spect.option.apply(spect, args); |
|
926 } |
|
927 else if (opts == "destroy") { |
|
928 spect.destroy(); |
|
929 $(this).removeData(dataID); |
|
930 } |
|
931 else { |
|
932 method.apply(spect, args); |
|
933 } |
|
934 } |
|
935 }); |
|
936 |
|
937 return returnValue; |
|
938 } |
|
939 |
|
940 // Initializing a new instance of spectrum |
|
941 return this.spectrum("destroy").each(function () { |
|
942 var spect = spectrum(this, opts); |
|
943 $(this).data(dataID, spect.id); |
|
944 }); |
|
945 }; |
|
946 |
|
947 $.fn.spectrum.load = true; |
|
948 $.fn.spectrum.loadOpts = {}; |
|
949 $.fn.spectrum.draggable = draggable; |
|
950 $.fn.spectrum.defaults = defaultOpts; |
|
951 |
|
952 $.spectrum = { }; |
|
953 $.spectrum.localization = { }; |
|
954 $.spectrum.palettes = { }; |
|
955 |
|
956 $.fn.spectrum.processNativeColorInputs = function () { |
|
957 var colorInput = $("<input type='color' value='!' />")[0]; |
|
958 var supportsColor = colorInput.type === "color" && colorInput.value != "!"; |
|
959 |
|
960 if (!supportsColor) { |
|
961 $("input[type=color]").spectrum({ |
|
962 preferredFormat: "hex6" |
|
963 }); |
|
964 } |
|
965 }; |
|
966 |
|
967 // TinyColor.js - <https://github.com/bgrins/TinyColor> - 2011 Brian Grinstead - v0.5 |
|
968 |
|
969 (function (window) { |
|
970 |
|
971 var trimLeft = /^[\s,#]+/, |
|
972 trimRight = /\s+$/, |
|
973 tinyCounter = 0, |
|
974 math = Math, |
|
975 mathRound = math.round, |
|
976 mathMin = math.min, |
|
977 mathMax = math.max, |
|
978 mathRandom = math.random, |
|
979 parseFloat = window.parseFloat; |
|
980 |
|
981 function tinycolor(color, opts) { |
|
982 |
|
983 // If input is already a tinycolor, return itself |
|
984 if (typeof color == "object" && color.hasOwnProperty("_tc_id")) { |
|
985 return color; |
|
986 } |
|
987 |
|
988 var rgb = inputToRGB(color); |
|
989 var r = rgb.r, g = rgb.g, b = rgb.b, a = parseFloat(rgb.a), format = rgb.format; |
|
990 |
|
991 return { |
|
992 ok: rgb.ok, |
|
993 format: format, |
|
994 _tc_id: tinyCounter++, |
|
995 alpha: a, |
|
996 toHsv: function () { |
|
997 var hsv = rgbToHsv(r, g, b); |
|
998 return { h: hsv.h, s: hsv.s, v: hsv.v, a: a }; |
|
999 }, |
|
1000 toHsvString: function () { |
|
1001 var hsv = rgbToHsv(r, g, b); |
|
1002 var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); |
|
1003 return (a == 1) ? |
|
1004 "hsv(" + h + ", " + s + "%, " + v + "%)" : |
|
1005 "hsva(" + h + ", " + s + "%, " + v + "%, " + a + ")"; |
|
1006 }, |
|
1007 toHsl: function () { |
|
1008 var hsl = rgbToHsl(r, g, b); |
|
1009 return { h: hsl.h, s: hsl.s, l: hsl.l, a: a }; |
|
1010 }, |
|
1011 toHslString: function () { |
|
1012 var hsl = rgbToHsl(r, g, b); |
|
1013 var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); |
|
1014 return (a == 1) ? |
|
1015 "hsl(" + h + ", " + s + "%, " + l + "%)" : |
|
1016 "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")"; |
|
1017 }, |
|
1018 toHex: function () { |
|
1019 return rgbToHex(r, g, b); |
|
1020 }, |
|
1021 toHexString: function (force6Char) { |
|
1022 return '#' + rgbToHex(r, g, b, force6Char); |
|
1023 }, |
|
1024 toRgb: function () { |
|
1025 return { r: mathRound(r), g: mathRound(g), b: mathRound(b), a: a }; |
|
1026 }, |
|
1027 toRgbString: function () { |
|
1028 return (a == 1) ? |
|
1029 "rgb(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ")" : |
|
1030 "rgba(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ", " + a + ")"; |
|
1031 }, |
|
1032 toName: function () { |
|
1033 return hexNames[rgbToHex(r, g, b)] || false; |
|
1034 }, |
|
1035 toFilter: function (opts, secondColor) { |
|
1036 |
|
1037 var hex = rgbToHex(r, g, b, true); |
|
1038 var secondHex = hex; |
|
1039 var alphaHex = Math.round(parseFloat(a) * 255).toString(16); |
|
1040 var secondAlphaHex = alphaHex; |
|
1041 var gradientType = opts && opts.gradientType ? "GradientType = 1, " : ""; |
|
1042 |
|
1043 if (secondColor) { |
|
1044 var s = tinycolor(secondColor); |
|
1045 secondHex = s.toHex(); |
|
1046 secondAlphaHex = Math.round(parseFloat(s.alpha) * 255).toString(16); |
|
1047 } |
|
1048 |
|
1049 return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr=#" + pad2(alphaHex) + hex + ",endColorstr=#" + pad2(secondAlphaHex) + secondHex + ")"; |
|
1050 }, |
|
1051 toString: function (format) { |
|
1052 format = format || this.format; |
|
1053 var formattedString = false; |
|
1054 if (format === "rgb") { |
|
1055 formattedString = this.toRgbString(); |
|
1056 } |
|
1057 if (format === "hex") { |
|
1058 formattedString = this.toHexString(); |
|
1059 } |
|
1060 if (format === "hex6") { |
|
1061 formattedString = this.toHexString(true); |
|
1062 } |
|
1063 if (format === "name") { |
|
1064 formattedString = this.toName(); |
|
1065 } |
|
1066 if (format === "hsl") { |
|
1067 formattedString = this.toHslString(); |
|
1068 } |
|
1069 if (format === "hsv") { |
|
1070 formattedString = this.toHsvString(); |
|
1071 } |
|
1072 |
|
1073 return formattedString || this.toHexString(true); |
|
1074 } |
|
1075 }; |
|
1076 } |
|
1077 |
|
1078 // If input is an object, force 1 into "1.0" to handle ratios properly |
|
1079 // String input requires "1.0" as input, so 1 will be treated as 1 |
|
1080 tinycolor.fromRatio = function (color) { |
|
1081 |
|
1082 if (typeof color == "object") { |
|
1083 for (var i in color) { |
|
1084 if (color[i] === 1) { |
|
1085 color[i] = "1.0"; |
|
1086 } |
|
1087 } |
|
1088 } |
|
1089 |
|
1090 return tinycolor(color); |
|
1091 |
|
1092 }; |
|
1093 |
|
1094 // Given a string or object, convert that input to RGB |
|
1095 // Possible string inputs: |
|
1096 // |
|
1097 // "red" |
|
1098 // "#f00" or "f00" |
|
1099 // "#ff0000" or "ff0000" |
|
1100 // "rgb 255 0 0" or "rgb (255, 0, 0)" |
|
1101 // "rgb 1.0 0 0" or "rgb (1, 0, 0)" |
|
1102 // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" |
|
1103 // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" |
|
1104 // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" |
|
1105 // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" |
|
1106 // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" |
|
1107 // |
|
1108 function inputToRGB(color) { |
|
1109 |
|
1110 var rgb = { r: 0, g: 0, b: 0 }; |
|
1111 var a = 1; |
|
1112 var ok = false; |
|
1113 var format = false; |
|
1114 |
|
1115 if (typeof color == "string") { |
|
1116 color = stringInputToObject(color); |
|
1117 } |
|
1118 |
|
1119 if (typeof color == "object") { |
|
1120 if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { |
|
1121 rgb = rgbToRgb(color.r, color.g, color.b); |
|
1122 ok = true; |
|
1123 format = "rgb"; |
|
1124 } |
|
1125 else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { |
|
1126 rgb = hsvToRgb(color.h, color.s, color.v); |
|
1127 ok = true; |
|
1128 format = "hsv"; |
|
1129 } |
|
1130 else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { |
|
1131 rgb = hslToRgb(color.h, color.s, color.l); |
|
1132 ok = true; |
|
1133 format = "hsl"; |
|
1134 } |
|
1135 |
|
1136 if (color.hasOwnProperty("a")) { |
|
1137 a = color.a; |
|
1138 } |
|
1139 } |
|
1140 |
|
1141 rgb.r = mathMin(255, mathMax(rgb.r, 0)); |
|
1142 rgb.g = mathMin(255, mathMax(rgb.g, 0)); |
|
1143 rgb.b = mathMin(255, mathMax(rgb.b, 0)); |
|
1144 |
|
1145 |
|
1146 // Don't let the range of [0,255] come back in [0,1]. |
|
1147 // Potentially lose a little bit of precision here, but will fix issues where |
|
1148 // .5 gets interpreted as half of the total, instead of half of 1. |
|
1149 // If it was supposed to be 128, this was already taken care of in the conversion function |
|
1150 if (rgb.r < 1) { rgb.r = mathRound(rgb.r); } |
|
1151 if (rgb.g < 1) { rgb.g = mathRound(rgb.g); } |
|
1152 if (rgb.b < 1) { rgb.b = mathRound(rgb.b); } |
|
1153 |
|
1154 return { |
|
1155 ok: ok, |
|
1156 format: (color && color.format) || format, |
|
1157 r: rgb.r, |
|
1158 g: rgb.g, |
|
1159 b: rgb.b, |
|
1160 a: a |
|
1161 }; |
|
1162 } |
|
1163 |
|
1164 |
|
1165 |
|
1166 // Conversion Functions |
|
1167 // -------------------- |
|
1168 |
|
1169 // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: |
|
1170 // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript> |
|
1171 |
|
1172 // `rgbToRgb` |
|
1173 // Handle bounds / percentage checking to conform to CSS color spec |
|
1174 // <http://www.w3.org/TR/css3-color/> |
|
1175 // *Assumes:* r, g, b in [0, 255] or [0, 1] |
|
1176 // *Returns:* { r, g, b } in [0, 255] |
|
1177 function rgbToRgb(r, g, b) { |
|
1178 return { |
|
1179 r: bound01(r, 255) * 255, |
|
1180 g: bound01(g, 255) * 255, |
|
1181 b: bound01(b, 255) * 255 |
|
1182 }; |
|
1183 } |
|
1184 |
|
1185 // `rgbToHsl` |
|
1186 // Converts an RGB color value to HSL. |
|
1187 // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] |
|
1188 // *Returns:* { h, s, l } in [0,1] |
|
1189 function rgbToHsl(r, g, b) { |
|
1190 |
|
1191 r = bound01(r, 255); |
|
1192 g = bound01(g, 255); |
|
1193 b = bound01(b, 255); |
|
1194 |
|
1195 var max = mathMax(r, g, b), min = mathMin(r, g, b); |
|
1196 var h, s, l = (max + min) / 2; |
|
1197 |
|
1198 if (max == min) { |
|
1199 h = s = 0; // achromatic |
|
1200 } |
|
1201 else { |
|
1202 var d = max - min; |
|
1203 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); |
|
1204 switch (max) { |
|
1205 case r: h = (g - b) / d + (g < b ? 6 : 0); break; |
|
1206 case g: h = (b - r) / d + 2; break; |
|
1207 case b: h = (r - g) / d + 4; break; |
|
1208 } |
|
1209 |
|
1210 h /= 6; |
|
1211 } |
|
1212 |
|
1213 return { h: h, s: s, l: l }; |
|
1214 } |
|
1215 |
|
1216 // `hslToRgb` |
|
1217 // Converts an HSL color value to RGB. |
|
1218 // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] |
|
1219 // *Returns:* { r, g, b } in the set [0, 255] |
|
1220 function hslToRgb(h, s, l) { |
|
1221 var r, g, b; |
|
1222 |
|
1223 h = bound01(h, 360); |
|
1224 s = bound01(s, 100); |
|
1225 l = bound01(l, 100); |
|
1226 |
|
1227 function hue2rgb(p, q, t) { |
|
1228 if (t < 0) t += 1; |
|
1229 if (t > 1) t -= 1; |
|
1230 if (t < 1 / 6) return p + (q - p) * 6 * t; |
|
1231 if (t < 1 / 2) return q; |
|
1232 if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; |
|
1233 return p; |
|
1234 } |
|
1235 |
|
1236 if (s === 0) { |
|
1237 r = g = b = l; // achromatic |
|
1238 } |
|
1239 else { |
|
1240 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; |
|
1241 var p = 2 * l - q; |
|
1242 r = hue2rgb(p, q, h + 1 / 3); |
|
1243 g = hue2rgb(p, q, h); |
|
1244 b = hue2rgb(p, q, h - 1 / 3); |
|
1245 } |
|
1246 |
|
1247 return { r: r * 255, g: g * 255, b: b * 255 }; |
|
1248 } |
|
1249 |
|
1250 // `rgbToHsv` |
|
1251 // Converts an RGB color value to HSV |
|
1252 // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] |
|
1253 // *Returns:* { h, s, v } in [0,1] |
|
1254 function rgbToHsv(r, g, b) { |
|
1255 |
|
1256 r = bound01(r, 255); |
|
1257 g = bound01(g, 255); |
|
1258 b = bound01(b, 255); |
|
1259 |
|
1260 var max = mathMax(r, g, b), min = mathMin(r, g, b); |
|
1261 var h, s, v = max; |
|
1262 |
|
1263 var d = max - min; |
|
1264 s = max === 0 ? 0 : d / max; |
|
1265 |
|
1266 if (max == min) { |
|
1267 h = 0; // achromatic |
|
1268 } |
|
1269 else { |
|
1270 switch (max) { |
|
1271 case r: h = (g - b) / d + (g < b ? 6 : 0); break; |
|
1272 case g: h = (b - r) / d + 2; break; |
|
1273 case b: h = (r - g) / d + 4; break; |
|
1274 } |
|
1275 h /= 6; |
|
1276 } |
|
1277 return { h: h, s: s, v: v }; |
|
1278 } |
|
1279 |
|
1280 // `hsvToRgb` |
|
1281 // Converts an HSV color value to RGB. |
|
1282 // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] |
|
1283 // *Returns:* { r, g, b } in the set [0, 255] |
|
1284 function hsvToRgb(h, s, v) { |
|
1285 h = bound01(h, 360) * 6; |
|
1286 s = bound01(s, 100); |
|
1287 v = bound01(v, 100); |
|
1288 |
|
1289 var i = math.floor(h), |
|
1290 f = h - i, |
|
1291 p = v * (1 - s), |
|
1292 q = v * (1 - f * s), |
|
1293 t = v * (1 - (1 - f) * s), |
|
1294 mod = i % 6, |
|
1295 r = [v, q, p, p, t, v][mod], |
|
1296 g = [t, v, v, q, p, p][mod], |
|
1297 b = [p, p, t, v, v, q][mod]; |
|
1298 |
|
1299 return { r: r * 255, g: g * 255, b: b * 255 }; |
|
1300 } |
|
1301 |
|
1302 // `rgbToHex` |
|
1303 // Converts an RGB color to hex |
|
1304 // Assumes r, g, and b are contained in the set [0, 255] |
|
1305 // Returns a 3 or 6 character hex |
|
1306 function rgbToHex(r, g, b, force6Char) { |
|
1307 |
|
1308 var hex = [ |
|
1309 pad2(mathRound(r).toString(16)), |
|
1310 pad2(mathRound(g).toString(16)), |
|
1311 pad2(mathRound(b).toString(16)) |
|
1312 ]; |
|
1313 |
|
1314 // Return a 3 character hex if possible |
|
1315 if (!force6Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { |
|
1316 return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); |
|
1317 } |
|
1318 |
|
1319 return hex.join(""); |
|
1320 } |
|
1321 |
|
1322 // `equals` |
|
1323 // Can be called with any tinycolor input |
|
1324 tinycolor.equals = function (color1, color2) { |
|
1325 if (!color1 || !color2) { return false; } |
|
1326 return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); |
|
1327 }; |
|
1328 tinycolor.random = function () { |
|
1329 return tinycolor.fromRatio({ |
|
1330 r: mathRandom(), |
|
1331 g: mathRandom(), |
|
1332 b: mathRandom() |
|
1333 }); |
|
1334 }; |
|
1335 |
|
1336 |
|
1337 // Modification Functions |
|
1338 // ---------------------- |
|
1339 // Thanks to less.js for some of the basics here |
|
1340 // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js> |
|
1341 |
|
1342 |
|
1343 tinycolor.desaturate = function (color, amount) { |
|
1344 var hsl = tinycolor(color).toHsl(); |
|
1345 hsl.s -= ((amount || 10) / 100); |
|
1346 hsl.s = clamp01(hsl.s); |
|
1347 return tinycolor(hsl); |
|
1348 }; |
|
1349 tinycolor.saturate = function (color, amount) { |
|
1350 var hsl = tinycolor(color).toHsl(); |
|
1351 hsl.s += ((amount || 10) / 100); |
|
1352 hsl.s = clamp01(hsl.s); |
|
1353 return tinycolor(hsl); |
|
1354 }; |
|
1355 tinycolor.greyscale = function (color) { |
|
1356 return tinycolor.desaturate(color, 100); |
|
1357 }; |
|
1358 tinycolor.lighten = function (color, amount) { |
|
1359 var hsl = tinycolor(color).toHsl(); |
|
1360 hsl.l += ((amount || 10) / 100); |
|
1361 hsl.l = clamp01(hsl.l); |
|
1362 return tinycolor(hsl); |
|
1363 }; |
|
1364 tinycolor.darken = function (color, amount) { |
|
1365 var hsl = tinycolor(color).toHsl(); |
|
1366 hsl.l -= ((amount || 10) / 100); |
|
1367 hsl.l = clamp01(hsl.l); |
|
1368 return tinycolor(hsl); |
|
1369 }; |
|
1370 tinycolor.complement = function (color) { |
|
1371 var hsl = tinycolor(color).toHsl(); |
|
1372 hsl.h = (hsl.h + 0.5) % 1; |
|
1373 return tinycolor(hsl); |
|
1374 }; |
|
1375 |
|
1376 |
|
1377 // Combination Functions |
|
1378 // --------------------- |
|
1379 // Thanks to jQuery xColor for some of the ideas behind these |
|
1380 // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js> |
|
1381 |
|
1382 tinycolor.triad = function (color) { |
|
1383 var hsl = tinycolor(color).toHsl(); |
|
1384 var h = hsl.h * 360; |
|
1385 return [ |
|
1386 tinycolor(color), |
|
1387 tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), |
|
1388 tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) |
|
1389 ]; |
|
1390 }; |
|
1391 tinycolor.tetrad = function (color) { |
|
1392 var hsl = tinycolor(color).toHsl(); |
|
1393 var h = hsl.h * 360; |
|
1394 return [ |
|
1395 tinycolor(color), |
|
1396 tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), |
|
1397 tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), |
|
1398 tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) |
|
1399 ]; |
|
1400 }; |
|
1401 tinycolor.splitcomplement = function (color) { |
|
1402 var hsl = tinycolor(color).toHsl(); |
|
1403 var h = hsl.h * 360; |
|
1404 return [ |
|
1405 tinycolor(color), |
|
1406 tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l }), |
|
1407 tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l }) |
|
1408 ]; |
|
1409 }; |
|
1410 tinycolor.analogous = function (color, results, slices) { |
|
1411 results = results || 6; |
|
1412 slices = slices || 30; |
|
1413 |
|
1414 var hsl = tinycolor(color).toHsl(); |
|
1415 var part = 360 / slices; |
|
1416 var ret = [tinycolor(color)]; |
|
1417 |
|
1418 hsl.h *= 360; |
|
1419 |
|
1420 for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { |
|
1421 hsl.h = (hsl.h + part) % 360; |
|
1422 ret.push(tinycolor(hsl)); |
|
1423 } |
|
1424 return ret; |
|
1425 }; |
|
1426 tinycolor.monochromatic = function (color, results) { |
|
1427 results = results || 6; |
|
1428 var hsv = tinycolor(color).toHsv(); |
|
1429 var h = hsv.h, s = hsv.s, v = hsv.v; |
|
1430 var ret = []; |
|
1431 var modification = 1 / results; |
|
1432 |
|
1433 while (results--) { |
|
1434 ret.push(tinycolor({ h: h, s: s, v: v })); |
|
1435 v = (v + modification) % 1; |
|
1436 } |
|
1437 |
|
1438 return ret; |
|
1439 }; |
|
1440 tinycolor.readable = function (color1, color2) { |
|
1441 var a = tinycolor(color1).toRgb(), b = tinycolor(color2).toRgb(); |
|
1442 return ( |
|
1443 (b.r - a.r) * (b.r - a.r) + |
|
1444 (b.g - a.g) * (b.g - a.g) + |
|
1445 (b.b - a.b) * (b.b - a.b) |
|
1446 ) > 0x28A4; |
|
1447 }; |
|
1448 |
|
1449 // Big List of Colors |
|
1450 // --------- |
|
1451 // <http://www.w3.org/TR/css3-color/#svg-color> |
|
1452 var names = tinycolor.names = { |
|
1453 aliceblue: "f0f8ff", |
|
1454 antiquewhite: "faebd7", |
|
1455 aqua: "0ff", |
|
1456 aquamarine: "7fffd4", |
|
1457 azure: "f0ffff", |
|
1458 beige: "f5f5dc", |
|
1459 bisque: "ffe4c4", |
|
1460 black: "000", |
|
1461 blanchedalmond: "ffebcd", |
|
1462 blue: "00f", |
|
1463 blueviolet: "8a2be2", |
|
1464 brown: "a52a2a", |
|
1465 burlywood: "deb887", |
|
1466 burntsienna: "ea7e5d", |
|
1467 cadetblue: "5f9ea0", |
|
1468 chartreuse: "7fff00", |
|
1469 chocolate: "d2691e", |
|
1470 coral: "ff7f50", |
|
1471 cornflowerblue: "6495ed", |
|
1472 cornsilk: "fff8dc", |
|
1473 crimson: "dc143c", |
|
1474 cyan: "0ff", |
|
1475 darkblue: "00008b", |
|
1476 darkcyan: "008b8b", |
|
1477 darkgoldenrod: "b8860b", |
|
1478 darkgray: "a9a9a9", |
|
1479 darkgreen: "006400", |
|
1480 darkgrey: "a9a9a9", |
|
1481 darkkhaki: "bdb76b", |
|
1482 darkmagenta: "8b008b", |
|
1483 darkolivegreen: "556b2f", |
|
1484 darkorange: "ff8c00", |
|
1485 darkorchid: "9932cc", |
|
1486 darkred: "8b0000", |
|
1487 darksalmon: "e9967a", |
|
1488 darkseagreen: "8fbc8f", |
|
1489 darkslateblue: "483d8b", |
|
1490 darkslategray: "2f4f4f", |
|
1491 darkslategrey: "2f4f4f", |
|
1492 darkturquoise: "00ced1", |
|
1493 darkviolet: "9400d3", |
|
1494 deeppink: "ff1493", |
|
1495 deepskyblue: "00bfff", |
|
1496 dimgray: "696969", |
|
1497 dimgrey: "696969", |
|
1498 dodgerblue: "1e90ff", |
|
1499 firebrick: "b22222", |
|
1500 floralwhite: "fffaf0", |
|
1501 forestgreen: "228b22", |
|
1502 fuchsia: "f0f", |
|
1503 gainsboro: "dcdcdc", |
|
1504 ghostwhite: "f8f8ff", |
|
1505 gold: "ffd700", |
|
1506 goldenrod: "daa520", |
|
1507 gray: "808080", |
|
1508 green: "008000", |
|
1509 greenyellow: "adff2f", |
|
1510 grey: "808080", |
|
1511 honeydew: "f0fff0", |
|
1512 hotpink: "ff69b4", |
|
1513 indianred: "cd5c5c", |
|
1514 indigo: "4b0082", |
|
1515 ivory: "fffff0", |
|
1516 khaki: "f0e68c", |
|
1517 lavender: "e6e6fa", |
|
1518 lavenderblush: "fff0f5", |
|
1519 lawngreen: "7cfc00", |
|
1520 lemonchiffon: "fffacd", |
|
1521 lightblue: "add8e6", |
|
1522 lightcoral: "f08080", |
|
1523 lightcyan: "e0ffff", |
|
1524 lightgoldenrodyellow: "fafad2", |
|
1525 lightgray: "d3d3d3", |
|
1526 lightgreen: "90ee90", |
|
1527 lightgrey: "d3d3d3", |
|
1528 lightpink: "ffb6c1", |
|
1529 lightsalmon: "ffa07a", |
|
1530 lightseagreen: "20b2aa", |
|
1531 lightskyblue: "87cefa", |
|
1532 lightslategray: "789", |
|
1533 lightslategrey: "789", |
|
1534 lightsteelblue: "b0c4de", |
|
1535 lightyellow: "ffffe0", |
|
1536 lime: "0f0", |
|
1537 limegreen: "32cd32", |
|
1538 linen: "faf0e6", |
|
1539 magenta: "f0f", |
|
1540 maroon: "800000", |
|
1541 mediumaquamarine: "66cdaa", |
|
1542 mediumblue: "0000cd", |
|
1543 mediumorchid: "ba55d3", |
|
1544 mediumpurple: "9370db", |
|
1545 mediumseagreen: "3cb371", |
|
1546 mediumslateblue: "7b68ee", |
|
1547 mediumspringgreen: "00fa9a", |
|
1548 mediumturquoise: "48d1cc", |
|
1549 mediumvioletred: "c71585", |
|
1550 midnightblue: "191970", |
|
1551 mintcream: "f5fffa", |
|
1552 mistyrose: "ffe4e1", |
|
1553 moccasin: "ffe4b5", |
|
1554 navajowhite: "ffdead", |
|
1555 navy: "000080", |
|
1556 oldlace: "fdf5e6", |
|
1557 olive: "808000", |
|
1558 olivedrab: "6b8e23", |
|
1559 orange: "ffa500", |
|
1560 orangered: "ff4500", |
|
1561 orchid: "da70d6", |
|
1562 palegoldenrod: "eee8aa", |
|
1563 palegreen: "98fb98", |
|
1564 paleturquoise: "afeeee", |
|
1565 palevioletred: "db7093", |
|
1566 papayawhip: "ffefd5", |
|
1567 peachpuff: "ffdab9", |
|
1568 peru: "cd853f", |
|
1569 pink: "ffc0cb", |
|
1570 plum: "dda0dd", |
|
1571 powderblue: "b0e0e6", |
|
1572 purple: "800080", |
|
1573 red: "f00", |
|
1574 rosybrown: "bc8f8f", |
|
1575 royalblue: "4169e1", |
|
1576 saddlebrown: "8b4513", |
|
1577 salmon: "fa8072", |
|
1578 sandybrown: "f4a460", |
|
1579 seagreen: "2e8b57", |
|
1580 seashell: "fff5ee", |
|
1581 sienna: "a0522d", |
|
1582 silver: "c0c0c0", |
|
1583 skyblue: "87ceeb", |
|
1584 slateblue: "6a5acd", |
|
1585 slategray: "708090", |
|
1586 slategrey: "708090", |
|
1587 snow: "fffafa", |
|
1588 springgreen: "00ff7f", |
|
1589 steelblue: "4682b4", |
|
1590 tan: "d2b48c", |
|
1591 teal: "008080", |
|
1592 thistle: "d8bfd8", |
|
1593 tomato: "ff6347", |
|
1594 turquoise: "40e0d0", |
|
1595 violet: "ee82ee", |
|
1596 wheat: "f5deb3", |
|
1597 white: "fff", |
|
1598 whitesmoke: "f5f5f5", |
|
1599 yellow: "ff0", |
|
1600 yellowgreen: "9acd32" |
|
1601 }; |
|
1602 |
|
1603 // Make it easy to access colors via `hexNames[hex]` |
|
1604 var hexNames = tinycolor.hexNames = flip(names); |
|
1605 |
|
1606 |
|
1607 // Utilities |
|
1608 // --------- |
|
1609 |
|
1610 // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` |
|
1611 function flip(o) { |
|
1612 var flipped = {}; |
|
1613 for (var i in o) { |
|
1614 if (o.hasOwnProperty(i)) { |
|
1615 flipped[o[i]] = i; |
|
1616 } |
|
1617 } |
|
1618 return flipped; |
|
1619 } |
|
1620 |
|
1621 // Take input from [0, n] and return it as [0, 1] |
|
1622 function bound01(n, max) { |
|
1623 if (isOnePointZero(n)) { n = "100%"; } |
|
1624 |
|
1625 var processPercent = isPercentage(n); |
|
1626 n = mathMin(max, mathMax(0, parseFloat(n))); |
|
1627 |
|
1628 // Automatically convert percentage into number |
|
1629 if (processPercent) { |
|
1630 n = n * (max / 100); |
|
1631 } |
|
1632 |
|
1633 // Handle floating point rounding errors |
|
1634 if (math.abs(n - max) < 0.000001) { |
|
1635 return 1; |
|
1636 } |
|
1637 else if (n >= 1) { |
|
1638 return (n % max) / parseFloat(max); |
|
1639 } |
|
1640 return n; |
|
1641 } |
|
1642 |
|
1643 // Force a number between 0 and 1 |
|
1644 function clamp01(val) { |
|
1645 return mathMin(1, mathMax(0, val)); |
|
1646 } |
|
1647 |
|
1648 // Parse an integer into hex |
|
1649 function parseHex(val) { |
|
1650 return parseInt(val, 16); |
|
1651 } |
|
1652 |
|
1653 // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 |
|
1654 // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0> |
|
1655 function isOnePointZero(n) { |
|
1656 return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; |
|
1657 } |
|
1658 |
|
1659 // Check to see if string passed in is a percentage |
|
1660 function isPercentage(n) { |
|
1661 return typeof n === "string" && n.indexOf('%') != -1; |
|
1662 } |
|
1663 |
|
1664 // Force a hex value to have 2 characters |
|
1665 function pad2(c) { |
|
1666 return c.length == 1 ? '0' + c : '' + c; |
|
1667 } |
|
1668 |
|
1669 var matchers = (function () { |
|
1670 |
|
1671 // <http://www.w3.org/TR/css3-values/#integers> |
|
1672 var CSS_INTEGER = "[-\\+]?\\d+%?"; |
|
1673 |
|
1674 // <http://www.w3.org/TR/css3-values/#number-value> |
|
1675 var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; |
|
1676 |
|
1677 // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. |
|
1678 var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; |
|
1679 |
|
1680 // Actual matching. |
|
1681 // Parentheses and commas are optional, but not required. |
|
1682 // Whitespace can take the place of commas or opening paren |
|
1683 var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; |
|
1684 var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; |
|
1685 |
|
1686 return { |
|
1687 rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), |
|
1688 rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), |
|
1689 hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), |
|
1690 hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), |
|
1691 hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), |
|
1692 hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, |
|
1693 hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ |
|
1694 }; |
|
1695 })(); |
|
1696 |
|
1697 // `stringInputToObject` |
|
1698 // Permissive string parsing. Take in a number of formats, and output an object |
|
1699 // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` |
|
1700 function stringInputToObject(color) { |
|
1701 |
|
1702 color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase(); |
|
1703 var named = false; |
|
1704 if (names[color]) { |
|
1705 color = names[color]; |
|
1706 named = true; |
|
1707 } |
|
1708 else if (color == 'transparent') { |
|
1709 return { r: 0, g: 0, b: 0, a: 0 }; |
|
1710 } |
|
1711 |
|
1712 // Try to match string input using regular expressions. |
|
1713 // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] |
|
1714 // Just return an object and let the conversion functions handle that. |
|
1715 // This way the result will be the same whether the tinycolor is initialized with string or object. |
|
1716 var match; |
|
1717 if ((match = matchers.rgb.exec(color))) { |
|
1718 return { r: match[1], g: match[2], b: match[3] }; |
|
1719 } |
|
1720 if ((match = matchers.rgba.exec(color))) { |
|
1721 return { r: match[1], g: match[2], b: match[3], a: match[4] }; |
|
1722 } |
|
1723 if ((match = matchers.hsl.exec(color))) { |
|
1724 return { h: match[1], s: match[2], l: match[3] }; |
|
1725 } |
|
1726 if ((match = matchers.hsla.exec(color))) { |
|
1727 return { h: match[1], s: match[2], l: match[3], a: match[4] }; |
|
1728 } |
|
1729 if ((match = matchers.hsv.exec(color))) { |
|
1730 return { h: match[1], s: match[2], v: match[3] }; |
|
1731 } |
|
1732 if ((match = matchers.hex6.exec(color))) { |
|
1733 return { |
|
1734 r: parseHex(match[1]), |
|
1735 g: parseHex(match[2]), |
|
1736 b: parseHex(match[3]), |
|
1737 format: named ? "name" : "hex" |
|
1738 }; |
|
1739 } |
|
1740 if ((match = matchers.hex3.exec(color))) { |
|
1741 return { |
|
1742 r: parseHex(match[1] + '' + match[1]), |
|
1743 g: parseHex(match[2] + '' + match[2]), |
|
1744 b: parseHex(match[3] + '' + match[3]), |
|
1745 format: named ? "name" : "hex" |
|
1746 }; |
|
1747 } |
|
1748 |
|
1749 return false; |
|
1750 } |
|
1751 |
|
1752 // Everything is ready, expose to window |
|
1753 window.tinycolor = tinycolor; |
|
1754 |
|
1755 })(this); |
|
1756 |
|
1757 $(function () { |
|
1758 if ($.fn.spectrum.load) { |
|
1759 $.fn.spectrum.processNativeColorInputs(); |
|
1760 } |
|
1761 }); |
|
1762 |
|
1763 |
|
1764 function log(){window.console&&(log=Function.prototype.bind?Function.prototype.bind.call(console.log,console):function(){Function.prototype.apply.call(console.log,console,arguments)},log.apply(this,arguments))}; |
|
1765 |
|
1766 |
|
1767 })(window, jQuery); |