123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- import $ from 'jquery';
- import clamp from 'mout/math/clamp';
- import bind from 'mout/function/bind';
- import { rgbstr2hex, hsb2hex, hex2hsb, hex2rgb, parseHex } from '../../utils/colors';
- const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
- const body = $('body');
- const MOUSEDOWN = 'mousedown touchstart MSPointerDown pointerdown';
- const MOUSEMOVE = 'mousemove touchmove MSPointerMove pointermove';
- const MOUSEUP = 'mouseup touchend MSPointerUp pointerup';
- const FOCUSIN = isFirefox ? 'focus' : 'focusin';
- export default class ColorpickerField {
- constructor(selector) {
- this.selector = selector;
- this.field = $(this.selector);
- this.options = Object.assign({}, this.field.data('grav-colorpicker'));
- this.built = false;
- this.attach();
- if (this.options.update) {
- this.field.on('change._grav_colorpicker', (event, field, hex, opacity) => {
- let backgroundColor = hex;
- let rgb = hex2rgb(hex);
- if (opacity < 1) {
- backgroundColor = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + opacity + ')';
- }
- let target = field.closest(this.options.update);
- if (!target.length) {
- target = field.siblings(this.options.update);
- }
- if (!target.length) {
- target = field.parent('.g-colorpicker').find(this.options.update);
- }
- target.css({ backgroundColor });
- });
- }
- }
- attach() {
- body.on(FOCUSIN, this.selector, (event) => this.show(event, event.currentTarget));
- body.on(MOUSEDOWN, this.selector + ' .g-colorpicker, ' + this.selector + ' .g-colorpicker i', this.bound('iconClick'));
- body.on('keydown', this.selector, (event) => {
- switch (event.keyCode) {
- case 9: // tab
- this.hide();
- break;
- case 13: // enter
- case 27: // esc
- this.hide();
- event.currentTarget.blur();
- break;
- }
- return true;
- });
- // Update on keyup
- body.on('keyup', this.selector, (event) => {
- this.updateFromInput(true, event.currentTarget);
- return true;
- });
- // Update on paste
- body.on('paste', this.selector, (event) => {
- setTimeout(() => this.updateFromInput(true, event.currentTarget), 1);
- });
- }
- show(event, target) {
- target = $(target);
- if (!this.built) {
- this.build();
- }
- this.element = target;
- this.reposition();
- this.wrapper.addClass('cp-visible');
- this.updateFromInput();
- let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
- this.wrapper.on(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
- body.on(MOUSEMOVE, this.bound('bodyMove'));
- body.on(MOUSEDOWN, this.bound('bodyClick'));
- body.on(MOUSEUP, this.bound('targetReset'));
- $(mainContainer).on('scroll', this.bound('reposition'));
- }
- hide() {
- if (!this.built) { return; }
- this.wrapper.removeClass('cp-visible');
- let mainContainer = $('#admin-main .content-wrapper').data('scrollbar').getViewElement();
- this.wrapper.undelegate(MOUSEDOWN, '.cp-grid, .cp-slider, .cp-opacity-slider', this.bound('bodyDown'));
- body.off(MOUSEMOVE, this.bound('bodyMove'));
- body.off(MOUSEDOWN, this.bound('bodyClick'));
- body.off(MOUSEUP, this.bound('targetReset'));
- $(mainContainer).off('scroll', this.bound('reposition'));
- }
- build() {
- this.wrapper = $('<div class="cp-wrapper cp-with-opacity cp-mode-hue" />');
- this.slider = $('<div class="cp-slider cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-picker" />'));
- this.opacitySlider = $('<div class="cp-opacity-slider cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-picker" />'));
- this.grid = $('<div class="cp-grid cp-sprite" />').appendTo(this.wrapper).append($('<div class="cp-grid-inner" />')).append($('<div class="cp-picker" />'));
- $('<div />').appendTo(this.grid.find('.cp-picker'));
- let tabs = $('<div class="cp-tabs" />').appendTo(this.wrapper);
- this.tabs = {
- hue: $('<div class="cp-tab-hue active" />').text('HUE').appendTo(tabs),
- brightness: $('<div class="cp-tab-brightness" />').text('BRI').appendTo(tabs),
- saturation: $('<div class="cp-tab-saturation" />').text('SAT').appendTo(tabs),
- wheel: $('<div class="cp-tab-wheel" />').text('WHEEL').appendTo(tabs),
- transparent: $('<div class="cp-tab-transp" />').text('TRANSPARENT').appendTo(tabs)
- };
- tabs.on(MOUSEDOWN, '> div', (event) => {
- let element = $(event.currentTarget);
- if (element.is(this.tabs.transparent)) {
- let sliderHeight = this.opacitySlider.height();
- this.opacity = 0;
- this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
- this.move(this.opacitySlider, { manualOpacity: true });
- return;
- }
- let active = tabs.find('.active');
- let mode = active.attr('class').replace(/\s|active|cp-tab-/g, '');
- let newMode = element.attr('class').replace(/\s|active|cp-tab-/g, '');
- this.wrapper.removeClass('cp-mode-' + mode).addClass('cp-mode-' + newMode);
- active.removeClass('active');
- element.addClass('active');
- this.mode = newMode;
- this.updateFromInput();
- });
- this.wrapper.appendTo('.content-wrapper');
- this.built = true;
- this.mode = 'hue';
- }
- reposition() {
- let ct = $('.content-wrapper')[0];
- let offset = this.element[0].getBoundingClientRect();
- let ctOffset = ct.getBoundingClientRect();
- let delta = { x: 0, y: 0 };
- if (this.options.offset) {
- delta.x = this.options.offset.x || 0;
- delta.y = this.options.offset.y || 0;
- }
- this.wrapper.css({
- top: offset.top + offset.height + ct.scrollTop - ctOffset.top + delta.y,
- left: offset.left + ct.scrollLeft - ctOffset.left + delta.x
- });
- }
- iconClick(event) {
- if (this.wrapper && this.wrapper.hasClass('cp-visible')) { return true; }
- event && event.preventDefault();
- let input = $(event.currentTarget).find('input');
- setTimeout(() => input.focus(), 50);
- }
- bodyMove(event) {
- event && event.preventDefault();
- if (this.target) { this.move(this.target, event); }
- }
- bodyClick(event) {
- let target = $(event.target);
- if (!target.closest('.cp-wrapper').length && !target.is(this.selector)) {
- this.hide();
- }
- }
- bodyDown(event) {
- event && event.preventDefault();
- this.target = $(event.currentTarget);
- this.move(this.target, event, true);
- }
- targetReset(event) {
- event && event.preventDefault();
- this.target = null;
- }
- move(target, event) {
- let input = this.element;
- let picker = target.find('.cp-picker');
- let clientRect = target[0].getBoundingClientRect();
- let offsetX = clientRect.left + window.scrollX;
- let offsetY = clientRect.top + window.scrollY;
- let x = Math.round((event ? event.pageX : 0) - offsetX);
- let y = Math.round((event ? event.pageY : 0) - offsetY);
- let wx;
- let wy;
- let r;
- let phi;
- // Touch support
- let touchEvents = event.changedTouches || (event.originalEvent && event.originalEvent.changedTouches);
- if (event && touchEvents) {
- x = (touchEvents ? touchEvents[0].pageX : 0) - offsetX;
- y = (touchEvents ? touchEvents[0].pageY : 0) - offsetY;
- }
- if (event && event.manualOpacity) {
- y = clientRect.height;
- }
- // Constrain picker to its container
- if (x < 0) x = 0;
- if (y < 0) y = 0;
- if (x > clientRect.width) x = clientRect.width;
- if (y > clientRect.height) y = clientRect.height;
- // Constrain color wheel values to the wheel
- if (target.parent('.cp-mode-wheel').length && picker.parent('.cp-grid').length) {
- 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) {
- 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.hasClass('cp-grid')) {
- picker.css({
- top: y,
- left: x
- });
- this.updateFromPicker(input, target);
- } else {
- picker.css({
- top: y
- });
- this.updateFromPicker(input, target);
- }
- }
- updateFromInput(dontFireEvent, element) {
- element = element ? $(element) : this.element;
- let value = element.val();
- let opacity = value.replace(/\s/g, '').match(/^rgba?\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},(.+)\)/);
- let hex;
- let hsb;
- value = rgbstr2hex(value) || value;
- opacity = opacity ? clamp(opacity[1], 0, 1) : 1;
- if (!(hex = parseHex(value))) { hex = '#ffffff'; }
- hsb = hex2hsb(hex);
- if (this.built) {
- // opacity
- this.opacity = opacity;
- var sliderHeight = this.opacitySlider.height();
- this.opacitySlider.find('.cp-picker').css({ 'top': clamp(sliderHeight - (sliderHeight * this.opacity), 0, sliderHeight) });
- // bg color
- let gridHeight = this.grid.height();
- let gridWidth = this.grid.width();
- let r;
- let phi;
- let x;
- let y;
- sliderHeight = this.slider.height();
- switch (this.mode) {
- case 'wheel':
- // Set grid position
- r = clamp(Math.ceil(hsb.s * 0.75), 0, gridHeight / 2);
- phi = hsb.h * Math.PI / 180;
- x = clamp(75 - Math.cos(phi) * r, 0, gridWidth);
- y = clamp(75 - Math.sin(phi) * r, 0, gridHeight);
- this.grid.css({ backgroundColor: 'transparent' }).find('.cp-picker').css({
- top: y,
- left: x
- });
- // Set slider position
- y = 150 - (hsb.b / (100 / gridHeight));
- if (hex === '') y = 0;
- this.slider.find('.cp-picker').css({ top: y });
- // Update panel color
- this.slider.css({
- backgroundColor: hsb2hex({
- h: hsb.h,
- s: hsb.s,
- b: 100
- })
- });
- break;
- case 'saturation':
- // Set grid position
- x = clamp((5 * hsb.h) / 12, 0, 150);
- y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
- this.grid.find('.cp-picker').css({
- top: y,
- left: x
- });
- // Set slider position
- y = clamp(sliderHeight - (hsb.s * (sliderHeight / 100)), 0, sliderHeight);
- this.slider.find('.cp-picker').css({ top: y });
- // Update UI
- this.slider.css({
- backgroundColor: hsb2hex({
- h: hsb.h,
- s: 100,
- b: hsb.b
- })
- });
- this.grid.find('.cp-grid-inner').css({ opacity: hsb.s / 100 });
- break;
- case 'brightness':
- // Set grid position
- x = clamp((5 * hsb.h) / 12, 0, 150);
- y = clamp(gridHeight - Math.ceil(hsb.s / (100 / gridHeight)), 0, gridHeight);
- this.grid.find('.cp-picker').css({
- top: y,
- left: x
- });
- // Set slider position
- y = clamp(sliderHeight - (hsb.b * (sliderHeight / 100)), 0, sliderHeight);
- this.slider.find('.cp-picker').css({ top: y });
- // Update UI
- this.slider.css({
- backgroundColor: hsb2hex({
- h: hsb.h,
- s: hsb.s,
- b: 100
- })
- });
- this.grid.find('.cp-grid-inner').css({ opacity: 1 - (hsb.b / 100) });
- break;
- case 'hue':
- default:
- // Set grid position
- x = clamp(Math.ceil(hsb.s / (100 / gridWidth)), 0, gridWidth);
- y = clamp(gridHeight - Math.ceil(hsb.b / (100 / gridHeight)), 0, gridHeight);
- this.grid.find('.cp-picker').css({
- top: y,
- left: x
- });
- // Set slider position
- y = clamp(sliderHeight - (hsb.h / (360 / sliderHeight)), 0, sliderHeight);
- this.slider.find('.cp-picker').css({ top: y });
- // Update panel color
- this.grid.css({
- backgroundColor: hsb2hex({
- h: hsb.h,
- s: 100,
- b: 100
- })
- });
- break;
- }
- }
- if (!dontFireEvent) { element.val(this.getValue(hex)); }
- (this.element || element).trigger('change._grav_colorpicker', [element, hex, opacity]);
- }
- updateFromPicker(input, target) {
- var getCoords = function(picker, container) {
- var left, top;
- if (!picker.length || !container) return null;
- left = picker[0].getBoundingClientRect().left;
- top = picker[0].getBoundingClientRect().top;
- return {
- x: left - container[0].getBoundingClientRect().left + (picker[0].offsetWidth / 2),
- y: top - container[0].getBoundingClientRect().top + (picker[0].offsetHeight / 2)
- };
- };
- let hex;
- let hue;
- let saturation;
- let brightness;
- let x;
- let y;
- let r;
- let phi;
- // Panel objects
- let grid = this.wrapper.find('.cp-grid');
- let slider = this.wrapper.find('.cp-slider');
- let opacitySlider = this.wrapper.find('.cp-opacity-slider');
- // Picker objects
- let gridPicker = grid.find('.cp-picker');
- let sliderPicker = slider.find('.cp-picker');
- let opacityPicker = opacitySlider.find('.cp-picker');
- // Picker positions
- let gridPos = getCoords(gridPicker, grid);
- let sliderPos = getCoords(sliderPicker, slider);
- let opacityPos = getCoords(opacityPicker, opacitySlider);
- // Sizes
- let gridWidth = grid[0].getBoundingClientRect().width;
- let gridHeight = grid[0].getBoundingClientRect().height;
- let sliderHeight = slider[0].getBoundingClientRect().height;
- let opacitySliderHeight = opacitySlider[0].getBoundingClientRect().height;
- let value = this.element.val();
- value = rgbstr2hex(value) || value;
- if (!(hex = parseHex(value))) { hex = '#ffffff'; }
- // Handle colors
- if (target.hasClass('cp-grid') || target.hasClass('cp-slider')) {
- // Determine HSB values
- switch (this.mode) {
- case 'wheel':
- // Calculate hue, saturation, and brightness
- x = (gridWidth / 2) - gridPos.x;
- y = (gridHeight / 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 = clamp(r / 0.75, 0, 100);
- hue = clamp(phi * 180 / Math.PI, 0, 360);
- brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 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 = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
- saturation = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
- brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
- hex = hsb2hex({
- h: hue,
- s: saturation,
- b: brightness
- });
- // Update UI
- slider.css({
- backgroundColor: hsb2hex({
- h: hue,
- s: 100,
- b: brightness
- })
- });
- grid.find('.cp-grid-inner').css({ opacity: saturation / 100 });
- break;
- case 'brightness':
- // Calculate hue, saturation, and brightness
- hue = clamp(parseInt(gridPos.x * (360 / gridWidth), 10), 0, 360);
- saturation = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 0, 100);
- brightness = clamp(100 - Math.floor(sliderPos.y * (100 / sliderHeight)), 0, 100);
- hex = hsb2hex({
- h: hue,
- s: saturation,
- b: brightness
- });
- // Update UI
- slider.css({
- backgroundColor: hsb2hex({
- h: hue,
- s: saturation,
- b: 100
- })
- });
- grid.find('.cp-grid-inner').css({ opacity: 1 - (brightness / 100) });
- break;
- default:
- // Calculate hue, saturation, and brightness
- hue = clamp(360 - parseInt(sliderPos.y * (360 / sliderHeight), 10), 0, 360);
- saturation = clamp(Math.floor(gridPos.x * (100 / gridWidth)), 0, 100);
- brightness = clamp(100 - Math.floor(gridPos.y * (100 / gridHeight)), 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 (target.hasClass('cp-opacity-slider')) {
- this.opacity = parseFloat(1 - (opacityPos.y / opacitySliderHeight)).toFixed(2);
- }
- // Adjust case
- input.val(this.getValue(hex));
- // Handle change event
- this.element.trigger('change._grav_colorpicker', [this.element, hex, this.opacity]);
- }
- getValue(hex) {
- if (this.opacity === 1) { return hex; }
- let rgb = hex2rgb(hex);
- return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + this.opacity + ')';
- }
- bound(name) {
- let bound = this._bound || (this._bound = {});
- return bound[name] || (bound[name] = bind(this[name], this));
- }
- }
- export let Instance = new ColorpickerField('[data-grav-colorpicker]');
|