UNPKG

saagie-ui

Version:

Saagie UI from Saagie Design System

288 lines (225 loc) 6.81 kB
import $ from 'domtastic'; import Capitalize from './capitalize'; export default class ToggleComponent { constructor(element, globalOptions) { this.$component = $(element); this.globalOptions = globalOptions; this.instanceId = false; this.focusRecoverElement = null; } _onInit() {} _onDestroy() {} _onOpen() { this.$component.addClass(this.options.openClass); } _onClose() { this.$component.removeClass(this.options.openClass); } _isOpen() { return this.$component.hasClass(this.options.openClass); } _isClickOutside(target) { const isInside = $.contains(this.$component[0], target); const isParent = this.$component[0] === target; return !isInside && !isParent; } init(customOptions) { // Default options this.options = { openClass: 'as--open', dataAttrBase: 'data-sui-CHANGE_THIS_PART', dataAttrSuffixToggle: '', dataAttrSuffixOpen: '-open', dataAttrSuffixClose: '-close', dataAttrSuffixConfirm: '-confirm', dataAttrClicksEnabled: true, htmlToBodyEnabled: true, focusRecoverEnabled: false, keysEnabled: true, confirmOnEnterKey: false, closeOnEscapeKey: true, clickOutsideEnabled: false, onInit() {}, onOpen() {}, onConfirm() {}, onClose() {}, onEnterKey() {}, onEscapeKey() {}, onDestroy() {}, }; // Merge global options if (this.globalOptions) { this.options = $.extend({}, this.options, this.globalOptions); } // Merge custom options if (customOptions) { this.options = $.extend({}, this.options, customOptions); } // Destroy component if already init if (this.instanceId) { this.destroy(); } /* eslint-disable-next-line prefer-destructuring */ this.instanceId = (window.crypto || window.msCrypto).getRandomValues(new Uint32Array(1))[0]; // Move html at the end of the body tag if (this.options.htmlToBodyEnabled) { document.body.appendChild(this.$component[0]); } // Enabled all clicks on elements with data- attributes if (this.options.dataAttrClicksEnabled) { this.bindDataClicks(); } this.bindEvents(); this._onInit(); this.options.onInit(this.$component); // Callback return this; } toggle(clickedElement) { if (this._isOpen()) { this.close(clickedElement); } else { this.open(clickedElement); } return this; } open(clickedElement) { if (this._isOpen()) { return this; } this._onOpen(clickedElement); this.options.onOpen(this.$component, clickedElement); // Callback // Store current element with focus this.focusRecoverElement = document.activeElement; // Remove focus on activeElement if (document.activeElement) { document.activeElement.blur(); } if (this.options.keysEnabled) { this.bindKeys(); } if (this.options.clickOutsideEnabled) { this.bindClickOutside(); } return this; } close(clickedElement) { if (!this._isOpen()) { return this; } this._onClose(clickedElement); this.options.onClose(this.$component, clickedElement); // Callback // Restore element with focus (before open) if (this.options.focusRecoverEnabled && this.focusRecoverElement) { this.focusRecoverElement.focus(); } // Remove eventListeners this.unbindClickOutside(); this.unbindKeys(); return this; } confirm(clickedElement) { this.options.onConfirm(this.$component, clickedElement); // Callback this.close(clickedElement); return this; } destroy() { // Remove eventListeners this.unbindEvents(); this.unbindDataClicks(); this.unbindClickOutside(); this.unbindKeys(); this._onDestroy(); this.options.onDestroy(this.$component); // Callback if (this.options.htmlToBodyEnabled) { this.$component.remove(); } this.instanceId = false; return this; } bindDataClicks() { const instance = this; const componentId = this.$component.attr('id'); // Clicks inside component ['close', 'confirm'].forEach((type) => { this.$component.on(`click.${type}`, `[${this.options.dataAttrBase}${this.options[`dataAttrSuffix${Capitalize(type)}`]}]`, function () { instance[type](this); }); }); if (!componentId) { return; } // Clicks outside component ['toggle', 'open', 'close', 'confirm'].forEach((type) => { $(document).on(`click.data-sui${this.instanceId}.${type}`, `[${this.options.dataAttrBase}${this.options[`dataAttrSuffix${Capitalize(type)}`]}="${componentId}"]`, function (e) { e.preventDefault(); instance[type](this); }); }); } unbindDataClicks() { $(document).off(`click.data-sui${this.instanceId}`); } bindEvents() { ['toggle', 'open', 'close', 'confirm', 'destroy'].forEach((type) => { this.$component.on(type, () => { this[type](); }); }); } unbindEvents() { this.$component.off('click toggle open close confirm destroy'); } bindKeys() { $(document).on(`keydown.sui${this.instanceId}`, (event) => { this._keysMethods(event); }); } unbindKeys() { $(document).off(`keydown.sui${this.instanceId}`); } bindClickOutside() { const instance = this; setTimeout(() => { $(document).on(`click.outside-sui${this.instanceId}`, (e) => { if (instance._isClickOutside(e.target)) { instance.close(); } }); }); } unbindClickOutside() { $(document).off(`click.outside-sui${this.instanceId}`); } _keysMethods(event) { // Escape key if (event.which === 27) { this.options.onEscapeKey(this.$component, event); // Callback if (this.options.closeOnEscapeKey) { this.close(); } } // Enter key if (event.which === 13 && this.options.enterKeyToConfirmEnabled) { event.preventDefault(); this.options.onEnterKey(this.$component, event); // Callback if (this.options.confirmOnEnterKey) { this.confirm(); } } // Tab key if (event.which === 9) { this._focusInComponent(event); } } _focusInComponent(event) { if (!document.activeElement || $.contains(this.$component[0], document.activeElement)) { return; } event.preventDefault(); const $focusElementsInComponent = $('button:not([tabindex="-1"]), [href]:not([tabindex="-1"]), input:not([tabindex="-1"]), select:not([tabindex="-1"]), textarea:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])', this.$component); if (!$focusElementsInComponent.length) { return; } $focusElementsInComponent[0].focus(); } }