@yagni-js/yagni-dom
Version:
Yet another functional library (DOM API related)
1,845 lines (1,749 loc) • 85.9 kB
JavaScript
/**
*
* @module @yagni-js/yagni-dom
* @version 2.2.0
* @author Yuri Egorov <ysegorov at gmail dot com>
* @license Unlicense http://unlicense.org
*
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var yagni = require('@yagni-js/yagni');
/**
* Takes an attribute `name` as an argument and returns **a new function**,
* which then takes an Element `el` as an argument and returns value
* of an attribute on the element.
* Returns **`null`** if attribute doesn't exist.
*
* Uses `getAttribute` method of Element.
*
* @category Element
*
* @param {String} name attribute name to get value for
* @returns {Function} to take `el` as an argument and return value of
* an attribute on the element or `null` if attribute doesn't exist
*
* @see getProp
* @see getData
*
* @example
*
* import {h, hText, getAttr} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {title: 'Foo'}, {}, [hText('Foo link')]);
* const el = spec();
*
* const getTitle = getAttr('title');
* const getHref = getAttr('href');
*
* const title = getTitle(el); // => 'Foo'
* const href = getHref(el); // => null
*
*/
function getAttr(name) {
return function _getAttr(el) {
return el.getAttribute(name);
};
}
/**
* Sets the value of an attribute on the element and returns element.
* If value is not defined (is `undefined` or `null`) it will not be set.
*
* Uses `setAttribute` method of Element.
*
* @category Element
*
* @param {Element} el target element
* @param {String} name attribute name
* @param {String} value attribute value
* @returns {Element} el
*
* @private
*
*/
function setAttribute(el, name, value) {
// NB. side effect
const res = yagni.isDefined(value) ? el.setAttribute(name, value) : el;
return el;
}
/**
* Takes an attribute `name` and `value` as arguments and returns
* **a new function**, which then takes an Element `el` as an argument and
* sets the value of an attribute on the element. Returns `el`.
* If value is not defined (is `undefined` or `null`) it will not be set.
*
* Uses `setAttribute` method of Element.
*
* @category Element
*
* @param {String} name attribute name
* @param {String} value attribute value
* @returns {Function} to take an Element `el` as an argument, set the value
* of an attribute on the element and return `el`
*
* @see setProp
* @see setData
*
* @example
*
* import {h, hText, getAttr, setAttr} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {title: 'Foo'}, {}, [hText('Foo link')]);
* const el = spec();
*
* const getHref = getAttr('href');
* const setHrefToTop = setAttr('href', '#top');
*
* const before = getHref(el); // => null
*
* const el2 = setHrefToTop(el); // el2 is el
*
* const after = getHref(el2); // => '#top'
*
*/
function setAttr(name, value) {
return function _setAttr(el) {
return setAttribute(el, name, value);
};
}
/**
* Takes Element `el` as an argument and returns **a new function**,
* which then takes an attribute `name` and `value` as arguments and sets
* the value of an attribute on the element. Returns `el`.
* If value is not defined (is `undefined` or `null`) it will not be set.
*
* Uses `setAttribute` method of Element.
*
* @category Element
*
* @param {Element} el target Element
* @returns {Function} to take an attribute `name` and `value`, set the
* value of an attribute on the element and return `el`
*
* @see setPropTo
* @see setDataTo
*
* @example
*
* import {h, hText, getAttr, setAttrTo} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {title: 'Foo'}, {}, [hText('Foo link')]);
* const el = spec();
*
* const getHref = getAttr('href');
* const setElAttr = setAttrTo(el);
*
* const before = getHref(el); // => null
*
* const el2 = setElAttr('href', '#top'); // => el2 is el
*
* const after = getHref(el2); // => '#top'
*
*/
function setAttrTo(el) {
return function _setAttrTo(name, value) {
return setAttribute(el, name, value);
};
}
/**
* Takes an object `obj` of `(name, value)` pairs as an argument and
* returns **a new function**, which then takes an Element `el` as an argument
* and iteratively sets the value of an attribute on the element for
* each `(name, value)` pair from `obj`. Returns `el`.
* If value for a correspondent attribute is not defined
* (is `undefined` or `null`) it will not be set.
*
* Object `obj` structure:
*
* {attr1: 'value1', attr2: 'value2', ...}
*
* Uses `setAttribute` method of Element.
*
* @function
* @category Element
*
* @param {Object} obj source object of attribute `(name, value)` pairs
* @returns {Function} to take an Element `el` as an argument, iteratively
* set attributes values on the `el` and return `el`
*
* @see setProps
* @see setDatas
*
* @example
*
* import {h, hText, getAttr, setAttrs} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {}, {}, [hText('Foo link')]);
* const el = spec();
*
* const getHref = getAttr('href');
* const getTitle = getAttr('title');
* const attrsSetter = setAttrs({title: 'Foo', href: '#top'});
*
* const title1 = getTitle(el); // => null
* const href1 = getHref(el); // => null
*
* const el2 = attrsSetter(el);
*
* const title2 = getTitle(el); // => 'Foo'
* const href2 = getHref(el); // => '#top'
*
*/
const setAttrs = yagni.reduceObj(setAttribute);
/**
* Takes an attribute `name` as an argument and returns **a new function**,
* which then takes an Element `el` as an argument, removes an attribute
* from the element and returns `el`.
*
* Uses `removeAttribute` method of Element.
*
* @category Element
*
* @param {String} name attribute name
* @returns {Function} to take an Element `el` as an argument, remove an
* attribute from the element and return `el`
*
* @example
*
* import {h, hText, getAttr, removeAttr} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {title: 'Foo', href: '#top'}, {}, [hText('Foo link')]);
*
* const removeTitle = remoteAttr('title');
*
* const el = spec();
* // => <a href="#top" title="Foo">Foo link</a>
*
* const el2 = removeTitle(el);
* // => <a href="#top">Foo link</a>
* // el2 === el
*
*/
function removeAttr(name) {
return yagni.tap(
function _removeAttr(el) {
return el.removeAttribute(name);
}
);
}
const doc = window.document;
/**
* Takes `tagName` as an argument and returns newly created HTML element
* specified by `tagName`.
*
* Uses `document.createElement` method.
*
* @category Element
*
* @param {String} tagName
* @returns {Element} newly created HTML element
*
* @see createElementNS
* @see createSVGElement
* @see createText
*
* @example
*
* import {createElement} from '@yagni-js/yagni-dom';
*
* const div = createElement('div'); // => <div></div>
*
*/
function createElement(tagName) {
return doc.createElement(tagName);
}
/**
* Takes `namespace` URI as an argument and returns **a new function**,
* which then takes `tagName` as an argument and returns newly created HTML
* element with the specified `namespace` URI and `tagName`.
*
* Uses `document.createElementNS` method.
*
* Important Namespace URIs:
*
* - [HTML] http://www.w3.org/1999/xhtml
* - [SVG] http://www.w3.org/2000/svg
* - [MathML] http://www.w3.org/1998/mathml
*
* @category Element
*
* @param {URI} namespace
* @returns {Function} to take `tagName` as an argument and return newly
* created HTML element with the specified `namespace` URI and `tagName`
*
* @see createElement
* @see createSVGElement
* @see createText
*
* @example
*
* import {createElementNS} from '@yagni-js/yagni-dom';
*
* const createSVGEl = createElementNS('http://www.w3.org/2000/svg');
*
* const svg = createSVGEl('svg');
* // => <svg xmlns="http://www.w3.org/2000/svg"></svg>
*
*/
function createElementNS(namespace) {
return function (tagName) {
return doc.createElementNS(namespace, tagName);
};
}
/**
* Takes `tagName` as an argument and returns newly created HTML element
* with `http://www.w3.org/2000/svg` namespace URI and specified `tagName`.
*
* Uses `document.createElementNS` method.
*
* @function
* @category Element
*
* @param {String} tagName
* @returns {Element} newly created HTML element with
* 'http://www.w3.org/2000/svg' namespace URI and specified `tagName`
*
* @see createElement
* @see createElementNS
* @see createText
*
* @example
*
* import {createSVGElement} from '@yagni-js/yagni-dom';
*
* const svg = createSVGElement('svg');
* // => <svg xmlns="http://www.w3.org/2000/svg"></svg>
*
*/
const createSVGElement = createElementNS('http://www.w3.org/2000/svg');
/**
* Takes `text` as an argument and returns newly created Text node.
*
* Uses `document.createTextNode` method.
*
* @category Element
*
* @param {String} text text to be put to text node
* @returns {TextNode} newly created Text node
*
* @see createElement
* @see createElementNS
* @see createSVGElement
*
* @example
*
* import {createText} from '@yagni-js/yagni-dom';
*
* const text = createText('foo'); // => #text "foo"
*
*/
function createText(text) {
return doc.createTextNode(text);
}
/**
* Takes some property `name` as an argument and returns **a new function**,
* which then takes an Element `el` as an argument and returns value of the
* specified property on the element.
*
* @function
* @category Element
*
* @param {String} name property name to get value for
* @returns {Function} to take Element `el` as an argument and return value of
* the specified property on the element or `undefined` if property
* doesn't exist
*
* @see getAttr
* @see getData
*
* @example
*
* import {h, hText, getProp} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {}, {foo: 'Foo'}, [hText('Foo link')]);
* const el = spec();
*
* const getFooProp = getProp('foo');
* const getBazProp = getProp('baz');
*
* const foo = getFooProp(el); // => 'Foo'
* const baz = getBazProp(el); // => undefined
*
*/
const getProp = yagni.pick;
/**
* Sets the value of a property on the element and returns element.
*
* @function
* @category Element
*
* @param {Element} el target element
* @param {String} name property name
* @param {*} value property value
* @returns {Element} el
*
* @private
*
*/
const setProperty = yagni.mutate;
/**
* Takes some property `name` and `value` as arguments and returns
* **a new function**, which then takes an Element `el` as an argument and
* sets the value of a property on the element. Returns `el`.
*
* @category Element
*
* @param {String} name property name
* @param {*} value property value
* @returns {Function} to take an Element `el` as an argument, set the value
* of a property on the element and return `el`
*
* @see setAttr
* @see setData
*
* @example
*
* import {h, hText, getProp, setProp} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {}, {foo: 'Foo'}, [hText('Foo link')]);
* const el = spec();
*
* const getFooProp = getProp('foo');
* const setFooProp = setProp('foo', 'Baz');
*
* const before = getFooProp(el); // => 'Foo'
*
* const el2 = setFooProp(el); // el2 is el
*
* const after = getFooProp(el); // => 'Baz'
*
*/
function setProp(name, value) {
return function _setProp(el) {
return setProperty(el, name, value);
};
}
/**
* Takes Element `el` as an argument and returns **a new function**,
* which then takes some property `name` and `value` as arguments and
* sets the value of a property on the element. Returns `el`.
*
* @category Element
*
* @param {Element} el target Element
* @returns {Function} to take some property `name` and `value`, set the value
* of a property on the element and return `el`
*
* @see setAttrTo
* @see setDataTo
*
* @example
*
* import {h, hText, getProp, setPropTo} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {}, {foo: 'Foo'}, [hText('Foo link')]);
* const el = spec();
*
* const getFooProp = getProp('foo');
* const setElProp = setPropTo(el);
*
* const before = getFooProp(el); // => 'Foo'
*
* const el2 = setElProp('foo', 'Baz'); // el2 is el
*
* const after = getFooProp(el); // => 'Baz'
*
*/
function setPropTo(el) {
return function _setPropTo(name, value) {
return setProperty(el, name, value);
};
}
/**
* Takes an object `obj` of `(name, value)` pairs as an argument and
* returns **a new function**, which then takes an Element `el` as an argument
* and iteratively sets the value of a property on the element for
* each `(name, value)` pair from `obj`. Returns `el`.
*
* Object `obj` structure:
*
* {prop1: 'value1', prop2: 'value2', ...}
*
* @function
* @category Element
*
* @param {Object} obj source object of `(name, value)` pairs
* @returns {Function} to take an Element `el` as an argument, iteratively
* set properties values on the `el` and return `el`
*
* @see setAttrs
* @see setDatas
*
* @example
*
* import {h, hText, getProp, setProps} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {}, {}, [hText('Foo link')]);
* const el = spec();
*
* const getFoo = getProp('foo');
* const getBaz = getProp('baz');
* const propsSetter = setProps({foo: 'Foo', baz: 42});
*
* const foo1 = getFoo(el); // => undefined
* const baz1 = getBaz(el); // => undefined
*
* const el2 = propsSetter(el); // el2 is el
*
* const foo2 = getFoo(el); // => 'Foo'
* const baz2 = getBaz(el); // => 42
*
*/
const setProps = yagni.reduceObj(setProperty);
/**
* Takes an Element `el` as an argument and returns value of the `textContent`
* property on the element.
*
* @function
* @category Element
*
* @param {Element} el target element
* @returns {String} value of the `textContent` property on the element
*
* @example
*
* import {h, hText, textContent} from '@yagni-js/yagni-dom';
*
* const spec = h('a', {}, {}, [hText('Foo link')]);
* const el = spec();
*
* const text = textContent(el); // => 'Foo link'
*
*/
const textContent = getProp('textContent');
/**
* Takes css `selector` as an argument and returns
* **a new function**, which then takes an Element `el` as an argument and
* returns `true` if `el` matches specified `selector`, returns `false`
* otherwise.
*
* Uses `matches` method of element.
*
* @category Element
*
* @param {String} selector css selector
* @returns {Function} to take Element `el` as an argument and return `true`
* if `el` matches `selector` or `false` otherwise
*
* @example
*
* import {createElement, addClass, matches} from '@yagni-js/yagni-dom';
*
* const div = createElement('div');
*
* const addFooClass = addClass('foo');
* const matchesFooClass = matches('.foo');
* const matchesBazClass = matches('.baz');
*
* const el = addFooClass(div); // => el is div
*
* const res1 = matchesFooClass(el); // => true
* const res2 = matchesBazClass(el); // => false
*
*/
function matches(selector) {
return function _matches(el) {
return el.matches(selector);
};
}
/**
* Takes css `selector` as an argument and returns
* **a new function**, which then takes an Element `el` as an argument and
* returns the closest ancestor of the `el` which matches the `selector`.
* Returns `null` if there is no such an ancestor.
*
* Uses `closest` method of element.
*
* @category Element
*
* @param {String} selector css selector
* @returns {Function} to take an Element `el` as an argument and return
* closest ancestor of `el` which matches the `selector` or `null` if there is
* no such an ancestor
*
* @example
*
* import {createElement, appendTo, addClass, closest} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li = createElement('li');
* const a = createElement('a');
* const span = createElement('span');
*
* const addFooClass = addClass('foo');
* const closestFoo = closest('.foo');
*
* const appendToUl = appendTo(ul);
* const appendToLi = appendTo(li);
* const appendToA = appendTo(a);
*
* const ul1 = appendToUl(li); // => ul1 is ul
* const li1 = appendToLi(a); // => li1 is li
* const a1 = appendToA(span); // => a1 is a
*
* const li2 = addFooClass(li); // => li2 is li
*
* const res1 = closestFoo(span); // => li
* const res2 = closestFoo(a); // => li
* const res3 = closestFoo(li); // => null
* const res4 = closestFoo(ul); // => null
*
*/
function closest(selector) {
return function _closest(el) {
return el.closest(selector);
};
}
const byIdRe = /^#([\w-]+)$/;
const byClassRe = /^(\.[\w-]+)+$/;
/**
* Takes css `selector` as an argument and returns **a new function**,
* which then takes an Element `el` as an argument and returns an element or
* an array of elements within `el` that matches specified `selector`.
*
* Uses `getElementById` method of element for searches by element's `id`.
* Uses `getElementsByClassName` method of element for searches by single or
* multiple class names.
* Uses `querySelectorAll` method of element for other cases.
*
* For searches by element id returns single element or `null` if there is
* no match.
* For other cases returns an array.
*
* @category Element
*
* @param {String} selector css selector
* @returns {Function} to take an Element `el` as an argument and return
* single element or an array of elements within `el` that matches `selector`,
* or `null` if there are no matches
*
* @example
*
* import {createElement, appendTo, setAttr, query} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li = createElement('li');
* const a = createElement('a');
* const span = createElement('span');
*
* const setFooBarClass = setAttr('class', 'foo bar');
* const setBazId = setAttr('id', 'baz');
* const queryFoo = query('.foo.bar');
* const queryBaz = query('#baz');
*
* const appendToUl = appendTo(ul);
* const appendToLi = appendTo(li);
* const appendToA = appendTo(a);
*
* const ul1 = appendToUl(li); // => ul1 is ul
* const li1 = appendToLi(a); // => li1 is li
* const a1 = appendToA(span); // => a1 is a
*
* const li2 = setFooBarClass(li); // => li2 is li
* const a2 = setBazId(a); // => a2 is a
*
* const res1 = queryFoo(ul); // => [li]
* const res2 = queryFoo(li); // => [li]
* const res3 = queryFoo(a); // => []
* const res4 = queryFoo(span); // => []
*
* const res5 = queryBaz(ul); // => a
* const res6 = queryBaz(a); // => a
* const res7 = queryBaz(span); // => null
*
*/
function query(selector) {
const byId = byIdRe.test(selector);
const byClass = byClassRe.test(selector);
return function _query(el) {
return byId ? doc.getElementById(selector.substr(1)) : yagni.toArray(
byClass ? el.getElementsByClassName(selector.replace(/\./g, ' ').trim()) : el.querySelectorAll(selector));
};
}
/**
* Takes css `selector` as an argument and returns **a new function**,
* which then takes an Element `el` as an argument and returns
* first element within `el` that matches specified `selector`.
*
* Uses `query` and returns first element from an array of found elements.
* For searches by element id returns `query` result as is.
*
* @category Element
*
* @param {String} selector css selector
* @returns {Function} to take an Element `el` as an argument and return
* first element within `el` that matches `selector`
*
* @example
*
* import {createElement, appendTo, queryFirst} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToUl = appendTo(ul);
* const firstLi = queryFirst('li');
* const firstSpan = queryFirst('span');
* const firstById = queryFirst('#first');
*
* const ul1 = appendToUl(li1); // => ul1 is ul
* const ul2 = appendToUl(li2); // => ul2 is ul
* const ul3 = appendToUl(li3); // => ul3 is ul
*
* const res1 = firstLi(ul); // => li1
* const res2 = firstSpan(li2); // => undefined
* const res3 = firstById(li3); // => null
*
*/
function queryFirst(selector) {
return yagni.pipe([
query(selector),
yagni.ifElse(
yagni.isArray,
yagni.first,
yagni.identity
)
]);
}
/**
* Takes an Element `el` and returns it's parent Element. Returns `null`
* if `el` has no parent or parent is not an Element.
*
* @function
* @category Element
*
* @param {Element} el source element
* @returns {Element} parent Element if exists, `null` otherwise
*
* @example
*
* import {createElement, appendTo, parent} from '@yagni-js/yagni-dom';
*
* const div = createElement('div');
* const p = createElement('p');
*
* const appendToDiv = appendTo(div);
*
* const div2 = appendToDiv(p); // => div2 is div
*
* const res = parent(p); // => res is div
*
*/
const parent = yagni.pick('parentElement');
/**
* Takes an Element `el` and returns it's first child Element. Returns `null`
* if there are no child elements.
*
* @function
* @category Element
*
* @param {Element} el source element
* @returns {Element} first child Element if exists, `null` otherwise
*
* @example
*
* import {createElement, appendTo, firstChild} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToList = appendTo(ul);
*
* const ul1 = appendToList(li1); // => ul1 is ul
* const ul2 = appendToList(li2); // => ul2 is ul
* const ul3 = appendToList(li3); // => ul3 is ul
*
* const res = firstChild(ul); // => li1
*
*/
const firstChild = yagni.pick('firstElementChild');
/**
* Takes an Element `el` and returns it's last child Element. Returns `null`
* if there are no child elements.
*
* @function
* @category Element
*
* @param {Element} el source element
* @returns {Element} last child Element if exists, `null` otherwise
*
* @example
*
* import {createElement, appendTo, lastChild} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToList = appendTo(ul);
*
* const ul1 = appendToList(li1); // => ul1 is ul
* const ul2 = appendToList(li2); // => ul2 is ul
* const ul3 = appendToList(li3); // => ul3 is ul
*
* const res = lastChild(ul); // => li3
*
*/
const lastChild = yagni.pick('lastElementChild');
/**
* Takes an Element `el` and returns it's immediately following Element.
* Returns `null` if `el` is the last element in the list.
*
* @function
* @category Element
*
* @param {Element} el source element
* @returns {Element} immediately following Element if exists, `null` otherwise
*
* @example
*
* import {createElement, appendTo, next} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToList = appendTo(ul);
*
* const ul1 = appendToList(li1); // => ul1 is ul
* const ul2 = appendToList(li2); // => ul2 is ul
* const ul3 = appendToList(li3); // => ul3 is ul
*
* const res = next(li1); // => li2
* const res = next(li2); // => li3
* const res = next(li3); // => null
*
*/
const next = yagni.pick('nextElementSibling');
/**
* Takes an Element `el` and returns it's immediately preceding Element.
* Returns `null` if `el` is the first element in the list.
*
* @function
* @category Element
*
* @param {Element} el source element
* @returns {Element} immediately preceding Element if exists, `null` otherwise
*
* @example
*
* import {createElement, appendTo, prev} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToList = appendTo(ul);
*
* const ul1 = appendToList(li1); // => ul1 is ul
* const ul2 = appendToList(li2); // => ul2 is ul
* const ul3 = appendToList(li3); // => ul3 is ul
*
* const res = prev(li1); // => null
* const res = prev(li2); // => li1
* const res = prev(li3); // => li2
*
*/
const prev = yagni.pick('previousElementSibling');
/**
* Takes an Element `el` and returns **a new array**, containing all child
* elements.
*
* @function
* @category Element
*
* @param {Element} el source element
* @returns {Array} array containing child elements
*
* @example
*
* import {createElement, appendTo, children} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToList = appendTo(ul);
*
* const ul1 = appendToList(li1); // => ul1 is ul
* const ul2 = appendToList(li2); // => ul2 is ul
* const ul3 = appendToList(li3); // => ul3 is ul
*
* const res = children(ul); // => [li1, li2, li3]
*
*/
const children = yagni.pipe([
yagni.pick('children'),
yagni.toArray
]);
/**
* Takes css `selector` to match against as an argument and returns
* **a new function**, which then takes an Element `el` as an argument
* and returns **a new array**, containing all matching siblings.
*
* @function
* @category Element
*
* @param {Element} el source element
* @returns {Array} array containing matching siblings
*
* @example
*
* import {createElement, appendTo, siblings} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToList = appendTo(ul);
* const others = siblings('li');
*
* const ul1 = appendToList(li1); // => ul1 is ul
* const ul2 = appendToList(li2); // => ul2 is ul
* const ul3 = appendToList(li3); // => ul3 is ul
*
* const res1 = others(li1); // => [li2, li3]
* const res2 = others(li2); // => [li1, li3]
* const res3 = others(li3); // => [li1, li2]
*
*/
function siblings(selector) {
const match = matches(selector);
return function _siblings(el) {
function matched(x) { return x !== el && match(x); }
return yagni.pipe([
parent,
children,
yagni.filter(matched)
])(el);
};
}
//
// alternative implementation for siblings()
//
// export function siblings(selector) {
// return call(
// pipe([
// fn2(
// and,
// pipe([
// equals,
// not
// ]),
// pipe([
// always(selector),
// matches
// ])
// ),
// filter
// ]),
// pipe([
// parent,
// children
// ])
// );
// }
/**
* Takes an Element `el` as an argument and returns **a new function**,
* which then takes another Element `target` as an argument, appends
* `el` to `target` as a last child and returns `target`.
*
* Uses `appendChild` method of `target` element.
*
* @category Tree mutation
*
* @param {Element} el element to be appended
* @returns {Function} to take another Element `target` as an argument,
* append `el` to `target` as a last child and return `target`
*
* @see appendTo
* @see appendAfter
* @see prepend
* @see prependTo
*
* @example
*
* import {createElement, append} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li = createElement('li');
*
* const appendLi = append(li);
*
* const res = appendLi(ul);
* // => res is ul
* // => <ul><li></li></ul>
*
*/
function append(el) {
return function _append(target) {
// NB. unused assignment
const child = target.appendChild(el);
return target;
};
}
/**
* Takes an Element `target` as an argument and returns **a new function**,
* which then takes another Element `el` as an argument, appends
* `el` to `target` as a last child and returns `target`.
*
* Uses `appendChild` method of `target` element.
*
* @category Tree mutation
*
* @param {Element} target element to append child to
* @returns {Function} to take another Element `el` as an argument,
* append `el` to `target` as a last child and return `target`
*
* @see append
* @see appendAfter
* @see prepend
* @see prependTo
*
* @example
*
* import {createElement, appendTo} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li = createElement('li');
*
* const appendToUl = appendTo(ul);
*
* const res = appendToUl(li);
* // => res is ul
* // => <ul><li></li></ul>
*
*/
function appendTo(target) {
return function _appendTo(el) {
// NB. unused assignment
const child = target.appendChild(el);
return target;
};
}
/**
* Takes an Element `target` as an argument and returns **a new function**,
* which then takes another Element `el` as an argument, appends `el` after
* `target` and returns `el`.
*
* Uses `insertBefore` method of parent element of `target`.
*
* @category Tree mutation
*
* @param {Element} target element to append after
* @returns {Function} to take another Element `el` as an argument,
* append `el` after `target` and return `el
*
* @see append
* @see appendTo
* @see prepend
* @see prependTo
*
* @example
*
* import {createElement, appendTo, appendAfter} from '@yagni-js/yagni-dom';
*
* const div = createElement('div');
* const ul = createElement('ul');
* const p = createElement('p');
*
* const appendToDiv = appendTo(div);
* const appendAfterUl = appendAfter(ul);
*
* const div1 = appendToDiv(ul);
* // => div1 is div
* // => <div><ul></ul></div>
*
* const res = appendAfterUl(p);
* // => res is p
* // => <div><ul></ul><p></p></div>
*
*/
function appendAfter(target) {
return function _appendAfter(el) {
// NB. unused assignment
const child = parent(target).insertBefore(el, next(target));
return el;
};
}
/**
* Takes an Element `el` as an argument and returns **a new function**,
* which then takes another Element `target` as an argument, appends
* `el` to `target` as a first child and returns `target`.
*
* Uses `insertBefore` method of `target` element.
*
* @category Tree mutation
*
* @param {Element} el element to be prepended
* @returns {Function} to take another Element `target` as an argument,
* append `el` to `target` as a first child and return `target`
*
* @see append
* @see appendTo
* @see appendAfter
* @see prependTo
*
* @example
*
* import {createElement, appendTo, prepend} from '@yagni-js/yagni-dom';
*
* const div = createElement('div');
* const ul = createElement('ul');
* const p = createElement('p');
*
* const appendToDiv = appendTo(div);
* const prependP = prepend(p);
*
* const div1 = appendToDiv(ul);
* // => div1 is div
* // => <div><ul></ul></div>
*
* const div2 = prependP(div);
* // => div2 is div
* // => <div><p></p><ul></ul></div>
*
*/
function prepend(el) {
return function _prepend(target) {
// NB. unused assignment
const child = target.insertBefore(el, firstChild(target));
return target;
};
}
/**
* Takes an Element `target` as an argument and returns **a new function**,
* which then takes another Element `el` as an argument, appends
* `el` to `target` as a first child and returns `target`.
*
* Uses `insertBefore` method of `target` element.
*
* @category Tree mutation
*
* @param {Element} target element to prepend to
* @returns {Function} to take another Element `el` as an argument,
* append `el` to `target` as a first child and return `target`
*
* @see append
* @see appendTo
* @see appendAfter
* @see prepend
*
* @example
*
* import {createElement, prependTo} from '@yagni-js/yagni-dom';
*
* const div = createElement('div');
* const ul = createElement('ul');
* const p = createElement('p');
*
* const prependToDiv = prependTo(div);
*
* const div1 = prependToDiv(ul);
* // => div1 is div
* // => <div><ul></ul></div>
*
* const div2 = prependToDiv(div);
* // => div2 is div
* // => <div><p></p><ul></ul></div>
*
*/
function prependTo(target) {
return function _prependTo(el) {
// NB. unused assignment
const child = target.insertBefore(el, firstChild(target));
return target;
};
}
/**
* Takes two Elements `el` and `child` as arguments, removes `child` from `el`
* and returns `el`.
*
* Uses `removeChild` method of `el`.
*
* @category Tree mutation
*
* @param {Element} el parent element to remove child from
* @param {Element} child child element to remove
* @returns {Element} parent element `el`
*
* @see remove
* @see removeChildren
* @see replace
*
* @example
*
* import {createElement, appendTo, removeChild} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li = createElement('li');
*
* const appendToUl = appendTo(ul);
*
* const ul1 = appendToUl(li);
* // => ul1 is ul
* // => <ul><li></li></ul>
*
* const ul2 = removeChild(ul, li);
* // => ul2 is ul
* // => <ul></ul>
*
*/
function removeChild(el, child) {
// NB. unused assignment
const res = el.removeChild(child);
return el;
}
/**
* Takes an Element `el` as an argument and removes it from DOM tree.
* Returns parent element if `el` has parent element, returns `el` if `el`
* has no parent element.
*
* Uses `removeChild` method of parent element of `el`.
*
* @function
* @category Tree mutation
*
* @param {Element} el element to remove
* @returns {Element} parent element if `el` has parent or `el` otherwise
*
* @see removeChild
* @see removeChildren
* @see replace
*
* @example
*
* import {createElement, appendTo, remove} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li = createElement('li');
*
* const appendToUl = appendTo(ul);
*
* const ul1 = appendToUl(li);
* // => ul1 is ul
* // => <ul><li></li></ul>
*
* const ul2 = remove(li);
* // => ul2 is ul
* // => <ul></ul>
*
*/
const remove = yagni.pipe([
yagni.transform({
parent: parent,
el: yagni.identity
}),
yagni.ifElse(
yagni.pipe([yagni.pick('parent'), yagni.isNil]),
yagni.pick('el'),
yagni.fn2(removeChild, yagni.pick('parent'), yagni.pick('el'))
)
]);
/**
* Takes an Element `el` as an argument, removes all children elements and
* returns `el`.
*
* Uses `removeChild` method of `el`.
*
* @category Tree mutation
*
* @param {Element} el element to remove children elements from
* @returns {Element} `el`
*
* @see remove
* @see removeChild
*
* @example
*
* import {createElement, appendTo, removeChildren} from '@yagni-js/yagni-dom';
*
* const ul = createElement('ul');
* const li1 = createElement('li');
* const li2 = createElement('li');
* const li3 = createElement('li');
*
* const appendToUl = appendTo(ul);
*
* const ul1 = appendToUl(li1); // => ul1 is ul
* const ul2 = appendToUl(li2); // => ul2 is ul
* const ul3 = appendToUl(li3); // => ul3 is ul
* // => <ul><li></li><li></li><li></li></ul>
*
* const res = removeChildren(ul);
* // => res is ul
* // => <ul></ul>
*
*/
function removeChildren(el) {
const elements = children(el);
return elements.reduce(removeChild, el);
}
/**
* Takes an Element `oldEl` as an argument and returns **a new function**,
* which then takes another Element `newEl` as an argument, replaces
* `oldEl` with `newEl` and returns `newEl`.
*
* Uses `replaceChild` method of parent element of `oldEl`.
*
* @category Tree mutation
*
* @param {Element} oldEl element to be replaced
* @returns {Function} to take another Element `newEl` as an argument,
* replace `oldEl` with `newEl` and return `newEl`
*
* @see remove
* @see removeChild
*
* @example
*
* import {createElement, appendTo, replace} from '@yagni-js/yagni-dom';
*
* const div = createElement('div');
* const a = createElement('a');
* const span = createElement('span');
*
* const appendToDiv = appendTo(div);
* const replaceA = replace(a);
*
* const div1 = appendToDiv(a);
* // => div1 is div
* // => <div><a></a></div>
*
* const span1 = replaceA(span);
* // => span1 is span
* // => <div><span></span></div>
*
*/
function replace(oldEl) {
return function _replace(newEl) {
// NB. unused assignment
const res = parent(oldEl).replaceChild(newEl, oldEl);
return newEl;
};
}
/**
* Marker to skip child node creation if necessary.
*
* @private
*
*/
const skipMarker = 'SKIP';
/**
* Takes some value as an arguments, compares it to skip marker and returns
* `true` if value equals to skip marker or `false` otherwise.
*
* @private
* @function
*
* @param {*} smth some value to test equality to skip marker
* @returns {Boolean} `true` if `smth` equals to skip marker or
* `false` otherwise
*
*/
const isSkipMarker = yagni.equals(skipMarker);
/**
* Function to always return skip marker on call.
*
* Is in use in `@yagni-js/yagni-parser` in html to `@yagni-js/yagni-dom`
* conversion.
*
* @category Hyperscript
*
* @returns {String} skip marker to indicate child node should not be created
* while creating children nodes
*
* @example
*
* import {h, hText, hSkip} from '@yagni-js/yagni-dom';
*
* const div1 = h('div', {}, {}, [hSkip()]);
* const div2 = h('div', {}, {}, [hText('')]);
*
* const el1 = div1(); // => el.firstChild is null
* const el2 = div2(); // => el.firstChild is TextNode
*
*/
const hSkip = yagni.always(skipMarker);
/**
* Takes an element `target` and a function `factory` as arguments,
* calls function `factory`, which must return an element or a node,
* and appends it to `target` as a child.
*
* @private
*
* @param {Element} target an element to append child to
* @param {Function} factory function to call to create an element or a node
* @returns {Element} target element
*
*/
function createChild(target, factory) {
const el = factory();
// NB. side effect - unused assignment
const child = target.appendChild(el);
return target
}
/**
* Takes an array `children` as an argument and returns **a new function**,
* which then takes an element `target` as an argument, creates from `children`
* child elements, appends newly created nodes to `target` and returns `target`
* back.
*
* An array `children` must contain functions to call to create dom elements.
*
* @private
*
* @param {Array} children array of functions to call to create dom elements
* @returns {Function} to take an element `target` as an argument, create
* child elements, append them to `target` and return `target` back
*
*/
function createChildren(children) {
return function _createChildren(target) {
return children.reduce(
function __createChildren(el, item) {
return yagni.isArray(item) ? item.reduce(__createChildren, el) : (
isSkipMarker(item) ? el : createChild(el, item));
},
target
);
};
}
/**
* Takes a string `tagName`, an object `attrs`, an object `props` and an
* array `children` as arguments and returns a function,
* which should be called later to create dom element.
*
* Uses `createElement`, `setAttrs` and `setProps` to create an element and
* set its attributes and properties.
*
* @category Hyperscript
*
* @param {String} tagName tag name of dom element to create
* @param {Object} attrs dom element attributes
* @param {Object} props dom element properties
* @param {Array} children an array of functions to call
* to create child dom elements
* @returns {Function} function to call to create dom element according to
* passed in arguments
*
* @see hSVG
* @see hText
* @see createElement
* @see setAttrs
* @see setProps
*
* @example
*
* import {h, hText} from '@yagni-js/yagni-dom';
*
* const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [
* h('li', {'class': 'list-item'}, {}, [hText('Foo')]),
* h('li', {'class': 'list-item'}, {}, [hText('Baz')]),
* h('li', {'class': 'list-item'}, {}, [hText('Bar')])
* ]);
*
* const el = list();
* // => <ul class="list" data-foo="baz">
* // <li class="list-item">Foo</li>
* // <li class="list-item">Baz</li>
* // <li class="list-item">Bar</li>
* // </ul>
*
*/
function h(tagName, attrs, props, children) {
return yagni.pipe([
yagni.lazy(createElement, tagName),
setAttrs(attrs),
setProps(props),
createChildren(children)
]);
}
/**
* Takes a string `tagName`, an object `attrs`, an object `props` and an
* array `children` as arguments and returns a function,
* which should be called later to create svg dom element.
*
* Uses `createSVGElement`, `setAttrs` and `setProps` to create an element and
* set its attributes and properties.
*
* @category Hyperscript
*
* @param {String} tagName tag name of svg dom element to create
* @param {Object} attrs svg dom element attributes
* @param {Object} props svg dom element properties
* @param {Array} children an array of functions to call
* to create child svg dom elements
* @returns {Function} function to call to create svg dom element according to
* passed in arguments
*
* @see h
* @see hText
* @see createSVGElement
* @see setAttrs
* @see setProps
*
* @example
*
* import {hSVG} from '@yagni-js/yagni-dom';
*
* const circle = hSVG('svg', {'viewBox': '0 0 24 24', 'class': 'icon'}, {}, [
* hSVG('circle', {cx: 12, cy: 12, r: 8}, {}, [])
* ]);
*
* const el = circle();
* // => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon">
* // <circle cx="12" cy="12" r="8"></circle>
* // </svg>
*
*/
function hSVG(tagName, attrs, props, children) {
return yagni.pipe([
yagni.lazy(createSVGElement, tagName),
setAttrs(attrs),
setProps(props),
createChildren(children)
]);
}
/**
* Takes a string `text` as an argument and returns **a new function**,
* which should be called later to create text node for passed in `text`.
*
* @category Hyperscript
*
* @param {String} text text for text node
* @returns {Function} to call to create text node
*
* @see h
* @see hSVG
*
* @example
*
* import {hText} from '@yagni-js/yagni-dom';
*
* const text = 'Foo-Baz-Bar';
*
* const textFactory = hText(text);
*
* const node = textFactory(); // => text node `Foo-Baz_bar'
*
*/
function hText(text) {
return yagni.lazy(createText, text);
}
/**
* Takes an element `target` as an argument and returns **a new function**,
* which then takes a function to call to create dom element,
* appends newly created dom element to `target` as child and
* returns `target` element back.
*
* Works only for dom elements, not for text or comment nodes.
*
* @category Hyperscript
*
* @param {Element} target target element to append newly created
* dom element to
* @returns {Function} to take a function to call to create dom element,
* append newly created dom element to `target` and return `target` back
*
* @see h
* @see hSVG
* @see renderAfter
* @see renderC
* @see renderR
*
* @example
*
* import {h, render, createElement} from '@yagni-js/yagni-dom';
*
* const div = createElement('div'); // => <div></div>
* const renderToDiv = render(div);
*
* const p = h('p', {'class': 'story'}, {}, [
* 'Here goes the story...'
* ]);
*
* const el = renderToDiv(p);
* // => el is div
* // => <div>
* // <p class="story">Here goes the story...</p>
* // </div>
*
*
*/
function render(target) {
return function (factory) {
const el = factory();
// NB. unused assignment
const res = target.insertAdjacentElement('beforeend', el);
return target;
};
}
/**
* Takes an element `target` as an argument and returns **a new function**,
* which then takes a function to call to create dom element,
* appends newly created dom element after `target` element and
* returns newly created dom element back.
*
* Works only for dom elements, not for text or comment nodes.
*
* @category Hyperscript
*
* @param {Element} target target element to append newly created
* dom element after
* @returns {Function} to take a function to call to create dom element,
* append newly created dom element after `target` and return it back
*
* @see h
* @see hSVG
* @see render
* @see renderC
* @see renderR
* @see appendAfter
*
* @example
*
* import {h, hText, renderAfter} from '@yagni-js/yagni-dom';
*
* const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [
* h('li', {'class': 'list-item'}, {}, [hText('Foo')]),
* h('li', {'class': 'list-item'}, {}, [hText('Baz')])
* ]);
* const item = h('li', {'class': 'list-item'}, {}, [hText('Bar')]);
*
* const el = list();
* // => <ul class="list" data-foo="baz">
* // <li class="list-item">Foo</li>
* // <li class="list-item">Baz</li>
* // </ul>
*
* const renderAfterBaz = renderAfter(el.lastChild);
*
* const res = renderAfterBaz(item);
* // => res is item
*
* // => el has the following structure:
*
* // => <ul class="list" data-foo="baz">
* // <li class="list-item">Foo</li>
* // <li class="list-item">Baz</li>
* // <li class="list-item">Bar</li>
* // </ul>
*
*/
function renderAfter(target) {
return function (factory) {
const el = factory();
return target.insertAdjacentElement('afterend', el);
};
}
/**
* Takes an element `target` as an argument and returns **a new function**,
* which then takes a function to call to create dom element,
* removes all children elements from `target`, appends newly created
* dom element to `target` and returns `target` element back.
*
* Works only for dom elements, not for text or comment nodes.
*
* @category Hyperscript
*
* @param {Element} target element to remove all children elements from
* and append newly created dom element to
* @returns {Function} to take a function to call to create dom element,
* remove all children elements from `target`, append newly created
* dom element to `target` and return `target` element back
*
* @see h
* @see hSVG
* @see render
* @see renderAfter
* @see renderR
* @see removeChildren
*
* @example
*
* import {h, hText, renderC} from '@yagni-js/yagni-dom';
*
* const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [
* h('li', {'class': 'list-item'}, {}, [hText('Foo')]),
* h('li', {'class': 'list-item'}, {}, [hText('Baz')])
* ]);
* const item = h('li', {'class': 'list-item'}, {}, [hText('Bar')]);
*
* const el = list();
* // => <ul class="list" data-foo="baz">
* // <li class="list-item">Foo</li>
* // <li class="list-item">Baz</li>
* // </ul>
*
* const clearListAndRender = renderC(el);
*
* const res = clearListAndRender(item);
* // => res is el
*
* // => <ul class="list" data-foo="baz">
* // <li class="list-item">Bar</li>
* // </ul>
*
*/
function renderC(target) {
return function (factory) {
const el = factory();
// NB. unused assignment
const cleaned = removeChildren(target);
// NB. unused assignment
const res = target.insertAdjacentElement('beforeend', el);
return target;
};
}
/**
* Takes an element `target` as an argument and returns **a new function**,
* which then takes a function to call to create dom element or text node,
* replaces `target` element with newly created dom element or text node
* and returns newly created dom element or text node back.
*
* @category Hyperscript
*
* @param {Element} target element to replace
* @returns {Function} to take a function to call to create dom element
* or text node, replace `target` element with newly created
* dom element or text node and return it back
*
* @see h
* @see hSVG
* @see render
* @see renderAfter
* @see renderC
* @see replace
*
* @example
*
* import {h, hText, renderR} from '@yagni-js/yagni-dom';
*
* const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [
* h('li', {'class': 'list-item'}, {}, [hText('Foo')]),
* h('li', {'class': 'list-item'}, {}, [hText('Baz')])
* ]);
* const item = h('li', {'class': 'list-item'}, {}, [hText('Bar')]);
*
* const el = list();
* // => <ul class="list" data-foo="