mmenu-js
Version:
The best javascript plugin for app look-alike on- and off-canvas menus with sliding submenus for your website and webapp.
183 lines (165 loc) • 5.42 kB
text/typescript
/**
* Create an element with classname.
*
* @param {string} selector The nodeName and classnames for the element to create.
* @return {HTMLElement} The created element.
*/
export const create = (selector: string): HTMLElement => {
const args = selector.split('.'),
elem = document.createElement(args.shift());
elem.classList.add(...args);
return elem;
};
/**
* Find all elements matching the selector.
* Basically the same as element.querySelectorAll() but it returns an actuall array.
*
* @param {HTMLElement} element Element to search in.
* @param {string} filter The filter to match.
* @return {array} Array of elements that match the filter.
*/
export const find = (
element: HTMLElement | Document,
filter: string
): HTMLElement[] => {
return filter.length ? [].slice.call(element.querySelectorAll(filter)) : [];
};
/**
* Find all child elements matching the (optional) selector.
*
* @param {HTMLElement} element Element to search in.
* @param {string} filter The filter to match.
* @return {array} Array of child elements that match the filter.
*/
export const children = (
element: HTMLElement,
filter?: string
): HTMLElement[] => {
const children: HTMLElement[] = Array.prototype.slice.call(
element.children
);
return filter
? children.filter((child) => child.matches(filter))
: children;
};
/**
* Find all text from direct child element.
*
* @param {HTMLElement} element Element to search in.
* @return {string} The text.
*/
export const childText = (
element: HTMLElement
): string => {
return element
? [].slice.call(element.childNodes)
.filter(node => node.nodeType === Node.TEXT_NODE)
.map(node => node.nodeValue.trim())
.join(' ')
: '';
}
/**
* Find text excluding text from within child elements.
* @param {HTMLElement} element Element to search in.
* @return {string} The text.
*/
export const text = (element: HTMLElement): string => {
return [].slice.call(element.childNodes)
.filter((child) => !child.ariaHidden)
.map((child) => child.textContent)
.join(' ');
};
/**
* Find all preceding elements matching the selector.
*
* @param {HTMLElement} element Element to start searching from.
* @param {string} filter The filter to match.
* @return {array} Array of preceding elements that match the selector.
*/
export const parents = (
element: HTMLElement,
filter?: string
): HTMLElement[] => {
/** Array of preceding elements that match the selector. */
let parents: HTMLElement[] = [];
/** Array of preceding elements that match the selector. */
let parent = element.parentElement;
while (parent) {
parents.push(parent);
parent = parent.parentElement;
}
return filter
? parents.filter((parent) => parent.matches(filter))
: parents;
};
/**
* Find all previous siblings matching the selecotr.
*
* @param {HTMLElement} element Element to start searching from.
* @param {string} filter The filter to match.
* @return {array} Array of previous siblings that match the selector.
*/
export const prevAll = (
element: HTMLElement,
filter?: string
): HTMLElement[] => {
/** Array of previous siblings that match the selector. */
let previous: HTMLElement[] = [];
/** Current element in the loop */
let current = element.previousElementSibling as HTMLElement;
while (current) {
if (!filter || current.matches(filter)) {
previous.push(current);
}
current = current.previousElementSibling as HTMLElement;
}
return previous;
};
/**
* Get an element offset relative to the document.
*
* @param {HTMLElement} element Element to start measuring from.
* @param {string} [direction=top] Offset top or left.
* @return {number} The element offset relative to the document.
*/
export const offset = (element: HTMLElement, direction?: string): number => {
return (
element.getBoundingClientRect()[direction] +
document.body[direction === 'left' ? 'scrollLeft' : 'scrollTop']
);
};
/**
* Filter out non-listitem listitems.
* @param {array} listitems Elements to filter.
* @return {array} The filtered set of listitems.
*/
export const filterLI = (listitems: HTMLElement[]): HTMLElement[] => {
return listitems.filter((listitem) => !listitem.matches('.mm-hidden'));
};
/**
* Find anchors in listitems (excluding anchor that open a sub-panel).
* @param {array} listitems Elements to filter.
* @return {array} The found set of anchors.
*/
export const filterLIA = (listitems: HTMLElement[]): HTMLElement[] => {
let anchors = [];
filterLI(listitems).forEach((listitem) => {
anchors.push(...children(listitem, 'a.mm-listitem__text'));
});
return anchors.filter((anchor) => !anchor.matches('.mm-btn--next'));
};
/**
* Refactor a classname on multiple elements.
* @param {HTMLElement} element Element to refactor.
* @param {string} oldClass Classname to remove.
* @param {string} newClass Classname to add.
*/
export const reClass = (
element: HTMLElement,
oldClass: string,
newClass: string
) => {
if (element.matches('.' + oldClass)) {
element.classList.add(newClass);
}
};