Commit 8f79b72dbfa32e97da16e6f2bb15ef8fe421b058
1 parent
5d182131
Exists in
staging
and in
4 other branches
Change the category color selection to use a colorpicker component.
The field display_color has turned into char(6) and stores the color rgb hex code. (ActionItem2915)
Showing
16 changed files
with
2896 additions
and
74 deletions
Show diff stats
app/controllers/admin/categories_controller.rb
| ... | ... | @@ -45,9 +45,11 @@ class CategoriesController < AdminController |
| 45 | 45 | if request.post? |
| 46 | 46 | @category.update_attributes!(params[:category]) |
| 47 | 47 | @saved = true |
| 48 | + session[:notice] = _("Category %s saved." % @category.name) | |
| 48 | 49 | redirect_to :action => 'index' |
| 49 | 50 | end |
| 50 | 51 | rescue Exception => e |
| 52 | + session[:notice] = _('Could not save category.') | |
| 51 | 53 | render :action => 'edit' |
| 52 | 54 | end |
| 53 | 55 | end | ... | ... |
app/helpers/application_helper.rb
| ... | ... | @@ -1415,4 +1415,14 @@ module ApplicationHelper |
| 1415 | 1415 | content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))}) |
| 1416 | 1416 | end |
| 1417 | 1417 | |
| 1418 | + def labelled_colorpicker_field(human_name, object_name, method, options = {}) | |
| 1419 | + options[:id] ||= 'text-field-' + FormsHelper.next_id_number | |
| 1420 | + content_tag('label', human_name, :for => options[:id], :class => 'formlabel') + | |
| 1421 | + colorpicker_field(object_name, method, options.merge(:class => 'colorpicker_field')) | |
| 1422 | + end | |
| 1423 | + | |
| 1424 | + def colorpicker_field(object_name, method, options = {}) | |
| 1425 | + text_field(object_name, method, options.merge(:class => 'colorpicker_field')) | |
| 1426 | + end | |
| 1427 | + | |
| 1418 | 1428 | end | ... | ... |
app/helpers/categories_helper.rb
| 1 | 1 | module CategoriesHelper |
| 2 | 2 | |
| 3 | - | |
| 4 | - COLORS = [ | |
| 5 | - [ N_('Do not display at the menu'), nil ], | |
| 6 | - [ N_('Orange'), 1], | |
| 7 | - [ N_('Green'), 2], | |
| 8 | - [ N_('Purple'), 3], | |
| 9 | - [ N_('Red'), 4], | |
| 10 | - [ N_('Dark Green'), 5], | |
| 11 | - [ N_('Blue Oil'), 6], | |
| 12 | - [ N_('Blue'), 7], | |
| 13 | - [ N_('Brown'), 8], | |
| 14 | - [ N_('Light Green'), 9], | |
| 15 | - [ N_('Light Blue'), 10], | |
| 16 | - [ N_('Dark Blue'), 11], | |
| 17 | - [ N_('Blue Pool'), 12], | |
| 18 | - [ N_('Beige'), 13], | |
| 19 | - [ N_('Yellow'), 14], | |
| 20 | - [ N_('Light Brown'), 15] | |
| 21 | - ] | |
| 22 | - | |
| 23 | 3 | TYPES = [ |
| 24 | 4 | [ _('General Category'), Category.to_s ], |
| 25 | 5 | [ _('Product Category'), ProductCategory.to_s ], |
| 26 | 6 | [ _('Region'), Region.to_s ], |
| 27 | 7 | ] |
| 28 | 8 | |
| 29 | - def select_color_for_category | |
| 30 | - if @category.top_level? | |
| 31 | - labelled_form_field(_('Display at the menu?'), select('category', 'display_color', CategoriesHelper::COLORS.map {|item| [gettext(item[0]), item[1]] })) | |
| 32 | - else | |
| 33 | - "" | |
| 34 | - end | |
| 9 | + def select_category_type(field) | |
| 10 | + value = params[field] | |
| 11 | + labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value))) | |
| 35 | 12 | end |
| 36 | 13 | |
| 37 | 14 | def display_color_for_category(category) |
| ... | ... | @@ -43,9 +20,9 @@ module CategoriesHelper |
| 43 | 20 | end |
| 44 | 21 | end |
| 45 | 22 | |
| 46 | - def select_category_type(field) | |
| 47 | - value = params[field] | |
| 48 | - labelled_form_field(_('Type of category'), select_tag('type', options_for_select(TYPES, value))) | |
| 23 | + def category_color_style(category) | |
| 24 | + return '' if category.display_color.blank? | |
| 25 | + 'background-color: #'+category.display_color+';' | |
| 49 | 26 | end |
| 50 | 27 | |
| 51 | 28 | #FIXME make this test |
| ... | ... | @@ -63,4 +40,14 @@ module CategoriesHelper |
| 63 | 40 | {:id => category_id ? "select-category-#{category_id}-link" : nil, :remote => true, :class => 'select-subcategory-link'}.merge(html_options) |
| 64 | 41 | end |
| 65 | 42 | |
| 43 | + protected | |
| 44 | + | |
| 45 | + def search_category_tree_for_color(category) | |
| 46 | + if category.display_color.blank? | |
| 47 | + category.parent.nil? ? nil : search_category_tree_for_color(category.parent) | |
| 48 | + else | |
| 49 | + category.display_color | |
| 50 | + end | |
| 51 | + end | |
| 52 | + | |
| 66 | 53 | end | ... | ... |
app/models/category.rb
| ... | ... | @@ -14,9 +14,6 @@ class Category < ActiveRecord::Base |
| 14 | 14 | validates_uniqueness_of :slug,:scope => [ :environment_id, :parent_id ], :message => N_('{fn} is already being used by another category.').fix_i18n |
| 15 | 15 | belongs_to :environment |
| 16 | 16 | |
| 17 | - validates_inclusion_of :display_color, :in => 1..15, :allow_nil => true | |
| 18 | - validates_uniqueness_of :display_color, :scope => :environment_id, :if => (lambda { |cat| ! cat.display_color.nil? }), :message => N_('{fn} was already assigned to another category.').fix_i18n | |
| 19 | - | |
| 20 | 17 | # Finds all top level categories for a given environment. |
| 21 | 18 | scope :top_level_for, lambda { |environment| |
| 22 | 19 | {:conditions => ['parent_id is null and environment_id = ?', environment.id ]} |
| ... | ... | @@ -42,6 +39,13 @@ class Category < ActiveRecord::Base |
| 42 | 39 | |
| 43 | 40 | acts_as_having_image |
| 44 | 41 | |
| 42 | + before_save :normalize_display_color | |
| 43 | + | |
| 44 | + def normalize_display_color | |
| 45 | + display_color.gsub!('#', '') if display_color | |
| 46 | + display_color = nil if display_color.blank? | |
| 47 | + end | |
| 48 | + | |
| 45 | 49 | scope :from_types, lambda { |types| |
| 46 | 50 | types.select{ |t| t.blank? }.empty? ? |
| 47 | 51 | { :conditions => { :type => types } } : | ... | ... |
app/views/categories/_category.html.erb
| 1 | 1 | <li> |
| 2 | 2 | <div class='treeitem'> |
| 3 | - <%= display_color_for_category(category) %> | |
| 4 | - <%= category.name %> | |
| 3 | + <% unless category_color_style(category).empty? %> | |
| 4 | + <span class="color_marker" style="<%= category_color_style(category) %>" ></span> | |
| 5 | + <% end %> | |
| 6 | + <span><%= category.name %></span> | |
| 7 | + | |
| 5 | 8 | <% if category.children.count > 0 %> |
| 6 | 9 | <div class='button' id="category-loading-<%= category.id %>" style="position: relative;"> |
| 7 | 10 | <a href="#" id="show-button-<%= category.id %>" class="show-button" onclick="return false;" data-category="<%= category.id %>"><%= _('Show') %></a> | ... | ... |
app/views/categories/_form.html.erb
| 1 | +<%= stylesheet_link_tag 'spectrum.css' %> | |
| 2 | +<%= javascript_include_tag "spectrum.js" %> | |
| 3 | +<%= javascript_include_tag "colorpicker-noosfero.js" %> | |
| 4 | + | |
| 1 | 5 | <%= error_messages_for 'category' %> |
| 2 | 6 | |
| 3 | 7 | <%= labelled_form_for 'category', :html => { :multipart => true} do |f| %> |
| ... | ... | @@ -13,12 +17,13 @@ |
| 13 | 17 | <% end %> |
| 14 | 18 | <% end %> |
| 15 | 19 | |
| 16 | - <%= select_color_for_category if !environment.enabled?('disable_categories_menu') %> | |
| 17 | - | |
| 18 | 20 | <%= required f.text_field('name') %> |
| 19 | 21 | |
| 20 | 22 | <%= labelled_check_box(_('Display in the menu'), 'category[display_in_menu]', '1', @category.display_in_menu) %> |
| 21 | 23 | |
| 24 | + <%= labelled_colorpicker_field(_('Pick a color'), :category, 'display_color' ) unless environment.enabled?('disable_categories_menu')%> | |
| 25 | + <span id="color_preview" class = "color_marker" style="<%= category_color_style(@category) %>" ></span> | |
| 26 | + | |
| 22 | 27 | <%= f.fields_for :image_builder, @category.image do |i| %> |
| 23 | 28 | <%= file_field_or_thumbnail(_('Image:'), @category.image, i) %> |
| 24 | 29 | <% end %> | ... | ... |
db/migrate/20140807134625_change_category_display_color_to_string.rb
0 → 100644
| ... | ... | @@ -0,0 +1,30 @@ |
| 1 | +class ChangeCategoryDisplayColorToString < ActiveRecord::Migration | |
| 2 | + | |
| 3 | + def self.up | |
| 4 | + change_table :categories do |t| | |
| 5 | + t.string :display_color_tmp, :limit => 6 | |
| 6 | + end | |
| 7 | + Category.update_all({:display_color_tmp => "ffa500"}, {:display_color => 1}) | |
| 8 | + Category.update_all({:display_color_tmp => "00FF00"}, {:display_color => 2}) | |
| 9 | + Category.update_all({:display_color_tmp => "a020f0"}, {:display_color => 3}) | |
| 10 | + Category.update_all({:display_color_tmp => "ff0000"}, {:display_color => 4}) | |
| 11 | + Category.update_all({:display_color_tmp => "006400"}, {:display_color => 5}) | |
| 12 | + Category.update_all({:display_color_tmp => "191970"}, {:display_color => 6}) | |
| 13 | + Category.update_all({:display_color_tmp => "0000ff"}, {:display_color => 7}) | |
| 14 | + Category.update_all({:display_color_tmp => "a52a2a"}, {:display_color => 8}) | |
| 15 | + Category.update_all({:display_color_tmp => "32cd32"}, {:display_color => 9}) | |
| 16 | + Category.update_all({:display_color_tmp => "add8e6"}, {:display_color => 10}) | |
| 17 | + Category.update_all({:display_color_tmp => "483d8b"}, {:display_color => 11}) | |
| 18 | + Category.update_all({:display_color_tmp => "b8e9ee"}, {:display_color => 12}) | |
| 19 | + Category.update_all({:display_color_tmp => "f5f5dc"}, {:display_color => 13}) | |
| 20 | + Category.update_all({:display_color_tmp => "ffff00"}, {:display_color => 14}) | |
| 21 | + Category.update_all({:display_color_tmp => "f4a460"}, {:display_color => 15}) | |
| 22 | + end | |
| 23 | + | |
| 24 | + def self.down | |
| 25 | + change_table :categories do |t| | |
| 26 | + t.remove :display_color | |
| 27 | + t.rename :display_color_tmp, :display_color | |
| 28 | + end | |
| 29 | + end | |
| 30 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | +jQuery(document).ready(function($) { | |
| 2 | + $(".colorpicker_field").spectrum({ | |
| 3 | + showInput: true, | |
| 4 | + showInitial: false, | |
| 5 | + preferredFormat: "hex", | |
| 6 | + allowEmpty: true, | |
| 7 | + showPalette: true, | |
| 8 | + palette: [ | |
| 9 | + ["rgb(0, 0, 0)", "rgb(67, 67, 67)", "rgb(102, 102, 102)", | |
| 10 | + "rgb(204, 204, 204)", "rgb(217, 217, 217)","rgb(255, 255, 255)"], | |
| 11 | + ["rgb(152, 0, 0)", "rgb(255, 0, 0)", "rgb(255, 153, 0)", "rgb(255, 255, 0)", "rgb(0, 255, 0)", | |
| 12 | + "rgb(0, 255, 255)", "rgb(74, 134, 232)", "rgb(0, 0, 255)", "rgb(153, 0, 255)", "rgb(255, 0, 255)"], | |
| 13 | + ["rgb(230, 184, 175)", "rgb(244, 204, 204)", "rgb(252, 229, 205)", "rgb(255, 242, 204)", "rgb(217, 234, 211)", | |
| 14 | + "rgb(208, 224, 227)", "rgb(201, 218, 248)", "rgb(207, 226, 243)", "rgb(217, 210, 233)", "rgb(234, 209, 220)", | |
| 15 | + "rgb(221, 126, 107)", "rgb(234, 153, 153)", "rgb(249, 203, 156)", "rgb(255, 229, 153)", "rgb(182, 215, 168)", | |
| 16 | + "rgb(162, 196, 201)", "rgb(164, 194, 244)", "rgb(159, 197, 232)", "rgb(180, 167, 214)", "rgb(213, 166, 189)", | |
| 17 | + "rgb(204, 65, 37)", "rgb(224, 102, 102)", "rgb(246, 178, 107)", "rgb(255, 217, 102)", "rgb(147, 196, 125)", | |
| 18 | + "rgb(118, 165, 175)", "rgb(109, 158, 235)", "rgb(111, 168, 220)", "rgb(142, 124, 195)", "rgb(194, 123, 160)", | |
| 19 | + "rgb(166, 28, 0)", "rgb(204, 0, 0)", "rgb(230, 145, 56)", "rgb(241, 194, 50)", "rgb(106, 168, 79)", | |
| 20 | + "rgb(69, 129, 142)", "rgb(60, 120, 216)", "rgb(61, 133, 198)", "rgb(103, 78, 167)", "rgb(166, 77, 121)", | |
| 21 | + "rgb(91, 15, 0)", "rgb(102, 0, 0)", "rgb(120, 63, 4)", "rgb(127, 96, 0)", "rgb(39, 78, 19)", | |
| 22 | + "rgb(12, 52, 61)", "rgb(28, 69, 135)", "rgb(7, 55, 99)", "rgb(32, 18, 77)", "rgb(76, 17, 48)"] | |
| 23 | + ] | |
| 24 | + }); | |
| 25 | +}); | ... | ... |
| ... | ... | @@ -0,0 +1,2259 @@ |
| 1 | +// Spectrum Colorpicker v1.4.1 | |
| 2 | +// https://github.com/bgrins/spectrum | |
| 3 | +// Author: Brian Grinstead | |
| 4 | +// License: MIT | |
| 5 | + | |
| 6 | +(function (window, $, undefined) { | |
| 7 | + "use strict"; | |
| 8 | + | |
| 9 | + var defaultOpts = { | |
| 10 | + | |
| 11 | + // Callbacks | |
| 12 | + beforeShow: noop, | |
| 13 | + move: noop, | |
| 14 | + change: noop, | |
| 15 | + show: noop, | |
| 16 | + hide: noop, | |
| 17 | + | |
| 18 | + // Options | |
| 19 | + color: false, | |
| 20 | + flat: false, | |
| 21 | + showInput: false, | |
| 22 | + allowEmpty: false, | |
| 23 | + showButtons: true, | |
| 24 | + clickoutFiresChange: false, | |
| 25 | + showInitial: false, | |
| 26 | + showPalette: false, | |
| 27 | + showPaletteOnly: false, | |
| 28 | + hideAfterPaletteSelect: false, | |
| 29 | + togglePaletteOnly: false, | |
| 30 | + showSelectionPalette: true, | |
| 31 | + localStorageKey: false, | |
| 32 | + appendTo: "body", | |
| 33 | + maxSelectionSize: 7, | |
| 34 | + cancelText: "cancel", | |
| 35 | + chooseText: "choose", | |
| 36 | + togglePaletteMoreText: "more", | |
| 37 | + togglePaletteLessText: "less", | |
| 38 | + clearText: "Clear Color Selection", | |
| 39 | + noColorSelectedText: "No Color Selected", | |
| 40 | + preferredFormat: false, | |
| 41 | + className: "", // Deprecated - use containerClassName and replacerClassName instead. | |
| 42 | + containerClassName: "", | |
| 43 | + replacerClassName: "", | |
| 44 | + showAlpha: false, | |
| 45 | + theme: "sp-light", | |
| 46 | + palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]], | |
| 47 | + selectionPalette: [], | |
| 48 | + disabled: false | |
| 49 | + }, | |
| 50 | + spectrums = [], | |
| 51 | + IE = !!/msie/i.exec( window.navigator.userAgent ), | |
| 52 | + rgbaSupport = (function() { | |
| 53 | + function contains( str, substr ) { | |
| 54 | + return !!~('' + str).indexOf(substr); | |
| 55 | + } | |
| 56 | + | |
| 57 | + var elem = document.createElement('div'); | |
| 58 | + var style = elem.style; | |
| 59 | + style.cssText = 'background-color:rgba(0,0,0,.5)'; | |
| 60 | + return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla'); | |
| 61 | + })(), | |
| 62 | + inputTypeColorSupport = (function() { | |
| 63 | + var colorInput = $("<input type='color' value='!' />")[0]; | |
| 64 | + return colorInput.type === "color" && colorInput.value !== "!"; | |
| 65 | + })(), | |
| 66 | + replaceInput = [ | |
| 67 | + "<div class='sp-replacer'>", | |
| 68 | + "<div class='sp-preview'><div class='sp-preview-inner'></div></div>", | |
| 69 | + "<div class='sp-dd'>▼</div>", | |
| 70 | + "</div>" | |
| 71 | + ].join(''), | |
| 72 | + markup = (function () { | |
| 73 | + | |
| 74 | + // IE does not support gradients with multiple stops, so we need to simulate | |
| 75 | + // that for the rainbow slider with 8 divs that each have a single gradient | |
| 76 | + var gradientFix = ""; | |
| 77 | + if (IE) { | |
| 78 | + for (var i = 1; i <= 6; i++) { | |
| 79 | + gradientFix += "<div class='sp-" + i + "'></div>"; | |
| 80 | + } | |
| 81 | + } | |
| 82 | + | |
| 83 | + return [ | |
| 84 | + "<div class='sp-container sp-hidden'>", | |
| 85 | + "<div class='sp-palette-container'>", | |
| 86 | + "<div class='sp-palette sp-thumb sp-cf'></div>", | |
| 87 | + "<div class='sp-palette-button-container sp-cf'>", | |
| 88 | + "<button type='button' class='sp-palette-toggle'></button>", | |
| 89 | + "</div>", | |
| 90 | + "</div>", | |
| 91 | + "<div class='sp-picker-container'>", | |
| 92 | + "<div class='sp-top sp-cf'>", | |
| 93 | + "<div class='sp-fill'></div>", | |
| 94 | + "<div class='sp-top-inner'>", | |
| 95 | + "<div class='sp-color'>", | |
| 96 | + "<div class='sp-sat'>", | |
| 97 | + "<div class='sp-val'>", | |
| 98 | + "<div class='sp-dragger'></div>", | |
| 99 | + "</div>", | |
| 100 | + "</div>", | |
| 101 | + "</div>", | |
| 102 | + "<div class='sp-clear sp-clear-display'>", | |
| 103 | + "</div>", | |
| 104 | + "<div class='sp-hue'>", | |
| 105 | + "<div class='sp-slider'></div>", | |
| 106 | + gradientFix, | |
| 107 | + "</div>", | |
| 108 | + "</div>", | |
| 109 | + "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>", | |
| 110 | + "</div>", | |
| 111 | + "<div class='sp-input-container sp-cf'>", | |
| 112 | + "<input class='sp-input' type='text' spellcheck='false' />", | |
| 113 | + "</div>", | |
| 114 | + "<div class='sp-initial sp-thumb sp-cf'></div>", | |
| 115 | + "<div class='sp-button-container sp-cf'>", | |
| 116 | + "<a class='sp-cancel' href='#'></a>", | |
| 117 | + "<button type='button' class='sp-choose'></button>", | |
| 118 | + "</div>", | |
| 119 | + "</div>", | |
| 120 | + "</div>" | |
| 121 | + ].join(""); | |
| 122 | + })(); | |
| 123 | + | |
| 124 | + function paletteTemplate (p, color, className, opts) { | |
| 125 | + var html = []; | |
| 126 | + for (var i = 0; i < p.length; i++) { | |
| 127 | + var current = p[i]; | |
| 128 | + if(current) { | |
| 129 | + var tiny = tinycolor(current); | |
| 130 | + var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light"; | |
| 131 | + c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : ""; | |
| 132 | + var formattedString = tiny.toString(opts.preferredFormat || "rgb"); | |
| 133 | + var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter(); | |
| 134 | + html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>'); | |
| 135 | + } else { | |
| 136 | + var cls = 'sp-clear-display'; | |
| 137 | + html.push($('<div />') | |
| 138 | + .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>') | |
| 139 | + .attr('title', opts.noColorSelectedText) | |
| 140 | + ) | |
| 141 | + .html() | |
| 142 | + ); | |
| 143 | + } | |
| 144 | + } | |
| 145 | + return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>"; | |
| 146 | + } | |
| 147 | + | |
| 148 | + function hideAll() { | |
| 149 | + for (var i = 0; i < spectrums.length; i++) { | |
| 150 | + if (spectrums[i]) { | |
| 151 | + spectrums[i].hide(); | |
| 152 | + } | |
| 153 | + } | |
| 154 | + } | |
| 155 | + | |
| 156 | + function instanceOptions(o, callbackContext) { | |
| 157 | + var opts = $.extend({}, defaultOpts, o); | |
| 158 | + opts.callbacks = { | |
| 159 | + 'move': bind(opts.move, callbackContext), | |
| 160 | + 'change': bind(opts.change, callbackContext), | |
| 161 | + 'show': bind(opts.show, callbackContext), | |
| 162 | + 'hide': bind(opts.hide, callbackContext), | |
| 163 | + 'beforeShow': bind(opts.beforeShow, callbackContext) | |
| 164 | + }; | |
| 165 | + | |
| 166 | + return opts; | |
| 167 | + } | |
| 168 | + | |
| 169 | + function spectrum(element, o) { | |
| 170 | + | |
| 171 | + var opts = instanceOptions(o, element), | |
| 172 | + flat = opts.flat, | |
| 173 | + showSelectionPalette = opts.showSelectionPalette, | |
| 174 | + localStorageKey = opts.localStorageKey, | |
| 175 | + theme = opts.theme, | |
| 176 | + callbacks = opts.callbacks, | |
| 177 | + resize = throttle(reflow, 10), | |
| 178 | + visible = false, | |
| 179 | + dragWidth = 0, | |
| 180 | + dragHeight = 0, | |
| 181 | + dragHelperHeight = 0, | |
| 182 | + slideHeight = 0, | |
| 183 | + slideWidth = 0, | |
| 184 | + alphaWidth = 0, | |
| 185 | + alphaSlideHelperWidth = 0, | |
| 186 | + slideHelperHeight = 0, | |
| 187 | + currentHue = 0, | |
| 188 | + currentSaturation = 0, | |
| 189 | + currentValue = 0, | |
| 190 | + currentAlpha = 1, | |
| 191 | + palette = [], | |
| 192 | + paletteArray = [], | |
| 193 | + paletteLookup = {}, | |
| 194 | + selectionPalette = opts.selectionPalette.slice(0), | |
| 195 | + maxSelectionSize = opts.maxSelectionSize, | |
| 196 | + draggingClass = "sp-dragging", | |
| 197 | + shiftMovementDirection = null; | |
| 198 | + | |
| 199 | + var doc = element.ownerDocument, | |
| 200 | + body = doc.body, | |
| 201 | + boundElement = $(element), | |
| 202 | + disabled = false, | |
| 203 | + container = $(markup, doc).addClass(theme), | |
| 204 | + pickerContainer = container.find(".sp-picker-container"), | |
| 205 | + dragger = container.find(".sp-color"), | |
| 206 | + dragHelper = container.find(".sp-dragger"), | |
| 207 | + slider = container.find(".sp-hue"), | |
| 208 | + slideHelper = container.find(".sp-slider"), | |
| 209 | + alphaSliderInner = container.find(".sp-alpha-inner"), | |
| 210 | + alphaSlider = container.find(".sp-alpha"), | |
| 211 | + alphaSlideHelper = container.find(".sp-alpha-handle"), | |
| 212 | + textInput = container.find(".sp-input"), | |
| 213 | + paletteContainer = container.find(".sp-palette"), | |
| 214 | + initialColorContainer = container.find(".sp-initial"), | |
| 215 | + cancelButton = container.find(".sp-cancel"), | |
| 216 | + clearButton = container.find(".sp-clear"), | |
| 217 | + chooseButton = container.find(".sp-choose"), | |
| 218 | + toggleButton = container.find(".sp-palette-toggle"), | |
| 219 | + isInput = boundElement.is("input"), | |
| 220 | + isInputTypeColor = isInput && inputTypeColorSupport && boundElement.attr("type") === "color", | |
| 221 | + shouldReplace = isInput && !flat, | |
| 222 | + replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]), | |
| 223 | + offsetElement = (shouldReplace) ? replacer : boundElement, | |
| 224 | + previewElement = replacer.find(".sp-preview-inner"), | |
| 225 | + initialColor = opts.color || (isInput && boundElement.val()), | |
| 226 | + colorOnShow = false, | |
| 227 | + preferredFormat = opts.preferredFormat, | |
| 228 | + currentPreferredFormat = preferredFormat, | |
| 229 | + clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange, | |
| 230 | + isEmpty = !initialColor, | |
| 231 | + allowEmpty = opts.allowEmpty && !isInputTypeColor; | |
| 232 | + | |
| 233 | + function applyOptions() { | |
| 234 | + | |
| 235 | + if (opts.showPaletteOnly) { | |
| 236 | + opts.showPalette = true; | |
| 237 | + } | |
| 238 | + | |
| 239 | + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); | |
| 240 | + | |
| 241 | + if (opts.palette) { | |
| 242 | + palette = opts.palette.slice(0); | |
| 243 | + paletteArray = $.isArray(palette[0]) ? palette : [palette]; | |
| 244 | + paletteLookup = {}; | |
| 245 | + for (var i = 0; i < paletteArray.length; i++) { | |
| 246 | + for (var j = 0; j < paletteArray[i].length; j++) { | |
| 247 | + var rgb = tinycolor(paletteArray[i][j]).toRgbString(); | |
| 248 | + paletteLookup[rgb] = true; | |
| 249 | + } | |
| 250 | + } | |
| 251 | + } | |
| 252 | + | |
| 253 | + container.toggleClass("sp-flat", flat); | |
| 254 | + container.toggleClass("sp-input-disabled", !opts.showInput); | |
| 255 | + container.toggleClass("sp-alpha-enabled", opts.showAlpha); | |
| 256 | + container.toggleClass("sp-clear-enabled", allowEmpty); | |
| 257 | + container.toggleClass("sp-buttons-disabled", !opts.showButtons); | |
| 258 | + container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly); | |
| 259 | + container.toggleClass("sp-palette-disabled", !opts.showPalette); | |
| 260 | + container.toggleClass("sp-palette-only", opts.showPaletteOnly); | |
| 261 | + container.toggleClass("sp-initial-disabled", !opts.showInitial); | |
| 262 | + container.addClass(opts.className).addClass(opts.containerClassName); | |
| 263 | + | |
| 264 | + reflow(); | |
| 265 | + } | |
| 266 | + | |
| 267 | + function initialize() { | |
| 268 | + | |
| 269 | + if (IE) { | |
| 270 | + container.find("*:not(input)").attr("unselectable", "on"); | |
| 271 | + } | |
| 272 | + | |
| 273 | + applyOptions(); | |
| 274 | + | |
| 275 | + if (shouldReplace) { | |
| 276 | + boundElement.after(replacer).hide(); | |
| 277 | + } | |
| 278 | + | |
| 279 | + if (!allowEmpty) { | |
| 280 | + clearButton.hide(); | |
| 281 | + } | |
| 282 | + | |
| 283 | + if (flat) { | |
| 284 | + boundElement.after(container).hide(); | |
| 285 | + } | |
| 286 | + else { | |
| 287 | + | |
| 288 | + var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo); | |
| 289 | + if (appendTo.length !== 1) { | |
| 290 | + appendTo = $("body"); | |
| 291 | + } | |
| 292 | + | |
| 293 | + appendTo.append(container); | |
| 294 | + } | |
| 295 | + | |
| 296 | + updateSelectionPaletteFromStorage(); | |
| 297 | + | |
| 298 | + offsetElement.bind("click.spectrum touchstart.spectrum", function (e) { | |
| 299 | + if (!disabled) { | |
| 300 | + toggle(); | |
| 301 | + } | |
| 302 | + | |
| 303 | + e.stopPropagation(); | |
| 304 | + | |
| 305 | + if (!$(e.target).is("input")) { | |
| 306 | + e.preventDefault(); | |
| 307 | + } | |
| 308 | + }); | |
| 309 | + | |
| 310 | + if(boundElement.is(":disabled") || (opts.disabled === true)) { | |
| 311 | + disable(); | |
| 312 | + } | |
| 313 | + | |
| 314 | + // Prevent clicks from bubbling up to document. This would cause it to be hidden. | |
| 315 | + container.click(stopPropagation); | |
| 316 | + | |
| 317 | + // Handle user typed input | |
| 318 | + textInput.change(setFromTextInput); | |
| 319 | + textInput.bind("paste", function () { | |
| 320 | + setTimeout(setFromTextInput, 1); | |
| 321 | + }); | |
| 322 | + textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } }); | |
| 323 | + | |
| 324 | + cancelButton.text(opts.cancelText); | |
| 325 | + cancelButton.bind("click.spectrum", function (e) { | |
| 326 | + e.stopPropagation(); | |
| 327 | + e.preventDefault(); | |
| 328 | + hide("cancel"); | |
| 329 | + }); | |
| 330 | + | |
| 331 | + clearButton.attr("title", opts.clearText); | |
| 332 | + clearButton.bind("click.spectrum", function (e) { | |
| 333 | + e.stopPropagation(); | |
| 334 | + e.preventDefault(); | |
| 335 | + isEmpty = true; | |
| 336 | + move(); | |
| 337 | + | |
| 338 | + if(flat) { | |
| 339 | + //for the flat style, this is a change event | |
| 340 | + updateOriginalInput(true); | |
| 341 | + } | |
| 342 | + }); | |
| 343 | + | |
| 344 | + chooseButton.text(opts.chooseText); | |
| 345 | + chooseButton.bind("click.spectrum", function (e) { | |
| 346 | + e.stopPropagation(); | |
| 347 | + e.preventDefault(); | |
| 348 | + | |
| 349 | + if (isValid()) { | |
| 350 | + updateOriginalInput(true); | |
| 351 | + hide(); | |
| 352 | + } | |
| 353 | + }); | |
| 354 | + | |
| 355 | + toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText); | |
| 356 | + toggleButton.bind("click.spectrum", function (e) { | |
| 357 | + e.stopPropagation(); | |
| 358 | + e.preventDefault(); | |
| 359 | + | |
| 360 | + opts.showPaletteOnly = !opts.showPaletteOnly; | |
| 361 | + | |
| 362 | + // To make sure the Picker area is drawn on the right, next to the | |
| 363 | + // Palette area (and not below the palette), first move the Palette | |
| 364 | + // to the left to make space for the picker, plus 5px extra. | |
| 365 | + // The 'applyOptions' function puts the whole container back into place | |
| 366 | + // and takes care of the button-text and the sp-palette-only CSS class. | |
| 367 | + if (!opts.showPaletteOnly && !flat) { | |
| 368 | + container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5)); | |
| 369 | + } | |
| 370 | + applyOptions(); | |
| 371 | + }); | |
| 372 | + | |
| 373 | + draggable(alphaSlider, function (dragX, dragY, e) { | |
| 374 | + currentAlpha = (dragX / alphaWidth); | |
| 375 | + isEmpty = false; | |
| 376 | + if (e.shiftKey) { | |
| 377 | + currentAlpha = Math.round(currentAlpha * 10) / 10; | |
| 378 | + } | |
| 379 | + | |
| 380 | + move(); | |
| 381 | + }, dragStart, dragStop); | |
| 382 | + | |
| 383 | + draggable(slider, function (dragX, dragY) { | |
| 384 | + currentHue = parseFloat(dragY / slideHeight); | |
| 385 | + isEmpty = false; | |
| 386 | + if (!opts.showAlpha) { | |
| 387 | + currentAlpha = 1; | |
| 388 | + } | |
| 389 | + move(); | |
| 390 | + }, dragStart, dragStop); | |
| 391 | + | |
| 392 | + draggable(dragger, function (dragX, dragY, e) { | |
| 393 | + | |
| 394 | + // shift+drag should snap the movement to either the x or y axis. | |
| 395 | + if (!e.shiftKey) { | |
| 396 | + shiftMovementDirection = null; | |
| 397 | + } | |
| 398 | + else if (!shiftMovementDirection) { | |
| 399 | + var oldDragX = currentSaturation * dragWidth; | |
| 400 | + var oldDragY = dragHeight - (currentValue * dragHeight); | |
| 401 | + var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY); | |
| 402 | + | |
| 403 | + shiftMovementDirection = furtherFromX ? "x" : "y"; | |
| 404 | + } | |
| 405 | + | |
| 406 | + var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x"; | |
| 407 | + var setValue = !shiftMovementDirection || shiftMovementDirection === "y"; | |
| 408 | + | |
| 409 | + if (setSaturation) { | |
| 410 | + currentSaturation = parseFloat(dragX / dragWidth); | |
| 411 | + } | |
| 412 | + if (setValue) { | |
| 413 | + currentValue = parseFloat((dragHeight - dragY) / dragHeight); | |
| 414 | + } | |
| 415 | + | |
| 416 | + isEmpty = false; | |
| 417 | + if (!opts.showAlpha) { | |
| 418 | + currentAlpha = 1; | |
| 419 | + } | |
| 420 | + | |
| 421 | + move(); | |
| 422 | + | |
| 423 | + }, dragStart, dragStop); | |
| 424 | + | |
| 425 | + if (!!initialColor) { | |
| 426 | + set(initialColor); | |
| 427 | + | |
| 428 | + // In case color was black - update the preview UI and set the format | |
| 429 | + // since the set function will not run (default color is black). | |
| 430 | + updateUI(); | |
| 431 | + currentPreferredFormat = preferredFormat || tinycolor(initialColor).format; | |
| 432 | + | |
| 433 | + addColorToSelectionPalette(initialColor); | |
| 434 | + } | |
| 435 | + else { | |
| 436 | + updateUI(); | |
| 437 | + } | |
| 438 | + | |
| 439 | + if (flat) { | |
| 440 | + show(); | |
| 441 | + } | |
| 442 | + | |
| 443 | + function paletteElementClick(e) { | |
| 444 | + if (e.data && e.data.ignore) { | |
| 445 | + set($(e.target).closest(".sp-thumb-el").data("color")); | |
| 446 | + move(); | |
| 447 | + } | |
| 448 | + else { | |
| 449 | + set($(e.target).closest(".sp-thumb-el").data("color")); | |
| 450 | + move(); | |
| 451 | + updateOriginalInput(true); | |
| 452 | + if (opts.hideAfterPaletteSelect) { | |
| 453 | + hide(); | |
| 454 | + } | |
| 455 | + } | |
| 456 | + | |
| 457 | + return false; | |
| 458 | + } | |
| 459 | + | |
| 460 | + var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum"; | |
| 461 | + paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick); | |
| 462 | + initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick); | |
| 463 | + } | |
| 464 | + | |
| 465 | + function updateSelectionPaletteFromStorage() { | |
| 466 | + | |
| 467 | + if (localStorageKey && window.localStorage) { | |
| 468 | + | |
| 469 | + // Migrate old palettes over to new format. May want to remove this eventually. | |
| 470 | + try { | |
| 471 | + var oldPalette = window.localStorage[localStorageKey].split(",#"); | |
| 472 | + if (oldPalette.length > 1) { | |
| 473 | + delete window.localStorage[localStorageKey]; | |
| 474 | + $.each(oldPalette, function(i, c) { | |
| 475 | + addColorToSelectionPalette(c); | |
| 476 | + }); | |
| 477 | + } | |
| 478 | + } | |
| 479 | + catch(e) { } | |
| 480 | + | |
| 481 | + try { | |
| 482 | + selectionPalette = window.localStorage[localStorageKey].split(";"); | |
| 483 | + } | |
| 484 | + catch (e) { } | |
| 485 | + } | |
| 486 | + } | |
| 487 | + | |
| 488 | + function addColorToSelectionPalette(color) { | |
| 489 | + if (showSelectionPalette) { | |
| 490 | + var rgb = tinycolor(color).toRgbString(); | |
| 491 | + if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) { | |
| 492 | + selectionPalette.push(rgb); | |
| 493 | + while(selectionPalette.length > maxSelectionSize) { | |
| 494 | + selectionPalette.shift(); | |
| 495 | + } | |
| 496 | + } | |
| 497 | + | |
| 498 | + if (localStorageKey && window.localStorage) { | |
| 499 | + try { | |
| 500 | + window.localStorage[localStorageKey] = selectionPalette.join(";"); | |
| 501 | + } | |
| 502 | + catch(e) { } | |
| 503 | + } | |
| 504 | + } | |
| 505 | + } | |
| 506 | + | |
| 507 | + function getUniqueSelectionPalette() { | |
| 508 | + var unique = []; | |
| 509 | + if (opts.showPalette) { | |
| 510 | + for (var i = 0; i < selectionPalette.length; i++) { | |
| 511 | + var rgb = tinycolor(selectionPalette[i]).toRgbString(); | |
| 512 | + | |
| 513 | + if (!paletteLookup[rgb]) { | |
| 514 | + unique.push(selectionPalette[i]); | |
| 515 | + } | |
| 516 | + } | |
| 517 | + } | |
| 518 | + | |
| 519 | + return unique.reverse().slice(0, opts.maxSelectionSize); | |
| 520 | + } | |
| 521 | + | |
| 522 | + function drawPalette() { | |
| 523 | + | |
| 524 | + var currentColor = get(); | |
| 525 | + | |
| 526 | + var html = $.map(paletteArray, function (palette, i) { | |
| 527 | + return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts); | |
| 528 | + }); | |
| 529 | + | |
| 530 | + updateSelectionPaletteFromStorage(); | |
| 531 | + | |
| 532 | + if (selectionPalette) { | |
| 533 | + html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts)); | |
| 534 | + } | |
| 535 | + | |
| 536 | + paletteContainer.html(html.join("")); | |
| 537 | + } | |
| 538 | + | |
| 539 | + function drawInitial() { | |
| 540 | + if (opts.showInitial) { | |
| 541 | + var initial = colorOnShow; | |
| 542 | + var current = get(); | |
| 543 | + initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts)); | |
| 544 | + } | |
| 545 | + } | |
| 546 | + | |
| 547 | + function dragStart() { | |
| 548 | + if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) { | |
| 549 | + reflow(); | |
| 550 | + } | |
| 551 | + container.addClass(draggingClass); | |
| 552 | + shiftMovementDirection = null; | |
| 553 | + boundElement.trigger('dragstart.spectrum', [ get() ]); | |
| 554 | + } | |
| 555 | + | |
| 556 | + function dragStop() { | |
| 557 | + container.removeClass(draggingClass); | |
| 558 | + boundElement.trigger('dragstop.spectrum', [ get() ]); | |
| 559 | + } | |
| 560 | + | |
| 561 | + function setFromTextInput() { | |
| 562 | + | |
| 563 | + var value = textInput.val(); | |
| 564 | + | |
| 565 | + if ((value === null || value === "") && allowEmpty) { | |
| 566 | + set(null); | |
| 567 | + updateOriginalInput(true); | |
| 568 | + } | |
| 569 | + else { | |
| 570 | + var tiny = tinycolor(value); | |
| 571 | + if (tiny.isValid()) { | |
| 572 | + set(tiny); | |
| 573 | + updateOriginalInput(true); | |
| 574 | + } | |
| 575 | + else { | |
| 576 | + textInput.addClass("sp-validation-error"); | |
| 577 | + } | |
| 578 | + } | |
| 579 | + } | |
| 580 | + | |
| 581 | + function toggle() { | |
| 582 | + if (visible) { | |
| 583 | + hide(); | |
| 584 | + } | |
| 585 | + else { | |
| 586 | + show(); | |
| 587 | + } | |
| 588 | + } | |
| 589 | + | |
| 590 | + function show() { | |
| 591 | + var event = $.Event('beforeShow.spectrum'); | |
| 592 | + | |
| 593 | + if (visible) { | |
| 594 | + reflow(); | |
| 595 | + return; | |
| 596 | + } | |
| 597 | + | |
| 598 | + boundElement.trigger(event, [ get() ]); | |
| 599 | + | |
| 600 | + if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) { | |
| 601 | + return; | |
| 602 | + } | |
| 603 | + | |
| 604 | + hideAll(); | |
| 605 | + visible = true; | |
| 606 | + | |
| 607 | + $(doc).bind("click.spectrum", hide); | |
| 608 | + $(window).bind("resize.spectrum", resize); | |
| 609 | + replacer.addClass("sp-active"); | |
| 610 | + container.removeClass("sp-hidden"); | |
| 611 | + | |
| 612 | + reflow(); | |
| 613 | + updateUI(); | |
| 614 | + | |
| 615 | + colorOnShow = get(); | |
| 616 | + | |
| 617 | + drawInitial(); | |
| 618 | + callbacks.show(colorOnShow); | |
| 619 | + boundElement.trigger('show.spectrum', [ colorOnShow ]); | |
| 620 | + } | |
| 621 | + | |
| 622 | + function hide(e) { | |
| 623 | + | |
| 624 | + // Return on right click | |
| 625 | + if (e && e.type == "click" && e.button == 2) { return; } | |
| 626 | + | |
| 627 | + // Return if hiding is unnecessary | |
| 628 | + if (!visible || flat) { return; } | |
| 629 | + visible = false; | |
| 630 | + | |
| 631 | + $(doc).unbind("click.spectrum", hide); | |
| 632 | + $(window).unbind("resize.spectrum", resize); | |
| 633 | + | |
| 634 | + replacer.removeClass("sp-active"); | |
| 635 | + container.addClass("sp-hidden"); | |
| 636 | + | |
| 637 | + var colorHasChanged = !tinycolor.equals(get(), colorOnShow); | |
| 638 | + | |
| 639 | + if (colorHasChanged) { | |
| 640 | + if (clickoutFiresChange && e !== "cancel") { | |
| 641 | + updateOriginalInput(true); | |
| 642 | + } | |
| 643 | + else { | |
| 644 | + revert(); | |
| 645 | + } | |
| 646 | + } | |
| 647 | + | |
| 648 | + callbacks.hide(get()); | |
| 649 | + boundElement.trigger('hide.spectrum', [ get() ]); | |
| 650 | + } | |
| 651 | + | |
| 652 | + function revert() { | |
| 653 | + set(colorOnShow, true); | |
| 654 | + } | |
| 655 | + | |
| 656 | + function set(color, ignoreFormatChange) { | |
| 657 | + if (tinycolor.equals(color, get())) { | |
| 658 | + // Update UI just in case a validation error needs | |
| 659 | + // to be cleared. | |
| 660 | + updateUI(); | |
| 661 | + return; | |
| 662 | + } | |
| 663 | + | |
| 664 | + var newColor, newHsv; | |
| 665 | + if (!color && allowEmpty) { | |
| 666 | + isEmpty = true; | |
| 667 | + } else { | |
| 668 | + isEmpty = false; | |
| 669 | + newColor = tinycolor(color); | |
| 670 | + newHsv = newColor.toHsv(); | |
| 671 | + | |
| 672 | + currentHue = (newHsv.h % 360) / 360; | |
| 673 | + currentSaturation = newHsv.s; | |
| 674 | + currentValue = newHsv.v; | |
| 675 | + currentAlpha = newHsv.a; | |
| 676 | + } | |
| 677 | + updateUI(); | |
| 678 | + | |
| 679 | + if (newColor && newColor.isValid() && !ignoreFormatChange) { | |
| 680 | + currentPreferredFormat = preferredFormat || newColor.getFormat(); | |
| 681 | + } | |
| 682 | + } | |
| 683 | + | |
| 684 | + function get(opts) { | |
| 685 | + opts = opts || { }; | |
| 686 | + | |
| 687 | + if (allowEmpty && isEmpty) { | |
| 688 | + return null; | |
| 689 | + } | |
| 690 | + | |
| 691 | + return tinycolor.fromRatio({ | |
| 692 | + h: currentHue, | |
| 693 | + s: currentSaturation, | |
| 694 | + v: currentValue, | |
| 695 | + a: Math.round(currentAlpha * 100) / 100 | |
| 696 | + }, { format: opts.format || currentPreferredFormat }); | |
| 697 | + } | |
| 698 | + | |
| 699 | + function isValid() { | |
| 700 | + return !textInput.hasClass("sp-validation-error"); | |
| 701 | + } | |
| 702 | + | |
| 703 | + function move() { | |
| 704 | + updateUI(); | |
| 705 | + | |
| 706 | + callbacks.move(get()); | |
| 707 | + boundElement.trigger('move.spectrum', [ get() ]); | |
| 708 | + } | |
| 709 | + | |
| 710 | + function updateUI() { | |
| 711 | + | |
| 712 | + textInput.removeClass("sp-validation-error"); | |
| 713 | + | |
| 714 | + updateHelperLocations(); | |
| 715 | + | |
| 716 | + // Update dragger background color (gradients take care of saturation and value). | |
| 717 | + var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 }); | |
| 718 | + dragger.css("background-color", flatColor.toHexString()); | |
| 719 | + | |
| 720 | + // Get a format that alpha will be included in (hex and names ignore alpha) | |
| 721 | + var format = currentPreferredFormat; | |
| 722 | + if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) { | |
| 723 | + if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") { | |
| 724 | + format = "rgb"; | |
| 725 | + } | |
| 726 | + } | |
| 727 | + | |
| 728 | + var realColor = get({ format: format }), | |
| 729 | + displayColor = ''; | |
| 730 | + | |
| 731 | + //reset background info for preview element | |
| 732 | + previewElement.removeClass("sp-clear-display"); | |
| 733 | + previewElement.css('background-color', 'transparent'); | |
| 734 | + | |
| 735 | + if (!realColor && allowEmpty) { | |
| 736 | + // Update the replaced elements background with icon indicating no color selection | |
| 737 | + previewElement.addClass("sp-clear-display"); | |
| 738 | + } | |
| 739 | + else { | |
| 740 | + var realHex = realColor.toHexString(), | |
| 741 | + realRgb = realColor.toRgbString(); | |
| 742 | + | |
| 743 | + // Update the replaced elements background color (with actual selected color) | |
| 744 | + if (rgbaSupport || realColor.alpha === 1) { | |
| 745 | + previewElement.css("background-color", realRgb); | |
| 746 | + } | |
| 747 | + else { | |
| 748 | + previewElement.css("background-color", "transparent"); | |
| 749 | + previewElement.css("filter", realColor.toFilter()); | |
| 750 | + } | |
| 751 | + | |
| 752 | + if (opts.showAlpha) { | |
| 753 | + var rgb = realColor.toRgb(); | |
| 754 | + rgb.a = 0; | |
| 755 | + var realAlpha = tinycolor(rgb).toRgbString(); | |
| 756 | + var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")"; | |
| 757 | + | |
| 758 | + if (IE) { | |
| 759 | + alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex)); | |
| 760 | + } | |
| 761 | + else { | |
| 762 | + alphaSliderInner.css("background", "-webkit-" + gradient); | |
| 763 | + alphaSliderInner.css("background", "-moz-" + gradient); | |
| 764 | + alphaSliderInner.css("background", "-ms-" + gradient); | |
| 765 | + // Use current syntax gradient on unprefixed property. | |
| 766 | + alphaSliderInner.css("background", | |
| 767 | + "linear-gradient(to right, " + realAlpha + ", " + realHex + ")"); | |
| 768 | + } | |
| 769 | + } | |
| 770 | + | |
| 771 | + displayColor = realColor.toString(format); | |
| 772 | + } | |
| 773 | + | |
| 774 | + // Update the text entry input as it changes happen | |
| 775 | + if (opts.showInput) { | |
| 776 | + textInput.val(displayColor); | |
| 777 | + } | |
| 778 | + | |
| 779 | + if (opts.showPalette) { | |
| 780 | + drawPalette(); | |
| 781 | + } | |
| 782 | + | |
| 783 | + drawInitial(); | |
| 784 | + } | |
| 785 | + | |
| 786 | + function updateHelperLocations() { | |
| 787 | + var s = currentSaturation; | |
| 788 | + var v = currentValue; | |
| 789 | + | |
| 790 | + if(allowEmpty && isEmpty) { | |
| 791 | + //if selected color is empty, hide the helpers | |
| 792 | + alphaSlideHelper.hide(); | |
| 793 | + slideHelper.hide(); | |
| 794 | + dragHelper.hide(); | |
| 795 | + } | |
| 796 | + else { | |
| 797 | + //make sure helpers are visible | |
| 798 | + alphaSlideHelper.show(); | |
| 799 | + slideHelper.show(); | |
| 800 | + dragHelper.show(); | |
| 801 | + | |
| 802 | + // Where to show the little circle in that displays your current selected color | |
| 803 | + var dragX = s * dragWidth; | |
| 804 | + var dragY = dragHeight - (v * dragHeight); | |
| 805 | + dragX = Math.max( | |
| 806 | + -dragHelperHeight, | |
| 807 | + Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight) | |
| 808 | + ); | |
| 809 | + dragY = Math.max( | |
| 810 | + -dragHelperHeight, | |
| 811 | + Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight) | |
| 812 | + ); | |
| 813 | + dragHelper.css({ | |
| 814 | + "top": dragY + "px", | |
| 815 | + "left": dragX + "px" | |
| 816 | + }); | |
| 817 | + | |
| 818 | + var alphaX = currentAlpha * alphaWidth; | |
| 819 | + alphaSlideHelper.css({ | |
| 820 | + "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px" | |
| 821 | + }); | |
| 822 | + | |
| 823 | + // Where to show the bar that displays your current selected hue | |
| 824 | + var slideY = (currentHue) * slideHeight; | |
| 825 | + slideHelper.css({ | |
| 826 | + "top": (slideY - slideHelperHeight) + "px" | |
| 827 | + }); | |
| 828 | + } | |
| 829 | + } | |
| 830 | + | |
| 831 | + function updateOriginalInput(fireCallback) { | |
| 832 | + var color = get(), | |
| 833 | + displayColor = '', | |
| 834 | + hasChanged = !tinycolor.equals(color, colorOnShow); | |
| 835 | + | |
| 836 | + if (color) { | |
| 837 | + displayColor = color.toString(currentPreferredFormat); | |
| 838 | + // Update the selection palette with the current color | |
| 839 | + addColorToSelectionPalette(color); | |
| 840 | + } | |
| 841 | + | |
| 842 | + if (isInput) { | |
| 843 | + boundElement.val(displayColor); | |
| 844 | + } | |
| 845 | + | |
| 846 | + colorOnShow = color; | |
| 847 | + | |
| 848 | + if (fireCallback && hasChanged) { | |
| 849 | + callbacks.change(color); | |
| 850 | + boundElement.trigger('change', [ color ]); | |
| 851 | + } | |
| 852 | + } | |
| 853 | + | |
| 854 | + function reflow() { | |
| 855 | + dragWidth = dragger.width(); | |
| 856 | + dragHeight = dragger.height(); | |
| 857 | + dragHelperHeight = dragHelper.height(); | |
| 858 | + slideWidth = slider.width(); | |
| 859 | + slideHeight = slider.height(); | |
| 860 | + slideHelperHeight = slideHelper.height(); | |
| 861 | + alphaWidth = alphaSlider.width(); | |
| 862 | + alphaSlideHelperWidth = alphaSlideHelper.width(); | |
| 863 | + | |
| 864 | + if (!flat) { | |
| 865 | + container.css("position", "absolute"); | |
| 866 | + container.offset(getOffset(container, offsetElement)); | |
| 867 | + } | |
| 868 | + | |
| 869 | + updateHelperLocations(); | |
| 870 | + | |
| 871 | + if (opts.showPalette) { | |
| 872 | + drawPalette(); | |
| 873 | + } | |
| 874 | + | |
| 875 | + boundElement.trigger('reflow.spectrum'); | |
| 876 | + } | |
| 877 | + | |
| 878 | + function destroy() { | |
| 879 | + boundElement.show(); | |
| 880 | + offsetElement.unbind("click.spectrum touchstart.spectrum"); | |
| 881 | + container.remove(); | |
| 882 | + replacer.remove(); | |
| 883 | + spectrums[spect.id] = null; | |
| 884 | + } | |
| 885 | + | |
| 886 | + function option(optionName, optionValue) { | |
| 887 | + if (optionName === undefined) { | |
| 888 | + return $.extend({}, opts); | |
| 889 | + } | |
| 890 | + if (optionValue === undefined) { | |
| 891 | + return opts[optionName]; | |
| 892 | + } | |
| 893 | + | |
| 894 | + opts[optionName] = optionValue; | |
| 895 | + applyOptions(); | |
| 896 | + } | |
| 897 | + | |
| 898 | + function enable() { | |
| 899 | + disabled = false; | |
| 900 | + boundElement.attr("disabled", false); | |
| 901 | + offsetElement.removeClass("sp-disabled"); | |
| 902 | + } | |
| 903 | + | |
| 904 | + function disable() { | |
| 905 | + hide(); | |
| 906 | + disabled = true; | |
| 907 | + boundElement.attr("disabled", true); | |
| 908 | + offsetElement.addClass("sp-disabled"); | |
| 909 | + } | |
| 910 | + | |
| 911 | + initialize(); | |
| 912 | + | |
| 913 | + var spect = { | |
| 914 | + show: show, | |
| 915 | + hide: hide, | |
| 916 | + toggle: toggle, | |
| 917 | + reflow: reflow, | |
| 918 | + option: option, | |
| 919 | + enable: enable, | |
| 920 | + disable: disable, | |
| 921 | + set: function (c) { | |
| 922 | + set(c); | |
| 923 | + updateOriginalInput(); | |
| 924 | + }, | |
| 925 | + get: get, | |
| 926 | + destroy: destroy, | |
| 927 | + container: container | |
| 928 | + }; | |
| 929 | + | |
| 930 | + spect.id = spectrums.push(spect) - 1; | |
| 931 | + | |
| 932 | + return spect; | |
| 933 | + } | |
| 934 | + | |
| 935 | + /** | |
| 936 | + * checkOffset - get the offset below/above and left/right element depending on screen position | |
| 937 | + * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js | |
| 938 | + */ | |
| 939 | + function getOffset(picker, input) { | |
| 940 | + var extraY = 0; | |
| 941 | + var dpWidth = picker.outerWidth(); | |
| 942 | + var dpHeight = picker.outerHeight(); | |
| 943 | + var inputHeight = input.outerHeight(); | |
| 944 | + var doc = picker[0].ownerDocument; | |
| 945 | + var docElem = doc.documentElement; | |
| 946 | + var viewWidth = docElem.clientWidth + $(doc).scrollLeft(); | |
| 947 | + var viewHeight = docElem.clientHeight + $(doc).scrollTop(); | |
| 948 | + var offset = input.offset(); | |
| 949 | + offset.top += inputHeight; | |
| 950 | + | |
| 951 | + offset.left -= | |
| 952 | + Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ? | |
| 953 | + Math.abs(offset.left + dpWidth - viewWidth) : 0); | |
| 954 | + | |
| 955 | + offset.top -= | |
| 956 | + Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ? | |
| 957 | + Math.abs(dpHeight + inputHeight - extraY) : extraY)); | |
| 958 | + | |
| 959 | + return offset; | |
| 960 | + } | |
| 961 | + | |
| 962 | + /** | |
| 963 | + * noop - do nothing | |
| 964 | + */ | |
| 965 | + function noop() { | |
| 966 | + | |
| 967 | + } | |
| 968 | + | |
| 969 | + /** | |
| 970 | + * stopPropagation - makes the code only doing this a little easier to read in line | |
| 971 | + */ | |
| 972 | + function stopPropagation(e) { | |
| 973 | + e.stopPropagation(); | |
| 974 | + } | |
| 975 | + | |
| 976 | + /** | |
| 977 | + * Create a function bound to a given object | |
| 978 | + * Thanks to underscore.js | |
| 979 | + */ | |
| 980 | + function bind(func, obj) { | |
| 981 | + var slice = Array.prototype.slice; | |
| 982 | + var args = slice.call(arguments, 2); | |
| 983 | + return function () { | |
| 984 | + return func.apply(obj, args.concat(slice.call(arguments))); | |
| 985 | + }; | |
| 986 | + } | |
| 987 | + | |
| 988 | + /** | |
| 989 | + * Lightweight drag helper. Handles containment within the element, so that | |
| 990 | + * when dragging, the x is within [0,element.width] and y is within [0,element.height] | |
| 991 | + */ | |
| 992 | + function draggable(element, onmove, onstart, onstop) { | |
| 993 | + onmove = onmove || function () { }; | |
| 994 | + onstart = onstart || function () { }; | |
| 995 | + onstop = onstop || function () { }; | |
| 996 | + var doc = element.ownerDocument || document; | |
| 997 | + var dragging = false; | |
| 998 | + var offset = {}; | |
| 999 | + var maxHeight = 0; | |
| 1000 | + var maxWidth = 0; | |
| 1001 | + var hasTouch = ('ontouchstart' in window); | |
| 1002 | + | |
| 1003 | + var duringDragEvents = {}; | |
| 1004 | + duringDragEvents["selectstart"] = prevent; | |
| 1005 | + duringDragEvents["dragstart"] = prevent; | |
| 1006 | + duringDragEvents["touchmove mousemove"] = move; | |
| 1007 | + duringDragEvents["touchend mouseup"] = stop; | |
| 1008 | + | |
| 1009 | + function prevent(e) { | |
| 1010 | + if (e.stopPropagation) { | |
| 1011 | + e.stopPropagation(); | |
| 1012 | + } | |
| 1013 | + if (e.preventDefault) { | |
| 1014 | + e.preventDefault(); | |
| 1015 | + } | |
| 1016 | + e.returnValue = false; | |
| 1017 | + } | |
| 1018 | + | |
| 1019 | + function move(e) { | |
| 1020 | + if (dragging) { | |
| 1021 | + // Mouseup happened outside of window | |
| 1022 | + if (IE && document.documentMode < 9 && !e.button) { | |
| 1023 | + return stop(); | |
| 1024 | + } | |
| 1025 | + | |
| 1026 | + var touches = e.originalEvent.touches; | |
| 1027 | + var pageX = touches ? touches[0].pageX : e.pageX; | |
| 1028 | + var pageY = touches ? touches[0].pageY : e.pageY; | |
| 1029 | + | |
| 1030 | + var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth)); | |
| 1031 | + var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight)); | |
| 1032 | + | |
| 1033 | + if (hasTouch) { | |
| 1034 | + // Stop scrolling in iOS | |
| 1035 | + prevent(e); | |
| 1036 | + } | |
| 1037 | + | |
| 1038 | + onmove.apply(element, [dragX, dragY, e]); | |
| 1039 | + } | |
| 1040 | + } | |
| 1041 | + | |
| 1042 | + function start(e) { | |
| 1043 | + var rightclick = (e.which) ? (e.which == 3) : (e.button == 2); | |
| 1044 | + var touches = e.originalEvent.touches; | |
| 1045 | + | |
| 1046 | + if (!rightclick && !dragging) { | |
| 1047 | + if (onstart.apply(element, arguments) !== false) { | |
| 1048 | + dragging = true; | |
| 1049 | + maxHeight = $(element).height(); | |
| 1050 | + maxWidth = $(element).width(); | |
| 1051 | + offset = $(element).offset(); | |
| 1052 | + | |
| 1053 | + $(doc).bind(duringDragEvents); | |
| 1054 | + $(doc.body).addClass("sp-dragging"); | |
| 1055 | + | |
| 1056 | + if (!hasTouch) { | |
| 1057 | + move(e); | |
| 1058 | + } | |
| 1059 | + | |
| 1060 | + prevent(e); | |
| 1061 | + } | |
| 1062 | + } | |
| 1063 | + } | |
| 1064 | + | |
| 1065 | + function stop() { | |
| 1066 | + if (dragging) { | |
| 1067 | + $(doc).unbind(duringDragEvents); | |
| 1068 | + $(doc.body).removeClass("sp-dragging"); | |
| 1069 | + onstop.apply(element, arguments); | |
| 1070 | + } | |
| 1071 | + dragging = false; | |
| 1072 | + } | |
| 1073 | + | |
| 1074 | + $(element).bind("touchstart mousedown", start); | |
| 1075 | + } | |
| 1076 | + | |
| 1077 | + function throttle(func, wait, debounce) { | |
| 1078 | + var timeout; | |
| 1079 | + return function () { | |
| 1080 | + var context = this, args = arguments; | |
| 1081 | + var throttler = function () { | |
| 1082 | + timeout = null; | |
| 1083 | + func.apply(context, args); | |
| 1084 | + }; | |
| 1085 | + if (debounce) clearTimeout(timeout); | |
| 1086 | + if (debounce || !timeout) timeout = setTimeout(throttler, wait); | |
| 1087 | + }; | |
| 1088 | + } | |
| 1089 | + | |
| 1090 | + /** | |
| 1091 | + * Define a jQuery plugin | |
| 1092 | + */ | |
| 1093 | + var dataID = "spectrum.id"; | |
| 1094 | + $.fn.spectrum = function (opts, extra) { | |
| 1095 | + | |
| 1096 | + if (typeof opts == "string") { | |
| 1097 | + | |
| 1098 | + var returnValue = this; | |
| 1099 | + var args = Array.prototype.slice.call( arguments, 1 ); | |
| 1100 | + | |
| 1101 | + this.each(function () { | |
| 1102 | + var spect = spectrums[$(this).data(dataID)]; | |
| 1103 | + if (spect) { | |
| 1104 | + var method = spect[opts]; | |
| 1105 | + if (!method) { | |
| 1106 | + throw new Error( "Spectrum: no such method: '" + opts + "'" ); | |
| 1107 | + } | |
| 1108 | + | |
| 1109 | + if (opts == "get") { | |
| 1110 | + returnValue = spect.get(); | |
| 1111 | + } | |
| 1112 | + else if (opts == "container") { | |
| 1113 | + returnValue = spect.container; | |
| 1114 | + } | |
| 1115 | + else if (opts == "option") { | |
| 1116 | + returnValue = spect.option.apply(spect, args); | |
| 1117 | + } | |
| 1118 | + else if (opts == "destroy") { | |
| 1119 | + spect.destroy(); | |
| 1120 | + $(this).removeData(dataID); | |
| 1121 | + } | |
| 1122 | + else { | |
| 1123 | + method.apply(spect, args); | |
| 1124 | + } | |
| 1125 | + } | |
| 1126 | + }); | |
| 1127 | + | |
| 1128 | + return returnValue; | |
| 1129 | + } | |
| 1130 | + | |
| 1131 | + // Initializing a new instance of spectrum | |
| 1132 | + return this.spectrum("destroy").each(function () { | |
| 1133 | + var options = $.extend({}, opts, $(this).data()); | |
| 1134 | + var spect = spectrum(this, options); | |
| 1135 | + $(this).data(dataID, spect.id); | |
| 1136 | + }); | |
| 1137 | + }; | |
| 1138 | + | |
| 1139 | + $.fn.spectrum.load = true; | |
| 1140 | + $.fn.spectrum.loadOpts = {}; | |
| 1141 | + $.fn.spectrum.draggable = draggable; | |
| 1142 | + $.fn.spectrum.defaults = defaultOpts; | |
| 1143 | + | |
| 1144 | + $.spectrum = { }; | |
| 1145 | + $.spectrum.localization = { }; | |
| 1146 | + $.spectrum.palettes = { }; | |
| 1147 | + | |
| 1148 | + $.fn.spectrum.processNativeColorInputs = function () { | |
| 1149 | + if (!inputTypeColorSupport) { | |
| 1150 | + $("input[type=color]").spectrum({ | |
| 1151 | + preferredFormat: "hex6" | |
| 1152 | + }); | |
| 1153 | + } | |
| 1154 | + }; | |
| 1155 | + | |
| 1156 | + // TinyColor v1.0.0 | |
| 1157 | + // https://github.com/bgrins/TinyColor | |
| 1158 | + // Brian Grinstead, MIT License | |
| 1159 | + | |
| 1160 | + (function() { | |
| 1161 | + | |
| 1162 | + var trimLeft = /^[\s,#]+/, | |
| 1163 | + trimRight = /\s+$/, | |
| 1164 | + tinyCounter = 0, | |
| 1165 | + math = Math, | |
| 1166 | + mathRound = math.round, | |
| 1167 | + mathMin = math.min, | |
| 1168 | + mathMax = math.max, | |
| 1169 | + mathRandom = math.random; | |
| 1170 | + | |
| 1171 | + var tinycolor = function tinycolor (color, opts) { | |
| 1172 | + | |
| 1173 | + color = (color) ? color : ''; | |
| 1174 | + opts = opts || { }; | |
| 1175 | + | |
| 1176 | + // If input is already a tinycolor, return itself | |
| 1177 | + if (color instanceof tinycolor) { | |
| 1178 | + return color; | |
| 1179 | + } | |
| 1180 | + // If we are called as a function, call using new instead | |
| 1181 | + if (!(this instanceof tinycolor)) { | |
| 1182 | + return new tinycolor(color, opts); | |
| 1183 | + } | |
| 1184 | + | |
| 1185 | + var rgb = inputToRGB(color); | |
| 1186 | + this._r = rgb.r, | |
| 1187 | + this._g = rgb.g, | |
| 1188 | + this._b = rgb.b, | |
| 1189 | + this._a = rgb.a, | |
| 1190 | + this._roundA = mathRound(100*this._a) / 100, | |
| 1191 | + this._format = opts.format || rgb.format; | |
| 1192 | + this._gradientType = opts.gradientType; | |
| 1193 | + | |
| 1194 | + // Don't let the range of [0,255] come back in [0,1]. | |
| 1195 | + // Potentially lose a little bit of precision here, but will fix issues where | |
| 1196 | + // .5 gets interpreted as half of the total, instead of half of 1 | |
| 1197 | + // If it was supposed to be 128, this was already taken care of by `inputToRgb` | |
| 1198 | + if (this._r < 1) { this._r = mathRound(this._r); } | |
| 1199 | + if (this._g < 1) { this._g = mathRound(this._g); } | |
| 1200 | + if (this._b < 1) { this._b = mathRound(this._b); } | |
| 1201 | + | |
| 1202 | + this._ok = rgb.ok; | |
| 1203 | + this._tc_id = tinyCounter++; | |
| 1204 | + }; | |
| 1205 | + | |
| 1206 | + tinycolor.prototype = { | |
| 1207 | + isDark: function() { | |
| 1208 | + return this.getBrightness() < 128; | |
| 1209 | + }, | |
| 1210 | + isLight: function() { | |
| 1211 | + return !this.isDark(); | |
| 1212 | + }, | |
| 1213 | + isValid: function() { | |
| 1214 | + return this._ok; | |
| 1215 | + }, | |
| 1216 | + getFormat: function() { | |
| 1217 | + return this._format; | |
| 1218 | + }, | |
| 1219 | + getAlpha: function() { | |
| 1220 | + return this._a; | |
| 1221 | + }, | |
| 1222 | + getBrightness: function() { | |
| 1223 | + var rgb = this.toRgb(); | |
| 1224 | + return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; | |
| 1225 | + }, | |
| 1226 | + setAlpha: function(value) { | |
| 1227 | + this._a = boundAlpha(value); | |
| 1228 | + this._roundA = mathRound(100*this._a) / 100; | |
| 1229 | + return this; | |
| 1230 | + }, | |
| 1231 | + toHsv: function() { | |
| 1232 | + var hsv = rgbToHsv(this._r, this._g, this._b); | |
| 1233 | + return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a }; | |
| 1234 | + }, | |
| 1235 | + toHsvString: function() { | |
| 1236 | + var hsv = rgbToHsv(this._r, this._g, this._b); | |
| 1237 | + var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100); | |
| 1238 | + return (this._a == 1) ? | |
| 1239 | + "hsv(" + h + ", " + s + "%, " + v + "%)" : | |
| 1240 | + "hsva(" + h + ", " + s + "%, " + v + "%, "+ this._roundA + ")"; | |
| 1241 | + }, | |
| 1242 | + toHsl: function() { | |
| 1243 | + var hsl = rgbToHsl(this._r, this._g, this._b); | |
| 1244 | + return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a }; | |
| 1245 | + }, | |
| 1246 | + toHslString: function() { | |
| 1247 | + var hsl = rgbToHsl(this._r, this._g, this._b); | |
| 1248 | + var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100); | |
| 1249 | + return (this._a == 1) ? | |
| 1250 | + "hsl(" + h + ", " + s + "%, " + l + "%)" : | |
| 1251 | + "hsla(" + h + ", " + s + "%, " + l + "%, "+ this._roundA + ")"; | |
| 1252 | + }, | |
| 1253 | + toHex: function(allow3Char) { | |
| 1254 | + return rgbToHex(this._r, this._g, this._b, allow3Char); | |
| 1255 | + }, | |
| 1256 | + toHexString: function(allow3Char) { | |
| 1257 | + return '#' + this.toHex(allow3Char); | |
| 1258 | + }, | |
| 1259 | + toHex8: function() { | |
| 1260 | + return rgbaToHex(this._r, this._g, this._b, this._a); | |
| 1261 | + }, | |
| 1262 | + toHex8String: function() { | |
| 1263 | + return '#' + this.toHex8(); | |
| 1264 | + }, | |
| 1265 | + toRgb: function() { | |
| 1266 | + return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a }; | |
| 1267 | + }, | |
| 1268 | + toRgbString: function() { | |
| 1269 | + return (this._a == 1) ? | |
| 1270 | + "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : | |
| 1271 | + "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")"; | |
| 1272 | + }, | |
| 1273 | + toPercentageRgb: function() { | |
| 1274 | + return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a }; | |
| 1275 | + }, | |
| 1276 | + toPercentageRgbString: function() { | |
| 1277 | + return (this._a == 1) ? | |
| 1278 | + "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : | |
| 1279 | + "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")"; | |
| 1280 | + }, | |
| 1281 | + toName: function() { | |
| 1282 | + if (this._a === 0) { | |
| 1283 | + return "transparent"; | |
| 1284 | + } | |
| 1285 | + | |
| 1286 | + if (this._a < 1) { | |
| 1287 | + return false; | |
| 1288 | + } | |
| 1289 | + | |
| 1290 | + return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false; | |
| 1291 | + }, | |
| 1292 | + toFilter: function(secondColor) { | |
| 1293 | + var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a); | |
| 1294 | + var secondHex8String = hex8String; | |
| 1295 | + var gradientType = this._gradientType ? "GradientType = 1, " : ""; | |
| 1296 | + | |
| 1297 | + if (secondColor) { | |
| 1298 | + var s = tinycolor(secondColor); | |
| 1299 | + secondHex8String = s.toHex8String(); | |
| 1300 | + } | |
| 1301 | + | |
| 1302 | + return "progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"; | |
| 1303 | + }, | |
| 1304 | + toString: function(format) { | |
| 1305 | + var formatSet = !!format; | |
| 1306 | + format = format || this._format; | |
| 1307 | + | |
| 1308 | + var formattedString = false; | |
| 1309 | + var hasAlpha = this._a < 1 && this._a >= 0; | |
| 1310 | + var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name"); | |
| 1311 | + | |
| 1312 | + if (needsAlphaFormat) { | |
| 1313 | + // Special case for "transparent", all other non-alpha formats | |
| 1314 | + // will return rgba when there is transparency. | |
| 1315 | + if (format === "name" && this._a === 0) { | |
| 1316 | + return this.toName(); | |
| 1317 | + } | |
| 1318 | + return this.toRgbString(); | |
| 1319 | + } | |
| 1320 | + if (format === "rgb") { | |
| 1321 | + formattedString = this.toRgbString(); | |
| 1322 | + } | |
| 1323 | + if (format === "prgb") { | |
| 1324 | + formattedString = this.toPercentageRgbString(); | |
| 1325 | + } | |
| 1326 | + if (format === "hex" || format === "hex6") { | |
| 1327 | + formattedString = this.toHexString(); | |
| 1328 | + } | |
| 1329 | + if (format === "hex3") { | |
| 1330 | + formattedString = this.toHexString(true); | |
| 1331 | + } | |
| 1332 | + if (format === "hex8") { | |
| 1333 | + formattedString = this.toHex8String(); | |
| 1334 | + } | |
| 1335 | + if (format === "name") { | |
| 1336 | + formattedString = this.toName(); | |
| 1337 | + } | |
| 1338 | + if (format === "hsl") { | |
| 1339 | + formattedString = this.toHslString(); | |
| 1340 | + } | |
| 1341 | + if (format === "hsv") { | |
| 1342 | + formattedString = this.toHsvString(); | |
| 1343 | + } | |
| 1344 | + | |
| 1345 | + return formattedString || this.toHexString(); | |
| 1346 | + }, | |
| 1347 | + | |
| 1348 | + _applyModification: function(fn, args) { | |
| 1349 | + var color = fn.apply(null, [this].concat([].slice.call(args))); | |
| 1350 | + this._r = color._r; | |
| 1351 | + this._g = color._g; | |
| 1352 | + this._b = color._b; | |
| 1353 | + this.setAlpha(color._a); | |
| 1354 | + return this; | |
| 1355 | + }, | |
| 1356 | + lighten: function() { | |
| 1357 | + return this._applyModification(lighten, arguments); | |
| 1358 | + }, | |
| 1359 | + brighten: function() { | |
| 1360 | + return this._applyModification(brighten, arguments); | |
| 1361 | + }, | |
| 1362 | + darken: function() { | |
| 1363 | + return this._applyModification(darken, arguments); | |
| 1364 | + }, | |
| 1365 | + desaturate: function() { | |
| 1366 | + return this._applyModification(desaturate, arguments); | |
| 1367 | + }, | |
| 1368 | + saturate: function() { | |
| 1369 | + return this._applyModification(saturate, arguments); | |
| 1370 | + }, | |
| 1371 | + greyscale: function() { | |
| 1372 | + return this._applyModification(greyscale, arguments); | |
| 1373 | + }, | |
| 1374 | + spin: function() { | |
| 1375 | + return this._applyModification(spin, arguments); | |
| 1376 | + }, | |
| 1377 | + | |
| 1378 | + _applyCombination: function(fn, args) { | |
| 1379 | + return fn.apply(null, [this].concat([].slice.call(args))); | |
| 1380 | + }, | |
| 1381 | + analogous: function() { | |
| 1382 | + return this._applyCombination(analogous, arguments); | |
| 1383 | + }, | |
| 1384 | + complement: function() { | |
| 1385 | + return this._applyCombination(complement, arguments); | |
| 1386 | + }, | |
| 1387 | + monochromatic: function() { | |
| 1388 | + return this._applyCombination(monochromatic, arguments); | |
| 1389 | + }, | |
| 1390 | + splitcomplement: function() { | |
| 1391 | + return this._applyCombination(splitcomplement, arguments); | |
| 1392 | + }, | |
| 1393 | + triad: function() { | |
| 1394 | + return this._applyCombination(triad, arguments); | |
| 1395 | + }, | |
| 1396 | + tetrad: function() { | |
| 1397 | + return this._applyCombination(tetrad, arguments); | |
| 1398 | + } | |
| 1399 | + }; | |
| 1400 | + | |
| 1401 | + // If input is an object, force 1 into "1.0" to handle ratios properly | |
| 1402 | + // String input requires "1.0" as input, so 1 will be treated as 1 | |
| 1403 | + tinycolor.fromRatio = function(color, opts) { | |
| 1404 | + if (typeof color == "object") { | |
| 1405 | + var newColor = {}; | |
| 1406 | + for (var i in color) { | |
| 1407 | + if (color.hasOwnProperty(i)) { | |
| 1408 | + if (i === "a") { | |
| 1409 | + newColor[i] = color[i]; | |
| 1410 | + } | |
| 1411 | + else { | |
| 1412 | + newColor[i] = convertToPercentage(color[i]); | |
| 1413 | + } | |
| 1414 | + } | |
| 1415 | + } | |
| 1416 | + color = newColor; | |
| 1417 | + } | |
| 1418 | + | |
| 1419 | + return tinycolor(color, opts); | |
| 1420 | + }; | |
| 1421 | + | |
| 1422 | + // Given a string or object, convert that input to RGB | |
| 1423 | + // Possible string inputs: | |
| 1424 | + // | |
| 1425 | + // "red" | |
| 1426 | + // "#f00" or "f00" | |
| 1427 | + // "#ff0000" or "ff0000" | |
| 1428 | + // "#ff000000" or "ff000000" | |
| 1429 | + // "rgb 255 0 0" or "rgb (255, 0, 0)" | |
| 1430 | + // "rgb 1.0 0 0" or "rgb (1, 0, 0)" | |
| 1431 | + // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1" | |
| 1432 | + // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" | |
| 1433 | + // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%" | |
| 1434 | + // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1" | |
| 1435 | + // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%" | |
| 1436 | + // | |
| 1437 | + function inputToRGB(color) { | |
| 1438 | + | |
| 1439 | + var rgb = { r: 0, g: 0, b: 0 }; | |
| 1440 | + var a = 1; | |
| 1441 | + var ok = false; | |
| 1442 | + var format = false; | |
| 1443 | + | |
| 1444 | + if (typeof color == "string") { | |
| 1445 | + color = stringInputToObject(color); | |
| 1446 | + } | |
| 1447 | + | |
| 1448 | + if (typeof color == "object") { | |
| 1449 | + if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) { | |
| 1450 | + rgb = rgbToRgb(color.r, color.g, color.b); | |
| 1451 | + ok = true; | |
| 1452 | + format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb"; | |
| 1453 | + } | |
| 1454 | + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) { | |
| 1455 | + color.s = convertToPercentage(color.s); | |
| 1456 | + color.v = convertToPercentage(color.v); | |
| 1457 | + rgb = hsvToRgb(color.h, color.s, color.v); | |
| 1458 | + ok = true; | |
| 1459 | + format = "hsv"; | |
| 1460 | + } | |
| 1461 | + else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) { | |
| 1462 | + color.s = convertToPercentage(color.s); | |
| 1463 | + color.l = convertToPercentage(color.l); | |
| 1464 | + rgb = hslToRgb(color.h, color.s, color.l); | |
| 1465 | + ok = true; | |
| 1466 | + format = "hsl"; | |
| 1467 | + } | |
| 1468 | + | |
| 1469 | + if (color.hasOwnProperty("a")) { | |
| 1470 | + a = color.a; | |
| 1471 | + } | |
| 1472 | + } | |
| 1473 | + | |
| 1474 | + a = boundAlpha(a); | |
| 1475 | + | |
| 1476 | + return { | |
| 1477 | + ok: ok, | |
| 1478 | + format: color.format || format, | |
| 1479 | + r: mathMin(255, mathMax(rgb.r, 0)), | |
| 1480 | + g: mathMin(255, mathMax(rgb.g, 0)), | |
| 1481 | + b: mathMin(255, mathMax(rgb.b, 0)), | |
| 1482 | + a: a | |
| 1483 | + }; | |
| 1484 | + } | |
| 1485 | + | |
| 1486 | + | |
| 1487 | + // Conversion Functions | |
| 1488 | + // -------------------- | |
| 1489 | + | |
| 1490 | + // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from: | |
| 1491 | + // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript> | |
| 1492 | + | |
| 1493 | + // `rgbToRgb` | |
| 1494 | + // Handle bounds / percentage checking to conform to CSS color spec | |
| 1495 | + // <http://www.w3.org/TR/css3-color/> | |
| 1496 | + // *Assumes:* r, g, b in [0, 255] or [0, 1] | |
| 1497 | + // *Returns:* { r, g, b } in [0, 255] | |
| 1498 | + function rgbToRgb(r, g, b){ | |
| 1499 | + return { | |
| 1500 | + r: bound01(r, 255) * 255, | |
| 1501 | + g: bound01(g, 255) * 255, | |
| 1502 | + b: bound01(b, 255) * 255 | |
| 1503 | + }; | |
| 1504 | + } | |
| 1505 | + | |
| 1506 | + // `rgbToHsl` | |
| 1507 | + // Converts an RGB color value to HSL. | |
| 1508 | + // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1] | |
| 1509 | + // *Returns:* { h, s, l } in [0,1] | |
| 1510 | + function rgbToHsl(r, g, b) { | |
| 1511 | + | |
| 1512 | + r = bound01(r, 255); | |
| 1513 | + g = bound01(g, 255); | |
| 1514 | + b = bound01(b, 255); | |
| 1515 | + | |
| 1516 | + var max = mathMax(r, g, b), min = mathMin(r, g, b); | |
| 1517 | + var h, s, l = (max + min) / 2; | |
| 1518 | + | |
| 1519 | + if(max == min) { | |
| 1520 | + h = s = 0; // achromatic | |
| 1521 | + } | |
| 1522 | + else { | |
| 1523 | + var d = max - min; | |
| 1524 | + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | |
| 1525 | + switch(max) { | |
| 1526 | + case r: h = (g - b) / d + (g < b ? 6 : 0); break; | |
| 1527 | + case g: h = (b - r) / d + 2; break; | |
| 1528 | + case b: h = (r - g) / d + 4; break; | |
| 1529 | + } | |
| 1530 | + | |
| 1531 | + h /= 6; | |
| 1532 | + } | |
| 1533 | + | |
| 1534 | + return { h: h, s: s, l: l }; | |
| 1535 | + } | |
| 1536 | + | |
| 1537 | + // `hslToRgb` | |
| 1538 | + // Converts an HSL color value to RGB. | |
| 1539 | + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100] | |
| 1540 | + // *Returns:* { r, g, b } in the set [0, 255] | |
| 1541 | + function hslToRgb(h, s, l) { | |
| 1542 | + var r, g, b; | |
| 1543 | + | |
| 1544 | + h = bound01(h, 360); | |
| 1545 | + s = bound01(s, 100); | |
| 1546 | + l = bound01(l, 100); | |
| 1547 | + | |
| 1548 | + function hue2rgb(p, q, t) { | |
| 1549 | + if(t < 0) t += 1; | |
| 1550 | + if(t > 1) t -= 1; | |
| 1551 | + if(t < 1/6) return p + (q - p) * 6 * t; | |
| 1552 | + if(t < 1/2) return q; | |
| 1553 | + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; | |
| 1554 | + return p; | |
| 1555 | + } | |
| 1556 | + | |
| 1557 | + if(s === 0) { | |
| 1558 | + r = g = b = l; // achromatic | |
| 1559 | + } | |
| 1560 | + else { | |
| 1561 | + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
| 1562 | + var p = 2 * l - q; | |
| 1563 | + r = hue2rgb(p, q, h + 1/3); | |
| 1564 | + g = hue2rgb(p, q, h); | |
| 1565 | + b = hue2rgb(p, q, h - 1/3); | |
| 1566 | + } | |
| 1567 | + | |
| 1568 | + return { r: r * 255, g: g * 255, b: b * 255 }; | |
| 1569 | + } | |
| 1570 | + | |
| 1571 | + // `rgbToHsv` | |
| 1572 | + // Converts an RGB color value to HSV | |
| 1573 | + // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] | |
| 1574 | + // *Returns:* { h, s, v } in [0,1] | |
| 1575 | + function rgbToHsv(r, g, b) { | |
| 1576 | + | |
| 1577 | + r = bound01(r, 255); | |
| 1578 | + g = bound01(g, 255); | |
| 1579 | + b = bound01(b, 255); | |
| 1580 | + | |
| 1581 | + var max = mathMax(r, g, b), min = mathMin(r, g, b); | |
| 1582 | + var h, s, v = max; | |
| 1583 | + | |
| 1584 | + var d = max - min; | |
| 1585 | + s = max === 0 ? 0 : d / max; | |
| 1586 | + | |
| 1587 | + if(max == min) { | |
| 1588 | + h = 0; // achromatic | |
| 1589 | + } | |
| 1590 | + else { | |
| 1591 | + switch(max) { | |
| 1592 | + case r: h = (g - b) / d + (g < b ? 6 : 0); break; | |
| 1593 | + case g: h = (b - r) / d + 2; break; | |
| 1594 | + case b: h = (r - g) / d + 4; break; | |
| 1595 | + } | |
| 1596 | + h /= 6; | |
| 1597 | + } | |
| 1598 | + return { h: h, s: s, v: v }; | |
| 1599 | + } | |
| 1600 | + | |
| 1601 | + // `hsvToRgb` | |
| 1602 | + // Converts an HSV color value to RGB. | |
| 1603 | + // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] | |
| 1604 | + // *Returns:* { r, g, b } in the set [0, 255] | |
| 1605 | + function hsvToRgb(h, s, v) { | |
| 1606 | + | |
| 1607 | + h = bound01(h, 360) * 6; | |
| 1608 | + s = bound01(s, 100); | |
| 1609 | + v = bound01(v, 100); | |
| 1610 | + | |
| 1611 | + var i = math.floor(h), | |
| 1612 | + f = h - i, | |
| 1613 | + p = v * (1 - s), | |
| 1614 | + q = v * (1 - f * s), | |
| 1615 | + t = v * (1 - (1 - f) * s), | |
| 1616 | + mod = i % 6, | |
| 1617 | + r = [v, q, p, p, t, v][mod], | |
| 1618 | + g = [t, v, v, q, p, p][mod], | |
| 1619 | + b = [p, p, t, v, v, q][mod]; | |
| 1620 | + | |
| 1621 | + return { r: r * 255, g: g * 255, b: b * 255 }; | |
| 1622 | + } | |
| 1623 | + | |
| 1624 | + // `rgbToHex` | |
| 1625 | + // Converts an RGB color to hex | |
| 1626 | + // Assumes r, g, and b are contained in the set [0, 255] | |
| 1627 | + // Returns a 3 or 6 character hex | |
| 1628 | + function rgbToHex(r, g, b, allow3Char) { | |
| 1629 | + | |
| 1630 | + var hex = [ | |
| 1631 | + pad2(mathRound(r).toString(16)), | |
| 1632 | + pad2(mathRound(g).toString(16)), | |
| 1633 | + pad2(mathRound(b).toString(16)) | |
| 1634 | + ]; | |
| 1635 | + | |
| 1636 | + // Return a 3 character hex if possible | |
| 1637 | + if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) { | |
| 1638 | + return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0); | |
| 1639 | + } | |
| 1640 | + | |
| 1641 | + return hex.join(""); | |
| 1642 | + } | |
| 1643 | + // `rgbaToHex` | |
| 1644 | + // Converts an RGBA color plus alpha transparency to hex | |
| 1645 | + // Assumes r, g, b and a are contained in the set [0, 255] | |
| 1646 | + // Returns an 8 character hex | |
| 1647 | + function rgbaToHex(r, g, b, a) { | |
| 1648 | + | |
| 1649 | + var hex = [ | |
| 1650 | + pad2(convertDecimalToHex(a)), | |
| 1651 | + pad2(mathRound(r).toString(16)), | |
| 1652 | + pad2(mathRound(g).toString(16)), | |
| 1653 | + pad2(mathRound(b).toString(16)) | |
| 1654 | + ]; | |
| 1655 | + | |
| 1656 | + return hex.join(""); | |
| 1657 | + } | |
| 1658 | + | |
| 1659 | + // `equals` | |
| 1660 | + // Can be called with any tinycolor input | |
| 1661 | + tinycolor.equals = function (color1, color2) { | |
| 1662 | + if (!color1 || !color2) { return false; } | |
| 1663 | + return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString(); | |
| 1664 | + }; | |
| 1665 | + tinycolor.random = function() { | |
| 1666 | + return tinycolor.fromRatio({ | |
| 1667 | + r: mathRandom(), | |
| 1668 | + g: mathRandom(), | |
| 1669 | + b: mathRandom() | |
| 1670 | + }); | |
| 1671 | + }; | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + // Modification Functions | |
| 1675 | + // ---------------------- | |
| 1676 | + // Thanks to less.js for some of the basics here | |
| 1677 | + // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js> | |
| 1678 | + | |
| 1679 | + function desaturate(color, amount) { | |
| 1680 | + amount = (amount === 0) ? 0 : (amount || 10); | |
| 1681 | + var hsl = tinycolor(color).toHsl(); | |
| 1682 | + hsl.s -= amount / 100; | |
| 1683 | + hsl.s = clamp01(hsl.s); | |
| 1684 | + return tinycolor(hsl); | |
| 1685 | + } | |
| 1686 | + | |
| 1687 | + function saturate(color, amount) { | |
| 1688 | + amount = (amount === 0) ? 0 : (amount || 10); | |
| 1689 | + var hsl = tinycolor(color).toHsl(); | |
| 1690 | + hsl.s += amount / 100; | |
| 1691 | + hsl.s = clamp01(hsl.s); | |
| 1692 | + return tinycolor(hsl); | |
| 1693 | + } | |
| 1694 | + | |
| 1695 | + function greyscale(color) { | |
| 1696 | + return tinycolor(color).desaturate(100); | |
| 1697 | + } | |
| 1698 | + | |
| 1699 | + function lighten (color, amount) { | |
| 1700 | + amount = (amount === 0) ? 0 : (amount || 10); | |
| 1701 | + var hsl = tinycolor(color).toHsl(); | |
| 1702 | + hsl.l += amount / 100; | |
| 1703 | + hsl.l = clamp01(hsl.l); | |
| 1704 | + return tinycolor(hsl); | |
| 1705 | + } | |
| 1706 | + | |
| 1707 | + function brighten(color, amount) { | |
| 1708 | + amount = (amount === 0) ? 0 : (amount || 10); | |
| 1709 | + var rgb = tinycolor(color).toRgb(); | |
| 1710 | + rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * - (amount / 100)))); | |
| 1711 | + rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * - (amount / 100)))); | |
| 1712 | + rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * - (amount / 100)))); | |
| 1713 | + return tinycolor(rgb); | |
| 1714 | + } | |
| 1715 | + | |
| 1716 | + function darken (color, amount) { | |
| 1717 | + amount = (amount === 0) ? 0 : (amount || 10); | |
| 1718 | + var hsl = tinycolor(color).toHsl(); | |
| 1719 | + hsl.l -= amount / 100; | |
| 1720 | + hsl.l = clamp01(hsl.l); | |
| 1721 | + return tinycolor(hsl); | |
| 1722 | + } | |
| 1723 | + | |
| 1724 | + // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue. | |
| 1725 | + // Values outside of this range will be wrapped into this range. | |
| 1726 | + function spin(color, amount) { | |
| 1727 | + var hsl = tinycolor(color).toHsl(); | |
| 1728 | + var hue = (mathRound(hsl.h) + amount) % 360; | |
| 1729 | + hsl.h = hue < 0 ? 360 + hue : hue; | |
| 1730 | + return tinycolor(hsl); | |
| 1731 | + } | |
| 1732 | + | |
| 1733 | + // Combination Functions | |
| 1734 | + // --------------------- | |
| 1735 | + // Thanks to jQuery xColor for some of the ideas behind these | |
| 1736 | + // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js> | |
| 1737 | + | |
| 1738 | + function complement(color) { | |
| 1739 | + var hsl = tinycolor(color).toHsl(); | |
| 1740 | + hsl.h = (hsl.h + 180) % 360; | |
| 1741 | + return tinycolor(hsl); | |
| 1742 | + } | |
| 1743 | + | |
| 1744 | + function triad(color) { | |
| 1745 | + var hsl = tinycolor(color).toHsl(); | |
| 1746 | + var h = hsl.h; | |
| 1747 | + return [ | |
| 1748 | + tinycolor(color), | |
| 1749 | + tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }), | |
| 1750 | + tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l }) | |
| 1751 | + ]; | |
| 1752 | + } | |
| 1753 | + | |
| 1754 | + function tetrad(color) { | |
| 1755 | + var hsl = tinycolor(color).toHsl(); | |
| 1756 | + var h = hsl.h; | |
| 1757 | + return [ | |
| 1758 | + tinycolor(color), | |
| 1759 | + tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }), | |
| 1760 | + tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }), | |
| 1761 | + tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l }) | |
| 1762 | + ]; | |
| 1763 | + } | |
| 1764 | + | |
| 1765 | + function splitcomplement(color) { | |
| 1766 | + var hsl = tinycolor(color).toHsl(); | |
| 1767 | + var h = hsl.h; | |
| 1768 | + return [ | |
| 1769 | + tinycolor(color), | |
| 1770 | + tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l}), | |
| 1771 | + tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l}) | |
| 1772 | + ]; | |
| 1773 | + } | |
| 1774 | + | |
| 1775 | + function analogous(color, results, slices) { | |
| 1776 | + results = results || 6; | |
| 1777 | + slices = slices || 30; | |
| 1778 | + | |
| 1779 | + var hsl = tinycolor(color).toHsl(); | |
| 1780 | + var part = 360 / slices; | |
| 1781 | + var ret = [tinycolor(color)]; | |
| 1782 | + | |
| 1783 | + for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) { | |
| 1784 | + hsl.h = (hsl.h + part) % 360; | |
| 1785 | + ret.push(tinycolor(hsl)); | |
| 1786 | + } | |
| 1787 | + return ret; | |
| 1788 | + } | |
| 1789 | + | |
| 1790 | + function monochromatic(color, results) { | |
| 1791 | + results = results || 6; | |
| 1792 | + var hsv = tinycolor(color).toHsv(); | |
| 1793 | + var h = hsv.h, s = hsv.s, v = hsv.v; | |
| 1794 | + var ret = []; | |
| 1795 | + var modification = 1 / results; | |
| 1796 | + | |
| 1797 | + while (results--) { | |
| 1798 | + ret.push(tinycolor({ h: h, s: s, v: v})); | |
| 1799 | + v = (v + modification) % 1; | |
| 1800 | + } | |
| 1801 | + | |
| 1802 | + return ret; | |
| 1803 | + } | |
| 1804 | + | |
| 1805 | + // Utility Functions | |
| 1806 | + // --------------------- | |
| 1807 | + | |
| 1808 | + tinycolor.mix = function(color1, color2, amount) { | |
| 1809 | + amount = (amount === 0) ? 0 : (amount || 50); | |
| 1810 | + | |
| 1811 | + var rgb1 = tinycolor(color1).toRgb(); | |
| 1812 | + var rgb2 = tinycolor(color2).toRgb(); | |
| 1813 | + | |
| 1814 | + var p = amount / 100; | |
| 1815 | + var w = p * 2 - 1; | |
| 1816 | + var a = rgb2.a - rgb1.a; | |
| 1817 | + | |
| 1818 | + var w1; | |
| 1819 | + | |
| 1820 | + if (w * a == -1) { | |
| 1821 | + w1 = w; | |
| 1822 | + } else { | |
| 1823 | + w1 = (w + a) / (1 + w * a); | |
| 1824 | + } | |
| 1825 | + | |
| 1826 | + w1 = (w1 + 1) / 2; | |
| 1827 | + | |
| 1828 | + var w2 = 1 - w1; | |
| 1829 | + | |
| 1830 | + var rgba = { | |
| 1831 | + r: rgb2.r * w1 + rgb1.r * w2, | |
| 1832 | + g: rgb2.g * w1 + rgb1.g * w2, | |
| 1833 | + b: rgb2.b * w1 + rgb1.b * w2, | |
| 1834 | + a: rgb2.a * p + rgb1.a * (1 - p) | |
| 1835 | + }; | |
| 1836 | + | |
| 1837 | + return tinycolor(rgba); | |
| 1838 | + }; | |
| 1839 | + | |
| 1840 | + | |
| 1841 | + // Readability Functions | |
| 1842 | + // --------------------- | |
| 1843 | + // <http://www.w3.org/TR/AERT#color-contrast> | |
| 1844 | + | |
| 1845 | + // `readability` | |
| 1846 | + // Analyze the 2 colors and returns an object with the following properties: | |
| 1847 | + // `brightness`: difference in brightness between the two colors | |
| 1848 | + // `color`: difference in color/hue between the two colors | |
| 1849 | + tinycolor.readability = function(color1, color2) { | |
| 1850 | + var c1 = tinycolor(color1); | |
| 1851 | + var c2 = tinycolor(color2); | |
| 1852 | + var rgb1 = c1.toRgb(); | |
| 1853 | + var rgb2 = c2.toRgb(); | |
| 1854 | + var brightnessA = c1.getBrightness(); | |
| 1855 | + var brightnessB = c2.getBrightness(); | |
| 1856 | + var colorDiff = ( | |
| 1857 | + Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) + | |
| 1858 | + Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) + | |
| 1859 | + Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b) | |
| 1860 | + ); | |
| 1861 | + | |
| 1862 | + return { | |
| 1863 | + brightness: Math.abs(brightnessA - brightnessB), | |
| 1864 | + color: colorDiff | |
| 1865 | + }; | |
| 1866 | + }; | |
| 1867 | + | |
| 1868 | + // `readable` | |
| 1869 | + // http://www.w3.org/TR/AERT#color-contrast | |
| 1870 | + // Ensure that foreground and background color combinations provide sufficient contrast. | |
| 1871 | + // *Example* | |
| 1872 | + // tinycolor.isReadable("#000", "#111") => false | |
| 1873 | + tinycolor.isReadable = function(color1, color2) { | |
| 1874 | + var readability = tinycolor.readability(color1, color2); | |
| 1875 | + return readability.brightness > 125 && readability.color > 500; | |
| 1876 | + }; | |
| 1877 | + | |
| 1878 | + // `mostReadable` | |
| 1879 | + // Given a base color and a list of possible foreground or background | |
| 1880 | + // colors for that base, returns the most readable color. | |
| 1881 | + // *Example* | |
| 1882 | + // tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000" | |
| 1883 | + tinycolor.mostReadable = function(baseColor, colorList) { | |
| 1884 | + var bestColor = null; | |
| 1885 | + var bestScore = 0; | |
| 1886 | + var bestIsReadable = false; | |
| 1887 | + for (var i=0; i < colorList.length; i++) { | |
| 1888 | + | |
| 1889 | + // We normalize both around the "acceptable" breaking point, | |
| 1890 | + // but rank brightness constrast higher than hue. | |
| 1891 | + | |
| 1892 | + var readability = tinycolor.readability(baseColor, colorList[i]); | |
| 1893 | + var readable = readability.brightness > 125 && readability.color > 500; | |
| 1894 | + var score = 3 * (readability.brightness / 125) + (readability.color / 500); | |
| 1895 | + | |
| 1896 | + if ((readable && ! bestIsReadable) || | |
| 1897 | + (readable && bestIsReadable && score > bestScore) || | |
| 1898 | + ((! readable) && (! bestIsReadable) && score > bestScore)) { | |
| 1899 | + bestIsReadable = readable; | |
| 1900 | + bestScore = score; | |
| 1901 | + bestColor = tinycolor(colorList[i]); | |
| 1902 | + } | |
| 1903 | + } | |
| 1904 | + return bestColor; | |
| 1905 | + }; | |
| 1906 | + | |
| 1907 | + | |
| 1908 | + // Big List of Colors | |
| 1909 | + // ------------------ | |
| 1910 | + // <http://www.w3.org/TR/css3-color/#svg-color> | |
| 1911 | + var names = tinycolor.names = { | |
| 1912 | + aliceblue: "f0f8ff", | |
| 1913 | + antiquewhite: "faebd7", | |
| 1914 | + aqua: "0ff", | |
| 1915 | + aquamarine: "7fffd4", | |
| 1916 | + azure: "f0ffff", | |
| 1917 | + beige: "f5f5dc", | |
| 1918 | + bisque: "ffe4c4", | |
| 1919 | + black: "000", | |
| 1920 | + blanchedalmond: "ffebcd", | |
| 1921 | + blue: "00f", | |
| 1922 | + blueviolet: "8a2be2", | |
| 1923 | + brown: "a52a2a", | |
| 1924 | + burlywood: "deb887", | |
| 1925 | + burntsienna: "ea7e5d", | |
| 1926 | + cadetblue: "5f9ea0", | |
| 1927 | + chartreuse: "7fff00", | |
| 1928 | + chocolate: "d2691e", | |
| 1929 | + coral: "ff7f50", | |
| 1930 | + cornflowerblue: "6495ed", | |
| 1931 | + cornsilk: "fff8dc", | |
| 1932 | + crimson: "dc143c", | |
| 1933 | + cyan: "0ff", | |
| 1934 | + darkblue: "00008b", | |
| 1935 | + darkcyan: "008b8b", | |
| 1936 | + darkgoldenrod: "b8860b", | |
| 1937 | + darkgray: "a9a9a9", | |
| 1938 | + darkgreen: "006400", | |
| 1939 | + darkgrey: "a9a9a9", | |
| 1940 | + darkkhaki: "bdb76b", | |
| 1941 | + darkmagenta: "8b008b", | |
| 1942 | + darkolivegreen: "556b2f", | |
| 1943 | + darkorange: "ff8c00", | |
| 1944 | + darkorchid: "9932cc", | |
| 1945 | + darkred: "8b0000", | |
| 1946 | + darksalmon: "e9967a", | |
| 1947 | + darkseagreen: "8fbc8f", | |
| 1948 | + darkslateblue: "483d8b", | |
| 1949 | + darkslategray: "2f4f4f", | |
| 1950 | + darkslategrey: "2f4f4f", | |
| 1951 | + darkturquoise: "00ced1", | |
| 1952 | + darkviolet: "9400d3", | |
| 1953 | + deeppink: "ff1493", | |
| 1954 | + deepskyblue: "00bfff", | |
| 1955 | + dimgray: "696969", | |
| 1956 | + dimgrey: "696969", | |
| 1957 | + dodgerblue: "1e90ff", | |
| 1958 | + firebrick: "b22222", | |
| 1959 | + floralwhite: "fffaf0", | |
| 1960 | + forestgreen: "228b22", | |
| 1961 | + fuchsia: "f0f", | |
| 1962 | + gainsboro: "dcdcdc", | |
| 1963 | + ghostwhite: "f8f8ff", | |
| 1964 | + gold: "ffd700", | |
| 1965 | + goldenrod: "daa520", | |
| 1966 | + gray: "808080", | |
| 1967 | + green: "008000", | |
| 1968 | + greenyellow: "adff2f", | |
| 1969 | + grey: "808080", | |
| 1970 | + honeydew: "f0fff0", | |
| 1971 | + hotpink: "ff69b4", | |
| 1972 | + indianred: "cd5c5c", | |
| 1973 | + indigo: "4b0082", | |
| 1974 | + ivory: "fffff0", | |
| 1975 | + khaki: "f0e68c", | |
| 1976 | + lavender: "e6e6fa", | |
| 1977 | + lavenderblush: "fff0f5", | |
| 1978 | + lawngreen: "7cfc00", | |
| 1979 | + lemonchiffon: "fffacd", | |
| 1980 | + lightblue: "add8e6", | |
| 1981 | + lightcoral: "f08080", | |
| 1982 | + lightcyan: "e0ffff", | |
| 1983 | + lightgoldenrodyellow: "fafad2", | |
| 1984 | + lightgray: "d3d3d3", | |
| 1985 | + lightgreen: "90ee90", | |
| 1986 | + lightgrey: "d3d3d3", | |
| 1987 | + lightpink: "ffb6c1", | |
| 1988 | + lightsalmon: "ffa07a", | |
| 1989 | + lightseagreen: "20b2aa", | |
| 1990 | + lightskyblue: "87cefa", | |
| 1991 | + lightslategray: "789", | |
| 1992 | + lightslategrey: "789", | |
| 1993 | + lightsteelblue: "b0c4de", | |
| 1994 | + lightyellow: "ffffe0", | |
| 1995 | + lime: "0f0", | |
| 1996 | + limegreen: "32cd32", | |
| 1997 | + linen: "faf0e6", | |
| 1998 | + magenta: "f0f", | |
| 1999 | + maroon: "800000", | |
| 2000 | + mediumaquamarine: "66cdaa", | |
| 2001 | + mediumblue: "0000cd", | |
| 2002 | + mediumorchid: "ba55d3", | |
| 2003 | + mediumpurple: "9370db", | |
| 2004 | + mediumseagreen: "3cb371", | |
| 2005 | + mediumslateblue: "7b68ee", | |
| 2006 | + mediumspringgreen: "00fa9a", | |
| 2007 | + mediumturquoise: "48d1cc", | |
| 2008 | + mediumvioletred: "c71585", | |
| 2009 | + midnightblue: "191970", | |
| 2010 | + mintcream: "f5fffa", | |
| 2011 | + mistyrose: "ffe4e1", | |
| 2012 | + moccasin: "ffe4b5", | |
| 2013 | + navajowhite: "ffdead", | |
| 2014 | + navy: "000080", | |
| 2015 | + oldlace: "fdf5e6", | |
| 2016 | + olive: "808000", | |
| 2017 | + olivedrab: "6b8e23", | |
| 2018 | + orange: "ffa500", | |
| 2019 | + orangered: "ff4500", | |
| 2020 | + orchid: "da70d6", | |
| 2021 | + palegoldenrod: "eee8aa", | |
| 2022 | + palegreen: "98fb98", | |
| 2023 | + paleturquoise: "afeeee", | |
| 2024 | + palevioletred: "db7093", | |
| 2025 | + papayawhip: "ffefd5", | |
| 2026 | + peachpuff: "ffdab9", | |
| 2027 | + peru: "cd853f", | |
| 2028 | + pink: "ffc0cb", | |
| 2029 | + plum: "dda0dd", | |
| 2030 | + powderblue: "b0e0e6", | |
| 2031 | + purple: "800080", | |
| 2032 | + red: "f00", | |
| 2033 | + rosybrown: "bc8f8f", | |
| 2034 | + royalblue: "4169e1", | |
| 2035 | + saddlebrown: "8b4513", | |
| 2036 | + salmon: "fa8072", | |
| 2037 | + sandybrown: "f4a460", | |
| 2038 | + seagreen: "2e8b57", | |
| 2039 | + seashell: "fff5ee", | |
| 2040 | + sienna: "a0522d", | |
| 2041 | + silver: "c0c0c0", | |
| 2042 | + skyblue: "87ceeb", | |
| 2043 | + slateblue: "6a5acd", | |
| 2044 | + slategray: "708090", | |
| 2045 | + slategrey: "708090", | |
| 2046 | + snow: "fffafa", | |
| 2047 | + springgreen: "00ff7f", | |
| 2048 | + steelblue: "4682b4", | |
| 2049 | + tan: "d2b48c", | |
| 2050 | + teal: "008080", | |
| 2051 | + thistle: "d8bfd8", | |
| 2052 | + tomato: "ff6347", | |
| 2053 | + turquoise: "40e0d0", | |
| 2054 | + violet: "ee82ee", | |
| 2055 | + wheat: "f5deb3", | |
| 2056 | + white: "fff", | |
| 2057 | + whitesmoke: "f5f5f5", | |
| 2058 | + yellow: "ff0", | |
| 2059 | + yellowgreen: "9acd32" | |
| 2060 | + }; | |
| 2061 | + | |
| 2062 | + // Make it easy to access colors via `hexNames[hex]` | |
| 2063 | + var hexNames = tinycolor.hexNames = flip(names); | |
| 2064 | + | |
| 2065 | + | |
| 2066 | + // Utilities | |
| 2067 | + // --------- | |
| 2068 | + | |
| 2069 | + // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }` | |
| 2070 | + function flip(o) { | |
| 2071 | + var flipped = { }; | |
| 2072 | + for (var i in o) { | |
| 2073 | + if (o.hasOwnProperty(i)) { | |
| 2074 | + flipped[o[i]] = i; | |
| 2075 | + } | |
| 2076 | + } | |
| 2077 | + return flipped; | |
| 2078 | + } | |
| 2079 | + | |
| 2080 | + // Return a valid alpha value [0,1] with all invalid values being set to 1 | |
| 2081 | + function boundAlpha(a) { | |
| 2082 | + a = parseFloat(a); | |
| 2083 | + | |
| 2084 | + if (isNaN(a) || a < 0 || a > 1) { | |
| 2085 | + a = 1; | |
| 2086 | + } | |
| 2087 | + | |
| 2088 | + return a; | |
| 2089 | + } | |
| 2090 | + | |
| 2091 | + // Take input from [0, n] and return it as [0, 1] | |
| 2092 | + function bound01(n, max) { | |
| 2093 | + if (isOnePointZero(n)) { n = "100%"; } | |
| 2094 | + | |
| 2095 | + var processPercent = isPercentage(n); | |
| 2096 | + n = mathMin(max, mathMax(0, parseFloat(n))); | |
| 2097 | + | |
| 2098 | + // Automatically convert percentage into number | |
| 2099 | + if (processPercent) { | |
| 2100 | + n = parseInt(n * max, 10) / 100; | |
| 2101 | + } | |
| 2102 | + | |
| 2103 | + // Handle floating point rounding errors | |
| 2104 | + if ((math.abs(n - max) < 0.000001)) { | |
| 2105 | + return 1; | |
| 2106 | + } | |
| 2107 | + | |
| 2108 | + // Convert into [0, 1] range if it isn't already | |
| 2109 | + return (n % max) / parseFloat(max); | |
| 2110 | + } | |
| 2111 | + | |
| 2112 | + // Force a number between 0 and 1 | |
| 2113 | + function clamp01(val) { | |
| 2114 | + return mathMin(1, mathMax(0, val)); | |
| 2115 | + } | |
| 2116 | + | |
| 2117 | + // Parse a base-16 hex value into a base-10 integer | |
| 2118 | + function parseIntFromHex(val) { | |
| 2119 | + return parseInt(val, 16); | |
| 2120 | + } | |
| 2121 | + | |
| 2122 | + // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 | |
| 2123 | + // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0> | |
| 2124 | + function isOnePointZero(n) { | |
| 2125 | + return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1; | |
| 2126 | + } | |
| 2127 | + | |
| 2128 | + // Check to see if string passed in is a percentage | |
| 2129 | + function isPercentage(n) { | |
| 2130 | + return typeof n === "string" && n.indexOf('%') != -1; | |
| 2131 | + } | |
| 2132 | + | |
| 2133 | + // Force a hex value to have 2 characters | |
| 2134 | + function pad2(c) { | |
| 2135 | + return c.length == 1 ? '0' + c : '' + c; | |
| 2136 | + } | |
| 2137 | + | |
| 2138 | + // Replace a decimal with it's percentage value | |
| 2139 | + function convertToPercentage(n) { | |
| 2140 | + if (n <= 1) { | |
| 2141 | + n = (n * 100) + "%"; | |
| 2142 | + } | |
| 2143 | + | |
| 2144 | + return n; | |
| 2145 | + } | |
| 2146 | + | |
| 2147 | + // Converts a decimal to a hex value | |
| 2148 | + function convertDecimalToHex(d) { | |
| 2149 | + return Math.round(parseFloat(d) * 255).toString(16); | |
| 2150 | + } | |
| 2151 | + // Converts a hex value to a decimal | |
| 2152 | + function convertHexToDecimal(h) { | |
| 2153 | + return (parseIntFromHex(h) / 255); | |
| 2154 | + } | |
| 2155 | + | |
| 2156 | + var matchers = (function() { | |
| 2157 | + | |
| 2158 | + // <http://www.w3.org/TR/css3-values/#integers> | |
| 2159 | + var CSS_INTEGER = "[-\\+]?\\d+%?"; | |
| 2160 | + | |
| 2161 | + // <http://www.w3.org/TR/css3-values/#number-value> | |
| 2162 | + var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?"; | |
| 2163 | + | |
| 2164 | + // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome. | |
| 2165 | + var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")"; | |
| 2166 | + | |
| 2167 | + // Actual matching. | |
| 2168 | + // Parentheses and commas are optional, but not required. | |
| 2169 | + // Whitespace can take the place of commas or opening paren | |
| 2170 | + var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; | |
| 2171 | + var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?"; | |
| 2172 | + | |
| 2173 | + return { | |
| 2174 | + rgb: new RegExp("rgb" + PERMISSIVE_MATCH3), | |
| 2175 | + rgba: new RegExp("rgba" + PERMISSIVE_MATCH4), | |
| 2176 | + hsl: new RegExp("hsl" + PERMISSIVE_MATCH3), | |
| 2177 | + hsla: new RegExp("hsla" + PERMISSIVE_MATCH4), | |
| 2178 | + hsv: new RegExp("hsv" + PERMISSIVE_MATCH3), | |
| 2179 | + hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, | |
| 2180 | + hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, | |
| 2181 | + hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/ | |
| 2182 | + }; | |
| 2183 | + })(); | |
| 2184 | + | |
| 2185 | + // `stringInputToObject` | |
| 2186 | + // Permissive string parsing. Take in a number of formats, and output an object | |
| 2187 | + // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}` | |
| 2188 | + function stringInputToObject(color) { | |
| 2189 | + | |
| 2190 | + color = color.replace(trimLeft,'').replace(trimRight, '').toLowerCase(); | |
| 2191 | + var named = false; | |
| 2192 | + if (names[color]) { | |
| 2193 | + color = names[color]; | |
| 2194 | + named = true; | |
| 2195 | + } | |
| 2196 | + else if (color == 'transparent') { | |
| 2197 | + return { r: 0, g: 0, b: 0, a: 0, format: "name" }; | |
| 2198 | + } | |
| 2199 | + | |
| 2200 | + // Try to match string input using regular expressions. | |
| 2201 | + // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360] | |
| 2202 | + // Just return an object and let the conversion functions handle that. | |
| 2203 | + // This way the result will be the same whether the tinycolor is initialized with string or object. | |
| 2204 | + var match; | |
| 2205 | + if ((match = matchers.rgb.exec(color))) { | |
| 2206 | + return { r: match[1], g: match[2], b: match[3] }; | |
| 2207 | + } | |
| 2208 | + if ((match = matchers.rgba.exec(color))) { | |
| 2209 | + return { r: match[1], g: match[2], b: match[3], a: match[4] }; | |
| 2210 | + } | |
| 2211 | + if ((match = matchers.hsl.exec(color))) { | |
| 2212 | + return { h: match[1], s: match[2], l: match[3] }; | |
| 2213 | + } | |
| 2214 | + if ((match = matchers.hsla.exec(color))) { | |
| 2215 | + return { h: match[1], s: match[2], l: match[3], a: match[4] }; | |
| 2216 | + } | |
| 2217 | + if ((match = matchers.hsv.exec(color))) { | |
| 2218 | + return { h: match[1], s: match[2], v: match[3] }; | |
| 2219 | + } | |
| 2220 | + if ((match = matchers.hex8.exec(color))) { | |
| 2221 | + return { | |
| 2222 | + a: convertHexToDecimal(match[1]), | |
| 2223 | + r: parseIntFromHex(match[2]), | |
| 2224 | + g: parseIntFromHex(match[3]), | |
| 2225 | + b: parseIntFromHex(match[4]), | |
| 2226 | + format: named ? "name" : "hex8" | |
| 2227 | + }; | |
| 2228 | + } | |
| 2229 | + if ((match = matchers.hex6.exec(color))) { | |
| 2230 | + return { | |
| 2231 | + r: parseIntFromHex(match[1]), | |
| 2232 | + g: parseIntFromHex(match[2]), | |
| 2233 | + b: parseIntFromHex(match[3]), | |
| 2234 | + format: named ? "name" : "hex" | |
| 2235 | + }; | |
| 2236 | + } | |
| 2237 | + if ((match = matchers.hex3.exec(color))) { | |
| 2238 | + return { | |
| 2239 | + r: parseIntFromHex(match[1] + '' + match[1]), | |
| 2240 | + g: parseIntFromHex(match[2] + '' + match[2]), | |
| 2241 | + b: parseIntFromHex(match[3] + '' + match[3]), | |
| 2242 | + format: named ? "name" : "hex" | |
| 2243 | + }; | |
| 2244 | + } | |
| 2245 | + | |
| 2246 | + return false; | |
| 2247 | + } | |
| 2248 | + | |
| 2249 | + window.tinycolor = tinycolor; | |
| 2250 | + })(); | |
| 2251 | + | |
| 2252 | + | |
| 2253 | + $(function () { | |
| 2254 | + if ($.fn.spectrum.load) { | |
| 2255 | + $.fn.spectrum.processNativeColorInputs(); | |
| 2256 | + } | |
| 2257 | + }); | |
| 2258 | + | |
| 2259 | +})(window, jQuery); | ... | ... |
public/stylesheets/application.css
| ... | ... | @@ -2549,6 +2549,15 @@ div#activation_enterprise label, div#activation_enterprise input, div#activation |
| 2549 | 2549 | color: #585858; |
| 2550 | 2550 | font-size: 11px; |
| 2551 | 2551 | } |
| 2552 | +.colorpicker_field { | |
| 2553 | + display: none; | |
| 2554 | +} | |
| 2555 | +.color_marker { | |
| 2556 | + display: inline-block; | |
| 2557 | + width: 10px; | |
| 2558 | + height: 10px; | |
| 2559 | + border: 1px solid; | |
| 2560 | +} | |
| 2552 | 2561 | .formfield input { |
| 2553 | 2562 | text-indent: 5px; |
| 2554 | 2563 | padding: 2px 0px; | ... | ... |
| ... | ... | @@ -0,0 +1,506 @@ |
| 1 | +/*** | |
| 2 | +Spectrum Colorpicker v1.4.1 | |
| 3 | +https://github.com/bgrins/spectrum | |
| 4 | +Author: Brian Grinstead | |
| 5 | +License: MIT | |
| 6 | +***/ | |
| 7 | + | |
| 8 | +.sp-container { | |
| 9 | + position:absolute; | |
| 10 | + top:0; | |
| 11 | + left:0; | |
| 12 | + display:inline-block; | |
| 13 | + *display: inline; | |
| 14 | + *zoom: 1; | |
| 15 | + /* https://github.com/bgrins/spectrum/issues/40 */ | |
| 16 | + z-index: 9999994; | |
| 17 | + overflow: hidden; | |
| 18 | +} | |
| 19 | +.sp-container.sp-flat { | |
| 20 | + position: relative; | |
| 21 | +} | |
| 22 | + | |
| 23 | +/* Fix for * { box-sizing: border-box; } */ | |
| 24 | +.sp-container, | |
| 25 | +.sp-container * { | |
| 26 | + -webkit-box-sizing: content-box; | |
| 27 | + -moz-box-sizing: content-box; | |
| 28 | + box-sizing: content-box; | |
| 29 | +} | |
| 30 | + | |
| 31 | +/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */ | |
| 32 | +.sp-top { | |
| 33 | + position:relative; | |
| 34 | + width: 100%; | |
| 35 | + display:inline-block; | |
| 36 | +} | |
| 37 | +.sp-top-inner { | |
| 38 | + position:absolute; | |
| 39 | + top:0; | |
| 40 | + left:0; | |
| 41 | + bottom:0; | |
| 42 | + right:0; | |
| 43 | +} | |
| 44 | +.sp-color { | |
| 45 | + position: absolute; | |
| 46 | + top:0; | |
| 47 | + left:0; | |
| 48 | + bottom:0; | |
| 49 | + right:20%; | |
| 50 | +} | |
| 51 | +.sp-hue { | |
| 52 | + position: absolute; | |
| 53 | + top:0; | |
| 54 | + right:0; | |
| 55 | + bottom:0; | |
| 56 | + left:84%; | |
| 57 | + height: 100%; | |
| 58 | +} | |
| 59 | + | |
| 60 | +.sp-clear-enabled .sp-hue { | |
| 61 | + top:33px; | |
| 62 | + height: 77.5%; | |
| 63 | +} | |
| 64 | + | |
| 65 | +.sp-fill { | |
| 66 | + padding-top: 80%; | |
| 67 | +} | |
| 68 | +.sp-sat, .sp-val { | |
| 69 | + position: absolute; | |
| 70 | + top:0; | |
| 71 | + left:0; | |
| 72 | + right:0; | |
| 73 | + bottom:0; | |
| 74 | +} | |
| 75 | + | |
| 76 | +.sp-alpha-enabled .sp-top { | |
| 77 | + margin-bottom: 18px; | |
| 78 | +} | |
| 79 | +.sp-alpha-enabled .sp-alpha { | |
| 80 | + display: block; | |
| 81 | +} | |
| 82 | +.sp-alpha-handle { | |
| 83 | + position:absolute; | |
| 84 | + top:-4px; | |
| 85 | + bottom: -4px; | |
| 86 | + width: 6px; | |
| 87 | + left: 50%; | |
| 88 | + cursor: pointer; | |
| 89 | + border: 1px solid black; | |
| 90 | + background: white; | |
| 91 | + opacity: .8; | |
| 92 | +} | |
| 93 | +.sp-alpha { | |
| 94 | + display: none; | |
| 95 | + position: absolute; | |
| 96 | + bottom: -14px; | |
| 97 | + right: 0; | |
| 98 | + left: 0; | |
| 99 | + height: 8px; | |
| 100 | +} | |
| 101 | +.sp-alpha-inner { | |
| 102 | + border: solid 1px #333; | |
| 103 | +} | |
| 104 | + | |
| 105 | +.sp-clear { | |
| 106 | + display: none; | |
| 107 | +} | |
| 108 | + | |
| 109 | +.sp-clear.sp-clear-display { | |
| 110 | + background-position: center; | |
| 111 | +} | |
| 112 | + | |
| 113 | +.sp-clear-enabled .sp-clear { | |
| 114 | + display: block; | |
| 115 | + position:absolute; | |
| 116 | + top:0px; | |
| 117 | + right:0; | |
| 118 | + bottom:0; | |
| 119 | + left:84%; | |
| 120 | + height: 28px; | |
| 121 | +} | |
| 122 | + | |
| 123 | +/* Don't allow text selection */ | |
| 124 | +.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button { | |
| 125 | + -webkit-user-select:none; | |
| 126 | + -moz-user-select: -moz-none; | |
| 127 | + -o-user-select:none; | |
| 128 | + user-select: none; | |
| 129 | +} | |
| 130 | + | |
| 131 | +.sp-container.sp-input-disabled .sp-input-container { | |
| 132 | + display: none; | |
| 133 | +} | |
| 134 | +.sp-container.sp-buttons-disabled .sp-button-container { | |
| 135 | + display: none; | |
| 136 | +} | |
| 137 | +.sp-container.sp-palette-buttons-disabled .sp-palette-button-container { | |
| 138 | + display: none; | |
| 139 | +} | |
| 140 | +.sp-palette-only .sp-picker-container { | |
| 141 | + display: none; | |
| 142 | +} | |
| 143 | +.sp-palette-disabled .sp-palette-container { | |
| 144 | + display: none; | |
| 145 | +} | |
| 146 | + | |
| 147 | +.sp-initial-disabled .sp-initial { | |
| 148 | + display: none; | |
| 149 | +} | |
| 150 | + | |
| 151 | + | |
| 152 | +/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */ | |
| 153 | +.sp-sat { | |
| 154 | + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); | |
| 155 | + background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); | |
| 156 | + background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); | |
| 157 | + background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); | |
| 158 | + background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); | |
| 159 | + background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0)); | |
| 160 | + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)"; | |
| 161 | + filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81'); | |
| 162 | +} | |
| 163 | +.sp-val { | |
| 164 | + background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); | |
| 165 | + background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); | |
| 166 | + background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); | |
| 167 | + background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); | |
| 168 | + background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); | |
| 169 | + background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); | |
| 170 | + -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)"; | |
| 171 | + filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000'); | |
| 172 | +} | |
| 173 | + | |
| 174 | +.sp-hue { | |
| 175 | + background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | |
| 176 | + background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | |
| 177 | + background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | |
| 178 | + background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); | |
| 179 | + background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); | |
| 180 | +} | |
| 181 | + | |
| 182 | +/* IE filters do not support multiple color stops. | |
| 183 | + Generate 6 divs, line them up, and do two color gradients for each. | |
| 184 | + Yes, really. | |
| 185 | + */ | |
| 186 | +.sp-1 { | |
| 187 | + height:17%; | |
| 188 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00'); | |
| 189 | +} | |
| 190 | +.sp-2 { | |
| 191 | + height:16%; | |
| 192 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00'); | |
| 193 | +} | |
| 194 | +.sp-3 { | |
| 195 | + height:17%; | |
| 196 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff'); | |
| 197 | +} | |
| 198 | +.sp-4 { | |
| 199 | + height:17%; | |
| 200 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff'); | |
| 201 | +} | |
| 202 | +.sp-5 { | |
| 203 | + height:16%; | |
| 204 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff'); | |
| 205 | +} | |
| 206 | +.sp-6 { | |
| 207 | + height:17%; | |
| 208 | + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000'); | |
| 209 | +} | |
| 210 | + | |
| 211 | +.sp-hidden { | |
| 212 | + display: none !important; | |
| 213 | +} | |
| 214 | + | |
| 215 | +/* Clearfix hack */ | |
| 216 | +.sp-cf:before, .sp-cf:after { content: ""; display: table; } | |
| 217 | +.sp-cf:after { clear: both; } | |
| 218 | +.sp-cf { *zoom: 1; } | |
| 219 | + | |
| 220 | +/* Mobile devices, make hue slider bigger so it is easier to slide */ | |
| 221 | +@media (max-device-width: 480px) { | |
| 222 | + .sp-color { right: 40%; } | |
| 223 | + .sp-hue { left: 63%; } | |
| 224 | + .sp-fill { padding-top: 60%; } | |
| 225 | +} | |
| 226 | +.sp-dragger { | |
| 227 | + border-radius: 5px; | |
| 228 | + height: 5px; | |
| 229 | + width: 5px; | |
| 230 | + border: 1px solid #fff; | |
| 231 | + background: #000; | |
| 232 | + cursor: pointer; | |
| 233 | + position:absolute; | |
| 234 | + top:0; | |
| 235 | + left: 0; | |
| 236 | +} | |
| 237 | +.sp-slider { | |
| 238 | + position: absolute; | |
| 239 | + top:0; | |
| 240 | + cursor:pointer; | |
| 241 | + height: 3px; | |
| 242 | + left: -1px; | |
| 243 | + right: -1px; | |
| 244 | + border: 1px solid #000; | |
| 245 | + background: white; | |
| 246 | + opacity: .8; | |
| 247 | +} | |
| 248 | + | |
| 249 | +/* | |
| 250 | +Theme authors: | |
| 251 | +Here are the basic themeable display options (colors, fonts, global widths). | |
| 252 | +See http://bgrins.github.io/spectrum/themes/ for instructions. | |
| 253 | +*/ | |
| 254 | + | |
| 255 | +.sp-container { | |
| 256 | + border-radius: 0; | |
| 257 | + background-color: #ECECEC; | |
| 258 | + border: solid 1px #f0c49B; | |
| 259 | + padding: 0; | |
| 260 | +} | |
| 261 | +.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear { | |
| 262 | + font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; | |
| 263 | + -webkit-box-sizing: border-box; | |
| 264 | + -moz-box-sizing: border-box; | |
| 265 | + -ms-box-sizing: border-box; | |
| 266 | + box-sizing: border-box; | |
| 267 | +} | |
| 268 | +.sp-top { | |
| 269 | + margin-bottom: 3px; | |
| 270 | +} | |
| 271 | +.sp-color, .sp-hue, .sp-clear { | |
| 272 | + border: solid 1px #666; | |
| 273 | +} | |
| 274 | + | |
| 275 | +/* Input */ | |
| 276 | +.sp-input-container { | |
| 277 | + float:right; | |
| 278 | + width: 100px; | |
| 279 | + margin-bottom: 4px; | |
| 280 | +} | |
| 281 | +.sp-initial-disabled .sp-input-container { | |
| 282 | + width: 100%; | |
| 283 | +} | |
| 284 | +.sp-input { | |
| 285 | + font-size: 12px !important; | |
| 286 | + border: 1px inset; | |
| 287 | + padding: 4px 5px; | |
| 288 | + margin: 0; | |
| 289 | + width: 100%; | |
| 290 | + background:transparent; | |
| 291 | + border-radius: 3px; | |
| 292 | + color: #222; | |
| 293 | +} | |
| 294 | +.sp-input:focus { | |
| 295 | + border: 1px solid orange; | |
| 296 | +} | |
| 297 | +.sp-input.sp-validation-error { | |
| 298 | + border: 1px solid red; | |
| 299 | + background: #fdd; | |
| 300 | +} | |
| 301 | +.sp-picker-container , .sp-palette-container { | |
| 302 | + float:left; | |
| 303 | + position: relative; | |
| 304 | + padding: 10px; | |
| 305 | + padding-bottom: 300px; | |
| 306 | + margin-bottom: -290px; | |
| 307 | +} | |
| 308 | +.sp-picker-container { | |
| 309 | + width: 172px; | |
| 310 | + border-left: solid 1px #fff; | |
| 311 | +} | |
| 312 | + | |
| 313 | +/* Palettes */ | |
| 314 | +.sp-palette-container { | |
| 315 | + border-right: solid 1px #ccc; | |
| 316 | +} | |
| 317 | + | |
| 318 | +.sp-palette-only .sp-palette-container { | |
| 319 | + border: 0; | |
| 320 | +} | |
| 321 | + | |
| 322 | +.sp-palette .sp-thumb-el { | |
| 323 | + display: block; | |
| 324 | + position:relative; | |
| 325 | + float:left; | |
| 326 | + width: 24px; | |
| 327 | + height: 15px; | |
| 328 | + margin: 3px; | |
| 329 | + cursor: pointer; | |
| 330 | + border:solid 2px transparent; | |
| 331 | +} | |
| 332 | +.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { | |
| 333 | + border-color: orange; | |
| 334 | +} | |
| 335 | +.sp-thumb-el { | |
| 336 | + position:relative; | |
| 337 | +} | |
| 338 | + | |
| 339 | +/* Initial */ | |
| 340 | +.sp-initial { | |
| 341 | + float: left; | |
| 342 | + border: solid 1px #333; | |
| 343 | +} | |
| 344 | +.sp-initial span { | |
| 345 | + width: 30px; | |
| 346 | + height: 25px; | |
| 347 | + border:none; | |
| 348 | + display:block; | |
| 349 | + float:left; | |
| 350 | + margin:0; | |
| 351 | +} | |
| 352 | + | |
| 353 | +.sp-initial .sp-clear-display { | |
| 354 | + background-position: center; | |
| 355 | +} | |
| 356 | + | |
| 357 | +/* Buttons */ | |
| 358 | +.sp-palette-button-container, | |
| 359 | +.sp-button-container { | |
| 360 | + float: right; | |
| 361 | +} | |
| 362 | + | |
| 363 | +/* Replacer (the little preview div that shows up instead of the <input>) */ | |
| 364 | +.sp-replacer { | |
| 365 | + margin:0; | |
| 366 | + overflow:hidden; | |
| 367 | + cursor:pointer; | |
| 368 | + padding: 4px; | |
| 369 | + display:inline-block; | |
| 370 | + *zoom: 1; | |
| 371 | + *display: inline; | |
| 372 | + border: solid 1px #91765d; | |
| 373 | + background: #eee; | |
| 374 | + color: #333; | |
| 375 | + vertical-align: middle; | |
| 376 | +} | |
| 377 | +.sp-replacer:hover, .sp-replacer.sp-active { | |
| 378 | + border-color: #F0C49B; | |
| 379 | + color: #111; | |
| 380 | +} | |
| 381 | +.sp-replacer.sp-disabled { | |
| 382 | + cursor:default; | |
| 383 | + border-color: silver; | |
| 384 | + color: silver; | |
| 385 | +} | |
| 386 | +.sp-dd { | |
| 387 | + padding: 2px 0; | |
| 388 | + height: 16px; | |
| 389 | + line-height: 16px; | |
| 390 | + float:left; | |
| 391 | + font-size:10px; | |
| 392 | +} | |
| 393 | +.sp-preview { | |
| 394 | + position:relative; | |
| 395 | + width:25px; | |
| 396 | + height: 20px; | |
| 397 | + border: solid 1px #222; | |
| 398 | + margin-right: 5px; | |
| 399 | + float:left; | |
| 400 | + z-index: 0; | |
| 401 | +} | |
| 402 | + | |
| 403 | +.sp-palette { | |
| 404 | + *width: 220px; | |
| 405 | + max-width: 220px; | |
| 406 | +} | |
| 407 | +.sp-palette .sp-thumb-el { | |
| 408 | + width:16px; | |
| 409 | + height: 16px; | |
| 410 | + margin:2px 1px; | |
| 411 | + border: solid 1px #d0d0d0; | |
| 412 | +} | |
| 413 | + | |
| 414 | +.sp-container { | |
| 415 | + padding-bottom:0; | |
| 416 | +} | |
| 417 | + | |
| 418 | + | |
| 419 | +/* Buttons: http://hellohappy.org/css3-buttons/ */ | |
| 420 | +.sp-container button { | |
| 421 | + background-color: #eeeeee; | |
| 422 | + background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); | |
| 423 | + background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); | |
| 424 | + background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); | |
| 425 | + background-image: -o-linear-gradient(top, #eeeeee, #cccccc); | |
| 426 | + background-image: linear-gradient(to bottom, #eeeeee, #cccccc); | |
| 427 | + border: 1px solid #ccc; | |
| 428 | + border-bottom: 1px solid #bbb; | |
| 429 | + border-radius: 3px; | |
| 430 | + color: #333; | |
| 431 | + font-size: 14px; | |
| 432 | + line-height: 1; | |
| 433 | + padding: 5px 4px; | |
| 434 | + text-align: center; | |
| 435 | + text-shadow: 0 1px 0 #eee; | |
| 436 | + vertical-align: middle; | |
| 437 | +} | |
| 438 | +.sp-container button:hover { | |
| 439 | + background-color: #dddddd; | |
| 440 | + background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); | |
| 441 | + background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); | |
| 442 | + background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); | |
| 443 | + background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); | |
| 444 | + background-image: linear-gradient(to bottom, #dddddd, #bbbbbb); | |
| 445 | + border: 1px solid #bbb; | |
| 446 | + border-bottom: 1px solid #999; | |
| 447 | + cursor: pointer; | |
| 448 | + text-shadow: 0 1px 0 #ddd; | |
| 449 | +} | |
| 450 | +.sp-container button:active { | |
| 451 | + border: 1px solid #aaa; | |
| 452 | + border-bottom: 1px solid #888; | |
| 453 | + -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | |
| 454 | + -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | |
| 455 | + -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | |
| 456 | + -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | |
| 457 | + box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; | |
| 458 | +} | |
| 459 | +.sp-cancel { | |
| 460 | + font-size: 11px; | |
| 461 | + color: #d93f3f !important; | |
| 462 | + margin:0; | |
| 463 | + padding:2px; | |
| 464 | + margin-right: 5px; | |
| 465 | + vertical-align: middle; | |
| 466 | + text-decoration:none; | |
| 467 | + | |
| 468 | +} | |
| 469 | +.sp-cancel:hover { | |
| 470 | + color: #d93f3f !important; | |
| 471 | + text-decoration: underline; | |
| 472 | +} | |
| 473 | + | |
| 474 | + | |
| 475 | +.sp-palette span:hover, .sp-palette span.sp-thumb-active { | |
| 476 | + border-color: #000; | |
| 477 | +} | |
| 478 | + | |
| 479 | +.sp-preview, .sp-alpha, .sp-thumb-el { | |
| 480 | + position:relative; | |
| 481 | + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); | |
| 482 | +} | |
| 483 | +.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner { | |
| 484 | + display:block; | |
| 485 | + position:absolute; | |
| 486 | + top:0;left:0;bottom:0;right:0; | |
| 487 | +} | |
| 488 | + | |
| 489 | +.sp-palette .sp-thumb-inner { | |
| 490 | + background-position: 50% 50%; | |
| 491 | + background-repeat: no-repeat; | |
| 492 | +} | |
| 493 | + | |
| 494 | +.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner { | |
| 495 | + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=); | |
| 496 | +} | |
| 497 | + | |
| 498 | +.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner { | |
| 499 | + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=); | |
| 500 | +} | |
| 501 | + | |
| 502 | +.sp-clear-display { | |
| 503 | + background-repeat:no-repeat; | |
| 504 | + background-position: center; | |
| 505 | + background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==); | |
| 506 | +} | ... | ... |
test/functional/application_controller_test.rb
| ... | ... | @@ -166,7 +166,7 @@ class ApplicationControllerTest < ActionController::TestCase |
| 166 | 166 | |
| 167 | 167 | should 'display only some categories in menu' do |
| 168 | 168 | @controller.stubs(:get_layout).returns('application') |
| 169 | - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true ) | |
| 169 | + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true ) | |
| 170 | 170 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) |
| 171 | 171 | get :index |
| 172 | 172 | assert_tag :tag => 'a', :content => /Category 2/ |
| ... | ... | @@ -174,7 +174,7 @@ class ApplicationControllerTest < ActionController::TestCase |
| 174 | 174 | |
| 175 | 175 | should 'not display some categories in menu' do |
| 176 | 176 | @controller.stubs(:get_layout).returns('application') |
| 177 | - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true) | |
| 177 | + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true) | |
| 178 | 178 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1) |
| 179 | 179 | get :index |
| 180 | 180 | assert_no_tag :tag => 'a', :content => /Category 2/ |
| ... | ... | @@ -239,7 +239,7 @@ class ApplicationControllerTest < ActionController::TestCase |
| 239 | 239 | |
| 240 | 240 | should 'not display categories menu if categories feature disabled' do |
| 241 | 241 | Environment.any_instance.stubs(:enabled?).with(anything).returns(true) |
| 242 | - c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 1, :parent_id => nil, :display_in_menu => true ) | |
| 242 | + c1 = Environment.default.categories.create!(:name => 'Category 1', :display_color => 'ffa500', :parent_id => nil, :display_in_menu => true ) | |
| 243 | 243 | c2 = Environment.default.categories.create!(:name => 'Category 2', :display_color => nil, :parent_id => c1.id, :display_in_menu => true ) |
| 244 | 244 | get :index |
| 245 | 245 | assert_no_tag :tag => 'a', :content => /Category 2/ | ... | ... |
test/functional/categories_controller_test.rb
| ... | ... | @@ -41,7 +41,7 @@ class CategoriesControllerTest < ActionController::TestCase |
| 41 | 41 | end |
| 42 | 42 | |
| 43 | 43 | def test_edit_save |
| 44 | - post :edit, :id => cat1.id, :category => { :name => 'new name for category' } | |
| 44 | + post :edit, :id => cat1.id, :category => { :name => 'new name for category', :display_color => nil } | |
| 45 | 45 | assert_redirected_to :action => 'index' |
| 46 | 46 | assert_equal 'new name for category', Category.find(cat1.id).name |
| 47 | 47 | end |
| ... | ... | @@ -134,7 +134,7 @@ class CategoriesControllerTest < ActionController::TestCase |
| 134 | 134 | env.save! |
| 135 | 135 | get :new |
| 136 | 136 | |
| 137 | - assert_no_tag :tag => 'select', :attributes => { :name => "category[display_color]" } | |
| 137 | + assert_no_tag :tag => 'input', :attributes => { :name => "category[display_color]" } | |
| 138 | 138 | end |
| 139 | 139 | |
| 140 | 140 | should 'display color selection if environment.categories_menu is true' do |
| ... | ... | @@ -142,7 +142,7 @@ class CategoriesControllerTest < ActionController::TestCase |
| 142 | 142 | env.save! |
| 143 | 143 | get :new |
| 144 | 144 | |
| 145 | - assert_tag :tag => 'select', :attributes => { :name => "category[display_color]" } | |
| 145 | + assert_tag :tag => 'input', :attributes => { :name => "category[display_color]" } | |
| 146 | 146 | end |
| 147 | 147 | |
| 148 | 148 | should 'not list regions and product categories' do | ... | ... |
test/unit/categories_helper_test.rb
| ... | ... | @@ -15,8 +15,20 @@ class CategoriesHelperTest < ActiveSupport::TestCase |
| 15 | 15 | expects(:options_for_select).with([['General Category', 'Category'],[ 'Product Category', 'ProductCategory'],[ 'Region', 'Region' ]], 'fieldvalue').returns('OPTIONS') |
| 16 | 16 | expects(:select_tag).with('type', 'OPTIONS').returns('TAG') |
| 17 | 17 | expects(:labelled_form_field).with(anything, 'TAG').returns('RESULT') |
| 18 | - | |
| 18 | + | |
| 19 | 19 | assert_equal 'RESULT', select_category_type('fieldname') |
| 20 | 20 | end |
| 21 | 21 | |
| 22 | + should 'return category color if its defined' do | |
| 23 | + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb') | |
| 24 | + assert_equal 'background-color: #fbfbfb;', category_color_style(category1) | |
| 25 | + end | |
| 26 | + | |
| 27 | + should 'not return category parent color if category color is not defined' do | |
| 28 | + e = fast_create(Environment) | |
| 29 | + category1 = fast_create(Category, :name => 'education', :display_color => 'fbfbfb', :environment_id => e.id) | |
| 30 | + category2 = fast_create(Category, :name => 'education', :display_color => nil, :parent_id => category1.id, :environment_id => e.id) | |
| 31 | + assert_equal '', category_color_style(category2) | |
| 32 | + end | |
| 33 | + | |
| 22 | 34 | end | ... | ... |
test/unit/category_test.rb
| ... | ... | @@ -159,36 +159,6 @@ class CategoryTest < ActiveSupport::TestCase |
| 159 | 159 | |
| 160 | 160 | end |
| 161 | 161 | |
| 162 | - should "limit the possibile display colors" do | |
| 163 | - c = build(Category, :name => 'test category', :environment_id => @env.id) | |
| 164 | - | |
| 165 | - c.display_color = 16 | |
| 166 | - c.valid? | |
| 167 | - assert c.errors[:display_color.to_s].present? | |
| 168 | - | |
| 169 | - valid = (1..15).map { |item| item.to_i } | |
| 170 | - valid.each do |item| | |
| 171 | - c.display_color = item | |
| 172 | - c.valid? | |
| 173 | - assert !c.errors[:display_color.to_s].present? | |
| 174 | - end | |
| 175 | - | |
| 176 | - end | |
| 177 | - | |
| 178 | - should 'avoid duplicated display colors' do | |
| 179 | - c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :display_color => 1) | |
| 180 | - | |
| 181 | - c = build(Category, :name => 'lalala', :environment_id => @env.id) | |
| 182 | - c.display_color = 1 | |
| 183 | - assert !c.valid? | |
| 184 | - assert c.errors[:display_color.to_s].present? | |
| 185 | - | |
| 186 | - c.display_color = 2 | |
| 187 | - c.valid? | |
| 188 | - assert !c.errors[:display_color.to_s].present? | |
| 189 | - | |
| 190 | - end | |
| 191 | - | |
| 192 | 162 | should 'be able to get top ancestor' do |
| 193 | 163 | c1 = fast_create(Category, :name => 'test category', :environment_id => @env.id) |
| 194 | 164 | c2 = fast_create(Category, :name => 'test category', :environment_id => @env.id, :parent_id => c1.id) | ... | ... |
test/unit/environment_test.rb
| ... | ... | @@ -156,7 +156,7 @@ class EnvironmentTest < ActiveSupport::TestCase |
| 156 | 156 | |
| 157 | 157 | should 'list displayable categories' do |
| 158 | 158 | env = fast_create(Environment) |
| 159 | - cat1 = create(Category, :environment => env, :name => 'category one', :display_color => 1) | |
| 159 | + cat1 = create(Category, :environment => env, :name => 'category one', :display_color => 'ffa500') | |
| 160 | 160 | assert ! cat1.new_record? |
| 161 | 161 | |
| 162 | 162 | # subcategories should be ignored | ... | ... |