diff --git a/lib/pageflow/engine.rb b/lib/pageflow/engine.rb
index 90f8661212..61068cdb49 100644
--- a/lib/pageflow/engine.rb
+++ b/lib/pageflow/engine.rb
@@ -22,7 +22,6 @@
require 'backbone-rails'
require 'marionette-rails'
require 'jquery-fileupload-rails'
-require 'jquery-minicolors-rails'
require 'i18n-js'
require 'http_accept_language'
require 'pageflow-public-i18n'
diff --git a/package/config/jest/index.js b/package/config/jest/index.js
index c1db0b160c..607dba5b38 100644
--- a/package/config/jest/index.js
+++ b/package/config/jest/index.js
@@ -13,7 +13,6 @@ module.exports = {
moduleNameMapper: {
'^jquery$': resolve('../../vendor/jquery'),
'^jquery-ui$': resolve('../../vendor/jquery-ui'),
- '^jquery.minicolors$': resolve('../../vendor/jquery.minicolors'),
'^backbone.marionette$': resolve('../../vendor/backbone.marionette'),
'^backbone.babysitter$': resolve('../../vendor/backbone.babysitter'),
'^backbone$': resolve('../../vendor/backbone'),
diff --git a/package/config/webpack.js b/package/config/webpack.js
index c7347d1b65..d2f938891d 100644
--- a/package/config/webpack.js
+++ b/package/config/webpack.js
@@ -5,7 +5,6 @@ module.exports = {
'cocktail': 'Cocktail',
'jquery': 'jQuery',
'jquery-ui': 'jQuery',
- 'jquery.minicolors': 'jQuery',
'underscore': '_',
'backbone.marionette': 'Backbone.Marionette',
'iscroll': 'IScroll',
diff --git a/package/config/webpack5.js b/package/config/webpack5.js
index 3c82a2511f..a55ba2b60c 100644
--- a/package/config/webpack5.js
+++ b/package/config/webpack5.js
@@ -5,7 +5,6 @@ module.exports = {
'cocktail': 'Cocktail',
'jquery': 'jQuery',
'jquery-ui': 'jQuery',
- 'jquery.minicolors': 'jQuery',
'underscore': '_',
'backbone.marionette': 'Backbone.Marionette',
'iscroll': 'IScroll',
diff --git a/package/spec/ui/views/ColorPicker-spec.js b/package/spec/ui/views/ColorPicker-spec.js
new file mode 100644
index 0000000000..e4af63d03b
--- /dev/null
+++ b/package/spec/ui/views/ColorPicker-spec.js
@@ -0,0 +1,511 @@
+import '@testing-library/jest-dom/extend-expect';
+
+import ColorPicker from 'ui/views/ColorPicker';
+
+describe('ColorPicker', () => {
+ let container, input, colorPicker;
+
+ beforeAll(() => {
+ if (!Element.prototype.setPointerCapture) {
+ Element.prototype.setPointerCapture = jest.fn();
+ }
+ });
+
+ afterEach(() => {
+ if (colorPicker) {
+ colorPicker.destroy();
+ colorPicker = null;
+ }
+
+ if (container && container.parentNode) {
+ container.remove();
+ }
+ });
+
+ function createColorPicker({value, ...options} = {}) {
+ container = document.createElement('div');
+ input = document.createElement('input');
+ input.type = 'text';
+ if (value) input.value = value;
+ container.appendChild(input);
+ document.body.appendChild(container);
+
+ colorPicker = new ColorPicker(input, options);
+ return colorPicker;
+ }
+
+ function picker() {
+ return colorPicker._picker;
+ }
+
+ function open() {
+ input.dispatchEvent(new Event('click', {bubbles: true}));
+ }
+
+ function blur() {
+ input.dispatchEvent(new FocusEvent('focusout', {bubbles: true, relatedTarget: document.body}));
+ }
+
+ describe('setup', () => {
+ it('wraps input in .color_picker-field div', () => {
+ createColorPicker();
+
+ expect(input.parentNode).toHaveClass('color_picker-field');
+ });
+
+ it('adds preview swatch to wrapper', () => {
+ createColorPicker();
+
+ expect(input.parentNode.querySelector('span')).not.toBeNull();
+ });
+
+ it('creates picker element inside wrapper', () => {
+ createColorPicker();
+
+ expect(input.parentNode.querySelector('.color_picker')).not.toBeNull();
+ });
+
+ it('sets wrapper color from initial input value', () => {
+ createColorPicker({value: '#ff0000'});
+
+ expect(input.parentNode).toHaveStyle('color: #ff0000');
+ });
+
+ it('does not set wrapper color for empty initial value', () => {
+ createColorPicker();
+
+ expect(input.parentNode.style.color).toBe('');
+ });
+
+ it('renders swatch buttons', () => {
+ createColorPicker({swatches: ['#aabbcc', '#ddeeff']});
+
+ var buttons = picker().querySelectorAll('.color_picker-swatches button');
+
+ expect(buttons).toHaveLength(2);
+ expect(buttons[0].textContent).toBe('#aabbcc');
+ expect(buttons[1].textContent).toBe('#ddeeff');
+ });
+ });
+
+ describe('open and close', () => {
+ it('opens picker on input click', () => {
+ createColorPicker();
+
+ open();
+
+ expect(picker()).toHaveClass('color_picker-open');
+ });
+
+ it('opens picker on Enter key', () => {
+ createColorPicker();
+
+ input.dispatchEvent(
+ new KeyboardEvent('keydown', {key: 'Enter', bubbles: true})
+ );
+
+ expect(picker()).toHaveClass('color_picker-open');
+ });
+
+ it('sets currentColor to null when opened with invalid input value', () => {
+ createColorPicker();
+ input.value = '#ttt';
+
+ open();
+
+ expect(colorPicker._currentColor).toBeNull();
+ });
+
+ it('opens picker on Alt+ArrowDown key', () => {
+ createColorPicker();
+
+ input.dispatchEvent(
+ new KeyboardEvent('keydown', {key: 'ArrowDown', altKey: true, bubbles: true})
+ );
+
+ expect(picker()).toHaveClass('color_picker-open');
+ });
+
+ it('does not open picker on input focus', () => {
+ createColorPicker();
+
+ input.dispatchEvent(new Event('focus'));
+
+ expect(picker()).not.toHaveClass('color_picker-open');
+ });
+
+ it('closes picker on outside mousedown', () => {
+ createColorPicker();
+ open();
+
+ document.body.dispatchEvent(new Event('mousedown', {bubbles: true}));
+
+ expect(picker()).not.toHaveClass('color_picker-open');
+ });
+
+ it('does not close picker on mousedown inside input', () => {
+ createColorPicker();
+ open();
+
+ input.dispatchEvent(new Event('mousedown', {bubbles: true}));
+
+ expect(picker()).toHaveClass('color_picker-open');
+ });
+
+ it('closes on Escape and keeps value', () => {
+ createColorPicker({value: '#aabbcc', swatches: ['#112233']});
+ open();
+ picker().querySelector('.color_picker-swatches button')
+ .dispatchEvent(new Event('click', {bubbles: true}));
+
+ document.dispatchEvent(
+ new KeyboardEvent('keydown', {key: 'Escape', bubbles: true})
+ );
+
+ expect(input.value).toBe('#112233');
+ expect(picker()).not.toHaveClass('color_picker-open');
+ });
+
+ it('closes on Enter from non-button element', () => {
+ createColorPicker();
+ open();
+
+ input.dispatchEvent(
+ new KeyboardEvent('keydown', {key: 'Enter', bubbles: true})
+ );
+
+ expect(picker()).not.toHaveClass('color_picker-open');
+ });
+
+ it('closes when focus moves to element outside wrapper', () => {
+ createColorPicker();
+ var outside = document.createElement('button');
+ document.body.appendChild(outside);
+ open();
+
+ input.dispatchEvent(
+ new FocusEvent('focusout', {bubbles: true, relatedTarget: outside})
+ );
+
+ expect(picker()).not.toHaveClass('color_picker-open');
+ outside.remove();
+ });
+
+ it('does not close on focusout with null relatedTarget', () => {
+ createColorPicker();
+ open();
+
+ input.dispatchEvent(
+ new FocusEvent('focusout', {bubbles: true, relatedTarget: null})
+ );
+
+ expect(picker()).toHaveClass('color_picker-open');
+ });
+ });
+
+ describe('swatch interaction', () => {
+ it('updates input value when swatch is clicked', () => {
+ createColorPicker({swatches: ['#aabbcc']});
+ open();
+
+ picker().querySelector('.color_picker-swatches button')
+ .dispatchEvent(new Event('click', {bubbles: true}));
+
+ expect(input.value).toBe('#aabbcc');
+ });
+
+ it('updates wrapper color when swatch is clicked', () => {
+ createColorPicker({swatches: ['#aabbcc']});
+ open();
+
+ picker().querySelector('.color_picker-swatches button')
+ .dispatchEvent(new Event('click', {bubbles: true}));
+
+ expect(input.parentNode).toHaveStyle('color: #aabbcc');
+ });
+ });
+
+ describe('input sync', () => {
+ it('updates wrapper color on input event', () => {
+ createColorPicker();
+
+ input.value = '#ff0000';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(input.parentNode).toHaveStyle('color: #ff0000');
+ });
+
+ it('keeps input empty on close when no color was picked', () => {
+ createColorPicker();
+ open();
+
+ document.body.dispatchEvent(new Event('mousedown', {bubbles: true}));
+
+ expect(input.value).toBe('');
+ });
+
+ it('sets currentColor to null when input changes to invalid color', () => {
+ createColorPicker({value: '#ff0000'});
+ open();
+
+ input.value = '#ttt';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(colorPicker._currentColor).toBeNull();
+ });
+
+ it('clears wrapper color when input is empty', () => {
+ createColorPicker({value: '#ff0000'});
+ input.value = '';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(input.parentNode.style.color).toBe('');
+ });
+
+ it('clears wrapper color when input contains an invalid color', () => {
+ createColorPicker({value: '#ff0000'});
+ input.value = '#ttt';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(input.parentNode.style.color).toBe('');
+ });
+ });
+
+ describe('blur normalization', () => {
+ it('converts rgb to hex on blur when picker is closed', () => {
+ createColorPicker();
+ input.value = 'rgb(255, 0, 0)';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ blur();
+
+ expect(input.value).toBe('#ff0000');
+ });
+
+ it('keeps input empty on blur', () => {
+ createColorPicker();
+
+ blur();
+
+ expect(input.value).toBe('');
+ });
+
+ it('clears input on blur with invalid value', () => {
+ createColorPicker({value: '#ff0000'});
+ input.value = '#ttt';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ blur();
+
+ expect(input.value).toBe('');
+ });
+
+ it('normalizes on blur even when picker is open', () => {
+ createColorPicker();
+ open();
+ input.value = 'rgb(255, 0, 0)';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ input.dispatchEvent(
+ new FocusEvent('focusout', {bubbles: true, relatedTarget: null})
+ );
+
+ expect(input.value).toBe('#ff0000');
+ });
+
+ it('normalizes on Enter when picker is closed', () => {
+ createColorPicker();
+ input.value = 'rgb(255, 0, 0)';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ input.dispatchEvent(
+ new KeyboardEvent('keydown', {key: 'Enter', bubbles: true})
+ );
+
+ expect(input.value).toBe('#ff0000');
+ });
+
+ it('normalizes on Enter when picker is open', () => {
+ createColorPicker();
+ open();
+ input.value = 'rgb(255, 0, 0)';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ input.dispatchEvent(
+ new KeyboardEvent('keydown', {key: 'Enter', bubbles: true})
+ );
+
+ expect(input.value).toBe('#ff0000');
+ });
+ });
+
+ describe('alpha option', () => {
+ it('hides alpha slider by default', () => {
+ createColorPicker();
+
+ expect(picker()).toHaveClass('color_picker-no_alpha');
+ });
+
+ it('shows alpha slider when enabled', () => {
+ createColorPicker({alpha: true});
+
+ expect(picker()).not.toHaveClass('color_picker-no_alpha');
+ });
+
+ it('outputs #rrggbb format without alpha', () => {
+ createColorPicker({swatches: ['#aabbcc']});
+ open();
+ picker().querySelector('.color_picker-swatches button')
+ .dispatchEvent(new Event('click', {bubbles: true}));
+
+ expect(input.value).toBe('#aabbcc');
+ });
+
+ it('outputs #rrggbb for fully opaque color with alpha enabled', () => {
+ createColorPicker({alpha: true, swatches: ['#aabbcc']});
+ open();
+ picker().querySelector('.color_picker-swatches button')
+ .dispatchEvent(new Event('click', {bubbles: true}));
+
+ expect(input.value).toBe('#aabbcc');
+ });
+
+ it('outputs #rrggbbaa for translucent color with alpha enabled', () => {
+ createColorPicker({alpha: true, value: '#ff000080'});
+ open();
+
+ expect(input.value).toBe('#ff000080');
+ });
+ });
+
+ describe('onChange', () => {
+ it('does not call onChange during construction', () => {
+ var handler = jest.fn();
+ createColorPicker({value: '#ff0000', onChange: handler});
+
+ expect(handler).not.toHaveBeenCalled();
+ });
+
+ it('calls onChange with formatted hex on valid input', () => {
+ var handler = jest.fn();
+ createColorPicker({onChange: handler});
+
+ input.value = '#ff0000';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(handler).toHaveBeenCalledWith('#ff0000');
+ });
+
+ it('calls onChange with null on invalid input', () => {
+ var handler = jest.fn();
+ createColorPicker({value: '#ff0000', onChange: handler});
+
+ input.value = '#ttt';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(handler).toHaveBeenCalledWith(null);
+ });
+
+ it('calls onChange with null on empty input', () => {
+ var handler = jest.fn();
+ createColorPicker({value: '#ff0000', onChange: handler});
+
+ input.value = '';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(handler).toHaveBeenCalledWith(null);
+ });
+ });
+
+ describe('defaultValue option', () => {
+ it('resets currentColor to default when input is cleared', () => {
+ createColorPicker({value: '#ff0000', defaultValue: '#aabbcc'});
+
+ input.value = '';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(colorPicker._currentColor).toEqual({r: 170, g: 187, b: 204, a: 1});
+ });
+
+ it('shows default color in wrapper when input is cleared', () => {
+ createColorPicker({value: '#ff0000', defaultValue: '#aabbcc'});
+
+ input.value = '';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(input.parentNode).toHaveStyle('color: #aabbcc');
+ });
+
+ it('calls onChange with default value when input is cleared', () => {
+ var handler = jest.fn();
+ createColorPicker({value: '#ff0000', defaultValue: '#aabbcc', onChange: handler});
+
+ input.value = '';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(handler).toHaveBeenCalledWith('#aabbcc');
+ });
+
+ it('normalizes input to default value on blur', () => {
+ createColorPicker({value: '#ff0000', defaultValue: '#aabbcc'});
+
+ input.value = '';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+ blur();
+
+ expect(input.value).toBe('#aabbcc');
+ });
+
+ it('uses updated default value', () => {
+ createColorPicker({value: '#ff0000', defaultValue: '#aabbcc'});
+
+ colorPicker.update({defaultValue: '#112233'});
+ input.value = '';
+ input.dispatchEvent(new Event('input', {bubbles: true}));
+
+ expect(colorPicker._currentColor).toEqual({r: 17, g: 34, b: 51, a: 1});
+ });
+ });
+
+ describe('update', () => {
+ it('re-renders swatches', () => {
+ createColorPicker({swatches: ['#aabbcc']});
+
+ colorPicker.update({swatches: ['#112233', '#445566', '#778899']});
+
+ var buttons = picker().querySelectorAll('.color_picker-swatches button');
+ expect(buttons).toHaveLength(3);
+ });
+ });
+
+ describe('destroy', () => {
+ it('removes picker element from DOM', () => {
+ createColorPicker();
+
+ colorPicker.destroy();
+ colorPicker = null;
+
+ expect(container.querySelector('.color_picker')).toBeNull();
+ });
+
+ it('unwraps input', () => {
+ createColorPicker();
+
+ colorPicker.destroy();
+ colorPicker = null;
+
+ expect(input.parentNode).toBe(container);
+ });
+
+ it('does not respond to input click after destroy', () => {
+ createColorPicker();
+
+ colorPicker.destroy();
+ colorPicker = null;
+
+ input.dispatchEvent(new Event('click', {bubbles: true}));
+
+ expect(document.querySelector('.color_picker.color_picker-open')).toBeNull();
+ });
+ });
+});
diff --git a/package/spec/ui/views/inputs/ColorInputView-spec.js b/package/spec/ui/views/inputs/ColorInputView-spec.js
index f218c50aa0..a2cac9650a 100644
--- a/package/spec/ui/views/inputs/ColorInputView-spec.js
+++ b/package/spec/ui/views/inputs/ColorInputView-spec.js
@@ -4,7 +4,6 @@ import '@testing-library/jest-dom/extend-expect';
import {ColorInputView} from 'pageflow/ui';
-import * as support from '$support';
import {ColorInput} from '$support/dominos/ui'
describe('pageflow.ColorInputView', () => {
@@ -479,6 +478,22 @@ describe('pageflow.ColorInputView', () => {
});
});
+ it('removes picker element when view is closed', () => {
+ var pickersBefore = document.querySelectorAll('.color_picker').length;
+ var colorInputView = new ColorInputView({
+ model: new Backbone.Model(),
+ propertyName: 'color'
+ });
+
+ ColorInput.render(colorInputView);
+
+ expect(document.querySelectorAll('.color_picker').length).toBe(pickersBefore + 1);
+
+ colorInputView.close();
+
+ expect(document.querySelectorAll('.color_picker').length).toBe(pickersBefore);
+ });
+
describe('with placeholderColor option', () => {
it('sets custom property', () => {
var colorInputView = new ColorInputView({
diff --git a/package/src/testHelpers/dominos/ui/inputs/ColorInput.js b/package/src/testHelpers/dominos/ui/inputs/ColorInput.js
index c28e1f6267..80cd1859e4 100644
--- a/package/src/testHelpers/dominos/ui/inputs/ColorInput.js
+++ b/package/src/testHelpers/dominos/ui/inputs/ColorInput.js
@@ -6,16 +6,20 @@ export const ColorInput = Base.extend({
},
fillIn: function(value, clock) {
- this._input().val(value);
- this._input().trigger('keyup');
+ var input = this._input()[0];
+ input.value = value;
+ input.dispatchEvent(new Event('input', {bubbles: true}));
clock.tick(500);
},
swatches: function() {
- return this.$el.find('.minicolors-swatches span').map(function() {
- return window.getComputedStyle(this)['background-color'];
- }).get();
+ var picker = this._input()[0].parentNode.querySelector('.color_picker');
+ var buttons = picker.querySelectorAll('.color_picker-swatches button');
+
+ return Array.from(buttons).map(function(button) {
+ return window.getComputedStyle(button).color;
+ });
},
_input: function() {
diff --git a/package/src/ui/views/ColorPicker.js b/package/src/ui/views/ColorPicker.js
new file mode 100644
index 0000000000..7aa0913f19
--- /dev/null
+++ b/package/src/ui/views/ColorPicker.js
@@ -0,0 +1,610 @@
+// Inspired by https://github.com/mdbassit/Coloris
+
+const ctx = typeof OffscreenCanvas !== 'undefined' &&
+ new OffscreenCanvas(1, 1).getContext('2d');
+
+const FALLBACK_COLOR = {r: 255, g: 255, b: 255, a: 1};
+
+const PICKER_HTML =
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
' +
+ '
';
+
+export default class ColorPicker {
+ constructor(input, options = {}) {
+ this._input = input;
+ this._colorAreaDims = {};
+ this._alpha = options.alpha || false;
+ this._onChange = options.onChange;
+ this._defaultColor = strToRGBA(options.defaultValue);
+ this._swatches = options.swatches || [];
+
+ this._wrapInput();
+ this._createPicker();
+ this._renderSwatches();
+ this._bindEvents();
+ this._updateColor(strToRGBA(this._input.value), {silent: true});
+ }
+
+ update(options) {
+ if ('defaultValue' in options) {
+ this._defaultColor = strToRGBA(options.defaultValue);
+ }
+ if (options.swatches) {
+ this._swatches = options.swatches;
+ this._renderSwatches();
+ }
+ }
+
+ destroy() {
+ this._close();
+ this._unbindEvents();
+ this._picker.remove();
+ this._unwrapInput();
+ }
+
+ // Setup
+
+ _createPicker() {
+ this._picker = document.createElement('div');
+ this._picker.className = 'color_picker';
+ this._picker.classList.toggle('color_picker-no_alpha', !this._alpha);
+ this._picker.innerHTML = PICKER_HTML;
+ this._input.parentNode.appendChild(this._picker);
+
+ this._colorArea = this._picker.querySelector('.color_picker-gradient');
+ this._colorMarker = this._picker.querySelector('.color_picker-marker');
+ this._hueSlider = this._picker.querySelector('.color_picker-hue input');
+ this._hueMarker = this._picker.querySelector('.color_picker-hue div');
+ this._alphaSlider = this._picker.querySelector('.color_picker-alpha input');
+ this._alphaMarker = this._picker.querySelector('.color_picker-alpha div');
+ this._swatchesContainer = this._picker.querySelector('.color_picker-swatches');
+ }
+
+ _wrapInput() {
+ const parent = this._input.parentNode;
+
+ if (!parent.classList.contains('color_picker-field')) {
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = '
';
+ parent.insertBefore(wrapper, this._input);
+ wrapper.className = 'color_picker-field';
+ wrapper.appendChild(this._input);
+ }
+ }
+
+ _unwrapInput() {
+ const wrapper = this._input.parentNode;
+
+ if (wrapper && wrapper.classList.contains('color_picker-field')) {
+ const parent = wrapper.parentNode;
+ parent.insertBefore(this._input, wrapper);
+ parent.removeChild(wrapper);
+ }
+ }
+
+ _renderSwatches() {
+ this._swatchesContainer.textContent = '';
+ this._swatchesContainer.classList.toggle('color_picker-empty', !this._swatches.length);
+
+ if (!this._swatches.length) {
+ return;
+ }
+
+ this._swatches.forEach(swatch => {
+ const button = document.createElement('button');
+ button.setAttribute('type', 'button');
+ button.title = swatch;
+ button.style.color = swatch;
+ button.textContent = swatch;
+ this._swatchesContainer.appendChild(button);
+ });
+ }
+
+ // Lifecycle
+
+ _open() {
+ if (this._picker.classList.contains('color_picker-open')) {
+ return;
+ }
+
+ this._picker.classList.add('color_picker-open');
+ this._updatePosition();
+ this._setColorFromStr(this._input.value);
+
+ this._addDocListeners();
+ }
+
+ _close() {
+ if (!this._picker.classList.contains('color_picker-open')) {
+ return;
+ }
+
+ this._picker.classList.remove('color_picker-open');
+ this._removeDocListeners();
+ }
+
+ _updatePosition() {
+ const margin = 2;
+ const pickerHeight = this._picker.offsetHeight;
+ const pickerWidth = this._picker.offsetWidth;
+ const inputRect = this._input.getBoundingClientRect();
+ const clipRect = getClipRect(this._input);
+
+ const spaceBelow = clipRect.bottom - inputRect.bottom;
+ const flipTop = pickerHeight + margin > spaceBelow &&
+ pickerHeight + margin <= inputRect.top - clipRect.top;
+
+ this._picker.classList.toggle('color_picker-top', flipTop);
+
+ if (flipTop) {
+ this._picker.style.top = 'auto';
+ this._picker.style.bottom = `calc(100% + ${margin}px)`;
+ } else {
+ this._picker.style.top = `calc(100% + ${margin}px)`;
+ this._picker.style.bottom = '';
+ }
+
+ if (inputRect.left + pickerWidth > clipRect.right) {
+ this._picker.style.left = 'auto';
+ this._picker.style.right = '0';
+ } else {
+ this._picker.style.left = '0';
+ this._picker.style.right = '';
+ }
+
+ const areaRect = this._colorArea.getBoundingClientRect();
+ this._colorAreaDims = {
+ width: this._colorArea.offsetWidth,
+ height: this._colorArea.offsetHeight,
+ x: areaRect.x + window.scrollX,
+ y: areaRect.y + window.scrollY
+ };
+ }
+
+ // Color interaction
+
+ _setColorFromStr(str, options) {
+ this._updateColor(strToRGBA(str), options);
+
+ const {h, s, v, a} = rgbaToHSVA(this._displayColor);
+
+ this._hueSlider.value = h;
+ this._picker.style.color = `hsl(${h}, 100%, 50%)`;
+ this._hueMarker.style.left = `${h / 360 * 100}%`;
+
+ this._colorMarker.style.left = `${this._colorAreaDims.width * s / 100}px`;
+ this._colorMarker.style.top = `${this._colorAreaDims.height - (this._colorAreaDims.height * v / 100)}px`;
+
+ this._alphaSlider.value = a * 100;
+ this._alphaMarker.style.left = `${a * 100}%`;
+ }
+
+ _moveMarker(event) {
+ let x = event.pageX - this._colorAreaDims.x;
+ let y = event.pageY - this._colorAreaDims.y;
+
+ this._setMarkerPosition(x, y);
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ _moveMarkerOnKeydown(dx, dy) {
+ let x = this._colorMarker.style.left.replace('px', '') * 1 + dx;
+ let y = this._colorMarker.style.top.replace('px', '') * 1 + dy;
+
+ this._setMarkerPosition(x, y);
+ }
+
+ _setHue() {
+ const hue = this._hueSlider.value * 1;
+ const x = this._colorMarker.style.left.replace('px', '') * 1;
+ const y = this._colorMarker.style.top.replace('px', '') * 1;
+
+ this._picker.style.color = `hsl(${hue}, 100%, 50%)`;
+ this._hueMarker.style.left = `${hue / 360 * 100}%`;
+
+ this._setColorAtPosition(x, y);
+ }
+
+ _setAlpha() {
+ const alpha = this._alphaSlider.value / 100;
+
+ this._alphaMarker.style.left = `${alpha * 100}%`;
+ this._updateColor({a: alpha});
+ this._syncInput();
+ }
+
+ _setMarkerPosition(x, y) {
+ x = (x < 0) ? 0 : (x > this._colorAreaDims.width) ? this._colorAreaDims.width : x;
+ y = (y < 0) ? 0 : (y > this._colorAreaDims.height) ? this._colorAreaDims.height : y;
+
+ this._colorMarker.style.left = `${x}px`;
+ this._colorMarker.style.top = `${y}px`;
+
+ this._setColorAtPosition(x, y);
+ this._colorMarker.focus();
+ }
+
+ _setColorAtPosition(x, y) {
+ const hsva = {
+ h: this._hueSlider.value * 1,
+ s: x / this._colorAreaDims.width * 100,
+ v: 100 - (y / this._colorAreaDims.height * 100),
+ a: this._alphaSlider.value / 100
+ };
+ const rgba = hsvaToRGBA(hsva);
+
+ this._updateColor(rgba);
+ this._syncInput();
+ }
+
+ _updateColor(rgba, {silent} = {}) {
+ rgba = rgba || this._defaultColor;
+ if (rgba && !this._alpha) rgba.a = 1;
+
+ this._currentColor = rgba && {...this._displayColor, ...rgba};
+ this._displayColor = this._currentColor || FALLBACK_COLOR;
+
+ const hex = rgbaToHex(this._displayColor);
+ const opaqueHex = hex.substring(0, 7);
+
+ this._colorMarker.style.color = opaqueHex;
+ this._alphaMarker.parentNode.style.color = opaqueHex;
+ this._alphaMarker.style.color = hex;
+
+ const formatted = this._formatHex(this._currentColor);
+ const wrapper = this._input.parentNode;
+ if (wrapper && wrapper.classList.contains('color_picker-field')) {
+ wrapper.style.color = formatted || '';
+ }
+
+ // Force repaint the color and alpha gradients (Chrome workaround)
+ this._colorArea.style.display = 'none';
+ this._colorArea.offsetHeight;
+ this._colorArea.style.display = '';
+ this._alphaMarker.nextElementSibling.style.display = 'none';
+ this._alphaMarker.nextElementSibling.offsetHeight;
+ this._alphaMarker.nextElementSibling.style.display = '';
+
+ if (!silent && this._onChange) this._onChange(formatted);
+ }
+
+ _syncInput() {
+ this._input.value = this._formatHex(this._currentColor) || '';
+ }
+
+ _formatHex(rgba) {
+ if (!rgba) return null;
+ const hex = rgbaToHex(rgba);
+ return rgba.a < 1 ? hex : hex.substring(0, 7);
+ }
+
+ _normalizeInputValue() {
+ const hex = this._formatHex(this._currentColor) || '';
+ if (hex !== this._input.value) {
+ this._input.value = hex;
+ }
+ }
+
+ // Event wiring
+
+ _bindEvents() {
+ this._onInputClick = () => {
+ this._open();
+ };
+
+ this._onInputKeydown = (event) => {
+ if (event.key === 'Enter') {
+ this._normalizeInputValue();
+ }
+
+ if (this._picker.classList.contains('color_picker-open')) {
+ return;
+ }
+
+ if (event.key === 'Enter' || (event.key === 'ArrowDown' && event.altKey)) {
+ this._open();
+ event.stopPropagation();
+ }
+ };
+
+ this._onPickerMousedown = (event) => {
+ this._picker.classList.remove('color_picker-keyboard_nav');
+ event.stopPropagation();
+ };
+
+ this._onAreaPointerdown = (event) => {
+ event.preventDefault();
+ this._colorArea.setPointerCapture(event.pointerId);
+ this._dragging = true;
+ };
+ this._onAreaPointermove = (event) => {
+ if (this._dragging) {
+ this._moveMarker(event);
+ }
+ };
+ this._onAreaPointerup = () => {
+ this._dragging = false;
+ };
+
+ this._onInputEvent = () => {
+ if (this._picker.classList.contains('color_picker-open')) {
+ this._setColorFromStr(this._input.value);
+ } else {
+ this._updateColor(strToRGBA(this._input.value));
+ }
+ };
+
+ this._onSwatchClick = (event) => {
+ if (event.target.closest('.color_picker-swatches button')) {
+ this._setColorFromStr(event.target.closest('.color_picker-swatches button').textContent);
+ this._syncInput();
+ }
+ };
+
+ this._onMarkerKeydown = (event) => {
+ const movements = {
+ ArrowUp: [0, -1],
+ ArrowDown: [0, 1],
+ ArrowLeft: [-1, 0],
+ ArrowRight: [1, 0]
+ };
+
+ if (movements[event.key]) {
+ this._moveMarkerOnKeydown(...movements[event.key]);
+ event.preventDefault();
+ }
+ };
+
+ this._onAreaClick = (event) => this._moveMarker(event);
+ this._onHueInput = () => this._setHue();
+ this._onAlphaInput = () => this._setAlpha();
+
+ const wrapper = this._input.parentNode;
+ this._onFocusout = (event) => {
+ if (event.target === this._input) {
+ this._normalizeInputValue();
+ }
+
+ if (this._picker.classList.contains('color_picker-open')) {
+ if (event.relatedTarget && !wrapper.contains(event.relatedTarget)) {
+ this._close();
+ }
+ }
+ };
+
+ this._input.addEventListener('click', this._onInputClick);
+ this._input.addEventListener('keydown', this._onInputKeydown);
+ this._input.addEventListener('input', this._onInputEvent);
+ wrapper.addEventListener('focusout', this._onFocusout);
+
+ this._picker.addEventListener('mousedown', this._onPickerMousedown);
+ this._colorArea.addEventListener('pointerdown', this._onAreaPointerdown);
+ this._colorArea.addEventListener('pointermove', this._onAreaPointermove);
+ this._colorArea.addEventListener('pointerup', this._onAreaPointerup);
+ this._swatchesContainer.addEventListener('click', this._onSwatchClick);
+ this._colorMarker.addEventListener('keydown', this._onMarkerKeydown);
+ this._colorArea.addEventListener('click', this._onAreaClick);
+ this._hueSlider.addEventListener('input', this._onHueInput);
+ this._alphaSlider.addEventListener('input', this._onAlphaInput);
+ }
+
+ _unbindEvents() {
+ this._input.removeEventListener('click', this._onInputClick);
+ this._input.removeEventListener('keydown', this._onInputKeydown);
+ this._input.removeEventListener('input', this._onInputEvent);
+
+ const wrapper = this._input.parentNode;
+ if (wrapper && wrapper.classList.contains('color_picker-field')) {
+ wrapper.removeEventListener('focusout', this._onFocusout);
+ }
+
+ this._picker.removeEventListener('mousedown', this._onPickerMousedown);
+ this._colorArea.removeEventListener('pointerdown', this._onAreaPointerdown);
+ this._colorArea.removeEventListener('pointermove', this._onAreaPointermove);
+ this._colorArea.removeEventListener('pointerup', this._onAreaPointerup);
+ this._swatchesContainer.removeEventListener('click', this._onSwatchClick);
+ this._colorMarker.removeEventListener('keydown', this._onMarkerKeydown);
+ this._colorArea.removeEventListener('click', this._onAreaClick);
+ this._hueSlider.removeEventListener('input', this._onHueInput);
+ this._alphaSlider.removeEventListener('input', this._onAlphaInput);
+
+ this._removeDocListeners();
+ }
+
+ _addDocListeners() {
+ this._onDocMousedown = (event) => {
+ this._picker.classList.remove('color_picker-keyboard_nav');
+
+ if (!this._input.parentNode.contains(event.target)) {
+ this._close();
+ }
+ };
+
+ this._onDocKeydown = (event) => {
+ if (event.key === 'Escape' ||
+ (event.key === 'Enter' && event.target.tagName !== 'BUTTON')) {
+ this._close();
+ this._input.focus({preventScroll: true});
+ return;
+ }
+
+ const navKeys = ['Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
+ if (navKeys.includes(event.key)) {
+ this._picker.classList.add('color_picker-keyboard_nav');
+ }
+ };
+
+ document.addEventListener('mousedown', this._onDocMousedown);
+ document.addEventListener('keydown', this._onDocKeydown);
+ }
+
+ _removeDocListeners() {
+ if (this._onDocMousedown) {
+ document.removeEventListener('mousedown', this._onDocMousedown);
+ document.removeEventListener('keydown', this._onDocKeydown);
+ }
+ }
+}
+
+function strToRGBA(str) {
+ if (!str) return null;
+
+ const regex = /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i;
+ let match, rgba;
+
+ if (!ctx) {
+ match = regex.exec(str);
+ if (match) {
+ return {r: match[3] * 1, g: match[4] * 1, b: match[5] * 1, a: match[6] * 1 || 1};
+ }
+
+ if (!/^#[0-9a-f]{3,8}$/i.test(str)) return null;
+
+ let hex = str.replace('#', '');
+ if (hex.length === 3 || hex.length === 4) {
+ hex = hex.split('').map(c => c + c).join('');
+ }
+ if (hex.length !== 6 && hex.length !== 8) return null;
+ match = hex.match(/.{2}/g).map(h => parseInt(h, 16));
+ return {r: match[0], g: match[1], b: match[2], a: match[3] !== undefined ? match[3] / 255 : 1};
+ }
+
+ ctx.fillStyle = '#010101';
+ ctx.fillStyle = str;
+
+ if (ctx.fillStyle === '#010101') {
+ ctx.fillStyle = '#020202';
+ ctx.fillStyle = str;
+ if (ctx.fillStyle === '#020202') return null;
+ }
+ match = regex.exec(ctx.fillStyle);
+
+ if (match) {
+ rgba = {
+ r: match[3] * 1,
+ g: match[4] * 1,
+ b: match[5] * 1,
+ a: match[6] * 1
+ };
+ } else {
+ match = ctx.fillStyle.replace('#', '').match(/.{2}/g).map(h => parseInt(h, 16));
+ rgba = {
+ r: match[0],
+ g: match[1],
+ b: match[2],
+ a: 1
+ };
+ }
+
+ return rgba;
+}
+
+function rgbaToHSVA(rgba) {
+ const red = rgba.r / 255;
+
+ const green = rgba.g / 255;
+ const blue = rgba.b / 255;
+ const xmax = Math.max(red, green, blue);
+ const xmin = Math.min(red, green, blue);
+ const chroma = xmax - xmin;
+ const value = xmax;
+ let hue = 0;
+ let saturation = 0;
+
+ if (chroma) {
+ if (xmax === red) { hue = ((green - blue) / chroma); }
+ if (xmax === green) { hue = 2 + (blue - red) / chroma; }
+ if (xmax === blue) { hue = 4 + (red - green) / chroma; }
+ if (xmax) { saturation = chroma / xmax; }
+ }
+
+ hue = Math.floor(hue * 60);
+
+ return {
+ h: hue < 0 ? hue + 360 : hue,
+ s: Math.round(saturation * 100),
+ v: Math.round(value * 100),
+ a: rgba.a
+ };
+}
+
+function hsvaToRGBA(hsva) {
+ const saturation = hsva.s / 100;
+ const value = hsva.v / 100;
+ let chroma = saturation * value;
+ let hueBy60 = hsva.h / 60;
+ let x = chroma * (1 - Math.abs(hueBy60 % 2 - 1));
+ let m = value - chroma;
+
+ chroma = (chroma + m);
+ x = (x + m);
+
+ const index = Math.floor(hueBy60) % 6;
+ const red = [chroma, x, m, m, x, chroma][index];
+ const green = [x, chroma, chroma, x, m, m][index];
+ const blue = [m, m, x, chroma, chroma, x][index];
+
+ return {
+ r: Math.round(red * 255),
+ g: Math.round(green * 255),
+ b: Math.round(blue * 255),
+ a: hsva.a
+ };
+}
+
+function rgbaToHex(rgba) {
+ let R = rgba.r.toString(16);
+ let G = rgba.g.toString(16);
+ let B = rgba.b.toString(16);
+ let A = Math.round(rgba.a * 255).toString(16);
+
+ if (rgba.r < 16) { R = '0' + R; }
+ if (rgba.g < 16) { G = '0' + G; }
+ if (rgba.b < 16) { B = '0' + B; }
+ if (rgba.a * 255 < 16) { A = '0' + A; }
+
+ return '#' + R + G + B + A;
+}
+
+function getClipRect(element) {
+ const viewport = {
+ top: 0,
+ left: 0,
+ right: document.documentElement.clientWidth,
+ bottom: document.documentElement.clientHeight
+ };
+
+ let ancestor = element.parentElement;
+
+ while (ancestor && ancestor !== document.documentElement) {
+ const overflow = getComputedStyle(ancestor).overflow;
+
+ if (overflow !== 'visible') {
+ const rect = ancestor.getBoundingClientRect();
+
+ return {
+ top: Math.max(viewport.top, rect.top),
+ left: Math.max(viewport.left, rect.left),
+ right: Math.min(viewport.right, rect.right),
+ bottom: Math.min(viewport.bottom, rect.bottom)
+ };
+ }
+
+ ancestor = ancestor.parentElement;
+ }
+
+ return viewport;
+}
diff --git a/package/src/ui/views/inputs/ColorInputView.js b/package/src/ui/views/inputs/ColorInputView.js
index 8f452e7169..09ed2f6727 100644
--- a/package/src/ui/views/inputs/ColorInputView.js
+++ b/package/src/ui/views/inputs/ColorInputView.js
@@ -1,6 +1,8 @@
import Marionette from 'backbone.marionette';
import _ from 'underscore';
-import 'jquery.minicolors';
+import ColorPicker from '../ColorPicker';
+
+export {ColorPicker};
import {inputView} from '../mixins/inputView';
import {inputWithPlaceholderText} from '../mixins/inputWithPlaceholderText';
@@ -35,6 +37,11 @@ import template from '../../templates/inputs/colorInput.jst';
* is used as placeholderColor option, it will be passed the value of the
* placeholderColorBinding attribute each time it changes.
*
+ * @param {boolean} [options.alpha]
+ * Allow picking colors with alpha channel. When enabled, translucent
+ * colors are stored in `#rrggbbaa` format. Fully opaque colors still
+ * use `#rrggbb`.
+ *
* @param {string[]} [options.swatches]
* Preset color values to be displayed inside the picker drop
* down. The default value, if present, is always used as the
@@ -52,27 +59,14 @@ export const ColorInputView = Marionette.ItemView.extend({
input: 'input'
},
- events: {
- 'mousedown': 'refreshPicker'
- },
-
onRender: function() {
this.setupAttributeBinding('placeholderColor', this.updatePlaceholderColor);
- this.ui.input.minicolors({
- changeDelay: 200,
- change: _.bind(function(color) {
- this._saving = true;
-
- if (color === this.defaultValue()) {
- this.model.unset(this.options.propertyName);
- }
- else {
- this.model.set(this.options.propertyName, color);
- }
-
- this._saving = false;
- }, this)
+ this._colorPicker = new ColorPicker(this.ui.input[0], {
+ alpha: this.options.alpha,
+ defaultValue: this.defaultValue(),
+ swatches: this.getSwatches(),
+ onChange: _.debounce(_.bind(this._onChange, this), 200)
});
this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
@@ -81,17 +75,20 @@ export const ColorInputView = Marionette.ItemView.extend({
this.listenTo(this.model, 'change:' + this.options.defaultValueBinding, this.updateSettings);
}
- this.updateSettings();
+ this.load();
},
updatePlaceholderColor(value) {
- this.el.style.setProperty('--placeholder-color', value);
+ if (value) {
+ this.el.style.setProperty('--placeholder-color', value);
+ }
+ else {
+ this.el.style.removeProperty('--placeholder-color');
+ }
},
updateSettings: function() {
- this.resetSwatchesInStoredSettings();
-
- this.ui.input.minicolors('settings', {
+ this._colorPicker.update({
defaultValue: this.defaultValue(),
swatches: this.getSwatches()
});
@@ -99,27 +96,23 @@ export const ColorInputView = Marionette.ItemView.extend({
this.load();
},
- // see https://github.com/claviska/jquery-minicolors/issues/287
- resetSwatchesInStoredSettings: function() {
- const settings = this.ui.input.data('minicolors-settings');
-
- if (settings) {
- delete settings.swatches;
- this.ui.input.data('minicolors-settings', settings);
- }
- },
-
load: function() {
+ var color = this.model.get(this.options.propertyName) || this.defaultValue() || '';
+
if (!this._saving) {
- this.ui.input.minicolors('value',
- this.model.get(this.options.propertyName) || this.defaultValue());
+ this.ui.input[0].value = color;
+
+ var wrapper = this.ui.input[0].parentNode;
+ if (wrapper && wrapper.classList.contains('color_picker-field')) {
+ wrapper.style.color = color;
+ }
}
this.$el.toggleClass('is_default', !this.model.has(this.options.propertyName));
},
- refreshPicker: function() {
- this.ui.input.minicolors('value', {});
+ onBeforeClose: function() {
+ this._colorPicker.destroy();
},
getSwatches: function() {
@@ -146,5 +139,18 @@ export const ColorInputView = Marionette.ItemView.extend({
else {
return bindingValue;
}
+ },
+
+ _onChange: function(color) {
+ this._saving = true;
+
+ if (!color || color === this.defaultValue()) {
+ this.model.unset(this.options.propertyName);
+ }
+ else {
+ this.model.set(this.options.propertyName, color);
+ }
+
+ this._saving = false;
}
});
diff --git a/package/vendor/jquery.minicolors.js b/package/vendor/jquery.minicolors.js
deleted file mode 100644
index 6c1add1d9a..0000000000
--- a/package/vendor/jquery.minicolors.js
+++ /dev/null
@@ -1,1108 +0,0 @@
-//
-// jQuery MiniColors: A tiny color picker built on jQuery
-//
-// Developed by Cory LaViska for A Beautiful Site, LLC
-//
-// Licensed under the MIT license: http://opensource.org/licenses/MIT
-//
-(function (factory) {
- if(typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['jquery'], factory);
- } else if(typeof exports === 'object') {
- // Node/CommonJS
- module.exports = factory(require('jquery'));
- } else {
- // Browser globals
- factory(jQuery);
- }
-}(function ($) {
- 'use strict';
-
- // Defaults
- $.minicolors = {
- defaults: {
- animationSpeed: 50,
- animationEasing: 'swing',
- change: null,
- changeDelay: 0,
- control: 'hue',
- defaultValue: '',
- format: 'hex',
- hide: null,
- hideSpeed: 100,
- inline: false,
- keywords: '',
- letterCase: 'lowercase',
- opacity: false,
- position: 'bottom left',
- show: null,
- showSpeed: 100,
- theme: 'default',
- swatches: []
- }
- };
-
- // Public methods
- $.extend($.fn, {
- minicolors: function(method, data) {
-
- switch(method) {
- // Destroy the control
- case 'destroy':
- $(this).each(function() {
- destroy($(this));
- });
- return $(this);
-
- // Hide the color picker
- case 'hide':
- hide();
- return $(this);
-
- // Get/set opacity
- case 'opacity':
- // Getter
- if(data === undefined) {
- // Getter
- return $(this).attr('data-opacity');
- } else {
- // Setter
- $(this).each(function() {
- updateFromInput($(this).attr('data-opacity', data));
- });
- }
- return $(this);
-
- // Get an RGB(A) object based on the current color/opacity
- case 'rgbObject':
- return rgbObject($(this), method === 'rgbaObject');
-
- // Get an RGB(A) string based on the current color/opacity
- case 'rgbString':
- case 'rgbaString':
- return rgbString($(this), method === 'rgbaString');
-
- // Get/set settings on the fly
- case 'settings':
- if(data === undefined) {
- return $(this).data('minicolors-settings');
- } else {
- // Setter
- $(this).each(function() {
- var settings = $(this).data('minicolors-settings') || {};
- destroy($(this));
- $(this).minicolors($.extend(true, settings, data));
- });
- }
- return $(this);
-
- // Show the color picker
- case 'show':
- show($(this).eq(0));
- return $(this);
-
- // Get/set the hex color value
- case 'value':
- if(data === undefined) {
- // Getter
- return $(this).val();
- } else {
- // Setter
- $(this).each(function() {
- if(typeof(data) === 'object' && data !== 'null') {
- if(data.opacity) {
- $(this).attr('data-opacity', keepWithin(data.opacity, 0, 1));
- }
- if(data.color) {
- $(this).val(data.color);
- }
- } else {
- $(this).val(data);
- }
- updateFromInput($(this));
- });
- }
- return $(this);
-
- // Initializes the control
- default:
- if(method !== 'create') data = method;
- $(this).each(function() {
- init($(this), data);
- });
- return $(this);
-
- }
-
- }
- });
-
- // Initialize input elements
- function init(input, settings) {
- var minicolors = $('
');
- var defaults = $.minicolors.defaults;
- var size;
- var swatches;
- var swatch;
- var panel;
- var i;
-
- // Do nothing if already initialized
- if(input.data('minicolors-initialized')) return;
-
- // Handle settings
- settings = $.extend(true, {}, defaults, settings);
-
- // The wrapper
- minicolors
- .addClass('minicolors-theme-' + settings.theme)
- .toggleClass('minicolors-with-opacity', settings.opacity);
-
- // Custom positioning
- if(settings.position !== undefined) {
- $.each(settings.position.split(' '), function() {
- minicolors.addClass('minicolors-position-' + this);
- });
- }
-
- // Input size
- if(settings.format === 'rgb') {
- size = settings.opacity ? '25' : '20';
- } else {
- size = settings.keywords ? '11' : '7';
- }
-
- // The input
- input
- .addClass('minicolors-input')
- .data('minicolors-initialized', false)
- .data('minicolors-settings', settings)
- .prop('size', size)
- .wrap(minicolors)
- .after(
- '
' +
- '
' +
- '
' +
- '
' +
- '
'
- );
-
- // The swatch
- if(!settings.inline) {
- input.after('
');
- input.next('.minicolors-input-swatch').on('click', function(event) {
- event.preventDefault();
- input.focus();
- });
- }
-
- // Prevent text selection in IE
- panel = input.parent().find('.minicolors-panel');
- panel.on('selectstart', function() { return false; }).end();
-
- // Swatches
- if(settings.swatches && settings.swatches.length !== 0) {
- panel.addClass('minicolors-with-swatches');
- swatches = $('
')
- .appendTo(panel);
- for(i = 0; i < settings.swatches.length; ++i) {
- swatch = settings.swatches[i];
- swatch = isRgb(swatch) ? parseRgb(swatch, true) : hex2rgb(parseHex(swatch, true));
- $('
')
- .appendTo(swatches)
- .data('swatch-color', settings.swatches[i])
- .find('.minicolors-swatch-color')
- .css({
- backgroundColor: rgb2hex(swatch),
- opacity: swatch.a
- });
- settings.swatches[i] = swatch;
- }
- }
-
- // Inline controls
- if(settings.inline) input.parent().addClass('minicolors-inline');
-
- updateFromInput(input, false);
-
- input.data('minicolors-initialized', true);
- }
-
- // Returns the input back to its original state
- function destroy(input) {
- var minicolors = input.parent();
-
- // Revert the input element
- input
- .removeData('minicolors-initialized')
- .removeData('minicolors-settings')
- .removeProp('size')
- .removeClass('minicolors-input');
-
- // Remove the wrap and destroy whatever remains
- minicolors.before(input).remove();
- }
-
- // Shows the specified dropdown panel
- function show(input) {
- var minicolors = input.parent();
- var panel = minicolors.find('.minicolors-panel');
- var settings = input.data('minicolors-settings');
-
- // Do nothing if uninitialized, disabled, inline, or already open
- if(
- !input.data('minicolors-initialized') ||
- input.prop('disabled') ||
- minicolors.hasClass('minicolors-inline') ||
- minicolors.hasClass('minicolors-focus')
- ) return;
-
- hide();
-
- minicolors.addClass('minicolors-focus');
- panel
- .stop(true, true)
- .fadeIn(settings.showSpeed, function() {
- if(settings.show) settings.show.call(input.get(0));
- });
- }
-
- // Hides all dropdown panels
- function hide() {
- $('.minicolors-focus').each(function() {
- var minicolors = $(this);
- var input = minicolors.find('.minicolors-input');
- var panel = minicolors.find('.minicolors-panel');
- var settings = input.data('minicolors-settings');
-
- panel.fadeOut(settings.hideSpeed, function() {
- if(settings.hide) settings.hide.call(input.get(0));
- minicolors.removeClass('minicolors-focus');
- });
-
- });
- }
-
- // Moves the selected picker
- function move(target, event, animate) {
- var input = target.parents('.minicolors').find('.minicolors-input');
- var settings = input.data('minicolors-settings');
- var picker = target.find('[class$=-picker]');
- var offsetX = target.offset().left;
- var offsetY = target.offset().top;
- var x = Math.round(event.pageX - offsetX);
- var y = Math.round(event.pageY - offsetY);
- var duration = animate ? settings.animationSpeed : 0;
- var wx, wy, r, phi;
-
- // Touch support
- if(event.originalEvent.changedTouches) {
- x = event.originalEvent.changedTouches[0].pageX - offsetX;
- y = event.originalEvent.changedTouches[0].pageY - offsetY;
- }
-
- // Constrain picker to its container
- if(x < 0) x = 0;
- if(y < 0) y = 0;
- if(x > target.width()) x = target.width();
- if(y > target.height()) y = target.height();
-
- // Constrain color wheel values to the wheel
- if(target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid')) {
- wx = 75 - x;
- wy = 75 - y;
- r = Math.sqrt(wx * wx + wy * wy);
- phi = Math.atan2(wy, wx);
- if(phi < 0) phi += Math.PI * 2;
- if(r > 75) {
- r = 75;
- x = 75 - (75 * Math.cos(phi));
- y = 75 - (75 * Math.sin(phi));
- }
- x = Math.round(x);
- y = Math.round(y);
- }
-
- // Move the picker
- if(target.is('.minicolors-grid')) {
- picker
- .stop(true)
- .animate({
- top: y + 'px',
- left: x + 'px'
- }, duration, settings.animationEasing, function() {
- updateFromControl(input, target);
- });
- } else {
- picker
- .stop(true)
- .animate({
- top: y + 'px'
- }, duration, settings.animationEasing, function() {
- updateFromControl(input, target);
- });
- }
- }
-
- // Sets the input based on the color picker values
- function updateFromControl(input, target) {
-
- function getCoords(picker, container) {
- var left, top;
- if(!picker.length || !container) return null;
- left = picker.offset().left;
- top = picker.offset().top;
-
- return {
- x: left - container.offset().left + (picker.outerWidth() / 2),
- y: top - container.offset().top + (picker.outerHeight() / 2)
- };
- }
-
- var hue, saturation, brightness, x, y, r, phi;
- var hex = input.val();
- var opacity = input.attr('data-opacity');
-
- // Helpful references
- var minicolors = input.parent();
- var settings = input.data('minicolors-settings');
- var swatch = minicolors.find('.minicolors-input-swatch');
-
- // Panel objects
- var grid = minicolors.find('.minicolors-grid');
- var slider = minicolors.find('.minicolors-slider');
- var opacitySlider = minicolors.find('.minicolors-opacity-slider');
-
- // Picker objects
- var gridPicker = grid.find('[class$=-picker]');
- var sliderPicker = slider.find('[class$=-picker]');
- var opacityPicker = opacitySlider.find('[class$=-picker]');
-
- // Picker positions
- var gridPos = getCoords(gridPicker, grid);
- var sliderPos = getCoords(sliderPicker, slider);
- var opacityPos = getCoords(opacityPicker, opacitySlider);
-
- // Handle colors
- if(target.is('.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider')) {
-
- // Determine HSB values
- switch(settings.control) {
- case 'wheel':
- // Calculate hue, saturation, and brightness
- x = (grid.width() / 2) - gridPos.x;
- y = (grid.height() / 2) - gridPos.y;
- r = Math.sqrt(x * x + y * y);
- phi = Math.atan2(y, x);
- if(phi < 0) phi += Math.PI * 2;
- if(r > 75) {
- r = 75;
- gridPos.x = 69 - (75 * Math.cos(phi));
- gridPos.y = 69 - (75 * Math.sin(phi));
- }
- saturation = keepWithin(r / 0.75, 0, 100);
- hue = keepWithin(phi * 180 / Math.PI, 0, 360);
- brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
- hex = hsb2hex({
- h: hue,
- s: saturation,
- b: brightness
- });
-
- // Update UI
- slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
- break;
-
- case 'saturation':
- // Calculate hue, saturation, and brightness
- hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
- saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
- brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
- hex = hsb2hex({
- h: hue,
- s: saturation,
- b: brightness
- });
-
- // Update UI
- slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness }));
- minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100);
- break;
-
- case 'brightness':
- // Calculate hue, saturation, and brightness
- hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360);
- saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
- brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100);
- hex = hsb2hex({
- h: hue,
- s: saturation,
- b: brightness
- });
-
- // Update UI
- slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 }));
- minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100));
- break;
-
- default:
- // Calculate hue, saturation, and brightness
- hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360);
- saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100);
- brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100);
- hex = hsb2hex({
- h: hue,
- s: saturation,
- b: brightness
- });
-
- // Update UI
- grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 }));
- break;
- }
-
- // Handle opacity
- if(settings.opacity) {
- opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2);
- } else {
- opacity = 1;
- }
-
- updateInput(input, hex, opacity);
- }
- else {
- // Set swatch color
- swatch.find('span').css({
- backgroundColor: hex,
- opacity: opacity
- });
-
- // Handle change event
- doChange(input, hex, opacity);
- }
- }
-
- // Sets the value of the input and does the appropriate conversions
- // to respect settings, also updates the swatch
- function updateInput(input, value, opacity) {
- var rgb;
-
- // Helpful references
- var minicolors = input.parent();
- var settings = input.data('minicolors-settings');
- var swatch = minicolors.find('.minicolors-input-swatch');
-
- if(settings.opacity) input.attr('data-opacity', opacity);
-
- // Set color string
- if(settings.format === 'rgb') {
- // Returns RGB(A) string
-
- // Checks for input format and does the conversion
- if(isRgb(value)) {
- rgb = parseRgb(value, true);
- }
- else {
- rgb = hex2rgb(parseHex(value, true));
- }
-
- opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1);
- if(isNaN(opacity) || !settings.opacity) opacity = 1;
-
- if(input.minicolors('rgbObject').a <= 1 && rgb && settings.opacity) {
- // Set RGBA string if alpha
- value = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')';
- } else {
- // Set RGB string (alpha = 1)
- value = 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
- }
- } else {
- // Returns hex color
-
- // Checks for input format and does the conversion
- if(isRgb(value)) {
- value = rgbString2hex(value);
- }
-
- value = convertCase(value, settings.letterCase);
- }
-
- // Update value from picker
- input.val(value);
-
- // Set swatch color
- swatch.find('span').css({
- backgroundColor: value,
- opacity: opacity
- });
-
- // Handle change event
- doChange(input, value, opacity);
- }
-
- // Sets the color picker values from the input
- function updateFromInput(input, preserveInputValue) {
- var hex, hsb, opacity, keywords, alpha, value, x, y, r, phi;
-
- // Helpful references
- var minicolors = input.parent();
- var settings = input.data('minicolors-settings');
- var swatch = minicolors.find('.minicolors-input-swatch');
-
- // Panel objects
- var grid = minicolors.find('.minicolors-grid');
- var slider = minicolors.find('.minicolors-slider');
- var opacitySlider = minicolors.find('.minicolors-opacity-slider');
-
- // Picker objects
- var gridPicker = grid.find('[class$=-picker]');
- var sliderPicker = slider.find('[class$=-picker]');
- var opacityPicker = opacitySlider.find('[class$=-picker]');
-
- // Determine hex/HSB values
- if(isRgb(input.val())) {
- // If input value is a rgb(a) string, convert it to hex color and update opacity
- hex = rgbString2hex(input.val());
- alpha = keepWithin(parseFloat(getAlpha(input.val())).toFixed(2), 0, 1);
- if(alpha) {
- input.attr('data-opacity', alpha);
- }
- } else {
- hex = convertCase(parseHex(input.val(), true), settings.letterCase);
- }
-
- if(!hex){
- hex = convertCase(parseInput(settings.defaultValue, true), settings.letterCase);
- }
- hsb = hex2hsb(hex);
-
- // Get array of lowercase keywords
- keywords = !settings.keywords ? [] : $.map(settings.keywords.split(','), function(a) {
- return $.trim(a.toLowerCase());
- });
-
- // Set color string
- if(input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1) {
- value = convertCase(input.val());
- } else {
- value = isRgb(input.val()) ? parseRgb(input.val()) : hex;
- }
-
- // Update input value
- if(!preserveInputValue) input.val(value);
-
- // Determine opacity value
- if(settings.opacity) {
- // Get from data-opacity attribute and keep within 0-1 range
- opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1);
- if(isNaN(opacity)) opacity = 1;
- input.attr('data-opacity', opacity);
- swatch.find('span').css('opacity', opacity);
-
- // Set opacity picker position
- y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height());
- opacityPicker.css('top', y + 'px');
- }
-
- // Set opacity to zero if input value is transparent
- if(input.val().toLowerCase() === 'transparent') {
- swatch.find('span').css('opacity', 0);
- }
-
- // Update swatch
- swatch.find('span').css('backgroundColor', hex);
-
- // Determine picker locations
- switch(settings.control) {
- case 'wheel':
- // Set grid position
- r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2);
- phi = hsb.h * Math.PI / 180;
- x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width());
- y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height());
- gridPicker.css({
- top: y + 'px',
- left: x + 'px'
- });
-
- // Set slider position
- y = 150 - (hsb.b / (100 / grid.height()));
- if(hex === '') y = 0;
- sliderPicker.css('top', y + 'px');
-
- // Update panel color
- slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
- break;
-
- case 'saturation':
- // Set grid position
- x = keepWithin((5 * hsb.h) / 12, 0, 150);
- y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
- gridPicker.css({
- top: y + 'px',
- left: x + 'px'
- });
-
- // Set slider position
- y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height());
- sliderPicker.css('top', y + 'px');
-
- // Update UI
- slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b }));
- minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100);
- break;
-
- case 'brightness':
- // Set grid position
- x = keepWithin((5 * hsb.h) / 12, 0, 150);
- y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height());
- gridPicker.css({
- top: y + 'px',
- left: x + 'px'
- });
-
- // Set slider position
- y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height());
- sliderPicker.css('top', y + 'px');
-
- // Update UI
- slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 }));
- minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100));
- break;
-
- default:
- // Set grid position
- x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width());
- y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height());
- gridPicker.css({
- top: y + 'px',
- left: x + 'px'
- });
-
- // Set slider position
- y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height());
- sliderPicker.css('top', y + 'px');
-
- // Update panel color
- grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 }));
- break;
- }
-
- // Fire change event, but only if minicolors is fully initialized
- if(input.data('minicolors-initialized')) {
- doChange(input, value, opacity);
- }
- }
-
- // Runs the change and changeDelay callbacks
- function doChange(input, value, opacity) {
- var settings = input.data('minicolors-settings');
- var lastChange = input.data('minicolors-lastChange');
- var obj, sel, i;
-
- // Only run if it actually changed
- if(!lastChange || lastChange.value !== value || lastChange.opacity !== opacity) {
-
- // Remember last-changed value
- input.data('minicolors-lastChange', {
- value: value,
- opacity: opacity
- });
-
- // Check and select applicable swatch
- if(settings.swatches && settings.swatches.length !== 0) {
- if(!isRgb(value)) {
- obj = hex2rgb(value);
- }
- else {
- obj = parseRgb(value, true);
- }
- sel = -1;
- for(i = 0; i < settings.swatches.length; ++i) {
- if(obj.r === settings.swatches[i].r && obj.g === settings.swatches[i].g && obj.b === settings.swatches[i].b && obj.a === settings.swatches[i].a) {
- sel = i;
- break;
- }
- }
-
- input.parent().find('.minicolors-swatches .minicolors-swatch').removeClass('selected');
- if(sel !== -1) {
- input.parent().find('.minicolors-swatches .minicolors-swatch').eq(i).addClass('selected');
- }
- }
-
- // Fire change event
- if(settings.change) {
- if(settings.changeDelay) {
- // Call after a delay
- clearTimeout(input.data('minicolors-changeTimeout'));
- input.data('minicolors-changeTimeout', setTimeout(function() {
- settings.change.call(input.get(0), value, opacity);
- }, settings.changeDelay));
- } else {
- // Call immediately
- settings.change.call(input.get(0), value, opacity);
- }
- }
- input.trigger('change').trigger('input');
- }
- }
-
- // Generates an RGB(A) object based on the input's value
- function rgbObject(input) {
- var rgb,
- opacity = $(input).attr('data-opacity');
- if( isRgb($(input).val()) ) {
- rgb = parseRgb($(input).val(), true);
- } else {
- var hex = parseHex($(input).val(), true);
- rgb = hex2rgb(hex);
- }
- if( !rgb ) return null;
- if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) });
- return rgb;
- }
-
- // Generates an RGB(A) string based on the input's value
- function rgbString(input, alpha) {
- var rgb,
- opacity = $(input).attr('data-opacity');
- if( isRgb($(input).val()) ) {
- rgb = parseRgb($(input).val(), true);
- } else {
- var hex = parseHex($(input).val(), true);
- rgb = hex2rgb(hex);
- }
- if( !rgb ) return null;
- if( opacity === undefined ) opacity = 1;
- if( alpha ) {
- return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')';
- } else {
- return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')';
- }
- }
-
- // Converts to the letter case specified in settings
- function convertCase(string, letterCase) {
- return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase();
- }
-
- // Parses a string and returns a valid hex string when possible
- function parseHex(string, expand) {
- string = string.replace(/^#/g, '');
- if(!string.match(/^[A-F0-9]{3,6}/ig)) return '';
- if(string.length !== 3 && string.length !== 6) return '';
- if(string.length === 3 && expand) {
- string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2];
- }
- return '#' + string;
- }
-
- // Parses a string and returns a valid RGB(A) string when possible
- function parseRgb(string, obj) {
- var values = string.replace(/[^\d,.]/g, '');
- var rgba = values.split(',');
-
- rgba[0] = keepWithin(parseInt(rgba[0], 10), 0, 255);
- rgba[1] = keepWithin(parseInt(rgba[1], 10), 0, 255);
- rgba[2] = keepWithin(parseInt(rgba[2], 10), 0, 255);
- if(rgba[3]) {
- rgba[3] = keepWithin(parseFloat(rgba[3], 10), 0, 1);
- }
-
- // Return RGBA object
- if( obj ) {
- if (rgba[3]) {
- return {
- r: rgba[0],
- g: rgba[1],
- b: rgba[2],
- a: rgba[3]
- };
- } else {
- return {
- r: rgba[0],
- g: rgba[1],
- b: rgba[2]
- };
- }
- }
-
- // Return RGBA string
- if(typeof(rgba[3]) !== 'undefined' && rgba[3] <= 1) {
- return 'rgba(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ', ' + rgba[3] + ')';
- } else {
- return 'rgb(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ')';
- }
-
- }
-
- // Parses a string and returns a valid color string when possible
- function parseInput(string, expand) {
- if(isRgb(string)) {
- // Returns a valid rgb(a) string
- return parseRgb(string);
- } else {
- return parseHex(string, expand);
- }
- }
-
- // Keeps value within min and max
- function keepWithin(value, min, max) {
- if(value < min) value = min;
- if(value > max) value = max;
- return value;
- }
-
- // Checks if a string is a valid RGB(A) string
- function isRgb(string) {
- var rgb = string.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
- return (rgb && rgb.length === 4) ? true : false;
- }
-
- // Function to get alpha from a RGB(A) string
- function getAlpha(rgba) {
- rgba = rgba.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+(\.\d{1,2})?|\.\d{1,2})[\s+]?/i);
- return (rgba && rgba.length === 6) ? rgba[4] : '1';
- }
-
- // Converts an HSB object to an RGB object
- function hsb2rgb(hsb) {
- var rgb = {};
- var h = Math.round(hsb.h);
- var s = Math.round(hsb.s * 255 / 100);
- var v = Math.round(hsb.b * 255 / 100);
- if(s === 0) {
- rgb.r = rgb.g = rgb.b = v;
- } else {
- var t1 = v;
- var t2 = (255 - s) * v / 255;
- var t3 = (t1 - t2) * (h % 60) / 60;
- if(h === 360) h = 0;
- if(h < 60) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; }
- else if(h < 120) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; }
- else if(h < 180) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; }
- else if(h < 240) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; }
- else if(h < 300) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; }
- else if(h < 360) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; }
- else { rgb.r = 0; rgb.g = 0; rgb.b = 0; }
- }
- return {
- r: Math.round(rgb.r),
- g: Math.round(rgb.g),
- b: Math.round(rgb.b)
- };
- }
-
- // Converts an RGB string to a hex string
- function rgbString2hex(rgb){
- rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
- return (rgb && rgb.length === 4) ? '#' +
- ('0' + parseInt(rgb[1],10).toString(16)).slice(-2) +
- ('0' + parseInt(rgb[2],10).toString(16)).slice(-2) +
- ('0' + parseInt(rgb[3],10).toString(16)).slice(-2) : '';
- }
-
- // Converts an RGB object to a hex string
- function rgb2hex(rgb) {
- var hex = [
- rgb.r.toString(16),
- rgb.g.toString(16),
- rgb.b.toString(16)
- ];
- $.each(hex, function(nr, val) {
- if(val.length === 1) hex[nr] = '0' + val;
- });
- return '#' + hex.join('');
- }
-
- // Converts an HSB object to a hex string
- function hsb2hex(hsb) {
- return rgb2hex(hsb2rgb(hsb));
- }
-
- // Converts a hex string to an HSB object
- function hex2hsb(hex) {
- var hsb = rgb2hsb(hex2rgb(hex));
- if(hsb.s === 0) hsb.h = 360;
- return hsb;
- }
-
- // Converts an RGB object to an HSB object
- function rgb2hsb(rgb) {
- var hsb = { h: 0, s: 0, b: 0 };
- var min = Math.min(rgb.r, rgb.g, rgb.b);
- var max = Math.max(rgb.r, rgb.g, rgb.b);
- var delta = max - min;
- hsb.b = max;
- hsb.s = max !== 0 ? 255 * delta / max : 0;
- if(hsb.s !== 0) {
- if(rgb.r === max) {
- hsb.h = (rgb.g - rgb.b) / delta;
- } else if(rgb.g === max) {
- hsb.h = 2 + (rgb.b - rgb.r) / delta;
- } else {
- hsb.h = 4 + (rgb.r - rgb.g) / delta;
- }
- } else {
- hsb.h = -1;
- }
- hsb.h *= 60;
- if(hsb.h < 0) {
- hsb.h += 360;
- }
- hsb.s *= 100/255;
- hsb.b *= 100/255;
- return hsb;
- }
-
- // Converts a hex string to an RGB object
- function hex2rgb(hex) {
- hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
- return {
- r: hex >> 16,
- g: (hex & 0x00FF00) >> 8,
- b: (hex & 0x0000FF)
- };
- }
-
- // Handle events
- $([document])
- // Hide on clicks outside of the control
- .on('mousedown.minicolors touchstart.minicolors', function(event) {
- if(!$(event.target).parents().add(event.target).hasClass('minicolors')) {
- hide();
- }
- })
- // Start moving
- .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) {
- var target = $(this);
- event.preventDefault();
- $(event.delegateTarget).data('minicolors-target', target);
- move(target, event, true);
- })
- // Move pickers
- .on('mousemove.minicolors touchmove.minicolors', function(event) {
- var target = $(event.delegateTarget).data('minicolors-target');
- if(target) move(target, event);
- })
- // Stop moving
- .on('mouseup.minicolors touchend.minicolors', function() {
- $(this).removeData('minicolors-target');
- })
- // Selected a swatch
- .on('click.minicolors', '.minicolors-swatches li', function(event) {
- event.preventDefault();
- var target = $(this), input = target.parents('.minicolors').find('.minicolors-input'), color = target.data('swatch-color');
- updateInput(input, color, getAlpha(color));
- updateFromInput(input);
- })
- // Show panel when swatch is clicked
- .on('mousedown.minicolors touchstart.minicolors', '.minicolors-input-swatch', function(event) {
- var input = $(this).parent().find('.minicolors-input');
- event.preventDefault();
- show(input);
- })
- // Show on focus
- .on('focus.minicolors', '.minicolors-input', function() {
- var input = $(this);
- if(!input.data('minicolors-initialized')) return;
- show(input);
- })
- // Update value on blur
- .on('blur.minicolors', '.minicolors-input', function() {
- var input = $(this);
- var settings = input.data('minicolors-settings');
- var keywords;
- var hex;
- var rgba;
- var swatchOpacity;
- var value;
-
- if(!input.data('minicolors-initialized')) return;
-
- // Get array of lowercase keywords
- keywords = !settings.keywords ? [] : $.map(settings.keywords.split(','), function(a) {
- return $.trim(a.toLowerCase());
- });
-
- // Set color string
- if(input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1) {
- value = input.val();
- } else {
- // Get RGBA values for easy conversion
- if(isRgb(input.val())) {
- rgba = parseRgb(input.val(), true);
- } else {
- hex = parseHex(input.val(), true);
- rgba = hex ? hex2rgb(hex) : null;
- }
-
- // Convert to format
- if(rgba === null) {
- value = settings.defaultValue;
- } else if(settings.format === 'rgb') {
- value = settings.opacity ?
- parseRgb('rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + input.attr('data-opacity') + ')') :
- parseRgb('rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')');
- } else {
- value = rgb2hex(rgba);
- }
- }
-
- // Update swatch opacity
- swatchOpacity = settings.opacity ? input.attr('data-opacity') : 1;
- if(value.toLowerCase() === 'transparent') swatchOpacity = 0;
- input
- .closest('.minicolors')
- .find('.minicolors-input-swatch > span')
- .css('opacity', swatchOpacity);
-
- // Set input value
- input.val(value);
-
- // Is it blank?
- if(input.val() === '') input.val(parseInput(settings.defaultValue, true));
-
- // Adjust case
- input.val(convertCase(input.val(), settings.letterCase));
-
- })
- // Handle keypresses
- .on('keydown.minicolors', '.minicolors-input', function(event) {
- var input = $(this);
- if(!input.data('minicolors-initialized')) return;
- switch(event.keyCode) {
- case 9: // tab
- hide();
- break;
- case 13: // enter
- case 27: // esc
- hide();
- input.blur();
- break;
- }
- })
- // Update on keyup
- .on('keyup.minicolors', '.minicolors-input', function() {
- var input = $(this);
- if(!input.data('minicolors-initialized')) return;
- updateFromInput(input, true);
- })
- // Update on paste
- .on('paste.minicolors', '.minicolors-input', function() {
- var input = $(this);
- if(!input.data('minicolors-initialized')) return;
- setTimeout(function() {
- updateFromInput(input, true);
- }, 1);
- });
-}));
diff --git a/pageflow.gemspec b/pageflow.gemspec
index 3c33243d81..47dcd39ff0 100644
--- a/pageflow.gemspec
+++ b/pageflow.gemspec
@@ -94,9 +94,6 @@ Gem::Specification.new do |s|
# Editor file upload helper
s.add_dependency 'jquery-fileupload-rails', '0.4.1'
- # Color picker
- s.add_dependency 'jquery-minicolors-rails', '~> 2.2'
-
s.add_dependency 'backbone-rails', '~> 1.0.0'
# Further helpers and conventions on top of Backbone
diff --git a/rollup.config.js b/rollup.config.js
index 9e908816f5..50f53fda3a 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -29,7 +29,6 @@ const editorGlobals = {
...frontendGlobals,
'backbone.babysitter': 'Backbone.ChildViewContainer',
'cocktail': 'Cocktail',
- 'jquery.minicolors': 'jQuery',
'backbone.marionette': 'Backbone.Marionette',
'wysihtml5': 'wysihtml5'
};