UNPKG

bootstrap-colorpicker

Version:

Bootstrap Colorpicker is a modular color picker plugin for Bootstrap 4.

418 lines (352 loc) 10.3 kB
'use strict'; import $ from 'jquery'; import _defaults from './options'; /** * Handles everything related to the UI of the colorpicker popup: show, hide, position,... * @ignore */ class PopupHandler { /** * @param {Colorpicker} colorpicker * @param {Window} root */ constructor(colorpicker, root) { /** * @type {Window} */ this.root = root; /** * @type {Colorpicker} */ this.colorpicker = colorpicker; /** * @type {jQuery} */ this.popoverTarget = null; /** * @type {jQuery} */ this.popoverTip = null; /** * If true, the latest click was inside the popover * @type {boolean} */ this.clicking = false; /** * @type {boolean} */ this.hidding = false; /** * @type {boolean} */ this.showing = false; } /** * @private * @returns {jQuery|false} */ get input() { return this.colorpicker.inputHandler.input; } /** * @private * @returns {boolean} */ get hasInput() { return this.colorpicker.inputHandler.hasInput(); } /** * @private * @returns {jQuery|false} */ get addon() { return this.colorpicker.addonHandler.addon; } /** * @private * @returns {boolean} */ get hasAddon() { return this.colorpicker.addonHandler.hasAddon(); } /** * @private * @returns {boolean} */ get isPopover() { return !this.colorpicker.options.inline && !!this.popoverTip; } /** * Binds the different colorpicker elements to the focus/mouse/touch events so it reacts in order to show or * hide the colorpicker popup accordingly. It also adds the proper classes. */ bind() { let cp = this.colorpicker; if (cp.options.inline) { cp.picker.addClass('colorpicker-inline colorpicker-visible'); return; // no need to bind show/hide events for inline elements } cp.picker.addClass('colorpicker-popup colorpicker-hidden'); // there is no input or addon if (!this.hasInput && !this.hasAddon) { return; } // create Bootstrap 4 popover if (cp.options.popover) { this.createPopover(); } // bind addon show/hide events if (this.hasAddon) { // enable focus on addons if (!this.addon.attr('tabindex')) { this.addon.attr('tabindex', 0); } this.addon.on({ 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.toggle, this) }); this.addon.on({ 'focus.colorpicker': $.proxy(this.show, this) }); this.addon.on({ 'focusout.colorpicker': $.proxy(this.hide, this) }); } // bind input show/hide events if (this.hasInput && !this.hasAddon) { this.input.on({ 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.show, this), 'focus.colorpicker': $.proxy(this.show, this) }); this.input.on({ 'focusout.colorpicker': $.proxy(this.hide, this) }); } // reposition popup on window resize $(this.root).on('resize.colorpicker', $.proxy(this.reposition, this)); } /** * Unbinds any event bound by this handler */ unbind() { if (this.hasInput) { this.input.off({ 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.show, this), 'focus.colorpicker': $.proxy(this.show, this) }); this.input.off({ 'focusout.colorpicker': $.proxy(this.hide, this) }); } if (this.hasAddon) { this.addon.off({ 'mousedown.colorpicker touchstart.colorpicker': $.proxy(this.toggle, this) }); this.addon.off({ 'focus.colorpicker': $.proxy(this.show, this) }); this.addon.off({ 'focusout.colorpicker': $.proxy(this.hide, this) }); } if (this.popoverTarget) { this.popoverTarget.popover('dispose'); } $(this.root).off('resize.colorpicker', $.proxy(this.reposition, this)); $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.hide, this)); $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.onClickingInside, this)); } isClickingInside(e) { if (!e) { return false; } return ( this.isOrIsInside(this.popoverTip, e.currentTarget) || this.isOrIsInside(this.popoverTip, e.target) || this.isOrIsInside(this.colorpicker.picker, e.currentTarget) || this.isOrIsInside(this.colorpicker.picker, e.target) ); } isOrIsInside(container, element) { if (!container || !element) { return false; } element = $(element); return ( element.is(container) || container.find(element).length > 0 ); } onClickingInside(e) { this.clicking = this.isClickingInside(e); } createPopover() { let cp = this.colorpicker; this.popoverTarget = this.hasAddon ? this.addon : this.input; cp.picker.addClass('colorpicker-bs-popover-content'); this.popoverTarget.popover( $.extend( true, {}, _defaults.popover, cp.options.popover, {trigger: 'manual', content: cp.picker, html: true} ) ); this.popoverTip = $(this.popoverTarget.popover('getTipElement').data('bs.popover').tip); this.popoverTip.addClass('colorpicker-bs-popover'); this.popoverTarget.on('shown.bs.popover', $.proxy(this.fireShow, this)); this.popoverTarget.on('hidden.bs.popover', $.proxy(this.fireHide, this)); } /** * If the widget is not inside a container or inline, rearranges its position relative to its element offset. * * @param {Event} [e] * @private */ reposition(e) { if (this.popoverTarget && this.isVisible()) { this.popoverTarget.popover('update'); } } /** * Toggles the colorpicker between visible or hidden * * @fires Colorpicker#colorpickerShow * @fires Colorpicker#colorpickerHide * @param {Event} [e] */ toggle(e) { if (this.isVisible()) { this.hide(e); } else { this.show(e); } } /** * Shows the colorpicker widget if hidden. * * @fires Colorpicker#colorpickerShow * @param {Event} [e] */ show(e) { if (this.isVisible() || this.showing || this.hidding) { return; } this.showing = true; this.hidding = false; this.clicking = false; let cp = this.colorpicker; cp.lastEvent.alias = 'show'; cp.lastEvent.e = e; // Prevent showing browser native HTML5 colorpicker if ( (e && (!this.hasInput || this.input.attr('type') === 'color')) && (e && e.preventDefault) ) { e.stopPropagation(); e.preventDefault(); } // If it's a popover, add event to the document to hide the picker when clicking outside of it if (this.isPopover) { $(this.root).on('resize.colorpicker', $.proxy(this.reposition, this)); } // add visible class before popover is shown cp.picker.addClass('colorpicker-visible').removeClass('colorpicker-hidden'); if (this.popoverTarget) { this.popoverTarget.popover('show'); } else { this.fireShow(); } } fireShow() { this.hidding = false; this.showing = false; if (this.isPopover) { // Add event to hide on outside click $(this.root.document).on('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.hide, this)); $(this.root.document).on('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.onClickingInside, this)); } /** * (Colorpicker) When show() is called and the widget can be shown. * * @event Colorpicker#colorpickerShow */ this.colorpicker.trigger('colorpickerShow'); } /** * Hides the colorpicker widget. * Hide is prevented when it is triggered by an event whose target element has been clicked/touched. * * @fires Colorpicker#colorpickerHide * @param {Event} [e] */ hide(e) { if (this.isHidden() || this.showing || this.hidding) { return; } let cp = this.colorpicker, clicking = (this.clicking || this.isClickingInside(e)); this.hidding = true; this.showing = false; this.clicking = false; cp.lastEvent.alias = 'hide'; cp.lastEvent.e = e; // TODO: fix having to click twice outside when losing focus and last 2 clicks where inside the colorpicker // Prevent hide if triggered by an event and an element inside the colorpicker has been clicked/touched if (clicking) { this.hidding = false; return; } if (this.popoverTarget) { this.popoverTarget.popover('hide'); } else { this.fireHide(); } } fireHide() { this.hidding = false; this.showing = false; let cp = this.colorpicker; // add hidden class after popover is hidden cp.picker.addClass('colorpicker-hidden').removeClass('colorpicker-visible'); // Unbind window and document events, since there is no need to keep them while the popup is hidden $(this.root).off('resize.colorpicker', $.proxy(this.reposition, this)); $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.hide, this)); $(this.root.document).off('mousedown.colorpicker touchstart.colorpicker', $.proxy(this.onClickingInside, this)); /** * (Colorpicker) When hide() is called and the widget can be hidden. * * @event Colorpicker#colorpickerHide */ cp.trigger('colorpickerHide'); } focus() { if (this.hasAddon) { return this.addon.focus(); } if (this.hasInput) { return this.input.focus(); } return false; } /** * Returns true if the colorpicker element has the colorpicker-visible class and not the colorpicker-hidden one. * False otherwise. * * @returns {boolean} */ isVisible() { return this.colorpicker.picker.hasClass('colorpicker-visible') && !this.colorpicker.picker.hasClass('colorpicker-hidden'); } /** * Returns true if the colorpicker element has the colorpicker-hidden class and not the colorpicker-visible one. * False otherwise. * * @returns {boolean} */ isHidden() { return this.colorpicker.picker.hasClass('colorpicker-hidden') && !this.colorpicker.picker.hasClass('colorpicker-visible'); } } export default PopupHandler;