UNPKG

@devstore/html-js

Version:

DOM manipulation utils

294 lines (232 loc) 8.68 kB
/* * Copyright (C) 2023 svinokot. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ export const createCustomElement = (name) => { const native = document.createElement('div'); native.innerHTML = `<${name}></${name}>`; return native.firstElementChild; }; export const getEventPath = event => event.path || (event.composedPath && event.composedPath()); export const create = (name, attributes, ...classes) => { const element = addAttributes(document.createElement(name), attributes); return classes.length ? addClass(element, ...classes) : element; }; export const removeChildren = (container) => { while (container.lastChild) { container.lastChild.remove(); } return container; }; export const addClass = (element, ...classes) => { element.classList?.add(...classes.filter(c => c)); return element; }; export const removeClass = (element, ...classes) => { element.classList?.remove(...classes); return element; }; export const toggleClass = (element, ...classes) => { classes && Object.keys(classes).forEach(key => classes[key] ? addClass(element, classes[key]) : removeClass(element, classes[key])); return element; }; const _flat = (acc, item) => item[Symbol.iterator] ? acc.concat(Array.from(item)) : acc.concat(item); //const _flat = (acc, item) => acc.concat(item); export const append = (container, ...childs) => { const elements = childs .filter(e => e) .reduce(_flat, []) .map(c => (typeof c === 'object') ? c : document.createTextNode(c.toString())); container.append ? container.append(...elements) : elements.forEach(c => c && container.appendChild(c)); return container; }; export const getAttribute = (element, attribute) => { const value = element.getAttribute && element.getAttribute(attribute); if (value === '') { return true; } else if (value) { const number = +value; return isNaN(number) ? value : number; } else { return value; } }; export const setAttribute = (element, attribute, value = true) => { if (value === null || value === undefined || value === false) { element.removeAttribute(attribute); } else { element.setAttribute(attribute, typeof value === 'boolean' ? '' : value.toString()); } return element; }; export const addAttributes = (element, attributes) => { attributes && Object.keys(attributes).forEach(key => setAttribute(element, key, attributes[key])); return element; }; export const removeAttributes = (element, ...attributes) => { attributes.forEach(key => element.removeAttribute(element, key)); return element; }; export const hasAttribute = (element, attribute, value = '', ...values) => { return [value, ...values].some(v => getAttribute(element, attribute) === v); }; export const addListeners = (element, events, eventOptions) => { Object.keys(events).forEach(key => events[key] && element.addEventListener(key, e => events[key](e, e.detail || {}), eventOptions)); return element; }; export const subscribe = (element, events, eventOptions) => { const unsubsribers = Object .keys(events) .map(key => { if (!events[key]) return; const callback = events[key]; const wrapper = e => callback(e, element); element.addEventListener(key, wrapper, eventOptions); return () => element.removeEventListener(key, wrapper, eventOptions); }); return () => unsubsribers.forEach(u => u && u()); }; const _defaultComparator = (a, b) => a === b; const _getCallbackProps = (callbackOrProps) => typeof callbackOrProps === 'function' ? { callback: callbackOrProps } : callbackOrProps; export const addProperty = (element, name, callbackOrProps) => { const { callback, comparator = _defaultComparator, reflect, value } = _getCallbackProps(callbackOrProps); const store = `_${name}`; const reflectName = (reflect === true) ? name : reflect; Object.defineProperty(element, name, { get() { return element[store]; }, set(value) { const prevValue = element[store]; if (!comparator(value, prevValue)) { element[store] = value; reflectName && setAttribute(element, reflectName, value); callback && callback(element, { value, prevValue }); } } }); element[store] = value; reflectName && value && setAttribute(element, reflectName, value); return element; }; export const addProperties = (element, properties) => { Object.keys(properties).forEach(key => addProperty(element, key, properties[key])); return element; }; export const addEventProperty = (element, name, props = {}) => { const handlerName = `${name}Changed`; const {camparator, callback, reflect, ...eventOptions} = props; return addProperty( element, name, { callback: (element, detail) => { element.dispatchEvent(new CustomEvent(handlerName, { ...eventOptions, detail })); callback && callback(element, detail); }, camparator, reflect } ); }; export const addEventReflectAttributes = (element, ...attributes) => { attributes.forEach(a => addListeners(element, {[`${a}Changed`]: (e, {value}) => setAttribute(element, a, value)})); return element; }; const _defaultBehaviorOptions = { filter: element => !hasAttribute(element, 'active', 'false', null, undefined), eventName: 'click' }; export const addReaction = (element, callback, options = _defaultBehaviorOptions) => { const { filter, eventName } = options; return addListeners(element, { [eventName]: event => { const sender = filter ? getEventPath(event).find(filter) : element.target; if (sender) { callback(event, { sender }); event.stopPropagation(); } } }); }; const _defaultStyleCompound = (name, value) => `${name}: ${value}`; export const getStyleString = (values, compound = _defaultStyleCompound) => { return Object.keys(values).map(k => compound(k, values[k])).join('; '); }; export const setStyle = (element, style) => { (style && typeof style === 'object') ? Object.keys(style).forEach(key => element.style[key] = style[key]) : setAttribute(element, 'style', style.toString()); return element; }; const _unique = array => [ ...new Set(array) ]; export const get = (elementOrSelector, idOrSelector) => { if (typeof elementOrSelector === 'string') { return elementOrSelector.startsWith('#') ? document.getElementById(elementOrSelector.substring(1)) : document.querySelector(elementOrSelector); } else { return elementOrSelector.querySelector(idOrSelector); } }; const _querySelector = (all, root, selector) => { if (all) { return Array.from(root.querySelectorAll(selector)); } else { const element = root.querySelector(selector); return element ? [element] : []; } }; const _select = (all, root, ...selectors) => { const elements = selectors.reduce((acc, selector) => { const elements = (typeof selector === 'string') ? _querySelector(all, root, selector) : selector; return acc.concat(elements); }, []); return _unique(elements); }; const _selectResolve = (all, elementOrSelector, ...selectors) => { return typeof elementOrSelector === 'string' ? _select(all, document, elementOrSelector, ...selectors) : (selectors.length) ? _select(all, elementOrSelector, ...selectors) : elementOrSelector; }; const batch = (elements, assembler, ...args) => { const result = []; for (const key in elements) { result.push(assembler(elements[key], ...args)); } return result; }; export const select = (elementOrSelector, ...selectors) => _createBatch(_selectResolve(false, elementOrSelector, ...selectors)); export const selectAll = (elementOrSelector, ...selectors) => _createBatch(_selectResolve(true, elementOrSelector, ...selectors)); function _createBatch(elements) { const _elements = [...elements]; const append = elementOrList => _createBatch(_unique(_elements.concat(elementOrList))); return { batch: (assembler, ...args) => batch(_elements, assembler, ...args), select: (elementOrSelector, ...selectors) => { const current = _selectResolve(false, elementOrSelector, ...selectors); return append(current); }, selectAll: (elementOrSelector, ...selectors) => { const current = _selectResolve(true, elementOrSelector, ...selectors); return append(current); }, append, elements: _elements }; }