'use strict'; import $ from 'jquery'; import { Motion } from './foundation.util.motion'; import { Plugin } from './foundation.core.plugin'; import { RegExpEscape } from './foundation.core.utils'; import { Triggers } from './foundation.util.triggers'; /** * Toggler module. * @module foundation.toggler * @requires foundation.util.motion * @requires foundation.util.triggers */ class Toggler extends Plugin { /** * Creates a new instance of Toggler. * @class * @name Toggler * @fires Toggler#init * @param {Object} element - jQuery object to add the trigger to. * @param {Object} options - Overrides to the default plugin settings. */ _setup(element, options) { this.$element = element; this.options = $.extend({}, Toggler.defaults, element.data(), options); this.className = ''; this.className = 'Toggler'; // ie9 back compat // Triggers init is idempotent, just need to make sure it is initialized Triggers.init($); this._init(); this._events(); } /** * Initializes the Toggler plugin by parsing the toggle class from data-toggler, or animation classes from data-animate. * @function * @private */ _init() { var input; // Parse animation classes if they were set if (this.options.animate) { input = this.options.animate.split(' '); this.animationIn = input[0]; this.animationOut = input[1] || null; } // Otherwise, parse toggle class else { input = this.$element.data('toggler'); // Allow for a . at the beginning of the string this.className = input[0] === '.' ? input.slice(1) : input; } // Add ARIA attributes to triggers: var id = this.$element[0].id, $triggers = $(`[data-open~="${id}"], [data-close~="${id}"], [data-toggle~="${id}"]`); // - aria-expanded: according to the element visibility. $triggers.attr('aria-expanded', !this.$element.is(':hidden')); // - aria-controls: adding the element id to it if not already in it. $triggers.each((index, trigger) => { const $trigger = $(trigger); const controls = $trigger.attr('aria-controls') || ''; const containsId = new RegExp(`\\b${RegExpEscape(id)}\\b`).test(controls); if (!containsId) $trigger.attr('aria-controls', controls ? `${controls} ${id}` : id); }); } /** * Initializes events for the toggle trigger. * @function * @private */ _events() { this.$element.off('toggle.zf.trigger').on('toggle.zf.trigger', this.toggle.bind(this)); } /** * Toggles the target class on the target element. An event is fired from the original trigger depending on if the resultant state was "on" or "off". * @function * @fires Toggler#on * @fires Toggler#off */ toggle() { this[ this.options.animate ? '_toggleAnimate' : '_toggleClass'](); } _toggleClass() { this.$element.toggleClass(this.className); var isOn = this.$element.hasClass(this.className); if (isOn) { /** * Fires if the target element has the class after a toggle. * @event Toggler#on */ this.$element.trigger('on.zf.toggler'); } else { /** * Fires if the target element does not have the class after a toggle. * @event Toggler#off */ this.$element.trigger('off.zf.toggler'); } this._updateARIA(isOn); this.$element.find('[data-mutate]').trigger('mutateme.zf.trigger'); } _toggleAnimate() { var _this = this; if (this.$element.is(':hidden')) { Motion.animateIn(this.$element, this.animationIn, function() { _this._updateARIA(true); this.trigger('on.zf.toggler'); this.find('[data-mutate]').trigger('mutateme.zf.trigger'); }); } else { Motion.animateOut(this.$element, this.animationOut, function() { _this._updateARIA(false); this.trigger('off.zf.toggler'); this.find('[data-mutate]').trigger('mutateme.zf.trigger'); }); } } _updateARIA(isOn) { var id = this.$element[0].id; $(`[data-open="${id}"], [data-close="${id}"], [data-toggle="${id}"]`) .attr({ 'aria-expanded': isOn ? true : false }); } /** * Destroys the instance of Toggler on the element. * @function */ _destroy() { this.$element.off('.zf.toggler'); } } Toggler.defaults = { /** * Tells the plugin if the element should animated when toggled. * @option * @type {boolean} * @default false */ animate: false }; export {Toggler};