diff --git a/src/extensions/bramble-extensions.json b/src/extensions/bramble-extensions.json
index 4fc74982096..ac5f31d3fc3 100644
--- a/src/extensions/bramble-extensions.json
+++ b/src/extensions/bramble-extensions.json
@@ -69,6 +69,17 @@
"extensions/default/InlineBorderRadiusEditor/BorderRadiusEditorTemplate.html"
]
},
+ {
+ "path": "extensions/default/InlineBoxShadowEditor",
+ "less": {
+ "dist/extensions/default/InlineBoxShadowEditor/css/style.css": [
+ "src/extensions/default/InlineBoxShadowEditor/css/style.less"
+ ]
+ },
+ "copy": [
+ "extensions/default/InlineBoxShadowEditor/BoxShadowEditorTemplate.html"
+ ]
+ },
{
"path": "extensions/default/InlineBoxModelEditor",
"less": {
@@ -218,5 +229,4 @@
]
}
}
-]
-
+]
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/BoxShadowColor.js b/src/extensions/default/InlineBoxShadowEditor/BoxShadowColor.js
new file mode 100644
index 00000000000..4c9b684be7d
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/BoxShadowColor.js
@@ -0,0 +1,101 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ var BoxShadowInput = require("BoxShadowInput").BoxShadowInput,
+ ColorUtils = brackets.getModule("utils/ColorUtils"),
+ tinycolor = require("thirdparty/tinycolor-min");
+
+ var DEFAULT_COLOR = "black";
+
+ function BoxShadowColor(parentRef, $parent, name, value, callback) {
+ BoxShadowInput.call(this, $parent, name);
+
+ this.type = "color";
+ this.color = DEFAULT_COLOR;
+ this._callback = callback.bind(parentRef);
+ this._init(value);
+ };
+
+ BoxShadowColor.prototype = Object.create(BoxShadowInput.prototype);
+ BoxShadowColor.prototype.constructor = BoxShadowColor;
+
+ BoxShadowColor.prototype._init = function(value) {
+ this.setValue(value);
+ this.bindEvents();
+ };
+
+ BoxShadowColor.prototype.setValue = function(value) {
+ var color = this._normalizeColorString(value);
+ this.color = color;
+
+ this._updateView();
+ };
+
+ BoxShadowColor.prototype.bindEvents = function() {
+ var self = this;
+
+ this.$input.bind("input", function() {
+ self._handleColorChange();
+ });
+ };
+
+ BoxShadowColor.prototype._updateView = function() {
+ this.$input.val(this.color);
+ };
+
+ /**
+ * Normalize the given color string into the format used by tinycolor, by adding a space
+ * after commas.
+ * @param {string} color The color to be corrected if it looks like an RGB or HSL color.
+ * @return {string} a normalized color string.
+ */
+ BoxShadowColor.prototype._normalizeColorString = function(color) {
+ if(color.length === 0) {
+ return "";
+ }
+ var normalizedColor = color;
+
+ // Convert 6-digit hex to 3-digit hex as TinyColor (#ffaacc -> #fac)
+ if (color.match(/^#[0-9a-fA-F]{6}/)) {
+ return tinycolor(color).toString();
+ }
+ if (color.match(/^(rgb|hsl)/i)) {
+ normalizedColor = normalizedColor.replace(/,\s*/g, ", ");
+ normalizedColor = normalizedColor.replace(/\(\s+/, "(");
+ normalizedColor = normalizedColor.replace(/\s+\)/, ")");
+ }
+ return normalizedColor;
+ };
+
+ BoxShadowColor.prototype._handleColorChange = function() {
+ var newColor = $.trim(this.$input.val()),
+ newColorObj = tinycolor(newColor),
+ newColorOk = newColorObj.isValid();
+
+ // TinyColor will auto correct an incomplete rgb or hsl value into a valid color value.
+ // eg. rgb(0,0,0 -> rgb(0, 0, 0)
+ // We want to avoid having TinyColor do this, because we don't want to sync the color
+ // to the UI if it's incomplete. To accomplish this, we first normalize the original
+ // color string into the format TinyColor would generate, and then compare it to what
+ // TinyColor actually generates to see if it's different. If so, then we assume the color
+ // was incomplete to begin with.
+ if(newColor.length === 0) {
+ newColorOk = true;
+ }
+ else if (newColorOk) {
+ newColorOk = (newColorObj.toString() === this._normalizeColorString(newColor));
+ }
+
+ this.color = newColor;
+
+ if (newColorOk) {
+ this.$input.css("border", "initial");
+ this._callback(this.type, this.name, this.color);
+ }
+ else {
+ this.$input.css("border", "2px solid red");
+ }
+ };
+
+ exports.BoxShadowColor = BoxShadowColor;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/BoxShadowEditor.js b/src/extensions/default/InlineBoxShadowEditor/BoxShadowEditor.js
new file mode 100644
index 00000000000..f870017b490
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/BoxShadowEditor.js
@@ -0,0 +1,143 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ var KeyEvent = brackets.getModule("utils/KeyEvent"),
+ PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
+ StringUtils = brackets.getModule("utils/StringUtils"),
+ Strings = brackets.getModule("strings"),
+ Mustache = brackets.getModule("thirdparty/mustache/mustache"),
+ BoxShadowLength = require("BoxShadowLength").BoxShadowLength,
+ BoxShadowColor = require("BoxShadowColor").BoxShadowColor,
+ BoxShadowInset = require("BoxShadowInset").BoxShadowInset,
+ BoxShadowUtils = require("BoxShadowUtils");
+
+ /** Mustache template that forms the bare DOM structure of the UI */
+ var BoxShadowEditorTemplate = require("text!BoxShadowEditorTemplate.html");
+
+ /**
+ * Box shadow editor control; may be used standalone or within an InlineBoxShadowEditor inline widget.
+ * @param {!jQuery} $parent DOM node into which to append the root of the box-shadow editor UI
+ * @param {!{h-shadow: string, v-shadow: string, blur: string, spread: string, color: string}} values Initial set of box-shadow values.
+ * @param {!function(string)} callback Called whenever values change
+ */
+ function BoxShadowEditor($parent, values, callback) {
+ // Create the DOM structure, filling in localized strings via Mustache
+ this.$element = $(Mustache.render(BoxShadowEditorTemplate, Strings));
+ $parent.append(this.$element);
+
+ this._callback = callback;
+ this._values = values;
+ this._originalValues = values;
+ this._redoValues = null;
+
+ // Get references
+ this._initializeInputs(values);
+ }
+
+ /**
+ * A object representing the current set of box-shadow values
+ * @type {}
+ */
+ BoxShadowEditor.prototype._values = null;
+
+ /**
+ * box shadow values that was selected before undo(), if undo was the last change made. Else null.
+ * @type {?string}
+ */
+ BoxShadowEditor.prototype._redoValues = null;
+
+ /**
+ * Initial value the BoxShadow picker was opened with
+ * @type {!string}
+ */
+ BoxShadowEditor.prototype._originalValues = null;
+
+
+ /** Returns the root DOM node of the BoxShadowPicker UI */
+ BoxShadowEditor.prototype.getRootElement = function () {
+ return this.$element;
+ };
+
+ BoxShadowEditor.prototype.setValues = function(values) {
+ this.hShadow.setValue(values.lengths["h-shadow"]);
+ this.vShadow.setValue(values.lengths["v-shadow"]);
+ this.blur.setValue(values.lengths["blur"]);
+ this.spread.setValue(values.lengths["spread"]);
+ this.color.setValue(values["color"]);
+ this.inset.setValue(values["inset"]);
+ };
+
+ BoxShadowEditor.prototype._initializeInputs = function(values) {
+ this.hShadow = new BoxShadowLength(this, this.$element, "h-shadow", values.lengths["h-shadow"], this.handleChanges);
+
+ this.vShadow = new BoxShadowLength(this, this.$element, "v-shadow", values.lengths["v-shadow"], this.handleChanges);
+
+ this.blur = new BoxShadowLength(this, this.$element, "blur", values.lengths["blur"], this.handleChanges);
+
+ this.spread = new BoxShadowLength(this, this.$element, "spread", values.lengths["spread"], this.handleChanges);
+
+ this.color = new BoxShadowColor(this, this.$element, "color", values["color"], this.handleChanges);
+
+ this.inset = new BoxShadowInset(this, this.$element, "inset", values["inset"], this.handleChanges);
+ };
+
+ BoxShadowEditor.prototype.focus = function() {
+ this.hShadow.focus();
+ };
+
+ BoxShadowEditor.prototype.destroy = function() {
+ };
+
+ BoxShadowEditor.prototype.getValues = function() {
+ return this._values;
+ };
+
+ // Utilty function to check if data is of correct format.
+ function _isValidNumber(data) {
+ return (data.match(/\-?\d*/) !== null);
+ };
+
+ BoxShadowEditor.prototype.handleChanges = function(type, name, value) {
+ if(type === "lengths") {
+ this._values.lengths[name] = value;
+ }
+ else if(type === "color") {
+ this._values[name] = value;
+ }
+ else if(type === "inset") {
+ this._values[name] = value;
+ }
+
+ this._callback(this._values);
+ };
+
+ BoxShadowEditor.prototype._undo = function() {
+ };
+
+ BoxShadowEditor.prototype._redo = function() {
+ };
+
+ /**
+ * Global handler for keys in the color editor. Catches undo/redo keys and traps
+ * arrow keys that would be handled by the scroller.
+ */
+ BoxShadowEditor.prototype._handleKeydown = function (event) {
+ var hasCtrl = (brackets.platform === "win") ? (event.ctrlKey) : (event.metaKey);
+ if (hasCtrl) {
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_Z:
+ if (event.shiftKey) {
+ this.redo();
+ } else {
+ this.undo();
+ }
+ return false;
+ case KeyEvent.DOM_VK_Y:
+ this.redo();
+ return false;
+ }
+ }
+ };
+
+ exports.BoxShadowEditor = BoxShadowEditor;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/BoxShadowEditorTemplate.html b/src/extensions/default/InlineBoxShadowEditor/BoxShadowEditorTemplate.html
new file mode 100644
index 00000000000..93468a7c809
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/BoxShadowEditorTemplate.html
@@ -0,0 +1,125 @@
+
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/BoxShadowInput.js b/src/extensions/default/InlineBoxShadowEditor/BoxShadowInput.js
new file mode 100644
index 00000000000..ef20b488cce
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/BoxShadowInput.js
@@ -0,0 +1,40 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ function BoxShadowInput($parent, name) {
+ this.name = name;
+ this.$parent = $parent;
+ this.$el = $parent.find("#" + this.name);
+ this.$input = this.$el.find("input");
+ };
+
+ /**
+ * @Override
+ * Method to handle input events.
+ */
+ BoxShadowInput.prototype.handleInput = function() {
+ };
+
+ /**
+ * @Override
+ * Sets the value to the input
+ */
+ BoxShadowInput.prototype.setValue = function(string) {
+ };
+
+ /**
+ * Get value method
+ * @Override
+ */
+ BoxShadowInput.prototype.getValue = function() {
+ };
+
+ /**
+ * Set focus to the input element
+ */
+ BoxShadowInput.prototype.focus = function() {
+ this.$input.focus();
+ };
+
+ exports.BoxShadowInput = BoxShadowInput;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/BoxShadowInset.js b/src/extensions/default/InlineBoxShadowEditor/BoxShadowInset.js
new file mode 100644
index 00000000000..6225f6e5b0b
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/BoxShadowInset.js
@@ -0,0 +1,74 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ var BoxShadowInput = require("BoxShadowInput").BoxShadowInput,
+ BoxShadowUtils = require("BoxShadowUtils");
+
+ var units = BoxShadowUtils.UNITS;
+
+ function BoxShadowInset(parentRef, $parent, name, value, callback) {
+ BoxShadowInput.call(this, $parent, name);
+
+ this.type = "inset";
+ this.value = false;
+
+ this._callback = callback.bind(parentRef);
+ this._init(value);
+ };
+
+ BoxShadowInset.prototype = Object.create(BoxShadowInput.prototype);
+ BoxShadowInset.prototype.constructor = BoxShadowInset;
+
+ /**
+ * Takes care of initialization stuff. !!Improve this comment.
+ */
+ BoxShadowInset.prototype._init = function(value) {
+ this.setValue(value);
+ this.bindEvents();
+ };
+
+ BoxShadowInset.prototype.bindEvents = function() {
+ var self = this;
+ self.$input.bind("change", function() {
+ self.handleInput();
+ });
+ };
+
+ /**
+
+ * @Override
+ * Method to handle input events.
+ */
+ BoxShadowInset.prototype.handleInput = function(value) {
+ if(this.$input.is(":checked") === true) {
+ this.value = true;
+ }
+ else {
+ this.value = false;
+ }
+ this._callback(this.type, this.name, this.getValue());
+ };
+
+ /**
+ * @Override
+ * Sets the value to the input
+ */
+ BoxShadowInset.prototype.setValue = function(value) {
+ this.value = value;
+ this._updateView();
+ };
+
+ /**
+ * Get value method
+ * @Override
+ */
+ BoxShadowInset.prototype.getValue = function() {
+ return this.value;
+ };
+
+ BoxShadowInset.prototype._updateView = function() {
+ this.$input.prop("checked", this.value);
+ };
+
+ exports.BoxShadowInset = BoxShadowInset;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/BoxShadowLength.js b/src/extensions/default/InlineBoxShadowEditor/BoxShadowLength.js
new file mode 100644
index 00000000000..6c0776893b2
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/BoxShadowLength.js
@@ -0,0 +1,113 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ var BoxShadowInput = require("BoxShadowInput").BoxShadowInput,
+ BoxShadowUtils = require("BoxShadowUtils");
+
+ var units = BoxShadowUtils.UNITS;
+
+ function BoxShadowLength(parentRef, $parent, name, value, callback) {
+ BoxShadowInput.call(this, $parent, name);
+
+ this.type = "lengths";
+ this.$units = this.$el.find(".units-container li");
+ this.$value = this.$el.find(".slider-value");
+ this.num = 0;
+ this.unit = "px";
+ this._callback = callback.bind(parentRef);
+ this._init(value);
+ };
+
+ BoxShadowLength.prototype = Object.create(BoxShadowInput.prototype);
+ BoxShadowLength.prototype.constructor = BoxShadowLength;
+
+ /**
+ * Takes care of initialization stuff. !!Improve this comment.
+ */
+ BoxShadowLength.prototype._init = function(value) {
+ this.setValue(value);
+ this.bindEvents();
+ };
+
+ BoxShadowLength.prototype.bindEvents = function() {
+ var self = this;
+ self.$input.bind("input", function() {
+ self.handleInput();
+ });
+
+ self.$units.bind("click", function() {
+ self.handleUnitChange(this);
+ });
+ };
+
+ /**
+
+ * @Override
+ * Method to handle input events.
+ */
+ BoxShadowLength.prototype.handleInput = function() {
+ var value = this.$input.val().trim();
+ this.num = value;
+ this._updateView();
+ this._callback(this.type, this.name, this.getValue());
+ };
+
+ BoxShadowLength.prototype.handleUnitChange = function(target) {
+ this.$units.removeClass("selected");
+
+ var newUnit = $(target).attr("class").split(" ")[0];
+ $(target).addClass("selected");
+
+ this.unit = newUnit;
+ this._updateView();
+ this._callback(this.type, this.name, this.getValue());
+ };
+
+ /**
+ * @Override
+ * Sets the value to the input
+ */
+ BoxShadowLength.prototype.setValue = function(value) {
+
+ if(value) {
+ var num, unit, matches, lengthRegex;
+ lengthRegex = new RegExp(BoxShadowUtils.LENGTH_REGEX);
+ matches = lengthRegex.exec(value);
+ this.num = parseInt(matches[1]);
+ this.unit = matches[2];
+ }
+ else {
+ this.num = 0;
+ this.unit = "px";
+ }
+
+ this._updateView();
+ };
+
+ /**
+ * Get value method
+ * @Override
+ */
+ BoxShadowLength.prototype.getValue = function() {
+ var result;
+ result = "".concat(this.num, this.unit);
+ return result;
+ };
+
+ BoxShadowLength.prototype._updateView = function() {
+ this.$input.val(this.num);
+
+ this.$value.text(this.getValue());
+
+ this.$units.removeClass("selected");
+
+ var targetClass = this.unit;
+ this.$units.each(function() {
+ if($(this).hasClass(targetClass)) {
+ $(this).addClass("selected");
+ }
+ });
+ };
+
+ exports.BoxShadowLength = BoxShadowLength;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/BoxShadowUtils.js b/src/extensions/default/InlineBoxShadowEditor/BoxShadowUtils.js
new file mode 100644
index 00000000000..d488d42c0d7
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/BoxShadowUtils.js
@@ -0,0 +1,22 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ var DEFAULT_BOX_SHADOW_VALUE = " 0px 0px 0px 0px black";
+
+ var DEFAULT_ORDER_OF_VALUES = ["lengths", "color", "inset"];
+
+ var BOX_SHADOW_REGEX = /((-?\d+)(px|em|%)\s+){1,3}?((-?\d+)(px|em|%))(\s+([a-z]+))?(\s+inset)?|(inset\s+)?((-?\d+)(px|em|%)\s+){1,3}?((-?\d+)(px|em|%))(\s+([a-z]+))?|(([a-z]+)\s+)?((-?\d+)(px|em|%)\s+){1,3}?((-?\d+)(px|em|%))(\s+inset)?|(inset\s+)?(([a-z]+)\s+)?((-?\d+)(px|em|%)\s+){1,3}?((-?\d+)(px|em|%))/g;
+
+ var LENGTH_REGEX = /(?:(-?\d+)(px|em|%))/g;
+
+ var LENGTH_TYPES = ["h-shadow", "v-shadow", "blur", "spread"];
+
+ var UNITS = ["px", "em", "%"];
+
+ exports.DEFAULT_BOX_SHADOW_VALUE = DEFAULT_BOX_SHADOW_VALUE;
+ exports.DEFAULT_ORDER_OF_VALUES = DEFAULT_ORDER_OF_VALUES;
+ exports.BOX_SHADOW_REGEX = BOX_SHADOW_REGEX;
+ exports.LENGTH_REGEX = LENGTH_REGEX;
+ exports.LENGTH_TYPES = LENGTH_TYPES;
+ exports.UNITS = UNITS;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/InlineBoxShadowEditor.js b/src/extensions/default/InlineBoxShadowEditor/InlineBoxShadowEditor.js
new file mode 100644
index 00000000000..7369bae1663
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/InlineBoxShadowEditor.js
@@ -0,0 +1,309 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ var InlineWidget = brackets.getModule("editor/InlineWidget").InlineWidget,
+ BoxShadowEditor = require("BoxShadowEditor").BoxShadowEditor,
+ ColorUtils = brackets.getModule("utils/ColorUtils"),
+ BoxShadowUtils = require("BoxShadowUtils");
+
+ /** @type {number} Global var used to provide a unique ID for each box-shadow editor instance's _origin field. */
+ var lastOriginId = 1;
+
+ var defaultOrder = BoxShadowUtils.DEFAULT_ORDER_OF_VALUES;
+
+ /**
+ * Inline widget containing a BoxShadowEditor control
+ * The widget responds to changes in the text editor to the box-shadow string. Also, it is responsible for propagating changes
+ * from the box-shadow editor GUI to the text editor.
+ * @param {!String} boxShadowString Initial box-shadow string.
+ * @param {!CodeMirror.TextMarker} marker
+ */
+ function InlineBoxShadowEditor(boxShadowString, marker) {
+ this._boxShadowString = boxShadowString;
+ this._marker = marker;
+ this._isOwnChange = false;
+ this._isHostChange = false;
+ this._origin = "+InlineBoxShadowEditor_" + (lastOriginId++);
+
+ this._values = {};
+ // orderOfValues can be either "lengths", "color" or "inset"
+ this._orderOfValues = [];
+ this._extractValues();
+
+ this._handleBoxShadowChange = this._handleBoxShadowChange.bind(this);
+ this._handleHostDocumentChange = this._handleHostDocumentChange.bind(this);
+
+ InlineWidget.call(this);
+ }
+
+ InlineBoxShadowEditor.prototype = Object.create(InlineWidget.prototype);
+ InlineBoxShadowEditor.prototype.constructor = InlineBoxShadowEditor;
+ InlineBoxShadowEditor.prototype.parentClass = InlineWidget.prototype;
+
+ /** @type {!BoxShadowPicker} BoxShadowPicker instance */
+ InlineBoxShadowEditor.prototype.BoxShadowEditor = null;
+
+ /** @type {!String} Current value of the box-shadow editor control */
+ InlineBoxShadowEditor.prototype._values = null;
+
+ /**
+ * Range of code we're attached to; _marker.find() may by null if sync is lost.
+ * @type {!CodeMirror.TextMarker}
+ */
+ InlineBoxShadowEditor.prototype._marker = null;
+
+ /** @type {boolean} True while we're syncing a BoxShadow editor change into the code editor */
+ InlineBoxShadowEditor.prototype._isOwnChange = null;
+
+ /** @type {boolean} True while we're syncing a code editor change into the BoxShadow editor */
+ InlineBoxShadowEditor.prototype._isHostChange = null;
+
+ /** @type {number} ID used to identify edits coming from this inline widget for undo batching */
+ InlineBoxShadowEditor.prototype._origin = null;
+
+ /**
+ * Returns the current text range of the box-shadow value we're attached to, or null if
+ * we've lost sync with what's in the code.
+ * @return {?{start:{line:number, ch:number}, end:{line:number, ch:number}}}
+ */
+ InlineBoxShadowEditor.prototype.getCurrentRange = function () {
+ var pos, start, end, line;
+
+ pos = this._marker && this._marker.find();
+
+ start = pos && pos.from;
+ if (!start) {
+ return null;
+ }
+
+ end = pos.to;
+
+ line = this.hostEditor.document.getLine(start.line);
+
+ start.ch = line.indexOf(':') + 1;
+ while(line[start.ch] === ' ') {
+ start.ch++;
+ }
+
+ end.line = start.line;
+ end.ch = line.indexOf(';');
+
+ if(end.ch === -1) {
+ end.ch = line.length;
+ while(end.ch > start.ch && line[end.ch] !== ';') {
+ end.ch--;
+ }
+ }
+
+ if (end.ch === undefined) {
+ // We were unable to resync the marker.
+ return null;
+ }
+ else {
+ return {start: start, end: end};
+ }
+ };
+
+ InlineBoxShadowEditor.prototype._extractValues = function() {
+ var self = this;
+
+ if(_isValidBoxShadowValue(this._boxShadowString) === false) {
+ return;
+ }
+
+ var values = this._boxShadowString.trim().split(" ");
+
+ var lengthTypes, lengthTypesIter, lengthType;
+ lengthTypes = BoxShadowUtils.LENGTH_TYPES;
+ lengthTypesIter = 0;
+
+ // Default case of box-shadows.
+ this._values.inset = false;
+ this._values.color = "";
+ this._values.lengths = {};
+ this._orderOfValues = [];
+ this._values = values.reduce(function(accumulator, currentValue, currentIndex) {
+ currentValue = currentValue.trim();
+
+ // Check for inset
+ if(currentValue === "inset") {
+ self._orderOfValues.push("inset");
+ accumulator.inset = true;
+ }
+ // Check for color
+ else if(currentValue.match(ColorUtils.COLOR_REGEX)) {
+ self._orderOfValues.push("color");
+ accumulator.color = currentValue;
+ }
+ // Check for a length
+ else if(currentValue.match(BoxShadowUtils.LENGTH_REGEX)) {
+ if(lengthTypesIter < lengthTypes.length) {
+ if(self._orderOfValues.includes("lengths") === false) {
+ self._orderOfValues.push("lengths");
+ }
+ lengthType = lengthTypes[lengthTypesIter++];
+ accumulator.lengths[lengthType] = currentValue;
+ }
+ else {
+ console.error("InlineBoxShadowEditor:: Too many length values for box-shadow.");
+ }
+ }
+
+ return accumulator;
+ }, this._values);
+
+ for(var i = 0;i < defaultOrder.length;i++) {
+ var orderValue = defaultOrder[i];
+ if(this._orderOfValues.includes(orderValue) === false) {
+ this._orderOfValues.push(orderValue);
+ }
+ }
+ };
+
+ InlineBoxShadowEditor.prototype._buildBoxShadowString = function () {
+ var self = this;
+
+ var boxShadowArr = [];
+
+ for(var i = 0;i < this._orderOfValues.length;i++) {
+ var orderValue = this._orderOfValues[i];
+ if(orderValue === "lengths") {
+ var lengthTypes, boxShadowArr, boxShadowString;
+ lengthTypes = BoxShadowUtils.LENGTH_TYPES;
+
+ boxShadowArr = lengthTypes.reduce(function(accumulator, currentValue){
+ accumulator.push(self._values.lengths[currentValue]);
+ return accumulator;
+ }, boxShadowArr);
+ }
+ else if(orderValue === "color" && self._values.color) {
+ boxShadowArr.push(self._values.color);
+ }
+ else if(orderValue === "inset" && self._values.inset === true) {
+ boxShadowArr.push("inset");
+ }
+ }
+
+ // Filter out undefined values.
+ boxShadowArr = boxShadowArr.filter(function(currentValue) {
+ return currentValue;
+ });
+
+ boxShadowString = boxShadowArr.join(" ");
+ return boxShadowString;
+ };
+
+ /**
+ * When the BoxShadow editor's values change, update text in code editor
+ * @param {!{horizontalOffset: Number, verticalOffset: Number, blurRadius: Number, spreadRadius: Number, color: string}} values New set of box-shadow values.
+ */
+ InlineBoxShadowEditor.prototype._handleBoxShadowChange = function (values) {
+ var self = this;
+ var range = this.getCurrentRange();
+ if (!range) {
+ return;
+ }
+
+ this._values = values;
+
+ // build the box-shadow value as a string.
+ var boxShadowString = this._buildBoxShadowString();
+
+ var endPos = {
+ line: range.start.line,
+ ch: range.start.ch + boxShadowString.length
+ };
+
+ this._isOwnChange = true;
+ this.hostEditor.document.batchOperation(function () {
+ // Replace old box-shadow in code with the editor's box-shadow values, and select it
+ self.hostEditor.setSelection(range.start, range.end);
+ self.hostEditor.document.replaceRange(boxShadowString, range.start, range.end, self._origin);
+ self.hostEditor.setSelection(range.start, endPos);
+ if (self._marker) {
+ self._marker.clear();
+ self._marker = self.hostEditor._codeMirror.markText(range.start, endPos);
+ }
+ });
+ this._isOwnChange = false;
+ };
+
+ /**
+ * @override
+ * @param {!Editor} hostEditor
+ */
+ InlineBoxShadowEditor.prototype.load = function (hostEditor) {
+ InlineBoxShadowEditor.prototype.parentClass.load.apply(this, arguments);
+
+ // Create BoxShadow picker control
+ this.boxShadowEditor = new BoxShadowEditor(this.$htmlContent, this._values, this._handleBoxShadowChange);
+ };
+
+ /**
+ * @override
+ * Perform sizing & focus once we've been added to Editor's DOM
+ */
+ InlineBoxShadowEditor.prototype.onAdded = function () {
+ InlineBoxShadowEditor.prototype.parentClass.onAdded.apply(this, arguments);
+
+ var doc = this.hostEditor.document;
+ doc.addRef();
+ doc.on("change", this._handleHostDocumentChange);
+
+ this.hostEditor.setInlineWidgetHeight(this, this.boxShadowEditor.getRootElement().outerHeight(), true);
+
+ this.boxShadowEditor.focus();
+ };
+
+ /**
+ * @override
+ * Called whenever the inline widget is closed, whether automatically or explicitly
+ */
+ InlineBoxShadowEditor.prototype.onClosed = function () {
+ InlineBoxShadowEditor.prototype.parentClass.onClosed.apply(this, arguments);
+
+ if (this._marker) {
+ this._marker.clear();
+ }
+
+ var doc = this.hostEditor.document;
+ doc.off("change", this._handleHostDocumentChange);
+ doc.releaseRef();
+ this.boxShadowEditor.destroy();
+ };
+
+ function _isValidBoxShadowValue(value) {
+ var boxShadowRegex = new RegExp(BoxShadowUtils.BOX_SHADOW_REGEX);
+ return boxShadowRegex.test(value);
+ }
+
+ /**
+ * When text in the code editor changes, update quick edit to reflect it
+ */
+ InlineBoxShadowEditor.prototype._handleHostDocumentChange = function () {
+ // Don't push the change into the box-shadow editor if it came from the box-shadow editor.
+ if (this._isOwnChange) {
+ return;
+ }
+
+ var range = this.getCurrentRange();
+ if (range) {
+ var newString = this.hostEditor.document.getRange(range.start, range.end);
+ if(_isValidBoxShadowValue(newString)) {
+ // extract values
+ this._boxShadowString = newString;
+ this._extractValues();
+ this._isHostChange = true;
+ this.boxShadowEditor.setValues(this._values);
+ this._isHostChange = false;
+ }
+
+ }
+ else {
+ // The edit caused our range to become invalid. Close the editor.
+ this.close();
+ }
+ };
+
+ exports.InlineBoxShadowEditor = InlineBoxShadowEditor;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/css/style.less b/src/extensions/default/InlineBoxShadowEditor/css/style.less
new file mode 100644
index 00000000000..ca4cbbc6cbf
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/css/style.less
@@ -0,0 +1,411 @@
+@sansFontFamily: Helvetica, Arial, "Meiryo UI", "MS Pゴシック", "MS PGothic", sans-serif;
+
+.box-shadow-editor {
+ padding: 18px 16px 0 16px;
+ font-family: @sansFontFamily;
+ min-height: 220px;
+
+ &:focus {
+ outline: none;
+ }
+
+ header {
+ margin-bottom: 5px;
+ padding-bottom: 10px;
+ font-size: 14px;
+ }
+
+ input {
+ margin: 0;
+ vertical-align: middle;
+ box-shadow: none;
+ width: 100%;
+
+ &:focus {
+ outline: none;
+ }
+ }
+}
+
+.box-shadow-editor table {
+ table-layout: fixed;
+ width: 100%;
+
+ tr:hover {
+ td {
+ background: rgba(0,0,0,.07);
+ }
+
+ .corner-icon:after {
+ animation: pulse .3s ease-in-out infinite;
+
+ @keyframes pulse {
+ 50% {
+ transform: scale(1.25);
+ }
+ }
+ }
+ }
+
+ td {
+ padding: 10px;
+ border: none;
+ vertical-align: middle;
+ margin: 0;
+ font-size: 13px;
+ white-space: nowrap;
+
+ &:first-child {
+ border-radius: 24px 0 0 24px;
+ }
+
+ &:last-child {
+ border-radius: 0 24px 24px 0;
+ }
+ }
+
+ td.slider-value {
+ width: 50px;
+ }
+
+ td .button-bar {
+ position: relative;
+ top: -1px;
+
+ li.selected a {
+ background-color: rgba(0,0,0,.4);
+ }
+ }
+
+ .label-container {
+ width: 80px;
+ position: relative;
+ }
+
+ .units-container {
+ padding-left: 0;
+ width: 120px;
+
+ .button-bar {
+ li {
+ a {
+ padding: 5px 6px 6px 6px;
+ min-width: 18px;
+ }
+ }
+ }
+ }
+
+ td label {
+ margin: 0;
+ cursor: default;
+ }
+}
+
+// Dark theme for the corner Icons
+.dark .box-shadow-editor .corner-icon {
+ border: solid 2px rgba(255,255,255,.2);
+
+ &:not(.all):after {
+ background: white;
+ }
+
+ &.all .dot {
+ background: white;
+ }
+}
+
+.dark .box-shadow-editor table {
+ td .button-bar li.selected a {
+ background-color: #404040;
+ }
+
+ tr:hover {
+ td {
+ background: rgba(255,255,255,.08);
+ }
+ }
+
+}
+
+.box-shadow-editor ul.button-bar {
+ display: inline-block;
+ padding: 0;
+ margin: 0;
+
+ li {
+ display: inline-block;
+ text-align: center;
+ }
+
+ a {
+ text-decoration: none;
+ color: rgba(0,0,0,.4);
+ cursor: default;
+ display: block;
+ padding: 5px 12px 6px 12px;
+ border-radius: 14px;
+ transition: all .1s ease-out;
+
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ li:not(.selected) a {
+ &:hover {
+ color: rgba(0,0,0,.8);
+ background: rgba(0,0,0,.1);
+ }
+ }
+}
+
+.dark .box-shadow-editor ul.button-bar {
+ a {
+ color: rgba(255,255,255,.4);
+ }
+
+ li:not(.selected) a {
+ &:hover {
+ color: rgba(255,255,255,.8);
+ background: rgba(0,0,0,.4);
+ }
+ }
+}
+
+.box-shadow-editor .button-bar a:focus,
+.box-shadow-editor .button-bar li.selected a:focus {
+ outline: none;
+ position: relative;
+ z-index: 999;
+}
+
+
+.box-shadow-editor .button-bar li.selected a {
+ background-color: #2492ba;
+ color: white;
+}
+
+// Dark UI theme
+
+@dark-bc-bg-highlight: #2a3b50;
+@dark-bc-highlight: rgba(255, 255, 255, 0.06);
+@dark-bc-text: #ccc;
+@dark-bc-text-alt: #fff;
+@dark-bc-highlight-hard: rgba(255, 255, 255, 0.2);
+@dark-bc-btn-bg: #3f3f3f;
+@dark-bc-btn-border: #202020;
+@dark-bc-btn-border-focused: #2893ef;
+@dark-bc-btn-border-focused-glow: transparent;
+@dark-bc-shadow: rgba(0, 0, 0, 0.24);
+@dark-bc-shadow-small: rgba(0, 0, 0, 0.06);
+@dark-bc-shadow-medium: rgba(0, 0, 0, 0.12);
+@dark-bc-input-bg: #555;
+
+.dark {
+ .box-shadow-editor section footer .box-shadow-value {
+ border-box-shadow: @dark-bc-btn-border;
+ box-shadow: @dark-bc-text;
+ }
+
+ .box-shadow-editor section footer input {
+ border: 1px solid @dark-bc-btn-border;
+ box-shadow: inset 0 1px 0 @dark-bc-shadow-small;
+ background: @dark-bc-input-bg;
+ box-shadow: @dark-bc-text;
+ }
+
+ .box-shadow-editor section footer input:focus {
+ background: @dark-bc-input-bg;
+ box-shadow: 0 0 0 1px @dark-bc-btn-border-focused-glow;
+ border: 1px solid @dark-bc-btn-border-focused;
+ }
+}
+
+// Styles for custom Slider
+
+@track-color-dark: #444;
+@track-color: rgba(0,0,0,.2);
+@thumb-color: white;
+
+@thumb-radius: 12px;
+@thumb-height: 24px;
+@thumb-width: 24px;
+@thumb-shadow-size: 1px;
+@thumb-shadow-blur: 1px;
+@thumb-shadow-color: #111;
+@thumb-border-width: 1px;
+@thumb-border-color: white;
+
+@track-width: 100%;
+@track-height: 6px;
+@track-shadow-size: 2px;
+@track-shadow-blur: 2px;
+@track-shadow-color: #222;
+@track-border-width: 1px;
+@track-border-color: black;
+
+@track-radius: 5px;
+@contrast: 5%;
+
+@toggle-width: 48px;
+
+.track() {
+ width: @track-width;
+ height: @track-height;
+ cursor: pointer;
+ animate: 0.2s;
+}
+
+.thumb() {
+ height: @thumb-height;
+ width: @thumb-width;
+ border-radius: @thumb-radius;
+ background: @thumb-color;
+ cursor: pointer;
+}
+
+.box-shadow-editor input[type=range] {
+ -webkit-appearance: none;
+ margin: @thumb-height/2 0;
+ width: @track-width;
+ background-color: transparent;
+
+ &:focus {
+ outline: none;
+ }
+
+ &::-webkit-slider-runnable-track {
+ .track();
+ background: @track-color;
+ border-radius: @track-radius;
+ }
+
+ &::-webkit-slider-thumb {
+ .thumb();
+ -webkit-appearance: none;
+ margin-top: ((-@track-border-width * 2 + @track-height) / 2) - (@thumb-height / 2);
+ }
+
+ &:focus::-webkit-slider-runnable-track {
+ background: lighten(@track-color, @contrast);
+ }
+
+ &::-moz-range-track {
+ .track();
+ background: @track-color;
+ border-radius: @track-radius;
+ border: @track-border-width solid @track-border-color;
+ }
+ &::-moz-range-thumb {
+ .thumb();
+ }
+
+ &::-ms-track {
+ .track();
+ background: transparent;
+ border-color: transparent;
+ border-width: @thumb-width 0;
+ color: transparent;
+ }
+
+ &::-ms-fill-lower {
+ background: darken(@track-color, @contrast);
+ border: @track-border-width solid @track-border-color;
+ border-radius: @track-radius*2;
+ }
+ &::-ms-fill-upper {
+ background: @track-color;
+ border: @track-border-width solid @track-border-color;
+ border-radius: @track-radius*2;
+ }
+ &::-ms-thumb {
+ .thumb();
+ }
+ &:focus::-ms-fill-lower {
+ background: @track-color;
+ }
+ &:focus::-ms-fill-upper {
+ background: lighten(@track-color, @contrast);
+ }
+}
+
+.box-shadow-editor .toggle-switch-container {
+ .toggle-switch {
+ position: relative;
+ display: inline-block;
+ width: @toggle-width;
+ height: @thumb-height;
+ }
+
+ input {
+ display: none;
+ }
+
+ .toggle-switch-slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ border-radius: 24px;
+ -webkit-transition: .4s;
+ transition: .4s;
+ }
+
+ .toggle-switch-slider:before {
+ position: absolute;
+ content: "";
+ height: @thumb-height - 4px;
+ width: @thumb-width - 4px;
+ top: 2px;
+ left: 2px;
+ background-color: white;
+ border-radius: 50%;
+ -webkit-transition: .4s;
+ transition: .4s;
+ }
+
+ input:checked + .toggle-switch-slider {
+ background-color: #2196F3;
+ }
+
+ input:focus + .toggle-switch-slider {
+ box-shadow: 0 0 1px #2196F3;
+ }
+
+ input:checked + .toggle-switch-slider:before {
+ -webkit-transform: translateX(@thumb-width);
+ -ms-transform: translateX(@thumb-width);
+ transform: translateX(@thumb-width);
+ }
+}
+
+.dark .box-shadow-editor input[type=range] {
+
+ &::-webkit-slider-runnable-track {
+ background: @track-color-dark;
+ }
+
+
+ &::-moz-range-track {
+ background: @track-color-dark;
+ }
+
+
+ &::-ms-fill-lower {
+ background: darken(@track-color-dark, @contrast);
+ }
+ &::-ms-fill-upper {
+ background: @track-color-dark;
+ }
+ &:focus::-ms-fill-lower {
+ background: @track-color-dark;
+ }
+ &:focus::-ms-fill-upper {
+ background: lighten(@track-color-dark, @contrast);
+ }
+}
diff --git a/src/extensions/default/InlineBoxShadowEditor/main.js b/src/extensions/default/InlineBoxShadowEditor/main.js
new file mode 100644
index 00000000000..ac96f0b1e29
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/main.js
@@ -0,0 +1,130 @@
+define(function(require, exports, module) {
+ "use strict";
+
+ var EditorManager = brackets.getModule("editor/EditorManager"),
+ ExtensionUtils = brackets.getModule("utils/ExtensionUtils"),
+ InlineBoxShadowEditor = require("InlineBoxShadowEditor").InlineBoxShadowEditor,
+ ColorUtils = brackets.getModule("utils/ColorUtils"),
+ BoxShadowUtils = require("BoxShadowUtils");
+
+ /**
+ * Prepare hostEditor for an InlineBoxShadowEditor at pos if possible. Return
+ * editor context if so; otherwise null.
+ *
+ * @param {Editor} hostEditor
+ * @param {{line:Number, ch:Number}} pos
+ * @return {?{values:{}, marker:TextMarker}}
+ */
+ function prepareEditorForProvider(hostEditor, pos) {
+ if(queryInlineBoxShadowEditorProvider(hostEditor, pos) === false) {
+ return null;
+ }
+
+ var cursorLine, semiColonPos, colonPos, endPos, cursorLineSubstring, marker, boxShadowString, isEmptyString, pos, firstCharacterPos, endPos,
+ boxShadowRegex;
+
+ boxShadowRegex = new RegExp(BoxShadowUtils.BOX_SHADOW_REGEX);
+
+ cursorLine = hostEditor.document.getLine(pos.line);
+ colonPos = cursorLine.indexOf(":");
+ semiColonPos = cursorLine.indexOf(";");
+ if(semiColonPos !== -1) {
+ endPos = semiColonPos;
+ }
+ else {
+ endPos = cursorLine.length;
+ }
+
+ cursorLineSubstring = cursorLine.substring(colonPos + 1, endPos).trim();
+
+ // Get the initial set of values of box-shadow property
+ isEmptyString = false;
+
+ if(cursorLineSubstring.length === 0) {
+ isEmptyString = true;
+ boxShadowString = BoxShadowUtils.DEFAULT_BOX_SHADOW_VALUE;
+ }
+ else {
+ if(boxShadowRegex.test(cursorLineSubstring) === true) {
+ boxShadowString = cursorLineSubstring;
+ }
+ else {
+ return null;
+ }
+ }
+
+ if(isEmptyString) {
+ //Edit a new css rule.
+ var from ,to, newText;
+ from = {line: pos.line, ch: colonPos + 1};
+ to = {line: pos.line, ch: cursorLine.length};
+ newText = boxShadowString.concat(";");
+ hostEditor._codeMirror.replaceRange(newText, from, to);
+ pos.ch = colonPos + 2;
+ endPos = {line: pos.line, ch: pos.ch + boxShadowString.length};
+ }
+ else {
+ firstCharacterPos = cursorLineSubstring.search(/\S/);
+ pos.ch = colonPos + 1 + Math.min(firstCharacterPos,1);
+ if (semiColonPos !== -1) {
+ endPos = {line: pos.line, ch: semiColonPos};
+ } else {
+ endPos = {line: pos.line, ch: cursorLine.length};
+ }
+ }
+
+ marker = hostEditor._codeMirror.markText(pos, endPos);
+ hostEditor.setSelection(pos, endPos);
+
+ return {
+ string: boxShadowString,
+ marker: marker
+ };
+ }
+
+ /**
+ * Registered as an inline editor provider: creates an InlineBoxShadowEditor when the cursor
+ * is on a line containing box-shadow property (in any flavor of code).
+ *
+ * @param {!Editor} hostEditor
+ * @param {!{line:Number, ch:Number}} pos
+ * @return {?$.Promise} synchronously resolved with an InlineWidget, or null if there's
+ * no box-shadow at pos.
+ */
+ function inlineBoxShadowEditorProvider(hostEditor, pos) {
+ var context = prepareEditorForProvider(hostEditor, pos),
+ inlineBoxShadowEditor,
+ result;
+
+ if(!context) {
+ return null;
+ }
+ else {
+ inlineBoxShadowEditor = new InlineBoxShadowEditor(context.string, context.marker);
+ inlineBoxShadowEditor.load(hostEditor);
+
+ result = new $.Deferred();
+ result.resolve(inlineBoxShadowEditor);
+ return result.promise();
+ }
+ }
+
+ function queryInlineBoxShadowEditorProvider(hostEditor, pos) {
+ var cursorLine = hostEditor.document.getLine(pos.line);
+
+ if(cursorLine.indexOf("box-shadow") !== -1) {
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ // Initialize extension
+ ExtensionUtils.loadStyleSheet(module, "css/style.less");
+
+ EditorManager.registerInlineEditProvider(inlineBoxShadowEditorProvider, queryInlineBoxShadowEditorProvider);
+
+ // for use by other InlineColorEditors
+ exports.prepareEditorForProvider = prepareEditorForProvider;
+});
\ No newline at end of file
diff --git a/src/extensions/default/InlineBoxShadowEditor/thirdparty/tinycolor-min.js b/src/extensions/default/InlineBoxShadowEditor/thirdparty/tinycolor-min.js
new file mode 100644
index 00000000000..89325118c40
--- /dev/null
+++ b/src/extensions/default/InlineBoxShadowEditor/thirdparty/tinycolor-min.js
@@ -0,0 +1,4 @@
+// TinyColor v1.1.1
+// https://github.com/bgrins/TinyColor
+// 2014-12-20, Brian Grinstead, MIT License
+!function(){function inputToRGB(color){var rgb={r:0,g:0,b:0},a=1,ok=!1,format=!1;return"string"==typeof color&&(color=stringInputToObject(color)),"object"==typeof color&&(color.hasOwnProperty("r")&&color.hasOwnProperty("g")&&color.hasOwnProperty("b")?(rgb=rgbToRgb(color.r,color.g,color.b),ok=!0,format="%"===String(color.r).substr(-1)?"prgb":"rgb"):color.hasOwnProperty("h")&&color.hasOwnProperty("s")&&color.hasOwnProperty("v")?(color.s=convertToPercentage(color.s),color.v=convertToPercentage(color.v),rgb=hsvToRgb(color.h,color.s,color.v),ok=!0,format="hsv"):color.hasOwnProperty("h")&&color.hasOwnProperty("s")&&color.hasOwnProperty("l")&&(color.s=convertToPercentage(color.s),color.l=convertToPercentage(color.l),rgb=hslToRgb(color.h,color.s,color.l),ok=!0,format="hsl"),color.hasOwnProperty("a")&&(a=color.a)),a=boundAlpha(a),{ok:ok,format:color.format||format,r:mathMin(255,mathMax(rgb.r,0)),g:mathMin(255,mathMax(rgb.g,0)),b:mathMin(255,mathMax(rgb.b,0)),a:a}}function rgbToRgb(r,g,b){return{r:255*bound01(r,255),g:255*bound01(g,255),b:255*bound01(b,255)}}function rgbToHsl(r,g,b){r=bound01(r,255),g=bound01(g,255),b=bound01(b,255);var h,s,max=mathMax(r,g,b),min=mathMin(r,g,b),l=(max+min)/2;if(max==min)h=s=0;else{var d=max-min;switch(s=l>.5?d/(2-max-min):d/(max+min),max){case r:h=(g-b)/d+(b>g?6:0);break;case g:h=(b-r)/d+2;break;case b:h=(r-g)/d+4}h/=6}return{h:h,s:s,l:l}}function hslToRgb(h,s,l){function hue2rgb(p,q,t){return 0>t&&(t+=1),t>1&&(t-=1),1/6>t?p+6*(q-p)*t:.5>t?q:2/3>t?p+(q-p)*(2/3-t)*6:p}var r,g,b;if(h=bound01(h,360),s=bound01(s,100),l=bound01(l,100),0===s)r=g=b=l;else{var q=.5>l?l*(1+s):l+s-l*s,p=2*l-q;r=hue2rgb(p,q,h+1/3),g=hue2rgb(p,q,h),b=hue2rgb(p,q,h-1/3)}return{r:255*r,g:255*g,b:255*b}}function rgbToHsv(r,g,b){r=bound01(r,255),g=bound01(g,255),b=bound01(b,255);var h,s,max=mathMax(r,g,b),min=mathMin(r,g,b),v=max,d=max-min;if(s=0===max?0:d/max,max==min)h=0;else{switch(max){case r:h=(g-b)/d+(b>g?6:0);break;case g:h=(b-r)/d+2;break;case b:h=(r-g)/d+4}h/=6}return{h:h,s:s,v:v}}function hsvToRgb(h,s,v){h=6*bound01(h,360),s=bound01(s,100),v=bound01(v,100);var i=math.floor(h),f=h-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s),mod=i%6,r=[v,q,p,p,t,v][mod],g=[t,v,v,q,p,p][mod],b=[p,p,t,v,v,q][mod];return{r:255*r,g:255*g,b:255*b}}function rgbToHex(r,g,b,allow3Char){var hex=[pad2(mathRound(r).toString(16)),pad2(mathRound(g).toString(16)),pad2(mathRound(b).toString(16))];return 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)?hex[0].charAt(0)+hex[1].charAt(0)+hex[2].charAt(0):hex.join("")}function rgbaToHex(r,g,b,a){var hex=[pad2(convertDecimalToHex(a)),pad2(mathRound(r).toString(16)),pad2(mathRound(g).toString(16)),pad2(mathRound(b).toString(16))];return hex.join("")}function desaturate(color,amount){amount=0===amount?0:amount||10;var hsl=tinycolor(color).toHsl();return hsl.s-=amount/100,hsl.s=clamp01(hsl.s),tinycolor(hsl)}function saturate(color,amount){amount=0===amount?0:amount||10;var hsl=tinycolor(color).toHsl();return hsl.s+=amount/100,hsl.s=clamp01(hsl.s),tinycolor(hsl)}function greyscale(color){return tinycolor(color).desaturate(100)}function lighten(color,amount){amount=0===amount?0:amount||10;var hsl=tinycolor(color).toHsl();return hsl.l+=amount/100,hsl.l=clamp01(hsl.l),tinycolor(hsl)}function brighten(color,amount){amount=0===amount?0:amount||10;var rgb=tinycolor(color).toRgb();return rgb.r=mathMax(0,mathMin(255,rgb.r-mathRound(255*-(amount/100)))),rgb.g=mathMax(0,mathMin(255,rgb.g-mathRound(255*-(amount/100)))),rgb.b=mathMax(0,mathMin(255,rgb.b-mathRound(255*-(amount/100)))),tinycolor(rgb)}function darken(color,amount){amount=0===amount?0:amount||10;var hsl=tinycolor(color).toHsl();return hsl.l-=amount/100,hsl.l=clamp01(hsl.l),tinycolor(hsl)}function spin(color,amount){var hsl=tinycolor(color).toHsl(),hue=(mathRound(hsl.h)+amount)%360;return hsl.h=0>hue?360+hue:hue,tinycolor(hsl)}function complement(color){var hsl=tinycolor(color).toHsl();return hsl.h=(hsl.h+180)%360,tinycolor(hsl)}function triad(color){var hsl=tinycolor(color).toHsl(),h=hsl.h;return[tinycolor(color),tinycolor({h:(h+120)%360,s:hsl.s,l:hsl.l}),tinycolor({h:(h+240)%360,s:hsl.s,l:hsl.l})]}function tetrad(color){var hsl=tinycolor(color).toHsl(),h=hsl.h;return[tinycolor(color),tinycolor({h:(h+90)%360,s:hsl.s,l:hsl.l}),tinycolor({h:(h+180)%360,s:hsl.s,l:hsl.l}),tinycolor({h:(h+270)%360,s:hsl.s,l:hsl.l})]}function splitcomplement(color){var hsl=tinycolor(color).toHsl(),h=hsl.h;return[tinycolor(color),tinycolor({h:(h+72)%360,s:hsl.s,l:hsl.l}),tinycolor({h:(h+216)%360,s:hsl.s,l:hsl.l})]}function analogous(color,results,slices){results=results||6,slices=slices||30;var hsl=tinycolor(color).toHsl(),part=360/slices,ret=[tinycolor(color)];for(hsl.h=(hsl.h-(part*results>>1)+720)%360;--results;)hsl.h=(hsl.h+part)%360,ret.push(tinycolor(hsl));return ret}function monochromatic(color,results){results=results||6;for(var hsv=tinycolor(color).toHsv(),h=hsv.h,s=hsv.s,v=hsv.v,ret=[],modification=1/results;results--;)ret.push(tinycolor({h:h,s:s,v:v})),v=(v+modification)%1;return ret}function flip(o){var flipped={};for(var i in o)o.hasOwnProperty(i)&&(flipped[o[i]]=i);return flipped}function boundAlpha(a){return a=parseFloat(a),(isNaN(a)||0>a||a>1)&&(a=1),a}function bound01(n,max){isOnePointZero(n)&&(n="100%");var processPercent=isPercentage(n);return n=mathMin(max,mathMax(0,parseFloat(n))),processPercent&&(n=parseInt(n*max,10)/100),math.abs(n-max)<1e-6?1:n%max/parseFloat(max)}function clamp01(val){return mathMin(1,mathMax(0,val))}function parseIntFromHex(val){return parseInt(val,16)}function isOnePointZero(n){return"string"==typeof n&&-1!=n.indexOf(".")&&1===parseFloat(n)}function isPercentage(n){return"string"==typeof n&&-1!=n.indexOf("%")}function pad2(c){return 1==c.length?"0"+c:""+c}function convertToPercentage(n){return 1>=n&&(n=100*n+"%"),n}function convertDecimalToHex(d){return Math.round(255*parseFloat(d)).toString(16)}function convertHexToDecimal(h){return parseIntFromHex(h)/255}function stringInputToObject(color){color=color.replace(trimLeft,"").replace(trimRight,"").toLowerCase();var named=!1;if(names[color])color=names[color],named=!0;else if("transparent"==color)return{r:0,g:0,b:0,a:0,format:"name"};var match;return(match=matchers.rgb.exec(color))?{r:match[1],g:match[2],b:match[3]}:(match=matchers.rgba.exec(color))?{r:match[1],g:match[2],b:match[3],a:match[4]}:(match=matchers.hsl.exec(color))?{h:match[1],s:match[2],l:match[3]}:(match=matchers.hsla.exec(color))?{h:match[1],s:match[2],l:match[3],a:match[4]}:(match=matchers.hsv.exec(color))?{h:match[1],s:match[2],v:match[3]}:(match=matchers.hsva.exec(color))?{h:match[1],s:match[2],v:match[3],a:match[4]}:(match=matchers.hex8.exec(color))?{a:convertHexToDecimal(match[1]),r:parseIntFromHex(match[2]),g:parseIntFromHex(match[3]),b:parseIntFromHex(match[4]),format:named?"name":"hex8"}:(match=matchers.hex6.exec(color))?{r:parseIntFromHex(match[1]),g:parseIntFromHex(match[2]),b:parseIntFromHex(match[3]),format:named?"name":"hex"}:(match=matchers.hex3.exec(color))?{r:parseIntFromHex(match[1]+""+match[1]),g:parseIntFromHex(match[2]+""+match[2]),b:parseIntFromHex(match[3]+""+match[3]),format:named?"name":"hex"}:!1}var trimLeft=/^[\s,#]+/,trimRight=/\s+$/,tinyCounter=0,math=Math,mathRound=math.round,mathMin=math.min,mathMax=math.max,mathRandom=math.random,tinycolor=function tinycolor(color,opts){if(color=color?color:"",opts=opts||{},color instanceof tinycolor)return color;if(!(this instanceof tinycolor))return new tinycolor(color,opts);var rgb=inputToRGB(color);this._originalInput=color,this._r=rgb.r,this._g=rgb.g,this._b=rgb.b,this._a=rgb.a,this._roundA=mathRound(100*this._a)/100,this._format=opts.format||rgb.format,this._gradientType=opts.gradientType,this._r<1&&(this._r=mathRound(this._r)),this._g<1&&(this._g=mathRound(this._g)),this._b<1&&(this._b=mathRound(this._b)),this._ok=rgb.ok,this._tc_id=tinyCounter++};tinycolor.prototype={isDark:function(){return this.getBrightness()<128},isLight:function(){return!this.isDark()},isValid:function(){return this._ok},getOriginalInput:function(){return this._originalInput},getFormat:function(){return this._format},getAlpha:function(){return this._a},getBrightness:function(){var rgb=this.toRgb();return(299*rgb.r+587*rgb.g+114*rgb.b)/1e3},setAlpha:function(value){return this._a=boundAlpha(value),this._roundA=mathRound(100*this._a)/100,this},toHsv:function(){var hsv=rgbToHsv(this._r,this._g,this._b);return{h:360*hsv.h,s:hsv.s,v:hsv.v,a:this._a}},toHsvString:function(){var hsv=rgbToHsv(this._r,this._g,this._b),h=mathRound(360*hsv.h),s=mathRound(100*hsv.s),v=mathRound(100*hsv.v);return 1==this._a?"hsv("+h+", "+s+"%, "+v+"%)":"hsva("+h+", "+s+"%, "+v+"%, "+this._roundA+")"},toHsl:function(){var hsl=rgbToHsl(this._r,this._g,this._b);return{h:360*hsl.h,s:hsl.s,l:hsl.l,a:this._a}},toHslString:function(){var hsl=rgbToHsl(this._r,this._g,this._b),h=mathRound(360*hsl.h),s=mathRound(100*hsl.s),l=mathRound(100*hsl.l);return 1==this._a?"hsl("+h+", "+s+"%, "+l+"%)":"hsla("+h+", "+s+"%, "+l+"%, "+this._roundA+")"},toHex:function(allow3Char){return rgbToHex(this._r,this._g,this._b,allow3Char)},toHexString:function(allow3Char){return"#"+this.toHex(allow3Char)},toHex8:function(){return rgbaToHex(this._r,this._g,this._b,this._a)},toHex8String:function(){return"#"+this.toHex8()},toRgb:function(){return{r:mathRound(this._r),g:mathRound(this._g),b:mathRound(this._b),a:this._a}},toRgbString:function(){return 1==this._a?"rgb("+mathRound(this._r)+", "+mathRound(this._g)+", "+mathRound(this._b)+")":"rgba("+mathRound(this._r)+", "+mathRound(this._g)+", "+mathRound(this._b)+", "+this._roundA+")"},toPercentageRgb:function(){return{r:mathRound(100*bound01(this._r,255))+"%",g:mathRound(100*bound01(this._g,255))+"%",b:mathRound(100*bound01(this._b,255))+"%",a:this._a}},toPercentageRgbString:function(){return 1==this._a?"rgb("+mathRound(100*bound01(this._r,255))+"%, "+mathRound(100*bound01(this._g,255))+"%, "+mathRound(100*bound01(this._b,255))+"%)":"rgba("+mathRound(100*bound01(this._r,255))+"%, "+mathRound(100*bound01(this._g,255))+"%, "+mathRound(100*bound01(this._b,255))+"%, "+this._roundA+")"},toName:function(){return 0===this._a?"transparent":this._a<1?!1:hexNames[rgbToHex(this._r,this._g,this._b,!0)]||!1},toFilter:function(secondColor){var hex8String="#"+rgbaToHex(this._r,this._g,this._b,this._a),secondHex8String=hex8String,gradientType=this._gradientType?"GradientType = 1, ":"";if(secondColor){var s=tinycolor(secondColor);secondHex8String=s.toHex8String()}return"progid:DXImageTransform.Microsoft.gradient("+gradientType+"startColorstr="+hex8String+",endColorstr="+secondHex8String+")"},toString:function(format){var formatSet=!!format;format=format||this._format;var formattedString=!1,hasAlpha=this._a<1&&this._a>=0,needsAlphaFormat=!formatSet&&hasAlpha&&("hex"===format||"hex6"===format||"hex3"===format||"name"===format);return needsAlphaFormat?"name"===format&&0===this._a?this.toName():this.toRgbString():("rgb"===format&&(formattedString=this.toRgbString()),"prgb"===format&&(formattedString=this.toPercentageRgbString()),("hex"===format||"hex6"===format)&&(formattedString=this.toHexString()),"hex3"===format&&(formattedString=this.toHexString(!0)),"hex8"===format&&(formattedString=this.toHex8String()),"name"===format&&(formattedString=this.toName()),"hsl"===format&&(formattedString=this.toHslString()),"hsv"===format&&(formattedString=this.toHsvString()),formattedString||this.toHexString())},_applyModification:function(fn,args){var color=fn.apply(null,[this].concat([].slice.call(args)));return this._r=color._r,this._g=color._g,this._b=color._b,this.setAlpha(color._a),this},lighten:function(){return this._applyModification(lighten,arguments)},brighten:function(){return this._applyModification(brighten,arguments)},darken:function(){return this._applyModification(darken,arguments)},desaturate:function(){return this._applyModification(desaturate,arguments)},saturate:function(){return this._applyModification(saturate,arguments)},greyscale:function(){return this._applyModification(greyscale,arguments)},spin:function(){return this._applyModification(spin,arguments)},_applyCombination:function(fn,args){return fn.apply(null,[this].concat([].slice.call(args)))},analogous:function(){return this._applyCombination(analogous,arguments)},complement:function(){return this._applyCombination(complement,arguments)},monochromatic:function(){return this._applyCombination(monochromatic,arguments)},splitcomplement:function(){return this._applyCombination(splitcomplement,arguments)},triad:function(){return this._applyCombination(triad,arguments)},tetrad:function(){return this._applyCombination(tetrad,arguments)}},tinycolor.fromRatio=function(color,opts){if("object"==typeof color){var newColor={};for(var i in color)color.hasOwnProperty(i)&&(newColor[i]="a"===i?color[i]:convertToPercentage(color[i]));color=newColor}return tinycolor(color,opts)},tinycolor.equals=function(color1,color2){return color1&&color2?tinycolor(color1).toRgbString()==tinycolor(color2).toRgbString():!1},tinycolor.random=function(){return tinycolor.fromRatio({r:mathRandom(),g:mathRandom(),b:mathRandom()})},tinycolor.mix=function(color1,color2,amount){amount=0===amount?0:amount||50;var w1,rgb1=tinycolor(color1).toRgb(),rgb2=tinycolor(color2).toRgb(),p=amount/100,w=2*p-1,a=rgb2.a-rgb1.a;w1=w*a==-1?w:(w+a)/(1+w*a),w1=(w1+1)/2;var w2=1-w1,rgba={r:rgb2.r*w1+rgb1.r*w2,g:rgb2.g*w1+rgb1.g*w2,b:rgb2.b*w1+rgb1.b*w2,a:rgb2.a*p+rgb1.a*(1-p)};return tinycolor(rgba)},tinycolor.readability=function(color1,color2){var c1=tinycolor(color1),c2=tinycolor(color2),rgb1=c1.toRgb(),rgb2=c2.toRgb(),brightnessA=c1.getBrightness(),brightnessB=c2.getBrightness(),colorDiff=Math.max(rgb1.r,rgb2.r)-Math.min(rgb1.r,rgb2.r)+Math.max(rgb1.g,rgb2.g)-Math.min(rgb1.g,rgb2.g)+Math.max(rgb1.b,rgb2.b)-Math.min(rgb1.b,rgb2.b);return{brightness:Math.abs(brightnessA-brightnessB),color:colorDiff}},tinycolor.isReadable=function(color1,color2){var readability=tinycolor.readability(color1,color2);return readability.brightness>125&&readability.color>500},tinycolor.mostReadable=function(baseColor,colorList){for(var bestColor=null,bestScore=0,bestIsReadable=!1,i=0;i125&&readability.color>500,score=3*(readability.brightness/125)+readability.color/500;(readable&&!bestIsReadable||readable&&bestIsReadable&&score>bestScore||!readable&&!bestIsReadable&&score>bestScore)&&(bestIsReadable=readable,bestScore=score,bestColor=tinycolor(colorList[i]))}return bestColor};var names=tinycolor.names={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"0ff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"00f",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",burntsienna:"ea7e5d",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"0ff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"f0f",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"663399",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"},hexNames=tinycolor.hexNames=flip(names),matchers=function(){var CSS_INTEGER="[-\\+]?\\d+%?",CSS_NUMBER="[-\\+]?\\d*\\.\\d+%?",CSS_UNIT="(?:"+CSS_NUMBER+")|(?:"+CSS_INTEGER+")",PERMISSIVE_MATCH3="[\\s|\\(]+("+CSS_UNIT+")[,|\\s]+("+CSS_UNIT+")[,|\\s]+("+CSS_UNIT+")\\s*\\)?",PERMISSIVE_MATCH4="[\\s|\\(]+("+CSS_UNIT+")[,|\\s]+("+CSS_UNIT+")[,|\\s]+("+CSS_UNIT+")[,|\\s]+("+CSS_UNIT+")\\s*\\)?";return{rgb:new RegExp("rgb"+PERMISSIVE_MATCH3),rgba:new RegExp("rgba"+PERMISSIVE_MATCH4),hsl:new RegExp("hsl"+PERMISSIVE_MATCH3),hsla:new RegExp("hsla"+PERMISSIVE_MATCH4),hsv:new RegExp("hsv"+PERMISSIVE_MATCH3),hsva:new RegExp("hsva"+PERMISSIVE_MATCH4),hex3:/^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex8:/^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/}}();"undefined"!=typeof module&&module.exports?module.exports=tinycolor:"function"==typeof define&&define.amd?define(function(){return tinycolor}):window.tinycolor=tinycolor}();