mmenu-js
Version:
The best javascript plugin for app look-alike on- and off-canvas menus with sliding submenus for your website and webapp.
253 lines (227 loc) • 9.03 kB
text/typescript
import Mmenu from './../oncanvas/mmenu.oncanvas';
import options from './_options';
import configs from './_configs';
import translate from './translations/translate';
import { extendShorthandOptions } from './_options';
import * as DOM from '../../_modules/dom';
import { extend } from '../../_modules/helpers';
// Add the translations.
translate();
// Add the options and configs.
Mmenu.options.screenReader = options;
Mmenu.configs.screenReader = configs;
export default function(this: Mmenu) {
// Extend options.
var options = extendShorthandOptions(this.opts.screenReader);
this.opts.screenReader = extend(options, Mmenu.options.screenReader);
// Extend configs.
var configs = this.conf.screenReader;
// Add Aria-* attributes
if (options.aria) {
// Add screenreader / aria hooks for add-ons
// In orde to keep this list short, only extend hooks that are actually used by other add-ons.
this.bind('initAddons:after', () => {
this.bind('initMenu:after', function(this: Mmenu) {
this.trigger(
'initMenu:after:sr-aria',
[].slice.call(arguments)
);
});
this.bind('initNavbar:after', function(this: Mmenu) {
this.trigger(
'initNavbar:after:sr-aria',
[].slice.call(arguments)
);
});
this.bind('openPanel:start', function(this: Mmenu) {
this.trigger(
'openPanel:start:sr-aria',
[].slice.call(arguments)
);
});
this.bind('close:start', function(this: Mmenu) {
this.trigger('close:start:sr-aria', [].slice.call(arguments));
});
this.bind('close:finish', function(this: Mmenu) {
this.trigger('close:finish:sr-aria', [].slice.call(arguments));
});
this.bind('open:start', function(this: Mmenu) {
this.trigger('open:start:sr-aria', [].slice.call(arguments));
});
this.bind('initOpened:after', function(this: Mmenu) {
this.trigger(
'initOpened:after:sr-aria',
[].slice.call(arguments)
);
});
});
// Update aria-hidden for hidden / visible listitems
this.bind('updateListview', () => {
this.node.pnls
.querySelectorAll('.mm-listitem')
.forEach(listitem => {
Mmenu.sr_aria(
listitem,
'hidden',
listitem.matches('.mm-hidden')
);
});
});
// Update aria-hidden for the panels when opening and closing a panel.
this.bind('openPanel:start', (panel: HTMLElement) => {
/** Panels that should be considered "hidden". */
var hidden: HTMLElement[] = DOM.find(this.node.pnls, '.mm-panel')
.filter(hide => hide !== panel)
.filter(hide => !hide.parentElement.matches('.mm-panel'));
/** Panels that should be considered "visible". */
var visible: HTMLElement[] = [panel];
DOM.find(
panel,
'.mm-listitem_vertical .mm-listitem_opened'
).forEach(listitem => {
visible.push(...DOM.children(listitem, '.mm-panel'));
});
// Set the panels to be considered "hidden" or "visible".
hidden.forEach(panel => {
Mmenu.sr_aria(panel, 'hidden', true);
});
visible.forEach(panel => {
Mmenu.sr_aria(panel, 'hidden', false);
});
});
this.bind('closePanel', (panel: HTMLElement) => {
Mmenu.sr_aria(panel, 'hidden', true);
});
// Add aria-haspopup and aria-owns to prev- and next buttons.
this.bind('initPanel:after', (panel: HTMLElement) => {
DOM.find(panel, '.mm-btn').forEach(button => {
Mmenu.sr_aria(button, 'haspopup', true);
let href = button.getAttribute('href');
if (href) {
Mmenu.sr_aria(button, 'owns', href.replace('#', ''));
}
});
});
// Add aria-hidden for navbars in panels.
this.bind('initNavbar:after', (panel: HTMLElement) => {
/** The navbar in the panel. */
var navbar = DOM.children(panel, '.mm-navbar')[0];
/** Whether or not the navbar should be considered "hidden". */
var hidden = navbar.matches('.mm-hidden');
// Set the navbar to be considered "hidden" or "visible".
Mmenu.sr_aria(navbar, 'hidden', hidden);
});
// Text
if (options.text) {
// Add aria-hidden to titles in navbars
if (this.opts.navbar.titleLink == 'parent') {
this.bind('initNavbar:after', (panel: HTMLElement) => {
/** The navbar in the panel. */
var navbar = DOM.children(panel, '.mm-navbar')[0];
/** Whether or not the navbar should be considered "hidden". */
var hidden = navbar.querySelector('.mm-btn_prev')
? true
: false;
// Set the navbar-title to be considered "hidden" or "visible".
Mmenu.sr_aria(
DOM.find(navbar, '.mm-navbar__title')[0],
'hidden',
hidden
);
});
}
}
}
// Add screenreader text
if (options.text) {
// Add screenreader / text hooks for add-ons
// In orde to keep this list short, only extend hooks that are actually used by other add-ons.
this.bind('initAddons:after', () => {
this.bind('setPage:after', function() {
this.trigger('setPage:after:sr-text', [].slice.call(arguments));
});
this.bind('initBlocker:after', function() {
this.trigger(
'initBlocker:after:sr-text',
[].slice.call(arguments)
);
});
});
// Add text to the prev-buttons.
this.bind('initNavbar:after', (panel: HTMLElement) => {
let navbar = DOM.children(panel, '.mm-navbar')[0];
if (navbar) {
let button = DOM.children(navbar, '.mm-btn_prev')[0];
if (button) {
button.innerHTML = Mmenu.sr_text(
this.i18n(configs.text.closeSubmenu)
);
}
}
});
// Add text to the next-buttons.
this.bind('initListview:after', (listview: HTMLElement) => {
let parent: HTMLElement = listview.closest('.mm-panel')['mmParent'];
if (parent) {
let next = DOM.children(parent, '.mm-btn_next')[0];
if (next) {
let text = this.i18n(
configs.text[
next.parentElement.matches('.mm-listitem_vertical')
? 'toggleSubmenu'
: 'openSubmenu'
]
);
next.innerHTML += Mmenu.sr_text(text);
}
}
});
}
}
// Methods
(function() {
var attr = function(
element: HTMLElement,
attr: string,
value: string | boolean
) {
element[attr] = value;
if (value) {
element.setAttribute(attr, value.toString());
} else {
element.removeAttribute(attr);
}
};
/**
* Add aria (property and) attribute to a HTML element.
*
* @param {HTMLElement} element The node to add the attribute to.
* @param {string} name The (non-aria-prefixed) attribute name.
* @param {string|boolean} value The attribute value.
*/
Mmenu.sr_aria = function(
element: HTMLElement,
name: string,
value: string | boolean
) {
attr(element, 'aria-' + name, value);
};
/**
* Add role attribute to a HTML element.
*
* @param {HTMLElement} element The node to add the attribute to.
* @param {string|boolean} value The attribute value.
*/
Mmenu.sr_role = function(element: HTMLElement, value: string | boolean) {
attr(element, 'role', value);
};
/**
* Wrap a text in a screen-reader-only node.
*
* @param {string} text The text to wrap.
* @return {string} The wrapped text.
*/
Mmenu.sr_text = function(text: string) {
return '<span class="mm-sronly">' + text + '</span>';
};
})();