Comparing version 4.1.1 to 4.1.2
@@ -1,1 +0,376 @@ | ||
"use strict";const t=Symbol("Open component"),e=Symbol("Closed component");function r(t,e,r="."){const s=e.split(r);let o=t[s[0].trim()];for(let e=1;e<s.length&&("object"==typeof o||"function"==typeof o);e++)o=(t=o)[s[e].trim()];return o}exports.Actribute=class{registry={};openAttr;closedAttr;constructor(t){this.openAttr=t?.open||"o-pen",this.closedAttr=t?.closed||"c-losed"}register(t){return Object.assign(this.registry,t)&&this}process(r,s){let o,n,i,c;"string"==typeof r?n=r:r instanceof Element?o=r:r instanceof Array?c=r:"object"==typeof r&&(o=r.el,n=r.attr,i=r.rattr,c=r.ctx),o||(o=document.body),n||(n="c-"),i||(i="r-"),c||(c=[]),s||(s=new Map);const h=o.attributes,p=h.length;let l,f,a,u,y,m=!1,g=o.hasAttribute(this.openAttr),d=o.hasAttribute(this.closedAttr);for([u,l]of s.entries())u(o,l,...c);for(f=0;f<p;f++)if(l=h[f],l&&(l.name.startsWith(n)||l.name.startsWith(i))&&l.name!==this.openAttr&&l.name!==this.closedAttr){if(m=!0,a=l.name.substring(n.length),this.registry.hasOwnProperty(a))u=this.registry[a],l.name.startsWith(i)&&!s.has(u)&&s.set(u,l),y=u(o,l,...c);else{if(!this.registry.hasOwnProperty("*"))throw new Error(`The component "${a}" was not found in the registry.`);y=this.registry["*"](o,l,...c)}y===t?g=!0:y===e&&(d=!0)}if((!m||s.size||g)&&!d){let t=o.firstElementChild;const e=[];for(s.size&&e.push(s);t;)this.process({el:t,attr:n,rattr:i,ctx:c},...e),t=t.nextElementSibling}return this}},exports.closed=e,exports.join=function(t,e){return(r,s,...o)=>{let n;for(let e of t)n=e(r,s,...o);return e||n}},exports.open=t,exports.props=function(t,e,s){"string"!=typeof t&&(t=t.toString());const o=[],n=e.length;let i,c,h;for(i of(t=t.trim()).split(s||" "))if(i=i.trim(),""!==i){for(c=void 0,h=-1;void 0===c&&++h<n;)c=r(e[h],i);if(void 0===c)throw new TypeError(`The property "${i}" was not found in any of the sources.`);o.push(c)}return o}; | ||
'use strict'; | ||
/** | ||
* This module exports the {@link Actribute} class which provides a structured way to attach behavior to HTML elements. The library | ||
* enables us to attach meaning to attributes in markup. The main motivation for its original | ||
* development was adding behavior to built-in elements without creating complexity and accessibility issues. However it can | ||
* be used more generally to implement a component and/or reactive system based on attributes. This has more flexibility and | ||
* composability than using tag names for components. It can also save a lot of typing when we use recursive components. | ||
* Bear in mind this can lead to messy code if too much complex behavior is embedded | ||
* in markup. Use with discretion. | ||
* | ||
* The attributes here name the components and are passed to them along with the element. Components can use the | ||
* element, attribute and context they receive in any way appropriate for their functions. Simple components may | ||
* use the attribute values literally or call {@link props()} with them to extract properties from objects (often context objects) | ||
* which they then use for their operation. More complex components could even interprete the values | ||
* as code (but this is not recommended). | ||
* | ||
* @example | ||
* import { Actribute, props } from 'deleight/actribute'; | ||
* | ||
* // initialize actribute: | ||
* const act = new Actribute(); | ||
* | ||
* // register components: | ||
* act.register({ | ||
* comp1: (element, attr, singleContext) => element.textContent = attr.value, | ||
* comp2: (element, attr, singleContext) => element.style.left = props(attr.value, [singleContext]) | ||
* }); | ||
* | ||
* | ||
* // process an element: | ||
* document.body.innerHTML = ` | ||
* <header></header> | ||
* <article c-comp1='I replace all the children here anyway' > <!-- using a raw value --> | ||
* <p>[comp1] is not applied to me</p> | ||
* <p>[comp1] is not applied to me</p> | ||
* </article> | ||
* <article r-comp2='a'> <!-- using a prop --> | ||
* <p>[comp2] is applied to me!</p> | ||
* <p>[comp2] is applied to me!/p> | ||
* <p>[comp2] is not applied to me!</p> | ||
* </article> | ||
* `; | ||
* const data = { a: '100px', b: 2, c: 3 }; | ||
* act.process([{el: document.body, ctx: data}]); | ||
* | ||
* // unregister a component: | ||
* delete act.registry.comp2; | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* A component can declare itself `open` by returning this symbol. This means | ||
* that { @link Actribute#process } will process its children after it. | ||
*/ | ||
const open = Symbol('Open component'); | ||
/** | ||
* A component can also declare itself closed by returning this symbol. This means | ||
* that { @link Actribute#process } will not process its children after it. | ||
*/ | ||
const closed = Symbol('Closed component'); | ||
/** | ||
* An Actribute class. Instances can be used to register components and process | ||
* elements. This is almost like a custom elements registry. | ||
* | ||
* @example | ||
* import { Actribute, props } from 'deleight/actribute'; | ||
* const act = new Actribute(); | ||
* | ||
*/ | ||
class Actribute { | ||
/** | ||
* The object that holds all registered components. The keys are the | ||
* component names and the values are the component functions. | ||
*/ | ||
registry = {}; | ||
/** | ||
* The attribute used to specify that the tree of an element with | ||
* components is open to nested processing. | ||
*/ | ||
openAttr; | ||
/** | ||
* The attribute used to specify that the tree of an element with | ||
* components is not open to nested processing. | ||
*/ | ||
closedAttr; | ||
/** | ||
* | ||
* Construct a new Actribute instance. | ||
* | ||
* A component specifier is of the form [attrPrefix][componentName]="..." | ||
* | ||
* When a component specifier is encountered, the component's function will be | ||
* invoked with the element and the matching attribute as arguments. | ||
* | ||
* The attribute can be string (where at least 1 property name is specified), | ||
* or boolean (where no property is specified). | ||
* | ||
* An 'open' element means its children are processed after it and a `closed` | ||
* element means they are not. Elements are 'open' by default when there are no | ||
* matching components on them and 'closed' otherwise. The optional `init` object | ||
* can be used to set the attribute names that are used to override this behavior. | ||
* The default open attribute is `o-pen` and the one for closed is `c-losed`. | ||
* | ||
* @example | ||
* import { Actribute } from 'deleight/actribute'; | ||
* const init = {open: 'o-pen', closed: 'c-losed'}; | ||
* const act = new Actribute(init); | ||
* | ||
* document.body.innerHTML = ` | ||
* <header></header> | ||
* <article > | ||
* <p>I am processed</p> | ||
* <p>I am processed</p> | ||
* </article> | ||
* <article c-losed> | ||
* <p>I am not processed</p> | ||
* <p>I am not processed</p> | ||
* <p>I am not processed</p> | ||
* </article> | ||
* `; | ||
* | ||
* act.process(document.body) | ||
* | ||
* @param {IActributeInit} init The value to assign to attrPrefix. Defaults to 'c-' | ||
* @constructor | ||
*/ | ||
constructor(init) { | ||
this.openAttr = init?.open || 'o-pen'; | ||
this.closedAttr = init?.closed || 'c-losed'; | ||
} | ||
/** | ||
* Registers multiple components at once using an object that maps | ||
* component names to component functions. | ||
* | ||
* @example | ||
* import { Actribute } from 'deleight/actribute'; | ||
* const act = new Actribute(); | ||
* act.register({ | ||
* comp1: (element, attr, singleContext) => element.textContent = attr.value, | ||
* comp2: (element, attr, singleContext) => element.style.left = props(attr.value, [singleContext]) | ||
* }); | ||
* | ||
* @param registerMap | ||
* @returns | ||
*/ | ||
register(registerMap) { | ||
return Object.assign(this.registry, registerMap) && this; | ||
} | ||
/** | ||
* Recursively processes `options.el` (or `document.body` by default) to | ||
* identify and apply components. Attributes with names starting with | ||
* `options.attr` (or `c-` by default) or `options.rattr` (or `r-` by default) | ||
* are treated as component specifiers. | ||
* | ||
* At elements where any components are encountered, the components | ||
* are called with the element, the attribute and any specified | ||
* context objects (`...(options.context || [])`). | ||
* | ||
* Where a component is encountered, decendants are not processed unless `this.open` | ||
* attribute is present on the element. At elements without a component, the descendants | ||
* are processed recursively, except `this.closed` boolean attribute is | ||
* specified. These are supposed to echo the semantics of the Shadow DOM API. | ||
* | ||
* If a component is not found and a wild-card component is registered (with '*'), | ||
* the widcard component is called instead. | ||
* | ||
* `options.attr` prefixes a component that is applied once while `options.rattr` | ||
* prefixes one that is applied recursively on all descendant elements which are not | ||
* within `closed` elements. Recursive attributes can save a lot of typing. | ||
* | ||
* Returns the same actribute to support call chaining. | ||
* | ||
* @example | ||
* import { Actribute, props } from 'deleight/actribute'; | ||
* const act = new Actribute(); | ||
* act.register({ | ||
* comp1: (element, attr, singleContext) => element.textContent = attr.value, | ||
* comp2: (element, attr, singleContext) => element.style.left = props(attr.value, [singleContext][0]) | ||
* }); | ||
* document.body.innerHTML = ` | ||
* <header></header> | ||
* <article c-comp1='I replace all the children here anyway' > <!-- using a raw value --> | ||
* <p>[comp1] is not applied to me</p> | ||
* <p>[comp1] is not applied to me</p> | ||
* </article> | ||
* <article r-comp2='a'> <!-- using a prop --> | ||
* <p>[comp2] is applied to me!</p> | ||
* <p>[comp2] is applied to me!/p> | ||
* <p>[comp2] is not applied to me!</p> | ||
* </article> | ||
* `; | ||
* const data = { a: '100px', b: 2, c: 3 }; | ||
* act.process([{el: document.body, ctx: data}]); | ||
* | ||
* @param {IProcessOptions} [options] | ||
* @returns {Actribute} | ||
*/ | ||
process(options, recursiveComps) { | ||
let element, attrPrefix, rattrPrefix, context; | ||
if (typeof options === 'string') | ||
attrPrefix = options; | ||
else if (options instanceof Element) | ||
element = options; | ||
else if (options instanceof Array) | ||
context = options; | ||
else if (typeof options === "object") { | ||
element = options.el; | ||
attrPrefix = options.attr; | ||
rattrPrefix = options.rattr; | ||
context = options.ctx; | ||
} | ||
if (!element) | ||
element = document.body; | ||
if (!attrPrefix) | ||
attrPrefix = 'c-'; | ||
if (!rattrPrefix) | ||
rattrPrefix = 'r-'; | ||
if (!context) | ||
context = []; | ||
if (!recursiveComps) | ||
recursiveComps = new Map(); | ||
const attrs = element.attributes, length = attrs.length; | ||
let attr, i, comp, compF, processed = false, localOpen = element.hasAttribute(this.openAttr), localClosed = element.hasAttribute(this.closedAttr), compResult; | ||
for ([compF, attr] of recursiveComps.entries()) { | ||
compF(element, attr, ...context); | ||
} | ||
for (i = 0; i < length; i++) { | ||
attr = attrs[i]; | ||
if (!attr) | ||
continue; // attr can get deleted by a component! | ||
if ((attr.name.startsWith(attrPrefix) || attr.name.startsWith(rattrPrefix)) && | ||
attr.name !== this.openAttr && attr.name !== this.closedAttr) { | ||
processed = true; | ||
comp = attr.name.substring(attrPrefix.length); | ||
if (this.registry.hasOwnProperty(comp)) { | ||
compF = this.registry[comp]; | ||
if (attr.name.startsWith(rattrPrefix) && !(recursiveComps.has(compF))) { | ||
recursiveComps.set(compF, attr); | ||
} | ||
compResult = compF(element, attr, ...context); | ||
} | ||
else if (this.registry.hasOwnProperty('*')) { | ||
compResult = this.registry['*'](element, attr, ...context); | ||
} | ||
else { | ||
throw new Error(`The component "${comp}" was not found in the registry.`); | ||
} | ||
if (compResult === open) | ||
localOpen = true; | ||
else if (compResult === closed) | ||
localClosed = true; | ||
} | ||
} | ||
if ((!processed || recursiveComps.size || localOpen) && !localClosed) { | ||
// local closed takes precedence over local open if they are both specified. | ||
let child = element.firstElementChild; | ||
const args = []; | ||
if (recursiveComps.size) | ||
args.push(recursiveComps); | ||
while (child) { | ||
this.process({ | ||
el: child, attr: attrPrefix, rattr: rattrPrefix, ctx: context | ||
}, ...args); | ||
child = child.nextElementSibling; | ||
} | ||
} | ||
return this; | ||
} | ||
} | ||
/** | ||
* Searches the object for the given prop. | ||
* prop can be specified as a dot-separated string so that | ||
* the lookup scheme is similar to a nested property access. | ||
* prop may contain any character except the propSep (space by default) | ||
* passed to the `process` method. | ||
* | ||
* @param {T} obj | ||
* @param {string} prop | ||
*/ | ||
function get(obj, prop, propSep = '.') { | ||
const props = prop.split(propSep); | ||
let result = obj[props[0].trim()]; | ||
for (let i = 1; i < props.length && | ||
(typeof result === "object" || typeof result === "function"); i++) { | ||
obj = result; | ||
result = obj[props[i].trim()]; | ||
} | ||
return result; | ||
} | ||
/** | ||
* Obtain 1 or more properties from the specified sources. Specify the property names | ||
* separated by a separator (`" "` by default). Returns an array of values for | ||
* each specified property in the names. Each property is returned from the | ||
* first object containing it. | ||
* | ||
* @example | ||
* const fbProps = { f: 20, g: 7 }; | ||
* const mainProps = { a: 1, b: { c: 1, d: 2 }, e: 3, f: 100 }; | ||
* console.log(props('e f g', [mainProps, fbProps])); // [3, 100, 7] | ||
* | ||
* @param names | ||
* @param scopes | ||
* @param sep | ||
* @returns | ||
*/ | ||
function props(names, scopes, sep) { | ||
if (typeof names !== 'string') | ||
names = names.toString(); | ||
const results = [], length = scopes.length; | ||
names = names.trim(); | ||
let name, val, i; | ||
for (name of names.split(sep || ' ')) { | ||
name = name.trim(); | ||
if (name === "") | ||
continue; // just too much space between prop names/keys. | ||
val = undefined; | ||
i = -1; | ||
while (val === undefined && ++i < length) | ||
val = get(scopes[i], name); | ||
if (val !== undefined) | ||
results.push(val); | ||
else { | ||
throw new TypeError(`The property "${name}" was not found in any of the sources.`); | ||
} | ||
} | ||
return results; | ||
} | ||
/** | ||
* Join multiple components, so that they can be applied as one component. | ||
* Can reduce repetitive markup in many cases. If you specify a returnValue | ||
* the new component will return it, else it will return the return value of | ||
* the last joined component. | ||
* | ||
* @example | ||
* | ||
* import ( join, Actribute ) from 'deleight/actribute'; | ||
* | ||
* document.body.innerHTML = ` | ||
* <div>I am not a component</div> | ||
* <main c-comp><p>I am inside a joined component</p></main> | ||
* <section>I am not a component</section> | ||
* <article>I am not a component</article> | ||
* `; | ||
* | ||
* const act = new Actribute(); | ||
* const comps = []; | ||
* const baseComp = (node) => comps.push(node.tagName); | ||
* const comp = join([baseComp, baseComp, baseComp]); | ||
* act.register({ comp }); | ||
* | ||
* act.process(document.body); | ||
* | ||
* console.log(comps.length); // 3 | ||
* console.log(comps[0]); // "MAIN" | ||
* console.log(comps[1]); // "MAIN" | ||
* console.log(comps[2]); // "MAIN" | ||
* | ||
* @param components | ||
* @param returnValue | ||
* @returns | ||
*/ | ||
function join(components, returnValue) { | ||
return (element, attr, ...context) => { | ||
let lastValue; | ||
for (let comp of components) | ||
lastValue = comp(element, attr, ...context); | ||
return returnValue || lastValue; | ||
}; | ||
} | ||
exports.Actribute = Actribute; | ||
exports.closed = closed; | ||
exports.join = join; | ||
exports.open = open; | ||
exports.props = props; |
@@ -1,1 +0,370 @@ | ||
const t=Symbol("Open component"),e=Symbol("Closed component");class r{registry={};openAttr;closedAttr;constructor(t){this.openAttr=t?.open||"o-pen",this.closedAttr=t?.closed||"c-losed"}register(t){return Object.assign(this.registry,t)&&this}process(r,s){let o,n,i,h;"string"==typeof r?n=r:r instanceof Element?o=r:r instanceof Array?h=r:"object"==typeof r&&(o=r.el,n=r.attr,i=r.rattr,h=r.ctx),o||(o=document.body),n||(n="c-"),i||(i="r-"),h||(h=[]),s||(s=new Map);const c=o.attributes,l=c.length;let f,a,p,u,y,m=!1,g=o.hasAttribute(this.openAttr),d=o.hasAttribute(this.closedAttr);for([u,f]of s.entries())u(o,f,...h);for(a=0;a<l;a++)if(f=c[a],f&&(f.name.startsWith(n)||f.name.startsWith(i))&&f.name!==this.openAttr&&f.name!==this.closedAttr){if(m=!0,p=f.name.substring(n.length),this.registry.hasOwnProperty(p))u=this.registry[p],f.name.startsWith(i)&&!s.has(u)&&s.set(u,f),y=u(o,f,...h);else{if(!this.registry.hasOwnProperty("*"))throw new Error(`The component "${p}" was not found in the registry.`);y=this.registry["*"](o,f,...h)}y===t?g=!0:y===e&&(d=!0)}if((!m||s.size||g)&&!d){let t=o.firstElementChild;const e=[];for(s.size&&e.push(s);t;)this.process({el:t,attr:n,rattr:i,ctx:h},...e),t=t.nextElementSibling}return this}}function s(t,e,r="."){const s=e.split(r);let o=t[s[0].trim()];for(let e=1;e<s.length&&("object"==typeof o||"function"==typeof o);e++)o=(t=o)[s[e].trim()];return o}function o(t,e,r){"string"!=typeof t&&(t=t.toString());const o=[],n=e.length;let i,h,c;for(i of(t=t.trim()).split(r||" "))if(i=i.trim(),""!==i){for(h=void 0,c=-1;void 0===h&&++c<n;)h=s(e[c],i);if(void 0===h)throw new TypeError(`The property "${i}" was not found in any of the sources.`);o.push(h)}return o}function n(t,e){return(r,s,...o)=>{let n;for(let e of t)n=e(r,s,...o);return e||n}}export{r as Actribute,e as closed,n as join,t as open,o as props}; | ||
/** | ||
* This module exports the {@link Actribute} class which provides a structured way to attach behavior to HTML elements. The library | ||
* enables us to attach meaning to attributes in markup. The main motivation for its original | ||
* development was adding behavior to built-in elements without creating complexity and accessibility issues. However it can | ||
* be used more generally to implement a component and/or reactive system based on attributes. This has more flexibility and | ||
* composability than using tag names for components. It can also save a lot of typing when we use recursive components. | ||
* Bear in mind this can lead to messy code if too much complex behavior is embedded | ||
* in markup. Use with discretion. | ||
* | ||
* The attributes here name the components and are passed to them along with the element. Components can use the | ||
* element, attribute and context they receive in any way appropriate for their functions. Simple components may | ||
* use the attribute values literally or call {@link props()} with them to extract properties from objects (often context objects) | ||
* which they then use for their operation. More complex components could even interprete the values | ||
* as code (but this is not recommended). | ||
* | ||
* @example | ||
* import { Actribute, props } from 'deleight/actribute'; | ||
* | ||
* // initialize actribute: | ||
* const act = new Actribute(); | ||
* | ||
* // register components: | ||
* act.register({ | ||
* comp1: (element, attr, singleContext) => element.textContent = attr.value, | ||
* comp2: (element, attr, singleContext) => element.style.left = props(attr.value, [singleContext]) | ||
* }); | ||
* | ||
* | ||
* // process an element: | ||
* document.body.innerHTML = ` | ||
* <header></header> | ||
* <article c-comp1='I replace all the children here anyway' > <!-- using a raw value --> | ||
* <p>[comp1] is not applied to me</p> | ||
* <p>[comp1] is not applied to me</p> | ||
* </article> | ||
* <article r-comp2='a'> <!-- using a prop --> | ||
* <p>[comp2] is applied to me!</p> | ||
* <p>[comp2] is applied to me!/p> | ||
* <p>[comp2] is not applied to me!</p> | ||
* </article> | ||
* `; | ||
* const data = { a: '100px', b: 2, c: 3 }; | ||
* act.process([{el: document.body, ctx: data}]); | ||
* | ||
* // unregister a component: | ||
* delete act.registry.comp2; | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* A component can declare itself `open` by returning this symbol. This means | ||
* that { @link Actribute#process } will process its children after it. | ||
*/ | ||
const open = Symbol('Open component'); | ||
/** | ||
* A component can also declare itself closed by returning this symbol. This means | ||
* that { @link Actribute#process } will not process its children after it. | ||
*/ | ||
const closed = Symbol('Closed component'); | ||
/** | ||
* An Actribute class. Instances can be used to register components and process | ||
* elements. This is almost like a custom elements registry. | ||
* | ||
* @example | ||
* import { Actribute, props } from 'deleight/actribute'; | ||
* const act = new Actribute(); | ||
* | ||
*/ | ||
class Actribute { | ||
/** | ||
* The object that holds all registered components. The keys are the | ||
* component names and the values are the component functions. | ||
*/ | ||
registry = {}; | ||
/** | ||
* The attribute used to specify that the tree of an element with | ||
* components is open to nested processing. | ||
*/ | ||
openAttr; | ||
/** | ||
* The attribute used to specify that the tree of an element with | ||
* components is not open to nested processing. | ||
*/ | ||
closedAttr; | ||
/** | ||
* | ||
* Construct a new Actribute instance. | ||
* | ||
* A component specifier is of the form [attrPrefix][componentName]="..." | ||
* | ||
* When a component specifier is encountered, the component's function will be | ||
* invoked with the element and the matching attribute as arguments. | ||
* | ||
* The attribute can be string (where at least 1 property name is specified), | ||
* or boolean (where no property is specified). | ||
* | ||
* An 'open' element means its children are processed after it and a `closed` | ||
* element means they are not. Elements are 'open' by default when there are no | ||
* matching components on them and 'closed' otherwise. The optional `init` object | ||
* can be used to set the attribute names that are used to override this behavior. | ||
* The default open attribute is `o-pen` and the one for closed is `c-losed`. | ||
* | ||
* @example | ||
* import { Actribute } from 'deleight/actribute'; | ||
* const init = {open: 'o-pen', closed: 'c-losed'}; | ||
* const act = new Actribute(init); | ||
* | ||
* document.body.innerHTML = ` | ||
* <header></header> | ||
* <article > | ||
* <p>I am processed</p> | ||
* <p>I am processed</p> | ||
* </article> | ||
* <article c-losed> | ||
* <p>I am not processed</p> | ||
* <p>I am not processed</p> | ||
* <p>I am not processed</p> | ||
* </article> | ||
* `; | ||
* | ||
* act.process(document.body) | ||
* | ||
* @param {IActributeInit} init The value to assign to attrPrefix. Defaults to 'c-' | ||
* @constructor | ||
*/ | ||
constructor(init) { | ||
this.openAttr = init?.open || 'o-pen'; | ||
this.closedAttr = init?.closed || 'c-losed'; | ||
} | ||
/** | ||
* Registers multiple components at once using an object that maps | ||
* component names to component functions. | ||
* | ||
* @example | ||
* import { Actribute } from 'deleight/actribute'; | ||
* const act = new Actribute(); | ||
* act.register({ | ||
* comp1: (element, attr, singleContext) => element.textContent = attr.value, | ||
* comp2: (element, attr, singleContext) => element.style.left = props(attr.value, [singleContext]) | ||
* }); | ||
* | ||
* @param registerMap | ||
* @returns | ||
*/ | ||
register(registerMap) { | ||
return Object.assign(this.registry, registerMap) && this; | ||
} | ||
/** | ||
* Recursively processes `options.el` (or `document.body` by default) to | ||
* identify and apply components. Attributes with names starting with | ||
* `options.attr` (or `c-` by default) or `options.rattr` (or `r-` by default) | ||
* are treated as component specifiers. | ||
* | ||
* At elements where any components are encountered, the components | ||
* are called with the element, the attribute and any specified | ||
* context objects (`...(options.context || [])`). | ||
* | ||
* Where a component is encountered, decendants are not processed unless `this.open` | ||
* attribute is present on the element. At elements without a component, the descendants | ||
* are processed recursively, except `this.closed` boolean attribute is | ||
* specified. These are supposed to echo the semantics of the Shadow DOM API. | ||
* | ||
* If a component is not found and a wild-card component is registered (with '*'), | ||
* the widcard component is called instead. | ||
* | ||
* `options.attr` prefixes a component that is applied once while `options.rattr` | ||
* prefixes one that is applied recursively on all descendant elements which are not | ||
* within `closed` elements. Recursive attributes can save a lot of typing. | ||
* | ||
* Returns the same actribute to support call chaining. | ||
* | ||
* @example | ||
* import { Actribute, props } from 'deleight/actribute'; | ||
* const act = new Actribute(); | ||
* act.register({ | ||
* comp1: (element, attr, singleContext) => element.textContent = attr.value, | ||
* comp2: (element, attr, singleContext) => element.style.left = props(attr.value, [singleContext][0]) | ||
* }); | ||
* document.body.innerHTML = ` | ||
* <header></header> | ||
* <article c-comp1='I replace all the children here anyway' > <!-- using a raw value --> | ||
* <p>[comp1] is not applied to me</p> | ||
* <p>[comp1] is not applied to me</p> | ||
* </article> | ||
* <article r-comp2='a'> <!-- using a prop --> | ||
* <p>[comp2] is applied to me!</p> | ||
* <p>[comp2] is applied to me!/p> | ||
* <p>[comp2] is not applied to me!</p> | ||
* </article> | ||
* `; | ||
* const data = { a: '100px', b: 2, c: 3 }; | ||
* act.process([{el: document.body, ctx: data}]); | ||
* | ||
* @param {IProcessOptions} [options] | ||
* @returns {Actribute} | ||
*/ | ||
process(options, recursiveComps) { | ||
let element, attrPrefix, rattrPrefix, context; | ||
if (typeof options === 'string') | ||
attrPrefix = options; | ||
else if (options instanceof Element) | ||
element = options; | ||
else if (options instanceof Array) | ||
context = options; | ||
else if (typeof options === "object") { | ||
element = options.el; | ||
attrPrefix = options.attr; | ||
rattrPrefix = options.rattr; | ||
context = options.ctx; | ||
} | ||
if (!element) | ||
element = document.body; | ||
if (!attrPrefix) | ||
attrPrefix = 'c-'; | ||
if (!rattrPrefix) | ||
rattrPrefix = 'r-'; | ||
if (!context) | ||
context = []; | ||
if (!recursiveComps) | ||
recursiveComps = new Map(); | ||
const attrs = element.attributes, length = attrs.length; | ||
let attr, i, comp, compF, processed = false, localOpen = element.hasAttribute(this.openAttr), localClosed = element.hasAttribute(this.closedAttr), compResult; | ||
for ([compF, attr] of recursiveComps.entries()) { | ||
compF(element, attr, ...context); | ||
} | ||
for (i = 0; i < length; i++) { | ||
attr = attrs[i]; | ||
if (!attr) | ||
continue; // attr can get deleted by a component! | ||
if ((attr.name.startsWith(attrPrefix) || attr.name.startsWith(rattrPrefix)) && | ||
attr.name !== this.openAttr && attr.name !== this.closedAttr) { | ||
processed = true; | ||
comp = attr.name.substring(attrPrefix.length); | ||
if (this.registry.hasOwnProperty(comp)) { | ||
compF = this.registry[comp]; | ||
if (attr.name.startsWith(rattrPrefix) && !(recursiveComps.has(compF))) { | ||
recursiveComps.set(compF, attr); | ||
} | ||
compResult = compF(element, attr, ...context); | ||
} | ||
else if (this.registry.hasOwnProperty('*')) { | ||
compResult = this.registry['*'](element, attr, ...context); | ||
} | ||
else { | ||
throw new Error(`The component "${comp}" was not found in the registry.`); | ||
} | ||
if (compResult === open) | ||
localOpen = true; | ||
else if (compResult === closed) | ||
localClosed = true; | ||
} | ||
} | ||
if ((!processed || recursiveComps.size || localOpen) && !localClosed) { | ||
// local closed takes precedence over local open if they are both specified. | ||
let child = element.firstElementChild; | ||
const args = []; | ||
if (recursiveComps.size) | ||
args.push(recursiveComps); | ||
while (child) { | ||
this.process({ | ||
el: child, attr: attrPrefix, rattr: rattrPrefix, ctx: context | ||
}, ...args); | ||
child = child.nextElementSibling; | ||
} | ||
} | ||
return this; | ||
} | ||
} | ||
/** | ||
* Searches the object for the given prop. | ||
* prop can be specified as a dot-separated string so that | ||
* the lookup scheme is similar to a nested property access. | ||
* prop may contain any character except the propSep (space by default) | ||
* passed to the `process` method. | ||
* | ||
* @param {T} obj | ||
* @param {string} prop | ||
*/ | ||
function get(obj, prop, propSep = '.') { | ||
const props = prop.split(propSep); | ||
let result = obj[props[0].trim()]; | ||
for (let i = 1; i < props.length && | ||
(typeof result === "object" || typeof result === "function"); i++) { | ||
obj = result; | ||
result = obj[props[i].trim()]; | ||
} | ||
return result; | ||
} | ||
/** | ||
* Obtain 1 or more properties from the specified sources. Specify the property names | ||
* separated by a separator (`" "` by default). Returns an array of values for | ||
* each specified property in the names. Each property is returned from the | ||
* first object containing it. | ||
* | ||
* @example | ||
* const fbProps = { f: 20, g: 7 }; | ||
* const mainProps = { a: 1, b: { c: 1, d: 2 }, e: 3, f: 100 }; | ||
* console.log(props('e f g', [mainProps, fbProps])); // [3, 100, 7] | ||
* | ||
* @param names | ||
* @param scopes | ||
* @param sep | ||
* @returns | ||
*/ | ||
function props(names, scopes, sep) { | ||
if (typeof names !== 'string') | ||
names = names.toString(); | ||
const results = [], length = scopes.length; | ||
names = names.trim(); | ||
let name, val, i; | ||
for (name of names.split(sep || ' ')) { | ||
name = name.trim(); | ||
if (name === "") | ||
continue; // just too much space between prop names/keys. | ||
val = undefined; | ||
i = -1; | ||
while (val === undefined && ++i < length) | ||
val = get(scopes[i], name); | ||
if (val !== undefined) | ||
results.push(val); | ||
else { | ||
throw new TypeError(`The property "${name}" was not found in any of the sources.`); | ||
} | ||
} | ||
return results; | ||
} | ||
/** | ||
* Join multiple components, so that they can be applied as one component. | ||
* Can reduce repetitive markup in many cases. If you specify a returnValue | ||
* the new component will return it, else it will return the return value of | ||
* the last joined component. | ||
* | ||
* @example | ||
* | ||
* import ( join, Actribute ) from 'deleight/actribute'; | ||
* | ||
* document.body.innerHTML = ` | ||
* <div>I am not a component</div> | ||
* <main c-comp><p>I am inside a joined component</p></main> | ||
* <section>I am not a component</section> | ||
* <article>I am not a component</article> | ||
* `; | ||
* | ||
* const act = new Actribute(); | ||
* const comps = []; | ||
* const baseComp = (node) => comps.push(node.tagName); | ||
* const comp = join([baseComp, baseComp, baseComp]); | ||
* act.register({ comp }); | ||
* | ||
* act.process(document.body); | ||
* | ||
* console.log(comps.length); // 3 | ||
* console.log(comps[0]); // "MAIN" | ||
* console.log(comps[1]); // "MAIN" | ||
* console.log(comps[2]); // "MAIN" | ||
* | ||
* @param components | ||
* @param returnValue | ||
* @returns | ||
*/ | ||
function join(components, returnValue) { | ||
return (element, attr, ...context) => { | ||
let lastValue; | ||
for (let comp of components) | ||
lastValue = comp(element, attr, ...context); | ||
return returnValue || lastValue; | ||
}; | ||
} | ||
export { Actribute, closed, join, open, props }; |
@@ -1,1 +0,194 @@ | ||
"use strict";function e(e,t,n){const r=e.split(",").map((e=>e.trim())),o=[];let l;for(let e of Array.from(t.sheet?.cssRules||[]))for(l of r)if(e.cssText.startsWith(l)&&(o.push(e),n))return o;return o}function t(t,n){return e(t,n,!0)[0]}function n(n,o,l,s){let c;o||(o=document.body),c=s?o instanceof HTMLStyleElement?e=>t(e,o):o.querySelector.bind(o):o instanceof HTMLStyleElement?t=>e(t,o):o.querySelectorAll.bind(o);const f=o instanceof HTMLStyleElement?o.sheet?.cssRules:o.children;let i;for(let[e,t]of Object.entries(n))i=parseInt(e),isNaN(i)?r(c(e),t,l):r(f[i>=0?i:f.length+i],t,l)}function r(e,t,r,o){let l,s;for(s of((e instanceof Element||e instanceof CSSRule)&&(e=[e]),t instanceof Array||(t=[t]),t))if(s instanceof Function)if(r)for(l of e)s(l);else s(...e);else for(l of e)n(s,l,r,o)}exports.apply=n,exports.applyTo=r,exports.parentSelector=function(e,t){let n=e.parentElement;for(;n&&!n.matches(t);)n=n.parentElement;return n},exports.querySelectors=function(e,t){return t||(t=document.body),"string"==typeof e&&(e=e.split(",")),e.map((e=>t.querySelector(e.trim())))},exports.ruleSelector=t,exports.ruleSelectorAll=e; | ||
'use strict'; | ||
/** | ||
* This module exports the {@link apply}, {@link applyTo}, {@link parentSelector} {@link ruleSelector}, | ||
* {@link ruleSelectorAll} and {@link querySelectors} for succinctly selecting and operating on DOM elements and CSSOM rules. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Functions similarly to querySelectorAll, but for selecting style rules in | ||
* a CSS stylesheet object. All rules that start with any of the selectors are | ||
* selected. | ||
* @example | ||
* import { ruleSelectorAll } from 'deleight/appliance'; | ||
* const firstSpanRule = ruleSelectorAll('span', document.getElementsByTagName('style')[0], true)[0]; | ||
* | ||
* @param {string} selectors | ||
* @param {HTMLStyleElement} styleElement | ||
* @param {boolean} [firstOnly] | ||
* @returns {Array<CSSRule>} | ||
*/ | ||
function ruleSelectorAll(selectors, styleElement, firstOnly) { | ||
const arrSelectors = selectors.split(",").map((item) => item.trim()); | ||
const result = []; | ||
let selector; | ||
for (let rule of Array.from(styleElement.sheet?.cssRules || [])) { | ||
for (selector of arrSelectors) { | ||
if (rule.cssText.startsWith(selector)) { | ||
result.push(rule); | ||
if (firstOnly) | ||
return result; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Similar to querySelector in the same way ruleSelectorAll is similar to | ||
* querySelectorAll. | ||
* @example | ||
* import { ruleSelector } from 'deleight/appliance'; | ||
* const firstSpanRule = ruleSelector('span', document.getElementsByTagName('style')[0]) | ||
* | ||
* | ||
* @param {string} selectors | ||
* @param {HTMLStyleElement} styleElement | ||
* @returns {CSSRule} | ||
*/ | ||
function ruleSelector(selectors, styleElement) { | ||
return ruleSelectorAll(selectors, styleElement, true)[0]; | ||
} | ||
/** | ||
* Return the first ancestor that matches the selector. | ||
* @example | ||
* import { parentSelector } from 'deleight/appliance'; | ||
* const removeListener = (e) => { | ||
* table.removeChild(component.beforeRemove(parentSelector(e.target, 'tr'))); | ||
* }; | ||
* | ||
* @param {Node} node | ||
* @param {string} selector | ||
* @returns {Element} | ||
*/ | ||
function parentSelector(node, selector) { | ||
let parent = node.parentElement; | ||
while (parent && !parent.matches(selector)) | ||
parent = parent.parentElement; | ||
return parent; | ||
} | ||
/** | ||
* Selects the first elements corresponding to each of the selector args. | ||
* This calls qcontainingElement.uerySelector on each of the selectors, | ||
* so that only first elements are selected. It is different from | ||
* querySelectorAll which will select *all* elements. | ||
* | ||
* The containing element defaults to document.body. | ||
* | ||
* @example | ||
* import { select } from 'deleight/appliance' | ||
* const [firstDiv, firstP, firstSpan] = select('div, p, span'); | ||
* | ||
* | ||
* @param { string[] } selectors | ||
* @param { Element } [containingElement] | ||
* | ||
*/ | ||
function querySelectors(selectors, containingElement) { | ||
if (!containingElement) | ||
containingElement = document.body; | ||
if (typeof selectors === 'string') | ||
selectors = selectors.split(','); | ||
return selectors.map(s => containingElement.querySelector(s.trim())); | ||
} | ||
/** | ||
* Select the elements matching the keys in applyMap and run the functions given by the values over them. | ||
* This eliminates the many calls to querySelectorAll, which is quite verbose. | ||
* | ||
* If a key is a number instead of | ||
* a string, the child element at the index is selected instead. Negative indices can also be used to count from | ||
* the end backwards. | ||
* | ||
* If a value in `applyMap` is an object but not an array, apply is called recursively on all selected | ||
* elements for it with the object used as the applyMap. | ||
* | ||
* Without the third argument (`atomic`), all selected elements are | ||
* passed to the functions at once. With the argument given as a truthy value, | ||
* the elements are passed one by one, so that the behavior is almost like that | ||
* of web components. | ||
* | ||
* A 4th argument (`selectFirst`) may also be specified to limit each selection to only the first matching | ||
* elements. In this case (and in all cases where there is only 1 match), the value of `atomic` will be irrelevant. | ||
* | ||
* @example | ||
* import { apply } from 'deleight/appliance'; | ||
* apply({ | ||
* main: main => { | ||
* const newContent = [...range(101, 120)].map(i => `My index is now ${i}`); | ||
* const lastChildren = Array.from(main.children).map(c => c.lastElementChild); | ||
* set(lastChildren, {textContent: newContent}); | ||
* } | ||
* }); | ||
* | ||
* @param {IApplyMap } applyMap | ||
* @param {HTMLElement} [containerElement] | ||
* @param {boolean|number} [atomic] | ||
* @param {boolean|number} [selectFirst] | ||
*/ | ||
function apply(applyMap, containerElement, atomic, selectFirst) { | ||
if (!containerElement) | ||
containerElement = document.body; | ||
let selectorAll; | ||
if (!selectFirst) { | ||
selectorAll = | ||
containerElement instanceof HTMLStyleElement | ||
? (selectors) => ruleSelectorAll(selectors, containerElement) | ||
: containerElement.querySelectorAll.bind(containerElement); | ||
} | ||
else { | ||
selectorAll = | ||
containerElement instanceof HTMLStyleElement | ||
? (selectors) => ruleSelector(selectors, containerElement) | ||
: containerElement.querySelector.bind(containerElement); | ||
} | ||
const children = containerElement instanceof HTMLStyleElement ? containerElement.sheet?.cssRules : containerElement.children; | ||
let index; | ||
for (let [selectors, functions] of Object.entries(applyMap)) { | ||
index = parseInt(selectors); | ||
if (isNaN(index)) | ||
applyTo(selectorAll(selectors), functions, atomic); | ||
else | ||
applyTo(children[index >= 0 ? index : children.length + index], functions, atomic); | ||
} | ||
} | ||
/** | ||
* Applies the given functions to the specified elements (or CSS rules). | ||
* | ||
* asComponent specifies whether the functions should be applied to each | ||
* element. If falsy/not specified, all the elements are passed to the functions | ||
* at once. | ||
* @example | ||
* import { applyTo } from 'deleight/appliance'; | ||
* applyTo(Array.from(document.body.children), (...bodyChildren) => console.log(bodyChildren.length)); | ||
* | ||
* @param {(Element|CSSRule)[]} elements | ||
* @param {IComponents} components | ||
* @param {boolean|undefined} [atomic] | ||
*/ | ||
function applyTo(elements, components, atomic, selectFirst) { | ||
let element, comp; | ||
if (elements instanceof Element || elements instanceof CSSRule) | ||
elements = [elements]; | ||
if (!(components instanceof Array)) | ||
components = [components]; | ||
for (comp of components) { | ||
if (comp instanceof Function) { | ||
if (atomic) | ||
for (element of elements) | ||
comp(element); | ||
else | ||
comp(...elements); | ||
} | ||
else { | ||
for (element of elements) | ||
apply(comp, element, atomic, selectFirst); | ||
} | ||
} | ||
} | ||
exports.apply = apply; | ||
exports.applyTo = applyTo; | ||
exports.parentSelector = parentSelector; | ||
exports.querySelectors = querySelectors; | ||
exports.ruleSelector = ruleSelector; | ||
exports.ruleSelectorAll = ruleSelectorAll; |
@@ -1,1 +0,187 @@ | ||
function e(e,t,n){const o=e.split(",").map((e=>e.trim())),r=[];let f;for(let e of Array.from(t.sheet?.cssRules||[]))for(f of o)if(e.cssText.startsWith(f)&&(r.push(e),n))return r;return r}function t(t,n){return e(t,n,!0)[0]}function n(e,t){let n=e.parentElement;for(;n&&!n.matches(t);)n=n.parentElement;return n}function o(e,t){return t||(t=document.body),"string"==typeof e&&(e=e.split(",")),e.map((e=>t.querySelector(e.trim())))}function r(n,o,r,s){let l;o||(o=document.body),l=s?o instanceof HTMLStyleElement?e=>t(e,o):o.querySelector.bind(o):o instanceof HTMLStyleElement?t=>e(t,o):o.querySelectorAll.bind(o);const i=o instanceof HTMLStyleElement?o.sheet?.cssRules:o.children;let c;for(let[e,t]of Object.entries(n))c=parseInt(e),isNaN(c)?f(l(e),t,r):f(i[c>=0?c:i.length+c],t,r)}function f(e,t,n,o){let f,s;for(s of((e instanceof Element||e instanceof CSSRule)&&(e=[e]),t instanceof Array||(t=[t]),t))if(s instanceof Function)if(n)for(f of e)s(f);else s(...e);else for(f of e)r(s,f,n,o)}export{r as apply,f as applyTo,n as parentSelector,o as querySelectors,t as ruleSelector,e as ruleSelectorAll}; | ||
/** | ||
* This module exports the {@link apply}, {@link applyTo}, {@link parentSelector} {@link ruleSelector}, | ||
* {@link ruleSelectorAll} and {@link querySelectors} for succinctly selecting and operating on DOM elements and CSSOM rules. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Functions similarly to querySelectorAll, but for selecting style rules in | ||
* a CSS stylesheet object. All rules that start with any of the selectors are | ||
* selected. | ||
* @example | ||
* import { ruleSelectorAll } from 'deleight/appliance'; | ||
* const firstSpanRule = ruleSelectorAll('span', document.getElementsByTagName('style')[0], true)[0]; | ||
* | ||
* @param {string} selectors | ||
* @param {HTMLStyleElement} styleElement | ||
* @param {boolean} [firstOnly] | ||
* @returns {Array<CSSRule>} | ||
*/ | ||
function ruleSelectorAll(selectors, styleElement, firstOnly) { | ||
const arrSelectors = selectors.split(",").map((item) => item.trim()); | ||
const result = []; | ||
let selector; | ||
for (let rule of Array.from(styleElement.sheet?.cssRules || [])) { | ||
for (selector of arrSelectors) { | ||
if (rule.cssText.startsWith(selector)) { | ||
result.push(rule); | ||
if (firstOnly) | ||
return result; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Similar to querySelector in the same way ruleSelectorAll is similar to | ||
* querySelectorAll. | ||
* @example | ||
* import { ruleSelector } from 'deleight/appliance'; | ||
* const firstSpanRule = ruleSelector('span', document.getElementsByTagName('style')[0]) | ||
* | ||
* | ||
* @param {string} selectors | ||
* @param {HTMLStyleElement} styleElement | ||
* @returns {CSSRule} | ||
*/ | ||
function ruleSelector(selectors, styleElement) { | ||
return ruleSelectorAll(selectors, styleElement, true)[0]; | ||
} | ||
/** | ||
* Return the first ancestor that matches the selector. | ||
* @example | ||
* import { parentSelector } from 'deleight/appliance'; | ||
* const removeListener = (e) => { | ||
* table.removeChild(component.beforeRemove(parentSelector(e.target, 'tr'))); | ||
* }; | ||
* | ||
* @param {Node} node | ||
* @param {string} selector | ||
* @returns {Element} | ||
*/ | ||
function parentSelector(node, selector) { | ||
let parent = node.parentElement; | ||
while (parent && !parent.matches(selector)) | ||
parent = parent.parentElement; | ||
return parent; | ||
} | ||
/** | ||
* Selects the first elements corresponding to each of the selector args. | ||
* This calls qcontainingElement.uerySelector on each of the selectors, | ||
* so that only first elements are selected. It is different from | ||
* querySelectorAll which will select *all* elements. | ||
* | ||
* The containing element defaults to document.body. | ||
* | ||
* @example | ||
* import { select } from 'deleight/appliance' | ||
* const [firstDiv, firstP, firstSpan] = select('div, p, span'); | ||
* | ||
* | ||
* @param { string[] } selectors | ||
* @param { Element } [containingElement] | ||
* | ||
*/ | ||
function querySelectors(selectors, containingElement) { | ||
if (!containingElement) | ||
containingElement = document.body; | ||
if (typeof selectors === 'string') | ||
selectors = selectors.split(','); | ||
return selectors.map(s => containingElement.querySelector(s.trim())); | ||
} | ||
/** | ||
* Select the elements matching the keys in applyMap and run the functions given by the values over them. | ||
* This eliminates the many calls to querySelectorAll, which is quite verbose. | ||
* | ||
* If a key is a number instead of | ||
* a string, the child element at the index is selected instead. Negative indices can also be used to count from | ||
* the end backwards. | ||
* | ||
* If a value in `applyMap` is an object but not an array, apply is called recursively on all selected | ||
* elements for it with the object used as the applyMap. | ||
* | ||
* Without the third argument (`atomic`), all selected elements are | ||
* passed to the functions at once. With the argument given as a truthy value, | ||
* the elements are passed one by one, so that the behavior is almost like that | ||
* of web components. | ||
* | ||
* A 4th argument (`selectFirst`) may also be specified to limit each selection to only the first matching | ||
* elements. In this case (and in all cases where there is only 1 match), the value of `atomic` will be irrelevant. | ||
* | ||
* @example | ||
* import { apply } from 'deleight/appliance'; | ||
* apply({ | ||
* main: main => { | ||
* const newContent = [...range(101, 120)].map(i => `My index is now ${i}`); | ||
* const lastChildren = Array.from(main.children).map(c => c.lastElementChild); | ||
* set(lastChildren, {textContent: newContent}); | ||
* } | ||
* }); | ||
* | ||
* @param {IApplyMap } applyMap | ||
* @param {HTMLElement} [containerElement] | ||
* @param {boolean|number} [atomic] | ||
* @param {boolean|number} [selectFirst] | ||
*/ | ||
function apply(applyMap, containerElement, atomic, selectFirst) { | ||
if (!containerElement) | ||
containerElement = document.body; | ||
let selectorAll; | ||
if (!selectFirst) { | ||
selectorAll = | ||
containerElement instanceof HTMLStyleElement | ||
? (selectors) => ruleSelectorAll(selectors, containerElement) | ||
: containerElement.querySelectorAll.bind(containerElement); | ||
} | ||
else { | ||
selectorAll = | ||
containerElement instanceof HTMLStyleElement | ||
? (selectors) => ruleSelector(selectors, containerElement) | ||
: containerElement.querySelector.bind(containerElement); | ||
} | ||
const children = containerElement instanceof HTMLStyleElement ? containerElement.sheet?.cssRules : containerElement.children; | ||
let index; | ||
for (let [selectors, functions] of Object.entries(applyMap)) { | ||
index = parseInt(selectors); | ||
if (isNaN(index)) | ||
applyTo(selectorAll(selectors), functions, atomic); | ||
else | ||
applyTo(children[index >= 0 ? index : children.length + index], functions, atomic); | ||
} | ||
} | ||
/** | ||
* Applies the given functions to the specified elements (or CSS rules). | ||
* | ||
* asComponent specifies whether the functions should be applied to each | ||
* element. If falsy/not specified, all the elements are passed to the functions | ||
* at once. | ||
* @example | ||
* import { applyTo } from 'deleight/appliance'; | ||
* applyTo(Array.from(document.body.children), (...bodyChildren) => console.log(bodyChildren.length)); | ||
* | ||
* @param {(Element|CSSRule)[]} elements | ||
* @param {IComponents} components | ||
* @param {boolean|undefined} [atomic] | ||
*/ | ||
function applyTo(elements, components, atomic, selectFirst) { | ||
let element, comp; | ||
if (elements instanceof Element || elements instanceof CSSRule) | ||
elements = [elements]; | ||
if (!(components instanceof Array)) | ||
components = [components]; | ||
for (comp of components) { | ||
if (comp instanceof Function) { | ||
if (atomic) | ||
for (element of elements) | ||
comp(element); | ||
else | ||
comp(...elements); | ||
} | ||
else { | ||
for (element of elements) | ||
apply(comp, element, atomic, selectFirst); | ||
} | ||
} | ||
} | ||
export { apply, applyTo, parentSelector, querySelectors, ruleSelector, ruleSelectorAll }; |
@@ -1,1 +0,12 @@ | ||
"use strict";var e=require("apption");Object.keys(e).forEach((function(t){"default"===t||Object.prototype.hasOwnProperty.call(exports,t)||Object.defineProperty(exports,t,{enumerable:!0,get:function(){return e[t]}})})); | ||
'use strict'; | ||
var apption = require('apption'); | ||
Object.keys(apption).forEach(function (k) { | ||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { | ||
enumerable: true, | ||
get: function () { return apption[k]; } | ||
}); | ||
}); |
@@ -1,1 +0,1 @@ | ||
export*from"apption"; | ||
export * from 'apption'; |
@@ -1,1 +0,268 @@ | ||
"use strict";async function e(e,...t){const n=[];for(let[e,r]of Array.from(t.entries()))r instanceof Promise?n.push(r):n.push(Promise.resolve(r));const r=await Promise.all(n);return r.map(((t,n)=>`${e[n]}${t}`)).join("")+e[r.length]}function t(e){return new Proxy(e,new n)}class n{children={};get(e,n){if(this.children.hasOwnProperty(n))return this.children[n];const r=e[n];return this.children[n]="string"==typeof r?r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"):"object"==typeof r?t(r):r}}exports.asyncTemplate=function(t,n,r){if(n||(n=["arg"]),r||(r="T"),n.includes(r))throw new Error(`The tag name ${r} clashes with the name of one of the arguments. \n Please change the tag name or the argument name to resolve this.`);const o=Function(r,...n,`return ${r}\`${t}\`;`);return(...t)=>o(e,...t)},exports.asyncTemplates=function(t,n,r,o){if(n||(n=[]),r||(r="item"),o||(o="T"),r===o)throw new Error(`The tag name ${o} is the same as the item name. \n Please change the tag name or the item name to resolve this.`);if(n.includes(o))throw new Error(`The tag name ${o} clashes with the name of one of the arguments. \n Please change the tag name or the argument name to resolve this.`);const a=Function(`\n function* gen(${o}, arr, ${n.join(", ")}) {\n for (let ${r} of arr) yield ${o}\`${t}\`;\n }\n return gen;`)();return(t,...n)=>a(e,t,...n)},exports.createFragment=function(e){const t=document.createElement("template");t.innerHTML=e;let n=t.content;return 1===n.children.length?n.children[0]:n},exports.elements=function*(e){for(let t of e.replace(",","").split(" "))yield document.createElement(t.trim())},exports.esc=t,exports.get=async function(e,t,n){let r=fetch(e,n).then((e=>e.text()));return t&&(r=r.catch((e=>""))),r},exports.tag=e,exports.template=function(e,t){return t||(t=["arg"]),Function(...t,`return \`${e}\`;`)},exports.templates=function(e,t,n){return t||(t=[]),n||(n="item"),Function(`\n function* gen(arr, ${t.join(", ")}) {\n for (let ${n} of arr) yield \`${e}\`;\n }\n return gen;`)()}; | ||
'use strict'; | ||
/** | ||
* This module exports primitives for building DOM from text. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* A template tag that will resolve only after all | ||
* interpolated promises have been resolved, finally returning the | ||
* intended string. | ||
* | ||
* @example | ||
* import { tag } from 'deleight/apriori'; | ||
* const t = tag`I will wait for this ${Promise.resolve("promise")}!!!` | ||
* // t === 'I will wait for this promise!!!' | ||
* | ||
* @param {Array<string>} strings | ||
* @param {...any} expressions | ||
* @returns {Promise<string>} | ||
*/ | ||
async function tag(strings, ...expressions) { | ||
const promiseExpressions = []; | ||
for (let [i, exp] of Array.from(expressions.entries())) { | ||
if (exp instanceof Promise) | ||
promiseExpressions.push(exp); | ||
else | ||
promiseExpressions.push(Promise.resolve(exp)); | ||
} | ||
const resolvedExpressions = await Promise.all(promiseExpressions); | ||
return (resolvedExpressions.map((exp, i) => `${strings[i]}${exp}`).join("") + | ||
strings[resolvedExpressions.length]); | ||
} | ||
/** | ||
* Effectively creates a template literal out of an existing template string and wraps it in a function | ||
* which can be called multiple times to 'render' the template with the given arguments. | ||
* | ||
* @example | ||
* import { template } from 'deleight/apriori'; | ||
* const t = template('I will render this ${"guy"} immediately!!!')(); | ||
* // t === 'I will render this guy immediately!!!' | ||
* | ||
* @param {string} templateStr the template string | ||
* @param {string[]} argNames The names of the parameters of the returned function (which can be 'seen' inside the template string). | ||
* Defaults to: ['arg']. | ||
* @returns {(...any): string} | ||
*/ | ||
function template(templateStr, argNames) { | ||
if (!argNames) | ||
argNames = ['arg']; | ||
return Function(...argNames, `return \`${templateStr}\`;`); | ||
} | ||
/** | ||
* Similar to template but the built template is also 'promise-aware' and will allow them to resolve to string values | ||
* before interpolating them. | ||
* | ||
* @example | ||
* import { asyncTemplate } from 'deleight/apriori'; | ||
* const t = await asyncTemplate('I will wait for this ${Promise.resolve("promise")}!!!')(); | ||
* // t === 'I will wait for this promise!!!' | ||
* | ||
* | ||
* @param {string} templateStr the template string | ||
* @param {Array<string>} argNames The names of the parameters of the returned function (which can be 'seen' inside the template string). | ||
* Defaults to: ['arg']. | ||
* @param {string} tagName Supply a tagName argument to change the name of the tag function inside the template string if | ||
* the default name (T) is present in argNames. | ||
* @returns {(...any): Promise<string>} | ||
*/ | ||
function asyncTemplate(templateStr, argNames, tagName) { | ||
if (!argNames) | ||
argNames = ['arg']; | ||
if (!tagName) | ||
tagName = "T"; | ||
if (argNames.includes(tagName)) { | ||
throw new Error(`The tag name ${tagName} clashes with the name of one of the arguments. | ||
Please change the tag name or the argument name to resolve this.`); | ||
} | ||
const f = Function(tagName, ...argNames, `return ${tagName}\`${templateStr}\`;`); | ||
return (...args) => f(tag, ...args); | ||
} | ||
/** | ||
* Similar to template, but will render an iterable (such as array) of items together instead | ||
* of rendering each item individually. It improves efficiency in these scenarios because only 1 rendering | ||
* function is called. | ||
* | ||
* @example | ||
* import { templates } from 'deleight/apriori'; | ||
* const t = arrayTemplate('I will render this ${it}/${other} immediately!!!', ['other'], 'it')([1, 2, 3, 4, 5], '(shared)').join(' & '); | ||
* // t === 'I will render this 1/(shared) immediately!!! & I will render this 2/(shared) immediately!!! & I will render this 3/(shared) immediately!!! & I will render this 4/(shared) immediately!!! & I will render this 5/(shared) immediately!!!' | ||
* | ||
* @param {string} templateStr The template string | ||
* @param {Array<string>} argNames The names of the parameters (after the iterable) of the returned function (which can be 'seen' inside the template string) | ||
* @param {string} itemName The name of the current item of the iterable as seen inside the template string. Defaults | ||
* to 'item' | ||
* Defaults to the empty string. | ||
* @returns {ITemplates} | ||
*/ | ||
function templates(templateStr, argNames, itemName) { | ||
if (!argNames) | ||
argNames = []; | ||
if (!itemName) | ||
itemName = "item"; | ||
return (Function(` | ||
function* gen(arr, ${argNames.join(', ')}) { | ||
for (let ${itemName} of arr) yield \`${templateStr}\`; | ||
} | ||
return gen;`))(); | ||
} | ||
/** | ||
* Async equivalent of arrayTemplate. The async template tag ('T' by default) | ||
* is applied to the template string. Use this when there are promises | ||
* among the arguents that will be passed to the returned function. | ||
* | ||
* @example | ||
* import { asyncTemplates } from 'deleight/apriori'; | ||
* let t = asyncTemplates('I will async render this ${item}')([1, 2, 3, 4, 5].map(i => Promise.resolve(i))); | ||
* console.log(t instanceof Promise); // true | ||
* t = (await t).join(' ') | ||
* // t === 'I will async render this 1 I will async render this 2 I will async render this 3 I will async render this 4 I will async render this 5' | ||
* | ||
* @param {string} templateStr The template string | ||
* @param {Array<string>} argNames The names of the parameters (after the iterable) of the returned function (which can be 'seen' inside the template string) | ||
* @param {string} itemName The name of the current item of the iterable as seen inside the template string. Defaults | ||
* to 'item' | ||
* @param {string} tagName Supply a tagName argument to change the name of the tag function inside the template string if | ||
* the default name (T) is present in argNames. | ||
* @returns {IAsyncTemplates} | ||
*/ | ||
function asyncTemplates(templateStr, argNames, itemName, tagName) { | ||
if (!argNames) | ||
argNames = []; | ||
if (!itemName) | ||
itemName = "item"; | ||
if (!tagName) | ||
tagName = "T"; | ||
if (itemName === tagName) { | ||
throw new Error(`The tag name ${tagName} is the same as the item name. | ||
Please change the tag name or the item name to resolve this.`); | ||
} | ||
if (argNames.includes(tagName)) { | ||
throw new Error(`The tag name ${tagName} clashes with the name of one of the arguments. | ||
Please change the tag name or the argument name to resolve this.`); | ||
} | ||
const f = (Function(` | ||
function* gen(${tagName}, arr, ${argNames.join(', ')}) { | ||
for (let ${itemName} of arr) yield ${tagName}\`${templateStr}\`; | ||
} | ||
return gen;`))(); | ||
return (arr, ...args) => f(tag, arr, ...args); | ||
} | ||
/** | ||
* Fetches text (typically markup) from the url. This is only a shorthand | ||
* for using `fetch`. | ||
* | ||
* @example | ||
* import { get } from 'deleight/apriori'; | ||
* const markup = await get('./something.html') | ||
* | ||
* | ||
* @param {string} url The url to pass to `fetch` | ||
* @param {boolean} [suppressErrors] Whether to return the empty string if an error occurs. | ||
* @param {RequestInit} [init] The `init` argument for `fetch` | ||
* @returns {Promise<string>} | ||
*/ | ||
async function get(url, suppressErrors, init) { | ||
let result = fetch(url, init).then((r) => r.text()); | ||
if (suppressErrors) | ||
result = result.catch((r) => ""); | ||
return result; | ||
} | ||
/** | ||
* Shorthand for creating a DocumentFragment from markup. If the | ||
* fragment has only one child, the child is returned instead. | ||
* So this is also a shorthand for creating single elements. | ||
* | ||
* @example | ||
* import { createFragment } from 'deleight/apriori'; | ||
* const frag1 = createFragment(` | ||
* <p>Para 1</p> | ||
* <p>Para 2</p> | ||
*`) | ||
* // <p>Para 1</p><p>Para 2</p> | ||
* | ||
* @param {string} markup The `outerHTML` of what to create | ||
* @returns {Node} | ||
*/ | ||
const createFragment = function (markup) { | ||
const temp = document.createElement("template"); | ||
temp.innerHTML = markup; | ||
let result = temp.content; | ||
if (result.children.length === 1) | ||
return result.children[0]; | ||
return result; | ||
}; | ||
/** | ||
* A generator for elements with the specified tag names. | ||
* The names are space-separated just like in a class attribute. | ||
* You can also separate with commma if you prefer. | ||
* | ||
* @example | ||
* import { elements } from 'deleight/apriori'; | ||
* const [div, p, span] = elements('div p span'); | ||
* const [div2, p2, span2] = elements('div, p, span'); | ||
* | ||
* @param {string} tagNames | ||
*/ | ||
function* elements(tagNames) { | ||
for (let tagName of tagNames.replace(',', '').split(' ')) | ||
yield document.createElement(tagName.trim()); | ||
} | ||
/** | ||
* Returns an object which escapes properties sourced from it. Escaping markup is a key component of template rendering, | ||
* so this is an important function to have here. | ||
* | ||
* NB: there are no tests yet. Please report any bugs. | ||
* | ||
* @example | ||
* import { esc } from 'deleight/apriori' | ||
* const obj = { a: 1, b: 'no special chars', c: '<p>But I am a paragraph</p>', d: { e: '<p>"esc" will still work here</p>' } } | ||
* const escObj = esc(obj); | ||
* console.log(escObj.c); // <p>But I am a paragraph</p> | ||
* console.log(escObj.d.e); // <p>"esc" will still work here</p> | ||
* | ||
* | ||
* @param {*} rawObject | ||
*/ | ||
function esc(rawObject) { | ||
return new Proxy(rawObject, new EscTrap()); | ||
} | ||
/** | ||
* Credits 'bjornd' (https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript) | ||
* | ||
* @param {*} unsafe | ||
* @returns | ||
*/ | ||
function escapeHtml(unsafe) { | ||
return unsafe | ||
.replace(/&/g, "&") | ||
.replace(/</g, "<") | ||
.replace(/>/g, ">") | ||
.replace(/"/g, """) | ||
.replace(/'/g, "'"); | ||
} | ||
class EscTrap { | ||
children = {}; | ||
get(target, p) { | ||
if (this.children.hasOwnProperty(p)) | ||
return this.children[p]; | ||
const result = target[p]; | ||
if (typeof result === 'string') | ||
return this.children[p] = escapeHtml(result); | ||
else if (typeof result === 'object') | ||
return this.children[p] = esc(result); | ||
else | ||
return this.children[p] = result; | ||
} | ||
} | ||
exports.asyncTemplate = asyncTemplate; | ||
exports.asyncTemplates = asyncTemplates; | ||
exports.createFragment = createFragment; | ||
exports.elements = elements; | ||
exports.esc = esc; | ||
exports.get = get; | ||
exports.tag = tag; | ||
exports.template = template; | ||
exports.templates = templates; |
@@ -1,1 +0,258 @@ | ||
async function e(e,...n){const t=[];for(let[e,r]of Array.from(n.entries()))r instanceof Promise?t.push(r):t.push(Promise.resolve(r));const r=await Promise.all(t);return r.map(((n,t)=>`${e[t]}${n}`)).join("")+e[r.length]}function n(e,n){return n||(n=["arg"]),Function(...n,`return \`${e}\`;`)}function t(n,t,r){if(t||(t=["arg"]),r||(r="T"),t.includes(r))throw new Error(`The tag name ${r} clashes with the name of one of the arguments. \n Please change the tag name or the argument name to resolve this.`);const o=Function(r,...t,`return ${r}\`${n}\`;`);return(...n)=>o(e,...n)}function r(e,n,t){return n||(n=[]),t||(t="item"),Function(`\n function* gen(arr, ${n.join(", ")}) {\n for (let ${t} of arr) yield \`${e}\`;\n }\n return gen;`)()}function o(n,t,r,o){if(t||(t=[]),r||(r="item"),o||(o="T"),r===o)throw new Error(`The tag name ${o} is the same as the item name. \n Please change the tag name or the item name to resolve this.`);if(t.includes(o))throw new Error(`The tag name ${o} clashes with the name of one of the arguments. \n Please change the tag name or the argument name to resolve this.`);const a=Function(`\n function* gen(${o}, arr, ${t.join(", ")}) {\n for (let ${r} of arr) yield ${o}\`${n}\`;\n }\n return gen;`)();return(n,...t)=>a(e,n,...t)}async function a(e,n,t){let r=fetch(e,t).then((e=>e.text()));return n&&(r=r.catch((e=>""))),r}const i=function(e){const n=document.createElement("template");n.innerHTML=e;let t=n.content;return 1===t.children.length?t.children[0]:t};function*c(e){for(let n of e.replace(",","").split(" "))yield document.createElement(n.trim())}function h(e){return new Proxy(e,new s)}class s{children={};get(e,n){if(this.children.hasOwnProperty(n))return this.children[n];const t=e[n];return this.children[n]="string"==typeof t?t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"):"object"==typeof t?h(t):t}}export{t as asyncTemplate,o as asyncTemplates,i as createFragment,c as elements,h as esc,a as get,e as tag,n as template,r as templates}; | ||
/** | ||
* This module exports primitives for building DOM from text. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* A template tag that will resolve only after all | ||
* interpolated promises have been resolved, finally returning the | ||
* intended string. | ||
* | ||
* @example | ||
* import { tag } from 'deleight/apriori'; | ||
* const t = tag`I will wait for this ${Promise.resolve("promise")}!!!` | ||
* // t === 'I will wait for this promise!!!' | ||
* | ||
* @param {Array<string>} strings | ||
* @param {...any} expressions | ||
* @returns {Promise<string>} | ||
*/ | ||
async function tag(strings, ...expressions) { | ||
const promiseExpressions = []; | ||
for (let [i, exp] of Array.from(expressions.entries())) { | ||
if (exp instanceof Promise) | ||
promiseExpressions.push(exp); | ||
else | ||
promiseExpressions.push(Promise.resolve(exp)); | ||
} | ||
const resolvedExpressions = await Promise.all(promiseExpressions); | ||
return (resolvedExpressions.map((exp, i) => `${strings[i]}${exp}`).join("") + | ||
strings[resolvedExpressions.length]); | ||
} | ||
/** | ||
* Effectively creates a template literal out of an existing template string and wraps it in a function | ||
* which can be called multiple times to 'render' the template with the given arguments. | ||
* | ||
* @example | ||
* import { template } from 'deleight/apriori'; | ||
* const t = template('I will render this ${"guy"} immediately!!!')(); | ||
* // t === 'I will render this guy immediately!!!' | ||
* | ||
* @param {string} templateStr the template string | ||
* @param {string[]} argNames The names of the parameters of the returned function (which can be 'seen' inside the template string). | ||
* Defaults to: ['arg']. | ||
* @returns {(...any): string} | ||
*/ | ||
function template(templateStr, argNames) { | ||
if (!argNames) | ||
argNames = ['arg']; | ||
return Function(...argNames, `return \`${templateStr}\`;`); | ||
} | ||
/** | ||
* Similar to template but the built template is also 'promise-aware' and will allow them to resolve to string values | ||
* before interpolating them. | ||
* | ||
* @example | ||
* import { asyncTemplate } from 'deleight/apriori'; | ||
* const t = await asyncTemplate('I will wait for this ${Promise.resolve("promise")}!!!')(); | ||
* // t === 'I will wait for this promise!!!' | ||
* | ||
* | ||
* @param {string} templateStr the template string | ||
* @param {Array<string>} argNames The names of the parameters of the returned function (which can be 'seen' inside the template string). | ||
* Defaults to: ['arg']. | ||
* @param {string} tagName Supply a tagName argument to change the name of the tag function inside the template string if | ||
* the default name (T) is present in argNames. | ||
* @returns {(...any): Promise<string>} | ||
*/ | ||
function asyncTemplate(templateStr, argNames, tagName) { | ||
if (!argNames) | ||
argNames = ['arg']; | ||
if (!tagName) | ||
tagName = "T"; | ||
if (argNames.includes(tagName)) { | ||
throw new Error(`The tag name ${tagName} clashes with the name of one of the arguments. | ||
Please change the tag name or the argument name to resolve this.`); | ||
} | ||
const f = Function(tagName, ...argNames, `return ${tagName}\`${templateStr}\`;`); | ||
return (...args) => f(tag, ...args); | ||
} | ||
/** | ||
* Similar to template, but will render an iterable (such as array) of items together instead | ||
* of rendering each item individually. It improves efficiency in these scenarios because only 1 rendering | ||
* function is called. | ||
* | ||
* @example | ||
* import { templates } from 'deleight/apriori'; | ||
* const t = arrayTemplate('I will render this ${it}/${other} immediately!!!', ['other'], 'it')([1, 2, 3, 4, 5], '(shared)').join(' & '); | ||
* // t === 'I will render this 1/(shared) immediately!!! & I will render this 2/(shared) immediately!!! & I will render this 3/(shared) immediately!!! & I will render this 4/(shared) immediately!!! & I will render this 5/(shared) immediately!!!' | ||
* | ||
* @param {string} templateStr The template string | ||
* @param {Array<string>} argNames The names of the parameters (after the iterable) of the returned function (which can be 'seen' inside the template string) | ||
* @param {string} itemName The name of the current item of the iterable as seen inside the template string. Defaults | ||
* to 'item' | ||
* Defaults to the empty string. | ||
* @returns {ITemplates} | ||
*/ | ||
function templates(templateStr, argNames, itemName) { | ||
if (!argNames) | ||
argNames = []; | ||
if (!itemName) | ||
itemName = "item"; | ||
return (Function(` | ||
function* gen(arr, ${argNames.join(', ')}) { | ||
for (let ${itemName} of arr) yield \`${templateStr}\`; | ||
} | ||
return gen;`))(); | ||
} | ||
/** | ||
* Async equivalent of arrayTemplate. The async template tag ('T' by default) | ||
* is applied to the template string. Use this when there are promises | ||
* among the arguents that will be passed to the returned function. | ||
* | ||
* @example | ||
* import { asyncTemplates } from 'deleight/apriori'; | ||
* let t = asyncTemplates('I will async render this ${item}')([1, 2, 3, 4, 5].map(i => Promise.resolve(i))); | ||
* console.log(t instanceof Promise); // true | ||
* t = (await t).join(' ') | ||
* // t === 'I will async render this 1 I will async render this 2 I will async render this 3 I will async render this 4 I will async render this 5' | ||
* | ||
* @param {string} templateStr The template string | ||
* @param {Array<string>} argNames The names of the parameters (after the iterable) of the returned function (which can be 'seen' inside the template string) | ||
* @param {string} itemName The name of the current item of the iterable as seen inside the template string. Defaults | ||
* to 'item' | ||
* @param {string} tagName Supply a tagName argument to change the name of the tag function inside the template string if | ||
* the default name (T) is present in argNames. | ||
* @returns {IAsyncTemplates} | ||
*/ | ||
function asyncTemplates(templateStr, argNames, itemName, tagName) { | ||
if (!argNames) | ||
argNames = []; | ||
if (!itemName) | ||
itemName = "item"; | ||
if (!tagName) | ||
tagName = "T"; | ||
if (itemName === tagName) { | ||
throw new Error(`The tag name ${tagName} is the same as the item name. | ||
Please change the tag name or the item name to resolve this.`); | ||
} | ||
if (argNames.includes(tagName)) { | ||
throw new Error(`The tag name ${tagName} clashes with the name of one of the arguments. | ||
Please change the tag name or the argument name to resolve this.`); | ||
} | ||
const f = (Function(` | ||
function* gen(${tagName}, arr, ${argNames.join(', ')}) { | ||
for (let ${itemName} of arr) yield ${tagName}\`${templateStr}\`; | ||
} | ||
return gen;`))(); | ||
return (arr, ...args) => f(tag, arr, ...args); | ||
} | ||
/** | ||
* Fetches text (typically markup) from the url. This is only a shorthand | ||
* for using `fetch`. | ||
* | ||
* @example | ||
* import { get } from 'deleight/apriori'; | ||
* const markup = await get('./something.html') | ||
* | ||
* | ||
* @param {string} url The url to pass to `fetch` | ||
* @param {boolean} [suppressErrors] Whether to return the empty string if an error occurs. | ||
* @param {RequestInit} [init] The `init` argument for `fetch` | ||
* @returns {Promise<string>} | ||
*/ | ||
async function get(url, suppressErrors, init) { | ||
let result = fetch(url, init).then((r) => r.text()); | ||
if (suppressErrors) | ||
result = result.catch((r) => ""); | ||
return result; | ||
} | ||
/** | ||
* Shorthand for creating a DocumentFragment from markup. If the | ||
* fragment has only one child, the child is returned instead. | ||
* So this is also a shorthand for creating single elements. | ||
* | ||
* @example | ||
* import { createFragment } from 'deleight/apriori'; | ||
* const frag1 = createFragment(` | ||
* <p>Para 1</p> | ||
* <p>Para 2</p> | ||
*`) | ||
* // <p>Para 1</p><p>Para 2</p> | ||
* | ||
* @param {string} markup The `outerHTML` of what to create | ||
* @returns {Node} | ||
*/ | ||
const createFragment = function (markup) { | ||
const temp = document.createElement("template"); | ||
temp.innerHTML = markup; | ||
let result = temp.content; | ||
if (result.children.length === 1) | ||
return result.children[0]; | ||
return result; | ||
}; | ||
/** | ||
* A generator for elements with the specified tag names. | ||
* The names are space-separated just like in a class attribute. | ||
* You can also separate with commma if you prefer. | ||
* | ||
* @example | ||
* import { elements } from 'deleight/apriori'; | ||
* const [div, p, span] = elements('div p span'); | ||
* const [div2, p2, span2] = elements('div, p, span'); | ||
* | ||
* @param {string} tagNames | ||
*/ | ||
function* elements(tagNames) { | ||
for (let tagName of tagNames.replace(',', '').split(' ')) | ||
yield document.createElement(tagName.trim()); | ||
} | ||
/** | ||
* Returns an object which escapes properties sourced from it. Escaping markup is a key component of template rendering, | ||
* so this is an important function to have here. | ||
* | ||
* NB: there are no tests yet. Please report any bugs. | ||
* | ||
* @example | ||
* import { esc } from 'deleight/apriori' | ||
* const obj = { a: 1, b: 'no special chars', c: '<p>But I am a paragraph</p>', d: { e: '<p>"esc" will still work here</p>' } } | ||
* const escObj = esc(obj); | ||
* console.log(escObj.c); // <p>But I am a paragraph</p> | ||
* console.log(escObj.d.e); // <p>"esc" will still work here</p> | ||
* | ||
* | ||
* @param {*} rawObject | ||
*/ | ||
function esc(rawObject) { | ||
return new Proxy(rawObject, new EscTrap()); | ||
} | ||
/** | ||
* Credits 'bjornd' (https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript) | ||
* | ||
* @param {*} unsafe | ||
* @returns | ||
*/ | ||
function escapeHtml(unsafe) { | ||
return unsafe | ||
.replace(/&/g, "&") | ||
.replace(/</g, "<") | ||
.replace(/>/g, ">") | ||
.replace(/"/g, """) | ||
.replace(/'/g, "'"); | ||
} | ||
class EscTrap { | ||
children = {}; | ||
get(target, p) { | ||
if (this.children.hasOwnProperty(p)) | ||
return this.children[p]; | ||
const result = target[p]; | ||
if (typeof result === 'string') | ||
return this.children[p] = escapeHtml(result); | ||
else if (typeof result === 'object') | ||
return this.children[p] = esc(result); | ||
else | ||
return this.children[p] = result; | ||
} | ||
} | ||
export { asyncTemplate, asyncTemplates, createFragment, elements, esc, get, tag, template, templates }; |
@@ -1,1 +0,250 @@ | ||
"use strict";class e{listener;constructor(e){this.listener=e}listen(e,t,n){for(let r of t)r.addEventListener(e,this.listener,n)}remove(e,...t){for(let n of t)n.removeEventListener(e,this.listener)}}const t={running:!1};function n(e,n){let s;return n||(n=t),e instanceof Array||(e=[e]),function(t){if(n.running)return;let o;for(s of(n.running=!0,e))if(o=s(t,n),n.result=o,o===r)break;return o instanceof Promise?o.then((()=>n.running=!1),(e=>{throw e})):n.running=!1,o}}const r=Symbol();function s(e,t){const r={};for(let[s,o]of Object.entries(e))if(t||o instanceof Array){let e;o instanceof Array&&"function"!=typeof o.at(-1)||(e=[o,null]),r[s]=e?n(e[0],e[1]):n(o[0],o[1])}else r[s]=o;return function(e){for(let[t,n]of Object.entries(r))if(e.target.matches&&e.target.matches(t))return n(e)}}const o=e=>t=>t.key!==e?r:"",i={enter:"Enter"},c=o(i.enter);exports.END=r,exports.EventListener=class extends e{constructor(e,t){super(n(e,t))}},exports.Listener=e,exports.MatchListener=class extends e{constructor(e,t){super(s(e,t))}},exports.eventListener=n,exports.keys=i,exports.matchListener=s,exports.onEnter=c,exports.onKey=o,exports.preventDefault=e=>e.preventDefault(),exports.stopPropagation=e=>e.stopPropagation(); | ||
'use strict'; | ||
/** | ||
* This module exports event handling helpers. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Base class for EventListener and MatchListener. This can be used to | ||
* wrap any listeners which will be shared by many elements. Call the | ||
* `listen` or `remove` method to add or remove the listener to/from | ||
* the given elements. | ||
* | ||
* @example | ||
* import { Listener } from 'deleight/eutility'; | ||
* listener = new Listener(() => login(input.value)); | ||
* listener.listen('keyup', [window.input1, window.input2]) | ||
* | ||
*/ | ||
class Listener { | ||
listener; | ||
constructor(listener) { | ||
this.listener = listener; | ||
} | ||
; | ||
listen(eventName, elements, options) { | ||
for (let element of elements) | ||
element.addEventListener(eventName, this.listener, options); | ||
} | ||
remove(eventName, ...elements) { | ||
for (let element of elements) | ||
element.removeEventListener(eventName, this.listener); | ||
} | ||
} | ||
const defaultRunContext = { running: false }; | ||
/** | ||
* Composes a listener function from the functions in ops. ops may be | ||
* a single function or an array of functions. | ||
* | ||
* The functions are called in the same order when handling events. They | ||
* will receive as erguments the event object and a run context. | ||
* | ||
* The run context can be passed as the second argument to this function | ||
* (or it will default to the global runContext). The context can be used | ||
* for communication and it also maintains a running property which will | ||
* ensure that no 2 handlers using it will run concurrently (providing the | ||
* API is respected). | ||
* | ||
* The API is as follows: | ||
* If a handler function returns a promise, it is assumed to still be running | ||
* until the promise resolves or rejects. Neither the handler nor any other | ||
* handlers using the same run context can start until it stops running. | ||
* | ||
* Individual op functions can also terminate event handling by returning the | ||
* END symbol. | ||
* | ||
* @example | ||
* import { eventListener } from 'deleight/eutility'; | ||
* input.onkeyup = eventListener([onEnter, () => login(input.value), preventDefault]); | ||
* apply({ | ||
* '#loginButton': button => { | ||
* button.onclick = eventListener(() => login(input.value)); | ||
* } | ||
* }, form); | ||
* | ||
* | ||
* @param {Function[] | Function} ops The function or functions making up the handler | ||
* @param {any} [runContext] The optional run context. | ||
* @returns | ||
*/ | ||
function eventListener(ops, runContext) { | ||
if (!runContext) | ||
runContext = defaultRunContext; | ||
if (!(ops instanceof Array)) | ||
ops = [ops]; | ||
let op; | ||
function listener(e) { | ||
if (runContext.running) | ||
return; | ||
runContext.running = true; | ||
let result; | ||
for (op of ops) { | ||
result = op(e, runContext); // might be a promise. up to the next op whether or not to await it. | ||
runContext.result = result; | ||
if (result === END) | ||
break; | ||
} | ||
// we only await a final promise: | ||
if (result instanceof Promise) | ||
result.then(() => runContext.running = false, err => { throw err; }); | ||
else | ||
runContext.running = false; | ||
return result; | ||
} | ||
return listener; | ||
} | ||
/** | ||
* Similar to eventListener function but has methods for attaching | ||
* and removing itself from multiple elements at the same time. | ||
* | ||
* This gives the listener a 'personality' and promotes its reuse | ||
* (good practice). | ||
* | ||
* @example | ||
* import { eventListener } from 'deleight/eutility'; | ||
* listener = new EventListener([onEnter, () => login(input.value), preventDefault]); | ||
* listener.listen('keyup', [window.input1, window.input2]) | ||
*/ | ||
class EventListener extends Listener { | ||
constructor(ops, runContext) { | ||
super(eventListener(ops, runContext)); | ||
} | ||
} | ||
/** | ||
* Symbol which will terminate event handling if returned by any of | ||
* the functions in the ops chain of an event handler created with | ||
* `eventHandler`. | ||
* | ||
* @example | ||
* import { END } from 'deleight/eutility'; | ||
* const keyEventBreaker = (e: KeyboardEvent) => (e.key !== key)? END: ''; | ||
*/ | ||
const END = Symbol(); | ||
/** | ||
* Takes advantage of event bubbling to listen for events on descendant | ||
* elements to reduce the number of listeners to create. | ||
* | ||
* @example | ||
* import { matchListener } from 'deleight/eutility'; | ||
* window.mainTable.onclick = matchListener({ | ||
* 'a.lbl': e => select(e.target.parentNode.parentNode), | ||
* 'span.remove': [removeListener, preventDefault, stopPropagation] | ||
* }, true); | ||
* | ||
* @param {IMatcher} matcher Map of event target matcher to associated handler function | ||
* @param {boolean} wrapListeners Whether to werap the matcher functions with `eventListener`. | ||
*/ | ||
function matchListener(matcher, wrapListeners) { | ||
const listenerMap = {}; | ||
for (let [selector, args] of Object.entries(matcher)) { | ||
if (wrapListeners || args instanceof Array) { | ||
let args2; | ||
if (!(args instanceof Array) || typeof args.at(-1) === "function") { | ||
args2 = [args, null]; | ||
} | ||
listenerMap[selector] = args2 | ||
? eventListener(args2[0], args2[1]) | ||
: eventListener(args[0], args[1]); | ||
} | ||
else | ||
listenerMap[selector] = args; | ||
} | ||
function listener(e) { | ||
for (let [selector, fn] of Object.entries(listenerMap)) { | ||
if (e.target.matches && e.target.matches(selector)) | ||
return fn(e); | ||
} | ||
} | ||
return listener; | ||
} | ||
/** | ||
* Similar to matchListener function but has methods for attaching | ||
* and removing itself from multiple elements at the same time. | ||
* | ||
* This gives the listener a 'personality' and promotes its reuse | ||
* (good practice). | ||
* | ||
* @example | ||
* import { MatchListener } from 'deleight/eutility'; | ||
* const listener = new MatchListener({ | ||
* 'a.lbl': e => select(e.target.parentNode.parentNode), | ||
* 'span.remove': [removeListener, preventDefault, stopPropagation] | ||
* }, true); | ||
* | ||
* listener.listen('click', [window.table1, window.table2], eventOptions) | ||
*/ | ||
class MatchListener extends Listener { | ||
constructor(matcher, wrapListeners) { | ||
super(matchListener(matcher, wrapListeners)); | ||
} | ||
} | ||
/** | ||
* Simply calls `stopPropagation` on the event. Useful for creating one-liner | ||
* event handlers. | ||
* | ||
* @example | ||
* import { eventListener, stopPropagation } from 'deleight/eutility'; | ||
* window.firstButton.onclick = eventListener([stopPropagation, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
* @param e The event object | ||
* @returns | ||
*/ | ||
const stopPropagation = (e) => e.stopPropagation(); | ||
/** | ||
* Simply calls `preventDefault` on the event. Useful for creating one-liner | ||
* event handlers. | ||
* | ||
* @example | ||
* import { eventListener, preventDefault } from 'deleight/eutility'; | ||
* window.firstButton.onclick = eventListener([preventDefault, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
* @param e The event object | ||
* @returns | ||
*/ | ||
const preventDefault = (e) => e.preventDefault(); | ||
/** | ||
* This returns a function which will stop an event handler run (typically for keyup, | ||
* keydown etc) from continuing if it has not been triggered by the specified key. | ||
* The returned functions are to be placed before the main handler functions in the `ops` | ||
* array passed to `eventListener`. | ||
* | ||
* @example | ||
* import { eventListener, onKey } from 'deleight/eutility'; | ||
* const aKeyGuard = onKey('a'); | ||
* window.firstInput.keyup = eventListener([aKeyGuard, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
* @returns {Function} | ||
*/ | ||
const onKey = (key) => (e) => e.key !== key ? END : ""; | ||
const keys = { enter: "Enter" }; | ||
/** | ||
* This will stop a key(up or down...) event handler run from continuing if | ||
* it has not been triggered by the enter key. | ||
* | ||
* @example | ||
* import { eventListener, onEnter } from 'deleight/eutility'; | ||
* window.firstInput.keyup = eventListener([onEnter, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
*/ | ||
const onEnter = onKey(keys.enter); | ||
exports.END = END; | ||
exports.EventListener = EventListener; | ||
exports.Listener = Listener; | ||
exports.MatchListener = MatchListener; | ||
exports.eventListener = eventListener; | ||
exports.keys = keys; | ||
exports.matchListener = matchListener; | ||
exports.onEnter = onEnter; | ||
exports.onKey = onKey; | ||
exports.preventDefault = preventDefault; | ||
exports.stopPropagation = stopPropagation; |
@@ -1,1 +0,238 @@ | ||
class e{listener;constructor(e){this.listener=e}listen(e,t,n){for(let r of t)r.addEventListener(e,this.listener,n)}remove(e,...t){for(let n of t)n.removeEventListener(e,this.listener)}}const t={running:!1};function n(e,n){let r;return n||(n=t),e instanceof Array||(e=[e]),function(t){if(n.running)return;let s;for(r of(n.running=!0,e))if(s=r(t,n),n.result=s,s===o)break;return s instanceof Promise?s.then((()=>n.running=!1),(e=>{throw e})):n.running=!1,s}}class r extends e{constructor(e,t){super(n(e,t))}}const o=Symbol();function s(e,t){const r={};for(let[o,s]of Object.entries(e))if(t||s instanceof Array){let e;s instanceof Array&&"function"!=typeof s.at(-1)||(e=[s,null]),r[o]=e?n(e[0],e[1]):n(s[0],s[1])}else r[o]=s;return function(e){for(let[t,n]of Object.entries(r))if(e.target.matches&&e.target.matches(t))return n(e)}}class i extends e{constructor(e,t){super(s(e,t))}}const c=e=>e.stopPropagation(),f=e=>e.preventDefault(),u=e=>t=>t.key!==e?o:"",l={enter:"Enter"},a=u(l.enter);export{o as END,r as EventListener,e as Listener,i as MatchListener,n as eventListener,l as keys,s as matchListener,a as onEnter,u as onKey,f as preventDefault,c as stopPropagation}; | ||
/** | ||
* This module exports event handling helpers. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Base class for EventListener and MatchListener. This can be used to | ||
* wrap any listeners which will be shared by many elements. Call the | ||
* `listen` or `remove` method to add or remove the listener to/from | ||
* the given elements. | ||
* | ||
* @example | ||
* import { Listener } from 'deleight/eutility'; | ||
* listener = new Listener(() => login(input.value)); | ||
* listener.listen('keyup', [window.input1, window.input2]) | ||
* | ||
*/ | ||
class Listener { | ||
listener; | ||
constructor(listener) { | ||
this.listener = listener; | ||
} | ||
; | ||
listen(eventName, elements, options) { | ||
for (let element of elements) | ||
element.addEventListener(eventName, this.listener, options); | ||
} | ||
remove(eventName, ...elements) { | ||
for (let element of elements) | ||
element.removeEventListener(eventName, this.listener); | ||
} | ||
} | ||
const defaultRunContext = { running: false }; | ||
/** | ||
* Composes a listener function from the functions in ops. ops may be | ||
* a single function or an array of functions. | ||
* | ||
* The functions are called in the same order when handling events. They | ||
* will receive as erguments the event object and a run context. | ||
* | ||
* The run context can be passed as the second argument to this function | ||
* (or it will default to the global runContext). The context can be used | ||
* for communication and it also maintains a running property which will | ||
* ensure that no 2 handlers using it will run concurrently (providing the | ||
* API is respected). | ||
* | ||
* The API is as follows: | ||
* If a handler function returns a promise, it is assumed to still be running | ||
* until the promise resolves or rejects. Neither the handler nor any other | ||
* handlers using the same run context can start until it stops running. | ||
* | ||
* Individual op functions can also terminate event handling by returning the | ||
* END symbol. | ||
* | ||
* @example | ||
* import { eventListener } from 'deleight/eutility'; | ||
* input.onkeyup = eventListener([onEnter, () => login(input.value), preventDefault]); | ||
* apply({ | ||
* '#loginButton': button => { | ||
* button.onclick = eventListener(() => login(input.value)); | ||
* } | ||
* }, form); | ||
* | ||
* | ||
* @param {Function[] | Function} ops The function or functions making up the handler | ||
* @param {any} [runContext] The optional run context. | ||
* @returns | ||
*/ | ||
function eventListener(ops, runContext) { | ||
if (!runContext) | ||
runContext = defaultRunContext; | ||
if (!(ops instanceof Array)) | ||
ops = [ops]; | ||
let op; | ||
function listener(e) { | ||
if (runContext.running) | ||
return; | ||
runContext.running = true; | ||
let result; | ||
for (op of ops) { | ||
result = op(e, runContext); // might be a promise. up to the next op whether or not to await it. | ||
runContext.result = result; | ||
if (result === END) | ||
break; | ||
} | ||
// we only await a final promise: | ||
if (result instanceof Promise) | ||
result.then(() => runContext.running = false, err => { throw err; }); | ||
else | ||
runContext.running = false; | ||
return result; | ||
} | ||
return listener; | ||
} | ||
/** | ||
* Similar to eventListener function but has methods for attaching | ||
* and removing itself from multiple elements at the same time. | ||
* | ||
* This gives the listener a 'personality' and promotes its reuse | ||
* (good practice). | ||
* | ||
* @example | ||
* import { eventListener } from 'deleight/eutility'; | ||
* listener = new EventListener([onEnter, () => login(input.value), preventDefault]); | ||
* listener.listen('keyup', [window.input1, window.input2]) | ||
*/ | ||
class EventListener extends Listener { | ||
constructor(ops, runContext) { | ||
super(eventListener(ops, runContext)); | ||
} | ||
} | ||
/** | ||
* Symbol which will terminate event handling if returned by any of | ||
* the functions in the ops chain of an event handler created with | ||
* `eventHandler`. | ||
* | ||
* @example | ||
* import { END } from 'deleight/eutility'; | ||
* const keyEventBreaker = (e: KeyboardEvent) => (e.key !== key)? END: ''; | ||
*/ | ||
const END = Symbol(); | ||
/** | ||
* Takes advantage of event bubbling to listen for events on descendant | ||
* elements to reduce the number of listeners to create. | ||
* | ||
* @example | ||
* import { matchListener } from 'deleight/eutility'; | ||
* window.mainTable.onclick = matchListener({ | ||
* 'a.lbl': e => select(e.target.parentNode.parentNode), | ||
* 'span.remove': [removeListener, preventDefault, stopPropagation] | ||
* }, true); | ||
* | ||
* @param {IMatcher} matcher Map of event target matcher to associated handler function | ||
* @param {boolean} wrapListeners Whether to werap the matcher functions with `eventListener`. | ||
*/ | ||
function matchListener(matcher, wrapListeners) { | ||
const listenerMap = {}; | ||
for (let [selector, args] of Object.entries(matcher)) { | ||
if (wrapListeners || args instanceof Array) { | ||
let args2; | ||
if (!(args instanceof Array) || typeof args.at(-1) === "function") { | ||
args2 = [args, null]; | ||
} | ||
listenerMap[selector] = args2 | ||
? eventListener(args2[0], args2[1]) | ||
: eventListener(args[0], args[1]); | ||
} | ||
else | ||
listenerMap[selector] = args; | ||
} | ||
function listener(e) { | ||
for (let [selector, fn] of Object.entries(listenerMap)) { | ||
if (e.target.matches && e.target.matches(selector)) | ||
return fn(e); | ||
} | ||
} | ||
return listener; | ||
} | ||
/** | ||
* Similar to matchListener function but has methods for attaching | ||
* and removing itself from multiple elements at the same time. | ||
* | ||
* This gives the listener a 'personality' and promotes its reuse | ||
* (good practice). | ||
* | ||
* @example | ||
* import { MatchListener } from 'deleight/eutility'; | ||
* const listener = new MatchListener({ | ||
* 'a.lbl': e => select(e.target.parentNode.parentNode), | ||
* 'span.remove': [removeListener, preventDefault, stopPropagation] | ||
* }, true); | ||
* | ||
* listener.listen('click', [window.table1, window.table2], eventOptions) | ||
*/ | ||
class MatchListener extends Listener { | ||
constructor(matcher, wrapListeners) { | ||
super(matchListener(matcher, wrapListeners)); | ||
} | ||
} | ||
/** | ||
* Simply calls `stopPropagation` on the event. Useful for creating one-liner | ||
* event handlers. | ||
* | ||
* @example | ||
* import { eventListener, stopPropagation } from 'deleight/eutility'; | ||
* window.firstButton.onclick = eventListener([stopPropagation, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
* @param e The event object | ||
* @returns | ||
*/ | ||
const stopPropagation = (e) => e.stopPropagation(); | ||
/** | ||
* Simply calls `preventDefault` on the event. Useful for creating one-liner | ||
* event handlers. | ||
* | ||
* @example | ||
* import { eventListener, preventDefault } from 'deleight/eutility'; | ||
* window.firstButton.onclick = eventListener([preventDefault, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
* @param e The event object | ||
* @returns | ||
*/ | ||
const preventDefault = (e) => e.preventDefault(); | ||
/** | ||
* This returns a function which will stop an event handler run (typically for keyup, | ||
* keydown etc) from continuing if it has not been triggered by the specified key. | ||
* The returned functions are to be placed before the main handler functions in the `ops` | ||
* array passed to `eventListener`. | ||
* | ||
* @example | ||
* import { eventListener, onKey } from 'deleight/eutility'; | ||
* const aKeyGuard = onKey('a'); | ||
* window.firstInput.keyup = eventListener([aKeyGuard, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
* @returns {Function} | ||
*/ | ||
const onKey = (key) => (e) => e.key !== key ? END : ""; | ||
const keys = { enter: "Enter" }; | ||
/** | ||
* This will stop a key(up or down...) event handler run from continuing if | ||
* it has not been triggered by the enter key. | ||
* | ||
* @example | ||
* import { eventListener, onEnter } from 'deleight/eutility'; | ||
* window.firstInput.keyup = eventListener([onEnter, (e, runContext) => { | ||
* // handle event here. | ||
* }]); | ||
* | ||
*/ | ||
const onEnter = onKey(keys.enter); | ||
export { END, EventListener, Listener, MatchListener, eventListener, keys, matchListener, onEnter, onKey, preventDefault, stopPropagation }; |
@@ -1,1 +0,234 @@ | ||
"use strict";function e(e){return e.next?e:e[Symbol.iterator]()}function*t(e,t,o){let n=t;for(o&&(yield o,n--);n-- >0;)yield e.next().value}const o=new WeakMap;exports.flat=function*(...t){const o=t.length;let n,r;for(t=t.map((t=>e(t)));;)for(n=0;n<o;n++){if(r=t[n].next(),r.done)return;yield r.value}},exports.getLength=function(e){return o.get(e)||e.length},exports.group=function*(o,n){const r=e(o);let l=r.next();for(;!l.done;)yield[...t(r,n,l.value)],l=r.next()},exports.items=function*(e,t){for(let o of t)yield e[o]},exports.iter=e,exports.iterLengths=o,exports.next=t,exports.range=function*(e,t,o){o||(o=1),null==t&&e&&(t=e,e=0);for(let n=e;n<t;n+=o)yield n},exports.repeat=function*(e,t){let o;if(void 0===t){const t=[];for(let o of e)t.push(o),yield o;for(;;)for(o of t)yield o}else for(let n=0;n<t;n++)for(o of e)yield o},exports.setLength=function(e,t){return o.set(e,t),e},exports.uItems=function*(e){const t=[...e];for(let e=t.length-1;e>=0;e--)yield t.splice(Math.round(Math.random()*e),1)[0]}; | ||
'use strict'; | ||
/** | ||
* This module exports many useful generators like `range` and `repeat`. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Forcs any iterable to become an iterator. Will throw | ||
* if not possible. | ||
* | ||
* @example | ||
* import { iter } from 'deleight/generational'; | ||
* const it = iter([1, 2, 3, 4, 5]); | ||
* | ||
* @param { any } it | ||
*/ | ||
function iter(it) { | ||
return (it.next) ? it : it[Symbol.iterator](); | ||
} | ||
/** | ||
* Fast and 'costless' range function for javascript based on generators. | ||
* | ||
* @example | ||
* import { range } from 'deleight/generational'; | ||
* const arr1000 = [...range(0, 1000)]; | ||
* // creates an array with 1000 items counting from 0 to 999. | ||
* | ||
* @param {number} start | ||
* @param {number} [end] | ||
* @param {number} [step] | ||
*/ | ||
function* range(start, end, step) { | ||
if (!step) | ||
step = 1; | ||
if ((end === undefined || end === null) && start) { | ||
end = start; | ||
start = 0; | ||
} | ||
for (let i = start; i < end; i += step) | ||
yield i; | ||
} | ||
/** | ||
* Returns a generator which iterates over the subset of the | ||
* 'arrayLike' object that matches the provided index. | ||
* | ||
* @example | ||
* import { items, range } from 'deleight/generational'; | ||
* const tenth = []...items(range(1000), range(0, 1000, 10))]; | ||
* // selects every 10th item in the array. | ||
* | ||
* @param {any} arrayLike | ||
* @param {Iterable<any>} index | ||
*/ | ||
function* items(arrayLike, index) { | ||
for (let i of index) | ||
yield arrayLike[i]; | ||
} | ||
/** | ||
* Returns a generator that yields first argument (`what`) the number of | ||
* times specified by the second argument (`times`). If `times` is not | ||
* given, `what` is repeated indefinitely. | ||
* | ||
* @example | ||
* import { repeat } from 'deleight/generational'; | ||
* const repeated = [...repeat([1, 2, 3], 4)]; | ||
* // [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] | ||
* | ||
* @param {Iterable<any>} what | ||
* @param {number} [times] | ||
*/ | ||
function* repeat(what, times) { | ||
let item; | ||
if (times === undefined) { | ||
const what2 = []; | ||
for (let item of what) { | ||
what2.push(item); | ||
yield item; | ||
} | ||
while (true) | ||
for (item of what2) | ||
yield item; | ||
} | ||
else | ||
for (let i = 0; i < times; i++) | ||
for (item of what) | ||
yield item; | ||
} | ||
/** | ||
* Returns an iterator over the next 'count' items of the iterator. | ||
* | ||
* Note that, for performance reasons, this only accepts an | ||
* iterator as the 'it' argument. All the other module functions accept | ||
* iterables. | ||
* | ||
* You can convert any iterable to an iterator using the `iter` funtion | ||
* as shown in the following example. | ||
* | ||
* If a firstValue is specified, it will be yielded first. | ||
* | ||
* @example | ||
* import { nextItems, iter } from 'deleight/generational'; | ||
* const it = iter([1, 'a', 2, 'b', 3, 'c', 4, 'd']); | ||
* const [num, let] = next(it, 2); | ||
* | ||
* @param {Iterator<any>} it | ||
* @param {number} count | ||
* @param { any } [firstValue] | ||
*/ | ||
function* next(it, count, firstValue) { | ||
let count2 = count; | ||
if (firstValue) { | ||
yield firstValue; | ||
count2--; | ||
} | ||
while (count2-- > 0) | ||
yield it.next().value; | ||
} | ||
/** | ||
* Returns an iterator of iterators over the next 'count' items of | ||
* the given iterable | ||
* | ||
* @example | ||
* import { next } from 'deleight/generational'; | ||
* const o1 = [1, 2, 3, 4]; | ||
* const o2 = ['a', 'b', 'c', 'd']; | ||
* const zip = group(flat(o1, o2), 2); | ||
* | ||
* @param { Iterable<any> } it | ||
* @param { number } count | ||
*/ | ||
function* group(it, count) { | ||
const it2 = iter(it); | ||
let nextItem = it2.next(); | ||
while (!nextItem.done) { | ||
yield [...next(it2, count, nextItem.value)]; | ||
nextItem = it2.next(); | ||
} | ||
} | ||
/** | ||
* Returns an iterator over the items of all the input iterators, starting from | ||
* the zero index to the maximum index of the first argument. The | ||
* effective length of the iterator is the multiple of the length of thr smallest | ||
* iterator and the number of iterators (number of args). | ||
* | ||
* Can be used to join arrays in a way no supported by `concat`, `push`, etc. | ||
* To pass an array as an iterator, call array.values(). | ||
* | ||
* @example | ||
* import { flat } from 'deleight/generational'; | ||
* for (let i of flat(range(10, range(15)))) { | ||
* console.log(i); // 0, 0, 1, 1, 2, 2, .... till smallest iterable (10) is exhausted. | ||
* } | ||
* | ||
* @param {...any[]} args | ||
*/ | ||
function* flat(...args) { | ||
const count = args.length; | ||
args = args.map(arg => iter(arg)); | ||
let i, nextItem; | ||
while (true) { | ||
for (i = 0; i < count; i++) { | ||
nextItem = args[i].next(); | ||
if (nextItem.done) | ||
return; | ||
else | ||
yield nextItem.value; | ||
} | ||
} | ||
} | ||
/** | ||
* Returns an unordered/random iterator over the input array.. | ||
* | ||
* @example | ||
* import { uItems } from 'deleight/generational'; | ||
* const unOrdered = uItems([1, 2, 3, 4]); // [4, 1, 3, 2] | ||
* | ||
* @param {Iterable<any>} it | ||
*/ | ||
function* uItems(it) { | ||
const arr = [...it]; | ||
for (let i = arr.length - 1; i >= 0; i--) { | ||
yield arr.splice(Math.round(Math.random() * i), 1)[0]; | ||
} | ||
} | ||
/** | ||
* Call to get the length of an object. The object must either | ||
* have a length property of be previously passed in a call to`setLength`. | ||
* | ||
* @example | ||
* import { getLength, setLength } from 'deleight/generational'; | ||
* const myRange = range(12); | ||
* setLength(myRange, 12); | ||
* getLength(myRange); // returns 12. | ||
* | ||
* @param {any} it | ||
*/ | ||
function getLength(it) { | ||
return iterLengths.get(it) || it.length; | ||
} | ||
/** | ||
* Stores the 'fake' lenghts of iterables passed in calls to `setLength`. | ||
* Can also be modified manually. | ||
*/ | ||
const iterLengths = new WeakMap(); | ||
/** | ||
* Attaches a 'fake' length to an object (likely iterable or iterator) | ||
* which does not have a length property, so that it can work well with | ||
* functions that use `getLength`. | ||
* | ||
* @example | ||
* import { getLength, setLength } from 'deleight/generational'; | ||
* const myRange = range(12); | ||
* setLength(myRange, 12); | ||
* getLength(myRange); // returns 12. | ||
* | ||
* @param {any} it | ||
*/ | ||
function setLength(it, length) { | ||
iterLengths.set(it, length); | ||
return it; | ||
} | ||
exports.flat = flat; | ||
exports.getLength = getLength; | ||
exports.group = group; | ||
exports.items = items; | ||
exports.iter = iter; | ||
exports.iterLengths = iterLengths; | ||
exports.next = next; | ||
exports.range = range; | ||
exports.repeat = repeat; | ||
exports.setLength = setLength; | ||
exports.uItems = uItems; |
@@ -1,1 +0,222 @@ | ||
function e(e){return e.next?e:e[Symbol.iterator]()}function*n(e,n,t){t||(t=1),null==n&&e&&(n=e,e=0);for(let o=e;o<n;o+=t)yield o}function*t(e,n){for(let t of n)yield e[t]}function*o(e,n){let t;if(void 0===n){const n=[];for(let t of e)n.push(t),yield t;for(;;)for(t of n)yield t}else for(let o=0;o<n;o++)for(t of e)yield t}function*l(e,n,t){let o=n;for(t&&(yield t,o--);o-- >0;)yield e.next().value}function*f(n,t){const o=e(n);let f=o.next();for(;!f.done;)yield[...l(o,t,f.value)],f=o.next()}function*i(...n){const t=n.length;let o,l;for(n=n.map((n=>e(n)));;)for(o=0;o<t;o++){if(l=n[o].next(),l.done)return;yield l.value}}function*r(e){const n=[...e];for(let e=n.length-1;e>=0;e--)yield n.splice(Math.round(Math.random()*e),1)[0]}function u(e){return c.get(e)||e.length}const c=new WeakMap;function d(e,n){return c.set(e,n),e}export{i as flat,u as getLength,f as group,t as items,e as iter,c as iterLengths,l as next,n as range,o as repeat,d as setLength,r as uItems}; | ||
/** | ||
* This module exports many useful generators like `range` and `repeat`. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Forcs any iterable to become an iterator. Will throw | ||
* if not possible. | ||
* | ||
* @example | ||
* import { iter } from 'deleight/generational'; | ||
* const it = iter([1, 2, 3, 4, 5]); | ||
* | ||
* @param { any } it | ||
*/ | ||
function iter(it) { | ||
return (it.next) ? it : it[Symbol.iterator](); | ||
} | ||
/** | ||
* Fast and 'costless' range function for javascript based on generators. | ||
* | ||
* @example | ||
* import { range } from 'deleight/generational'; | ||
* const arr1000 = [...range(0, 1000)]; | ||
* // creates an array with 1000 items counting from 0 to 999. | ||
* | ||
* @param {number} start | ||
* @param {number} [end] | ||
* @param {number} [step] | ||
*/ | ||
function* range(start, end, step) { | ||
if (!step) | ||
step = 1; | ||
if ((end === undefined || end === null) && start) { | ||
end = start; | ||
start = 0; | ||
} | ||
for (let i = start; i < end; i += step) | ||
yield i; | ||
} | ||
/** | ||
* Returns a generator which iterates over the subset of the | ||
* 'arrayLike' object that matches the provided index. | ||
* | ||
* @example | ||
* import { items, range } from 'deleight/generational'; | ||
* const tenth = []...items(range(1000), range(0, 1000, 10))]; | ||
* // selects every 10th item in the array. | ||
* | ||
* @param {any} arrayLike | ||
* @param {Iterable<any>} index | ||
*/ | ||
function* items(arrayLike, index) { | ||
for (let i of index) | ||
yield arrayLike[i]; | ||
} | ||
/** | ||
* Returns a generator that yields first argument (`what`) the number of | ||
* times specified by the second argument (`times`). If `times` is not | ||
* given, `what` is repeated indefinitely. | ||
* | ||
* @example | ||
* import { repeat } from 'deleight/generational'; | ||
* const repeated = [...repeat([1, 2, 3], 4)]; | ||
* // [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] | ||
* | ||
* @param {Iterable<any>} what | ||
* @param {number} [times] | ||
*/ | ||
function* repeat(what, times) { | ||
let item; | ||
if (times === undefined) { | ||
const what2 = []; | ||
for (let item of what) { | ||
what2.push(item); | ||
yield item; | ||
} | ||
while (true) | ||
for (item of what2) | ||
yield item; | ||
} | ||
else | ||
for (let i = 0; i < times; i++) | ||
for (item of what) | ||
yield item; | ||
} | ||
/** | ||
* Returns an iterator over the next 'count' items of the iterator. | ||
* | ||
* Note that, for performance reasons, this only accepts an | ||
* iterator as the 'it' argument. All the other module functions accept | ||
* iterables. | ||
* | ||
* You can convert any iterable to an iterator using the `iter` funtion | ||
* as shown in the following example. | ||
* | ||
* If a firstValue is specified, it will be yielded first. | ||
* | ||
* @example | ||
* import { nextItems, iter } from 'deleight/generational'; | ||
* const it = iter([1, 'a', 2, 'b', 3, 'c', 4, 'd']); | ||
* const [num, let] = next(it, 2); | ||
* | ||
* @param {Iterator<any>} it | ||
* @param {number} count | ||
* @param { any } [firstValue] | ||
*/ | ||
function* next(it, count, firstValue) { | ||
let count2 = count; | ||
if (firstValue) { | ||
yield firstValue; | ||
count2--; | ||
} | ||
while (count2-- > 0) | ||
yield it.next().value; | ||
} | ||
/** | ||
* Returns an iterator of iterators over the next 'count' items of | ||
* the given iterable | ||
* | ||
* @example | ||
* import { next } from 'deleight/generational'; | ||
* const o1 = [1, 2, 3, 4]; | ||
* const o2 = ['a', 'b', 'c', 'd']; | ||
* const zip = group(flat(o1, o2), 2); | ||
* | ||
* @param { Iterable<any> } it | ||
* @param { number } count | ||
*/ | ||
function* group(it, count) { | ||
const it2 = iter(it); | ||
let nextItem = it2.next(); | ||
while (!nextItem.done) { | ||
yield [...next(it2, count, nextItem.value)]; | ||
nextItem = it2.next(); | ||
} | ||
} | ||
/** | ||
* Returns an iterator over the items of all the input iterators, starting from | ||
* the zero index to the maximum index of the first argument. The | ||
* effective length of the iterator is the multiple of the length of thr smallest | ||
* iterator and the number of iterators (number of args). | ||
* | ||
* Can be used to join arrays in a way no supported by `concat`, `push`, etc. | ||
* To pass an array as an iterator, call array.values(). | ||
* | ||
* @example | ||
* import { flat } from 'deleight/generational'; | ||
* for (let i of flat(range(10, range(15)))) { | ||
* console.log(i); // 0, 0, 1, 1, 2, 2, .... till smallest iterable (10) is exhausted. | ||
* } | ||
* | ||
* @param {...any[]} args | ||
*/ | ||
function* flat(...args) { | ||
const count = args.length; | ||
args = args.map(arg => iter(arg)); | ||
let i, nextItem; | ||
while (true) { | ||
for (i = 0; i < count; i++) { | ||
nextItem = args[i].next(); | ||
if (nextItem.done) | ||
return; | ||
else | ||
yield nextItem.value; | ||
} | ||
} | ||
} | ||
/** | ||
* Returns an unordered/random iterator over the input array.. | ||
* | ||
* @example | ||
* import { uItems } from 'deleight/generational'; | ||
* const unOrdered = uItems([1, 2, 3, 4]); // [4, 1, 3, 2] | ||
* | ||
* @param {Iterable<any>} it | ||
*/ | ||
function* uItems(it) { | ||
const arr = [...it]; | ||
for (let i = arr.length - 1; i >= 0; i--) { | ||
yield arr.splice(Math.round(Math.random() * i), 1)[0]; | ||
} | ||
} | ||
/** | ||
* Call to get the length of an object. The object must either | ||
* have a length property of be previously passed in a call to`setLength`. | ||
* | ||
* @example | ||
* import { getLength, setLength } from 'deleight/generational'; | ||
* const myRange = range(12); | ||
* setLength(myRange, 12); | ||
* getLength(myRange); // returns 12. | ||
* | ||
* @param {any} it | ||
*/ | ||
function getLength(it) { | ||
return iterLengths.get(it) || it.length; | ||
} | ||
/** | ||
* Stores the 'fake' lenghts of iterables passed in calls to `setLength`. | ||
* Can also be modified manually. | ||
*/ | ||
const iterLengths = new WeakMap(); | ||
/** | ||
* Attaches a 'fake' length to an object (likely iterable or iterator) | ||
* which does not have a length property, so that it can work well with | ||
* functions that use `getLength`. | ||
* | ||
* @example | ||
* import { getLength, setLength } from 'deleight/generational'; | ||
* const myRange = range(12); | ||
* setLength(myRange, 12); | ||
* getLength(myRange); // returns 12. | ||
* | ||
* @param {any} it | ||
*/ | ||
function setLength(it, length) { | ||
iterLengths.set(it, length); | ||
return it; | ||
} | ||
export { flat, getLength, group, items, iter, iterLengths, next, range, repeat, setLength, uItems }; |
@@ -1,1 +0,524 @@ | ||
"use strict";const e=Symbol(),t=Symbol();class n{one;map;constructor(e,t){this.one=e,this.map=t}#e(t){const n={};if(t.hasOwnProperty(e)){let s;for(let[r,o]of Object.entries(t[e]))s=this.map[r],n.hasOwnProperty(s)||(n[s]={[e]:{}}),n[s][e][r]=o}else for(let[s,r]of Object.entries(this.map))n[r]={[e]:{[s]:t}};return n}get(){return this.one.get(this.map)}set(e){return this.one.set(this.#e(e))}delete(){return this.one.delete(this.map)}call(e){return this.one.call(this.#e(e))}}class s{many;constructor(e){this.many=e}extend(e){let t;for(let[n,r]of Object.entries(e))this.many.hasOwnProperty(n)?(t=this.many[n],t instanceof s?t.extend(r):this.many[n]=r):this.many[n]=r;return this}contract(...e){for(let t of e)delete this.many[t];return this}slice(...e){const t={};for(let n of e)t[n]=this.many[n]||{};return new s(t)}view(e){return new n(this,e)}get(e,t){const n={};let r,o,i,f,a;if("string"==typeof e)for([o,f]of Object.entries(this.many))f instanceof s?n[o]=f.get(e,t):(r=f[e],t&&r instanceof s&&(r=r.many[t]),n[o]=r);else if(e instanceof Array)for([o,f]of Object.entries(this.many))if(f instanceof s)n[o]=f.get(e,t);else for(i of(n[o]=r={},e))a=f[i],t&&a instanceof s&&(a=a.many[t]),r[i]=a;else if("object"==typeof e)for(let[o,c]of Object.entries(e))if(f=this.many[o],f instanceof s)n[o]=f.get(c,t);else if(c instanceof Array)for(i of(n[o]=r={},c))a=f[i],t&&a instanceof s&&(a=a.many[t]),r[i]=a;else r=f[c],t&&r instanceof s&&(r=r.many[t]),n[o]=r;return n}set(t,n){let r,o,i,f;for(let[a,c]of Object.entries(t))if(c.hasOwnProperty(e))for([r,i]of Object.entries(c[e]))o=this.many[r],o instanceof s?o.set({prop:i},n):n?(f=o[a],f instanceof s&&f.many.hasOwnProperty(n)&&(f.many[n]=i)):o[a]=i;else for([r,o]of Object.entries(this.many))o instanceof s?o.set({prop:c},n):n?(f=o[a],f instanceof s&&f.many.hasOwnProperty(n)&&(f.many[n]=c)):o[a]=c;return this}delete(e){let t,n,r;if("string"==typeof e)for([t,r]of Object.entries(this.many))r instanceof s?r.delete(e):delete r[e];else if(e instanceof Array)for([t,r]of Object.entries(this.many))if(r instanceof s)r.delete(e);else for(n of e)delete r[n];else if("object"==typeof e)for(let[t,o]of Object.entries(e))if(r=this.many[t],r instanceof s)r.delete(o);else if(o instanceof Array)for(n of o)delete r[n];else delete r[o];return this}call(n){let r,o,i;const f={};let a;if("string"==typeof n&&(n=[n]),n instanceof Array)for(let e of n)for([r,o]of Object.entries(this.many))f[r]=o[e]?.();else for(let[c,l]of Object.entries(n))if(f[c]=a={},l.hasOwnProperty(e))for([r,i]of Object.entries(l[e]))o=this.many[r],o instanceof s?a[r]=o.call({prop:i}):i.hasOwnProperty(t)?a[r]=o[c]?.(...i[t]):a[r]=o[c]?.(i);else for([r,o]of Object.entries(this.many))o instanceof s?a[r]=o.call({prop:l}):l.hasOwnProperty(t)?a[r]=o[c]?.(...l[t]):a[r]=o[c]?.(l);return f}}function r(e){return new Proxy(e,i)}const o=Symbol(),i={get:(n,s)=>s===o?n:Object.assign(((...r)=>{let o;return 1===r.length&&"object"==typeof(o=r[0])&&o.hasOwnProperty(e)?n.call({[s]:o}):n.call({[s]:{[t]:r}})}),{get value(){return n.get(s)}}),set:(e,t,n)=>(e.set({[t]:n}),!0),deleteProperty:(e,t)=>(e.delete(t),!0)};exports.One=s,exports.View=n,exports.args=t,exports.map=e,exports.one=function(e){return r(new s(e))},exports.unwrap=function(e){return e[o]},exports.wrap=r; | ||
'use strict'; | ||
/** | ||
* This module enables reactivity by exporting primitives for multiplying the effects of single operations. | ||
* | ||
* @module | ||
*/ | ||
const map = Symbol(), args = Symbol(); | ||
class View { | ||
one; | ||
map; | ||
/** | ||
* Represents a single view of a `One` instance. A view can be described as an | ||
* object comprising one property each from the different objects that are part of | ||
* the `One` object. We can perform actions on a view without explicitly specifying | ||
* the properties. | ||
* | ||
* @example | ||
* import { One, View, args } from "deleight/onetomany"; | ||
* const one = new One({ a1: [], a2: [1, 2, 3, 4, 5] }); | ||
* | ||
* const view = new View(one, { a1: 'push', a2: 'shift' }) | ||
* // push one array and simultaneously shift the other | ||
* // to transfer content... | ||
* | ||
* view.call({ [args]: one.many.a2 }) | ||
* | ||
* @param one | ||
* @param map | ||
* | ||
* @constructor | ||
*/ | ||
constructor(one, map) { this.one = one; this.map = map; } | ||
#oneWhat(what) { | ||
const oneWhat = {}; | ||
if (what.hasOwnProperty(map)) { | ||
let prop; | ||
for (let [key, val] of Object.entries(what[map])) { | ||
prop = this.map[key]; | ||
if (!oneWhat.hasOwnProperty(prop)) | ||
oneWhat[prop] = { [map]: {} }; | ||
oneWhat[prop][map][key] = val; | ||
} | ||
} | ||
else { | ||
for (let [key, prop] of Object.entries(this.map)) | ||
oneWhat[prop] = { [map]: { [key]: what } }; | ||
} | ||
return oneWhat; | ||
} | ||
get() { return this.one.get(this.map); } | ||
set(what) { return this.one.set(this.#oneWhat(what)); } | ||
delete() { return this.one.delete(this.map); } | ||
call(what) { return this.one.call(this.#oneWhat(what)); } | ||
} | ||
class One { | ||
many; | ||
/** | ||
* Creates a single object that propagates actions on it to multiple objects. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* | ||
* // set the same value on all objects | ||
* o1.set({ p1: 78 }); | ||
* | ||
* @param many | ||
*/ | ||
constructor(many) { this.many = many; } | ||
/** | ||
* Joins `many` with `this.many` and returns `this`. The benefit of | ||
* using this function instead of something like `Object.assign` is to handle some | ||
* special cases and ensure that the updated `One` is (almost) correctly typed during | ||
* development. | ||
* | ||
* If the same key exists in the joined objects, the new one will overwrite the | ||
* current one, except if the current value is a `One` instance when `extend` will | ||
* be called on it instead. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* const o2 = o1.extend({ fourth }); | ||
* // One({ first, second, third, fourth }) | ||
* | ||
* @param many | ||
*/ | ||
extend(many) { | ||
let currentVal; | ||
for (let [key, value] of Object.entries(many)) { | ||
if (this.many.hasOwnProperty(key)) { | ||
currentVal = this.many[key]; | ||
if (currentVal instanceof One) { | ||
currentVal.extend(value); | ||
} | ||
else | ||
this.many[key] = value; | ||
} | ||
else | ||
this.many[key] = value; | ||
} | ||
return this; | ||
} | ||
/** | ||
* The opposite of extend. Removes the specified keys from `this.many` and | ||
* returns `this` typed differently. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* const o2 = o1.contract('first', 'third'); | ||
* // One({ second }) | ||
* | ||
* @param keys | ||
* @returns | ||
*/ | ||
contract(...keys) { | ||
for (let k of keys) | ||
delete this.many[k]; | ||
return this; | ||
} | ||
/** | ||
* Creates and returns another instance of `One` containing only the | ||
* objects with these names. If a name is not present in `this.many`, | ||
* a new object is created for it in the returned `One`. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* const o2 = o1.slice('first', 'second') | ||
* // One({ first, second }) | ||
* | ||
* @param names | ||
*/ | ||
slice(...names) { | ||
const many = {}; | ||
for (let name of names) | ||
many[name] = this.many[name] || {}; | ||
return new One(many); | ||
} | ||
; | ||
/** | ||
* Creates and returns an instance of `View` for only the | ||
* specified properties. `what` is an object mapping object keys (in `this.many`) | ||
* to the property names. | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex } }); | ||
* o1.view({first: 'p1', second: 'p2'}); | ||
* view.get(); // { first: 78, second: 12 } | ||
* | ||
* @param map | ||
*/ | ||
view(map) { return new View(this, map); } | ||
; | ||
/** | ||
* Gets the property (or properties) with the specified name(s) from all the | ||
* objects in `this.many`. | ||
* | ||
* 1. if `what` is a string, returns only the property specified (as an object mapping | ||
* object key to property value). | ||
* 2. if `what` is an array of strings, returns 'sub-objects' of all the objects | ||
* consisting of the specified property names (also mapped like 1). | ||
* 3. if what is an object, it is treated as a map of object names to property | ||
* name(s) to return. | ||
* 4. if an object in `this.many` is a `One` instance, its `get` method is | ||
* called for its property value(s). | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex } }); | ||
* | ||
* o1.get('p1'); | ||
* // { first: 78, second: 78, third: 78 } | ||
* | ||
* o1.get('p1', 'p2'); | ||
* // { first: { p1: 78, p2: 56 }, second: { p1: 78, p2: 12 }, third: { p1: 78, p2: 12 } } | ||
* | ||
* o1.get({ first: 'p2', second: 'p1' }); | ||
* // { first: 56, second: 78 } | ||
* | ||
* @param what | ||
*/ | ||
get(what, valueKey) { | ||
const result = {}; | ||
let val, key, item, obj, subVal; | ||
if (typeof what === 'string') { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
result[key] = obj.get(what, valueKey); | ||
else { | ||
val = obj[what]; | ||
if (valueKey && val instanceof One) | ||
val = val.many[valueKey]; | ||
result[key] = val; | ||
} | ||
} | ||
} | ||
else if (what instanceof Array) { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
result[key] = obj.get(what, valueKey); | ||
else { | ||
result[key] = val = {}; | ||
for (item of what) { | ||
subVal = obj[item]; | ||
if (valueKey && subVal instanceof One) | ||
subVal = subVal.many[valueKey]; | ||
val[item] = subVal; | ||
} | ||
} | ||
} | ||
} | ||
else if (typeof what === 'object') { | ||
for (let [key, props] of Object.entries(what)) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
result[key] = obj.get(props, valueKey); | ||
else { | ||
if (props instanceof Array) { | ||
result[key] = val = {}; | ||
for (item of props) { | ||
subVal = obj[item]; | ||
if (valueKey && subVal instanceof One) | ||
subVal = subVal.many[valueKey]; | ||
val[item] = subVal; | ||
} | ||
} | ||
else { | ||
val = obj[props]; | ||
if (valueKey && val instanceof One) | ||
val = val.many[valueKey]; | ||
result[key] = val; | ||
} | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
; | ||
/** | ||
* Sets one or more properties on all the objects in this instance. | ||
* `what` is an object mapping property names to property values. | ||
* | ||
* Each value in `what` is set on all the objects in this `One`. | ||
* The same value will be set unless the value is an object containing the `[map]` property. | ||
* In such a case, the value of the property is treated as a map of object key (in `this.many`) | ||
* to object property value. That is, the objects with corresponding | ||
* keys will have their property values set to the corresponding values. | ||
* | ||
* This will call `set` on any nested `One` objects accordingly. | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex }, p3: complex }); | ||
* | ||
* o1.get('p2', 'p3'); | ||
* // { first: { p2: 56, p3: complex }, second: { p2: 12, p3: complex }, third: { p2: 12, p3: complex } } | ||
* | ||
* @param what | ||
*/ | ||
set(what, valueKey) { | ||
let key, obj, subValue, propVal; | ||
for (let [prop, value] of Object.entries(what)) { | ||
if (value.hasOwnProperty(map)) { | ||
for ([key, subValue] of Object.entries(value[map])) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
obj.set({ prop: subValue }, valueKey); | ||
else { | ||
if (valueKey) { | ||
propVal = obj[prop]; | ||
if (propVal instanceof One && propVal.many.hasOwnProperty(valueKey)) { | ||
propVal.many[valueKey] = subValue; | ||
} | ||
} | ||
else | ||
obj[prop] = subValue; | ||
} | ||
} | ||
} | ||
else { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
obj.set({ prop: value }, valueKey); | ||
else { | ||
if (valueKey) { | ||
propVal = obj[prop]; | ||
if (propVal instanceof One && propVal.many.hasOwnProperty(valueKey)) { | ||
propVal.many[valueKey] = value; | ||
} | ||
} | ||
else | ||
obj[prop] = value; | ||
} | ||
} | ||
} | ||
} | ||
return this; | ||
} | ||
; | ||
/** | ||
* Performs `delete` on the objects in this `One`. The argument is | ||
* treated the same way as in `get`. | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex }, p3: complex }); | ||
* | ||
* o1.delete('p1', 'p2'); | ||
* // One({ first: { p3: complex }, second: { p3: complex }, third: { p3: complex } }) | ||
* | ||
* @param what | ||
*/ | ||
delete(what) { | ||
let key, item, obj; | ||
if (typeof what === 'string') { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
obj.delete(what); | ||
else | ||
delete obj[what]; | ||
} | ||
} | ||
else if (what instanceof Array) { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
obj.delete(what); | ||
else | ||
for (item of what) | ||
delete obj[item]; | ||
} | ||
} | ||
else if (typeof what === 'object') { | ||
for (let [key, props] of Object.entries(what)) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
obj.delete(props); | ||
else { | ||
if (props instanceof Array) | ||
for (item of props) | ||
delete obj[item]; | ||
else | ||
delete obj[props]; | ||
} | ||
} | ||
} | ||
return this; | ||
} | ||
; | ||
/** | ||
* Calls corresponding methods in a similar way to set. In this | ||
* case, call arguments are interpreted instead of property values. | ||
* | ||
* Multiple arguments should be provided as an object with the `[arg]` property whose value | ||
* should be an array containing the arguments. A regular array is considered a single argument. | ||
* | ||
* Nested one objects will have their call method invoked correspondingly. | ||
* | ||
* @example | ||
* import { One, map, args } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const o4 = new One({ arr1, arr2, arr3 }); | ||
* o4.call({ push: 78 }); | ||
* o4.call({ push: { [args]: [56, 57] } }); | ||
* // arr1 = arr2 = arr3 = [78, 56, 57] | ||
* | ||
* @param what | ||
*/ | ||
call(what) { | ||
let key, obj, subValue; | ||
const result = {}; | ||
let propResult; | ||
if (typeof what === 'string') | ||
what = [what]; | ||
if (what instanceof Array) { | ||
for (let prop of what) { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
result[key] = obj[prop]?.(); | ||
} | ||
} | ||
} | ||
else { | ||
for (let [prop, value] of Object.entries(what)) { | ||
result[prop] = propResult = {}; | ||
if (value.hasOwnProperty(map)) { | ||
for ([key, subValue] of Object.entries(value[map])) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
propResult[key] = obj.call({ prop: subValue }); | ||
else { | ||
if (subValue.hasOwnProperty(args)) | ||
propResult[key] = obj[prop]?.(...subValue[args]); | ||
else | ||
propResult[key] = obj[prop]?.(subValue); | ||
} | ||
} | ||
} | ||
else { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
propResult[key] = obj.call({ prop: value }); | ||
else { | ||
if (value.hasOwnProperty(args)) | ||
propResult[key] = obj[prop]?.(...value[args]); | ||
else | ||
propResult[key] = obj[prop]?.(value); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
/** | ||
* A simple wrapper around `One` for a more concise syntax. | ||
* | ||
* @example | ||
* import { one, map, args } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const o4 = one({ arr1, arr2, arr3 }); | ||
* o4.push(78, 56, 57); | ||
* o4.push({ [map]: { arr1: 66, arr2: { [args]: [77, 88] }, arr3: 99 } }); | ||
* // arr1 === [78, 56, 57, 66] | ||
* // arr2 === [78, 56, 57, 77, 88] | ||
* // arr3 === [78, 56, 57, 99] | ||
* | ||
* @param many | ||
* @returns | ||
*/ | ||
function one(many) { return wrap(new One(many)); } | ||
/** | ||
* Wraps a pre-existing `One` | ||
* | ||
* @example | ||
* import { One, wrap } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const o4 = new One({ arr1, arr2, arr3 }); | ||
* const wo4 = wrap(o4); | ||
* | ||
* @param one | ||
* @returns | ||
*/ | ||
function wrap(one) { | ||
return new Proxy(one, oneTrap); | ||
} | ||
/** | ||
* The opposite of `wrap` | ||
* | ||
* @example | ||
* import { one, unwrap } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const wo4 = one({ arr1, arr2, arr3 }); | ||
* const o4 = unwrap(wo4); | ||
* | ||
* @param wrapped | ||
* @returns | ||
*/ | ||
function unwrap(wrapped) { return wrapped[self]; } | ||
const self = Symbol(); | ||
const oneTrap = { | ||
get(target, p) { | ||
if (p === self) | ||
return target; | ||
return Object.assign((...methodArgs) => { | ||
let arg1; | ||
if (methodArgs.length === 1 && typeof (arg1 = methodArgs[0]) === 'object' && | ||
arg1.hasOwnProperty(map)) { | ||
return target.call({ [p]: arg1 }); | ||
} | ||
else | ||
return target.call({ [p]: { [args]: methodArgs } }); | ||
}, { | ||
get value() { return target.get(p); } | ||
}); | ||
}, | ||
set(target, p, value) { | ||
target.set({ [p]: value }); | ||
return true; | ||
}, | ||
deleteProperty(target, p) { | ||
target.delete(p); | ||
return true; | ||
} | ||
}; | ||
exports.One = One; | ||
exports.View = View; | ||
exports.args = args; | ||
exports.map = map; | ||
exports.one = one; | ||
exports.unwrap = unwrap; | ||
exports.wrap = wrap; |
@@ -1,1 +0,516 @@ | ||
const e=Symbol(),t=Symbol();class n{one;map;constructor(e,t){this.one=e,this.map=t}#e(t){const n={};if(t.hasOwnProperty(e)){let s;for(let[r,o]of Object.entries(t[e]))s=this.map[r],n.hasOwnProperty(s)||(n[s]={[e]:{}}),n[s][e][r]=o}else for(let[s,r]of Object.entries(this.map))n[r]={[e]:{[s]:t}};return n}get(){return this.one.get(this.map)}set(e){return this.one.set(this.#e(e))}delete(){return this.one.delete(this.map)}call(e){return this.one.call(this.#e(e))}}class s{many;constructor(e){this.many=e}extend(e){let t;for(let[n,r]of Object.entries(e))this.many.hasOwnProperty(n)?(t=this.many[n],t instanceof s?t.extend(r):this.many[n]=r):this.many[n]=r;return this}contract(...e){for(let t of e)delete this.many[t];return this}slice(...e){const t={};for(let n of e)t[n]=this.many[n]||{};return new s(t)}view(e){return new n(this,e)}get(e,t){const n={};let r,o,i,f,a;if("string"==typeof e)for([o,f]of Object.entries(this.many))f instanceof s?n[o]=f.get(e,t):(r=f[e],t&&r instanceof s&&(r=r.many[t]),n[o]=r);else if(e instanceof Array)for([o,f]of Object.entries(this.many))if(f instanceof s)n[o]=f.get(e,t);else for(i of(n[o]=r={},e))a=f[i],t&&a instanceof s&&(a=a.many[t]),r[i]=a;else if("object"==typeof e)for(let[o,c]of Object.entries(e))if(f=this.many[o],f instanceof s)n[o]=f.get(c,t);else if(c instanceof Array)for(i of(n[o]=r={},c))a=f[i],t&&a instanceof s&&(a=a.many[t]),r[i]=a;else r=f[c],t&&r instanceof s&&(r=r.many[t]),n[o]=r;return n}set(t,n){let r,o,i,f;for(let[a,c]of Object.entries(t))if(c.hasOwnProperty(e))for([r,i]of Object.entries(c[e]))o=this.many[r],o instanceof s?o.set({prop:i},n):n?(f=o[a],f instanceof s&&f.many.hasOwnProperty(n)&&(f.many[n]=i)):o[a]=i;else for([r,o]of Object.entries(this.many))o instanceof s?o.set({prop:c},n):n?(f=o[a],f instanceof s&&f.many.hasOwnProperty(n)&&(f.many[n]=c)):o[a]=c;return this}delete(e){let t,n,r;if("string"==typeof e)for([t,r]of Object.entries(this.many))r instanceof s?r.delete(e):delete r[e];else if(e instanceof Array)for([t,r]of Object.entries(this.many))if(r instanceof s)r.delete(e);else for(n of e)delete r[n];else if("object"==typeof e)for(let[t,o]of Object.entries(e))if(r=this.many[t],r instanceof s)r.delete(o);else if(o instanceof Array)for(n of o)delete r[n];else delete r[o];return this}call(n){let r,o,i;const f={};let a;if("string"==typeof n&&(n=[n]),n instanceof Array)for(let e of n)for([r,o]of Object.entries(this.many))f[r]=o[e]?.();else for(let[c,l]of Object.entries(n))if(f[c]=a={},l.hasOwnProperty(e))for([r,i]of Object.entries(l[e]))o=this.many[r],o instanceof s?a[r]=o.call({prop:i}):i.hasOwnProperty(t)?a[r]=o[c]?.(...i[t]):a[r]=o[c]?.(i);else for([r,o]of Object.entries(this.many))o instanceof s?a[r]=o.call({prop:l}):l.hasOwnProperty(t)?a[r]=o[c]?.(...l[t]):a[r]=o[c]?.(l);return f}}function r(e){return o(new s(e))}function o(e){return new Proxy(e,a)}function i(e){return e[f]}const f=Symbol(),a={get:(n,s)=>s===f?n:Object.assign(((...r)=>{let o;return 1===r.length&&"object"==typeof(o=r[0])&&o.hasOwnProperty(e)?n.call({[s]:o}):n.call({[s]:{[t]:r}})}),{get value(){return n.get(s)}}),set:(e,t,n)=>(e.set({[t]:n}),!0),deleteProperty:(e,t)=>(e.delete(t),!0)};export{s as One,n as View,t as args,e as map,r as one,i as unwrap,o as wrap}; | ||
/** | ||
* This module enables reactivity by exporting primitives for multiplying the effects of single operations. | ||
* | ||
* @module | ||
*/ | ||
const map = Symbol(), args = Symbol(); | ||
class View { | ||
one; | ||
map; | ||
/** | ||
* Represents a single view of a `One` instance. A view can be described as an | ||
* object comprising one property each from the different objects that are part of | ||
* the `One` object. We can perform actions on a view without explicitly specifying | ||
* the properties. | ||
* | ||
* @example | ||
* import { One, View, args } from "deleight/onetomany"; | ||
* const one = new One({ a1: [], a2: [1, 2, 3, 4, 5] }); | ||
* | ||
* const view = new View(one, { a1: 'push', a2: 'shift' }) | ||
* // push one array and simultaneously shift the other | ||
* // to transfer content... | ||
* | ||
* view.call({ [args]: one.many.a2 }) | ||
* | ||
* @param one | ||
* @param map | ||
* | ||
* @constructor | ||
*/ | ||
constructor(one, map) { this.one = one; this.map = map; } | ||
#oneWhat(what) { | ||
const oneWhat = {}; | ||
if (what.hasOwnProperty(map)) { | ||
let prop; | ||
for (let [key, val] of Object.entries(what[map])) { | ||
prop = this.map[key]; | ||
if (!oneWhat.hasOwnProperty(prop)) | ||
oneWhat[prop] = { [map]: {} }; | ||
oneWhat[prop][map][key] = val; | ||
} | ||
} | ||
else { | ||
for (let [key, prop] of Object.entries(this.map)) | ||
oneWhat[prop] = { [map]: { [key]: what } }; | ||
} | ||
return oneWhat; | ||
} | ||
get() { return this.one.get(this.map); } | ||
set(what) { return this.one.set(this.#oneWhat(what)); } | ||
delete() { return this.one.delete(this.map); } | ||
call(what) { return this.one.call(this.#oneWhat(what)); } | ||
} | ||
class One { | ||
many; | ||
/** | ||
* Creates a single object that propagates actions on it to multiple objects. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* | ||
* // set the same value on all objects | ||
* o1.set({ p1: 78 }); | ||
* | ||
* @param many | ||
*/ | ||
constructor(many) { this.many = many; } | ||
/** | ||
* Joins `many` with `this.many` and returns `this`. The benefit of | ||
* using this function instead of something like `Object.assign` is to handle some | ||
* special cases and ensure that the updated `One` is (almost) correctly typed during | ||
* development. | ||
* | ||
* If the same key exists in the joined objects, the new one will overwrite the | ||
* current one, except if the current value is a `One` instance when `extend` will | ||
* be called on it instead. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* const o2 = o1.extend({ fourth }); | ||
* // One({ first, second, third, fourth }) | ||
* | ||
* @param many | ||
*/ | ||
extend(many) { | ||
let currentVal; | ||
for (let [key, value] of Object.entries(many)) { | ||
if (this.many.hasOwnProperty(key)) { | ||
currentVal = this.many[key]; | ||
if (currentVal instanceof One) { | ||
currentVal.extend(value); | ||
} | ||
else | ||
this.many[key] = value; | ||
} | ||
else | ||
this.many[key] = value; | ||
} | ||
return this; | ||
} | ||
/** | ||
* The opposite of extend. Removes the specified keys from `this.many` and | ||
* returns `this` typed differently. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* const o2 = o1.contract('first', 'third'); | ||
* // One({ second }) | ||
* | ||
* @param keys | ||
* @returns | ||
*/ | ||
contract(...keys) { | ||
for (let k of keys) | ||
delete this.many[k]; | ||
return this; | ||
} | ||
/** | ||
* Creates and returns another instance of `One` containing only the | ||
* objects with these names. If a name is not present in `this.many`, | ||
* a new object is created for it in the returned `One`. | ||
* | ||
* @example | ||
* import { One } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* const o2 = o1.slice('first', 'second') | ||
* // One({ first, second }) | ||
* | ||
* @param names | ||
*/ | ||
slice(...names) { | ||
const many = {}; | ||
for (let name of names) | ||
many[name] = this.many[name] || {}; | ||
return new One(many); | ||
} | ||
; | ||
/** | ||
* Creates and returns an instance of `View` for only the | ||
* specified properties. `what` is an object mapping object keys (in `this.many`) | ||
* to the property names. | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex } }); | ||
* o1.view({first: 'p1', second: 'p2'}); | ||
* view.get(); // { first: 78, second: 12 } | ||
* | ||
* @param map | ||
*/ | ||
view(map) { return new View(this, map); } | ||
; | ||
/** | ||
* Gets the property (or properties) with the specified name(s) from all the | ||
* objects in `this.many`. | ||
* | ||
* 1. if `what` is a string, returns only the property specified (as an object mapping | ||
* object key to property value). | ||
* 2. if `what` is an array of strings, returns 'sub-objects' of all the objects | ||
* consisting of the specified property names (also mapped like 1). | ||
* 3. if what is an object, it is treated as a map of object names to property | ||
* name(s) to return. | ||
* 4. if an object in `this.many` is a `One` instance, its `get` method is | ||
* called for its property value(s). | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex } }); | ||
* | ||
* o1.get('p1'); | ||
* // { first: 78, second: 78, third: 78 } | ||
* | ||
* o1.get('p1', 'p2'); | ||
* // { first: { p1: 78, p2: 56 }, second: { p1: 78, p2: 12 }, third: { p1: 78, p2: 12 } } | ||
* | ||
* o1.get({ first: 'p2', second: 'p1' }); | ||
* // { first: 56, second: 78 } | ||
* | ||
* @param what | ||
*/ | ||
get(what, valueKey) { | ||
const result = {}; | ||
let val, key, item, obj, subVal; | ||
if (typeof what === 'string') { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
result[key] = obj.get(what, valueKey); | ||
else { | ||
val = obj[what]; | ||
if (valueKey && val instanceof One) | ||
val = val.many[valueKey]; | ||
result[key] = val; | ||
} | ||
} | ||
} | ||
else if (what instanceof Array) { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
result[key] = obj.get(what, valueKey); | ||
else { | ||
result[key] = val = {}; | ||
for (item of what) { | ||
subVal = obj[item]; | ||
if (valueKey && subVal instanceof One) | ||
subVal = subVal.many[valueKey]; | ||
val[item] = subVal; | ||
} | ||
} | ||
} | ||
} | ||
else if (typeof what === 'object') { | ||
for (let [key, props] of Object.entries(what)) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
result[key] = obj.get(props, valueKey); | ||
else { | ||
if (props instanceof Array) { | ||
result[key] = val = {}; | ||
for (item of props) { | ||
subVal = obj[item]; | ||
if (valueKey && subVal instanceof One) | ||
subVal = subVal.many[valueKey]; | ||
val[item] = subVal; | ||
} | ||
} | ||
else { | ||
val = obj[props]; | ||
if (valueKey && val instanceof One) | ||
val = val.many[valueKey]; | ||
result[key] = val; | ||
} | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
; | ||
/** | ||
* Sets one or more properties on all the objects in this instance. | ||
* `what` is an object mapping property names to property values. | ||
* | ||
* Each value in `what` is set on all the objects in this `One`. | ||
* The same value will be set unless the value is an object containing the `[map]` property. | ||
* In such a case, the value of the property is treated as a map of object key (in `this.many`) | ||
* to object property value. That is, the objects with corresponding | ||
* keys will have their property values set to the corresponding values. | ||
* | ||
* This will call `set` on any nested `One` objects accordingly. | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex }, p3: complex }); | ||
* | ||
* o1.get('p2', 'p3'); | ||
* // { first: { p2: 56, p3: complex }, second: { p2: 12, p3: complex }, third: { p2: 12, p3: complex } } | ||
* | ||
* @param what | ||
*/ | ||
set(what, valueKey) { | ||
let key, obj, subValue, propVal; | ||
for (let [prop, value] of Object.entries(what)) { | ||
if (value.hasOwnProperty(map)) { | ||
for ([key, subValue] of Object.entries(value[map])) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
obj.set({ prop: subValue }, valueKey); | ||
else { | ||
if (valueKey) { | ||
propVal = obj[prop]; | ||
if (propVal instanceof One && propVal.many.hasOwnProperty(valueKey)) { | ||
propVal.many[valueKey] = subValue; | ||
} | ||
} | ||
else | ||
obj[prop] = subValue; | ||
} | ||
} | ||
} | ||
else { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
obj.set({ prop: value }, valueKey); | ||
else { | ||
if (valueKey) { | ||
propVal = obj[prop]; | ||
if (propVal instanceof One && propVal.many.hasOwnProperty(valueKey)) { | ||
propVal.many[valueKey] = value; | ||
} | ||
} | ||
else | ||
obj[prop] = value; | ||
} | ||
} | ||
} | ||
} | ||
return this; | ||
} | ||
; | ||
/** | ||
* Performs `delete` on the objects in this `One`. The argument is | ||
* treated the same way as in `get`. | ||
* | ||
* @example | ||
* import { One, map } from "deleight/onetomany"; | ||
* const first = {}, second = {}, third = {}, fourth = {}; | ||
* const many = { first, second, third }; | ||
* const o1 = new One(many); | ||
* o1.set({ p1: 78 }); | ||
* const complex = { first: 56, second: 12, third: 12 }; | ||
* o1.set({ p2: { [map]: complex }, p3: complex }); | ||
* | ||
* o1.delete('p1', 'p2'); | ||
* // One({ first: { p3: complex }, second: { p3: complex }, third: { p3: complex } }) | ||
* | ||
* @param what | ||
*/ | ||
delete(what) { | ||
let key, item, obj; | ||
if (typeof what === 'string') { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
obj.delete(what); | ||
else | ||
delete obj[what]; | ||
} | ||
} | ||
else if (what instanceof Array) { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
obj.delete(what); | ||
else | ||
for (item of what) | ||
delete obj[item]; | ||
} | ||
} | ||
else if (typeof what === 'object') { | ||
for (let [key, props] of Object.entries(what)) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
obj.delete(props); | ||
else { | ||
if (props instanceof Array) | ||
for (item of props) | ||
delete obj[item]; | ||
else | ||
delete obj[props]; | ||
} | ||
} | ||
} | ||
return this; | ||
} | ||
; | ||
/** | ||
* Calls corresponding methods in a similar way to set. In this | ||
* case, call arguments are interpreted instead of property values. | ||
* | ||
* Multiple arguments should be provided as an object with the `[arg]` property whose value | ||
* should be an array containing the arguments. A regular array is considered a single argument. | ||
* | ||
* Nested one objects will have their call method invoked correspondingly. | ||
* | ||
* @example | ||
* import { One, map, args } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const o4 = new One({ arr1, arr2, arr3 }); | ||
* o4.call({ push: 78 }); | ||
* o4.call({ push: { [args]: [56, 57] } }); | ||
* // arr1 = arr2 = arr3 = [78, 56, 57] | ||
* | ||
* @param what | ||
*/ | ||
call(what) { | ||
let key, obj, subValue; | ||
const result = {}; | ||
let propResult; | ||
if (typeof what === 'string') | ||
what = [what]; | ||
if (what instanceof Array) { | ||
for (let prop of what) { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
result[key] = obj[prop]?.(); | ||
} | ||
} | ||
} | ||
else { | ||
for (let [prop, value] of Object.entries(what)) { | ||
result[prop] = propResult = {}; | ||
if (value.hasOwnProperty(map)) { | ||
for ([key, subValue] of Object.entries(value[map])) { | ||
obj = this.many[key]; | ||
if (obj instanceof One) | ||
propResult[key] = obj.call({ prop: subValue }); | ||
else { | ||
if (subValue.hasOwnProperty(args)) | ||
propResult[key] = obj[prop]?.(...subValue[args]); | ||
else | ||
propResult[key] = obj[prop]?.(subValue); | ||
} | ||
} | ||
} | ||
else { | ||
for ([key, obj] of Object.entries(this.many)) { | ||
if (obj instanceof One) | ||
propResult[key] = obj.call({ prop: value }); | ||
else { | ||
if (value.hasOwnProperty(args)) | ||
propResult[key] = obj[prop]?.(...value[args]); | ||
else | ||
propResult[key] = obj[prop]?.(value); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
/** | ||
* A simple wrapper around `One` for a more concise syntax. | ||
* | ||
* @example | ||
* import { one, map, args } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const o4 = one({ arr1, arr2, arr3 }); | ||
* o4.push(78, 56, 57); | ||
* o4.push({ [map]: { arr1: 66, arr2: { [args]: [77, 88] }, arr3: 99 } }); | ||
* // arr1 === [78, 56, 57, 66] | ||
* // arr2 === [78, 56, 57, 77, 88] | ||
* // arr3 === [78, 56, 57, 99] | ||
* | ||
* @param many | ||
* @returns | ||
*/ | ||
function one(many) { return wrap(new One(many)); } | ||
/** | ||
* Wraps a pre-existing `One` | ||
* | ||
* @example | ||
* import { One, wrap } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const o4 = new One({ arr1, arr2, arr3 }); | ||
* const wo4 = wrap(o4); | ||
* | ||
* @param one | ||
* @returns | ||
*/ | ||
function wrap(one) { | ||
return new Proxy(one, oneTrap); | ||
} | ||
/** | ||
* The opposite of `wrap` | ||
* | ||
* @example | ||
* import { one, unwrap } from "deleight/onetomany"; | ||
* const arr1 = [], arr2 = [], arr3 = []; | ||
* const wo4 = one({ arr1, arr2, arr3 }); | ||
* const o4 = unwrap(wo4); | ||
* | ||
* @param wrapped | ||
* @returns | ||
*/ | ||
function unwrap(wrapped) { return wrapped[self]; } | ||
const self = Symbol(); | ||
const oneTrap = { | ||
get(target, p) { | ||
if (p === self) | ||
return target; | ||
return Object.assign((...methodArgs) => { | ||
let arg1; | ||
if (methodArgs.length === 1 && typeof (arg1 = methodArgs[0]) === 'object' && | ||
arg1.hasOwnProperty(map)) { | ||
return target.call({ [p]: arg1 }); | ||
} | ||
else | ||
return target.call({ [p]: { [args]: methodArgs } }); | ||
}, { | ||
get value() { return target.get(p); } | ||
}); | ||
}, | ||
set(target, p, value) { | ||
target.set({ [p]: value }); | ||
return true; | ||
}, | ||
deleteProperty(target, p) { | ||
target.delete(p); | ||
return true; | ||
} | ||
}; | ||
export { One, View, args, map, one, unwrap, wrap }; |
@@ -1,1 +0,204 @@ | ||
"use strict";const e={before(e,t){t.parentNode?.insertBefore(e,t)},append(e,t){t.appendChild(e)}};exports.insert=function(t,r,o){o||(o=e.append);let n=t;n.next||(n=t[Symbol.iterator]());for(let e of r)o(e,n.next().value);return[t,r]},exports.inserter=e,exports.remove=function(e,t){t||(e=[...e]);for(let t of e)t.parentNode?.removeChild(t);return e},exports.set=function(e,t,r){const o={},n={};let s,l,i,f,a=new Map;for(let[e,r]of Object.entries(t))a.has(r)?n[e]=a.get(r):(s=r,s.next||(s=r[Symbol.iterator]()),o[e]=s,a.set(r,e));let p,c={};for(let t of e){for([l,i]of Object.entries(o))f=i.next().value,r&&void 0===f&&(f=""),c[l]=f,l.startsWith("_")?(l=l.slice(1),t.setAttribute(l,f)):t[l]=f;for([l,p]of Object.entries(n))l.startsWith("_")?(l=l.slice(1),t.setAttribute(l,c[p])):t[l]=c[p]}return[e,t]},exports.update=function(e,t,r){let o,n;const s=document.createComment(""),l=[];r||(e=Array.from(e),t=Array.from(t));for(let t of e)o=t.parentNode,n=s.cloneNode(!1),o?.replaceChild(n,t),l.push([n,o]);const i=l.values();let f;for(let e of t)f=i.next(),f.done?o.appendChild(e):([n,o]=f.value,o?.replaceChild(e,n));for(;!(f=i.next()).done;)[n,o]=f.value,o.removeChild(n);return[e,t]}; | ||
'use strict'; | ||
/** | ||
* This module exports primitives for 'bulk' manipulation of the DOM. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Insert the values using the elements as target. The way they are inserted | ||
* depend on the inserter. If not provided, the default inserter will append the values | ||
* to the corresponding elements. | ||
* | ||
* @example | ||
* // Insert a span into all the children of the first main element: | ||
* import { insert } from 'deleight/queryoperator'; | ||
* const span = document.createElement('span'); | ||
* const main = document.querySelector('main'); | ||
* insert(main.children, main.children.map(() => span.cloneNode())) | ||
* | ||
* | ||
* @param {Iterable<Node>} elements The target nodes. | ||
* @param {Iterable<Node>} values The new nodes to insert. | ||
* @param {IInserter} [insertWith] The insertion function | ||
*/ | ||
function insert(elements, values, insertWith) { | ||
if (!insertWith) | ||
insertWith = inserter.append; // the default inserter | ||
let elements2 = elements; | ||
if (!elements2.next) | ||
elements2 = elements[Symbol.iterator](); | ||
for (let value of values) { | ||
insertWith(value, elements2.next().value); | ||
} | ||
return [elements, values]; | ||
} | ||
/** | ||
* Default inserters for use with `insert` | ||
*/ | ||
const inserter = { | ||
/** | ||
* Inserts the node before the target using `insertBefore` | ||
* @param {Node} node | ||
* @param {Node} target | ||
*/ | ||
before(node, target) { | ||
target.parentNode?.insertBefore(node, target); | ||
}, | ||
/** | ||
* Append the node to the target using `appendChild` | ||
* @param {Node} node | ||
* @param {Node} target | ||
*/ | ||
append(node, target) { | ||
target.appendChild(node); | ||
}, | ||
}; | ||
/** | ||
* Set specified properties and/or attributes on the specified elements | ||
* (or their children). Pass an iterable of elements (often an array) as the | ||
* first arg and an object mapping property names to value iterables to be matched against | ||
* the elenments at corresponding indices. | ||
* | ||
* If a key in `values` starts with the underscore (`_`), the attribute with | ||
* the name following the underscore will be set. | ||
* | ||
* | ||
* @example | ||
* // Shuffle the class attributes of all the children of the first main element: | ||
* import { set } from 'deleight/queryoperator'; | ||
* import { uItems } from 'deleight/generational'; | ||
* const main = document.querySelector('main'); | ||
* const values = uItems(main.children.map(c => c.className)); | ||
* set(main.children, {_class: values}); | ||
* | ||
* @param {(Element|CSSStyleRule)[]} elements | ||
* @param {ISetMap} values | ||
* @param { boolean } undefinedIsEmpty | ||
*/ | ||
function set(elements, values, undefinedIsEmpty) { | ||
const localMemberValues = {}; | ||
const deps = {}; | ||
let memberValues2; | ||
let allValues = new Map(); | ||
for (let [key, memberValues] of Object.entries(values)) { | ||
if (allValues.has(memberValues)) | ||
deps[key] = allValues.get(memberValues); | ||
else { | ||
memberValues2 = memberValues; | ||
if (!(memberValues2.next)) | ||
memberValues2 = memberValues[Symbol.iterator](); | ||
localMemberValues[key] = memberValues2; | ||
allValues.set(memberValues, key); | ||
} | ||
} | ||
let member, memberValues, memberValue; | ||
let currentValues = {}, dep; | ||
for (let element of elements) { | ||
for ([member, memberValues] of Object.entries(localMemberValues)) { | ||
memberValue = memberValues.next().value; | ||
if (undefinedIsEmpty && memberValue === undefined) | ||
memberValue = ''; | ||
currentValues[member] = memberValue; | ||
if (member.startsWith("_")) { | ||
member = member.slice(1); | ||
element.setAttribute(member, memberValue); | ||
} | ||
else { | ||
element[member] = memberValue; | ||
} | ||
} | ||
for ([member, dep] of Object.entries(deps)) { | ||
if (member.startsWith("_")) { | ||
member = member.slice(1); | ||
element.setAttribute(member, currentValues[dep]); | ||
} | ||
else { | ||
element[member] = currentValues[dep]; | ||
} | ||
} | ||
} | ||
return [elements, values]; | ||
} | ||
/** | ||
* Correctly replace the specified nodes with corresponding values. | ||
* | ||
* This will materialize `elements` and `values` unless `lazy` | ||
* is supplied as a truthy value. | ||
* | ||
* @example | ||
* // Safely shuffle all the children of the first main element: | ||
* import { update } from 'deleight/queryoperator'; | ||
* import { uItems } from 'deleight/generational'; | ||
* const main = document.querySelector('main'); | ||
* update(main.children, uItems(main.children)) | ||
* | ||
* @param {Iterable<Node>} elements The nodes to replace. | ||
* @param {Iterable<Node>} values The replacement nodes. | ||
* @param { boolean } [lazy] | ||
*/ | ||
function update(elements, values, lazy) { | ||
let parentNode, tempNode; | ||
const template = document.createComment(""); // document.createElement('template'); | ||
const temps = []; | ||
if (!lazy) { | ||
elements = Array.from(elements); | ||
values = Array.from(values); | ||
} | ||
for (let element of elements) { | ||
parentNode = element.parentNode; | ||
tempNode = template.cloneNode(false); | ||
parentNode?.replaceChild(tempNode, element); | ||
temps.push([tempNode, parentNode]); | ||
} | ||
/* at this point we have replaced what we want to replace with temporary values */ | ||
const temps2 = temps.values(); | ||
let nextTemp; | ||
for (let value of values) { | ||
nextTemp = temps2.next(); | ||
if (!nextTemp.done) { | ||
[tempNode, parentNode] = nextTemp.value; | ||
parentNode?.replaceChild(value, tempNode); | ||
} | ||
else | ||
parentNode.appendChild(value); | ||
// this will allow us replace fewer nodes with more nodes if necessary. | ||
} | ||
while (!(nextTemp = temps2.next()).done) { | ||
[tempNode, parentNode] = nextTemp.value; | ||
parentNode.removeChild(tempNode); | ||
} // this will allow us to replace more nodes with fewer nodes if necessary: | ||
return [elements, values]; // we can, eg run cleanups or inits on either of these. | ||
} | ||
/** | ||
* Remove the elements from their parent nodes. | ||
* | ||
* This will materialize `elements` unless `lazy` | ||
* is supplied and its value is truthy. | ||
* | ||
* @example | ||
* import { update } from 'deleight/queryoperator'; | ||
* const main = document.querySelector('main'); | ||
* remoove(main.children); | ||
* | ||
* @param {Iterable<Node>} elements | ||
* @param { boolean } [lazy] | ||
*/ | ||
function remove(elements, lazy) { | ||
if (!lazy) | ||
elements = [...elements]; | ||
for (let element of elements) | ||
element.parentNode?.removeChild(element); | ||
return elements; // we can, eg run cleanups on these. | ||
} | ||
/** | ||
* notes: | ||
* 1. add tests for `undefinedIsEmpty` parameter in `set`. | ||
* 2. add tests for `update` using elements and values of different sizes. | ||
*/ | ||
exports.insert = insert; | ||
exports.inserter = inserter; | ||
exports.remove = remove; | ||
exports.set = set; | ||
exports.update = update; |
@@ -1,1 +0,198 @@ | ||
function e(e,o,r){r||(r=t.append);let n=e;n.next||(n=e[Symbol.iterator]());for(let e of o)r(e,n.next().value);return[e,o]}const t={before(e,t){t.parentNode?.insertBefore(e,t)},append(e,t){t.appendChild(e)}};function o(e,t,o){const r={},n={};let l,f,a,i,s=new Map;for(let[e,o]of Object.entries(t))s.has(o)?n[e]=s.get(o):(l=o,l.next||(l=o[Symbol.iterator]()),r[e]=l,s.set(o,e));let c,d={};for(let t of e){for([f,a]of Object.entries(r))i=a.next().value,o&&void 0===i&&(i=""),d[f]=i,f.startsWith("_")?(f=f.slice(1),t.setAttribute(f,i)):t[f]=i;for([f,c]of Object.entries(n))f.startsWith("_")?(f=f.slice(1),t.setAttribute(f,d[c])):t[f]=d[c]}return[e,t]}function r(e,t,o){let r,n;const l=document.createComment(""),f=[];o||(e=Array.from(e),t=Array.from(t));for(let t of e)r=t.parentNode,n=l.cloneNode(!1),r?.replaceChild(n,t),f.push([n,r]);const a=f.values();let i;for(let e of t)i=a.next(),i.done?r.appendChild(e):([n,r]=i.value,r?.replaceChild(e,n));for(;!(i=a.next()).done;)[n,r]=i.value,r.removeChild(n);return[e,t]}function n(e,t){t||(e=[...e]);for(let t of e)t.parentNode?.removeChild(t);return e}export{e as insert,t as inserter,n as remove,o as set,r as update}; | ||
/** | ||
* This module exports primitives for 'bulk' manipulation of the DOM. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Insert the values using the elements as target. The way they are inserted | ||
* depend on the inserter. If not provided, the default inserter will append the values | ||
* to the corresponding elements. | ||
* | ||
* @example | ||
* // Insert a span into all the children of the first main element: | ||
* import { insert } from 'deleight/queryoperator'; | ||
* const span = document.createElement('span'); | ||
* const main = document.querySelector('main'); | ||
* insert(main.children, main.children.map(() => span.cloneNode())) | ||
* | ||
* | ||
* @param {Iterable<Node>} elements The target nodes. | ||
* @param {Iterable<Node>} values The new nodes to insert. | ||
* @param {IInserter} [insertWith] The insertion function | ||
*/ | ||
function insert(elements, values, insertWith) { | ||
if (!insertWith) | ||
insertWith = inserter.append; // the default inserter | ||
let elements2 = elements; | ||
if (!elements2.next) | ||
elements2 = elements[Symbol.iterator](); | ||
for (let value of values) { | ||
insertWith(value, elements2.next().value); | ||
} | ||
return [elements, values]; | ||
} | ||
/** | ||
* Default inserters for use with `insert` | ||
*/ | ||
const inserter = { | ||
/** | ||
* Inserts the node before the target using `insertBefore` | ||
* @param {Node} node | ||
* @param {Node} target | ||
*/ | ||
before(node, target) { | ||
target.parentNode?.insertBefore(node, target); | ||
}, | ||
/** | ||
* Append the node to the target using `appendChild` | ||
* @param {Node} node | ||
* @param {Node} target | ||
*/ | ||
append(node, target) { | ||
target.appendChild(node); | ||
}, | ||
}; | ||
/** | ||
* Set specified properties and/or attributes on the specified elements | ||
* (or their children). Pass an iterable of elements (often an array) as the | ||
* first arg and an object mapping property names to value iterables to be matched against | ||
* the elenments at corresponding indices. | ||
* | ||
* If a key in `values` starts with the underscore (`_`), the attribute with | ||
* the name following the underscore will be set. | ||
* | ||
* | ||
* @example | ||
* // Shuffle the class attributes of all the children of the first main element: | ||
* import { set } from 'deleight/queryoperator'; | ||
* import { uItems } from 'deleight/generational'; | ||
* const main = document.querySelector('main'); | ||
* const values = uItems(main.children.map(c => c.className)); | ||
* set(main.children, {_class: values}); | ||
* | ||
* @param {(Element|CSSStyleRule)[]} elements | ||
* @param {ISetMap} values | ||
* @param { boolean } undefinedIsEmpty | ||
*/ | ||
function set(elements, values, undefinedIsEmpty) { | ||
const localMemberValues = {}; | ||
const deps = {}; | ||
let memberValues2; | ||
let allValues = new Map(); | ||
for (let [key, memberValues] of Object.entries(values)) { | ||
if (allValues.has(memberValues)) | ||
deps[key] = allValues.get(memberValues); | ||
else { | ||
memberValues2 = memberValues; | ||
if (!(memberValues2.next)) | ||
memberValues2 = memberValues[Symbol.iterator](); | ||
localMemberValues[key] = memberValues2; | ||
allValues.set(memberValues, key); | ||
} | ||
} | ||
let member, memberValues, memberValue; | ||
let currentValues = {}, dep; | ||
for (let element of elements) { | ||
for ([member, memberValues] of Object.entries(localMemberValues)) { | ||
memberValue = memberValues.next().value; | ||
if (undefinedIsEmpty && memberValue === undefined) | ||
memberValue = ''; | ||
currentValues[member] = memberValue; | ||
if (member.startsWith("_")) { | ||
member = member.slice(1); | ||
element.setAttribute(member, memberValue); | ||
} | ||
else { | ||
element[member] = memberValue; | ||
} | ||
} | ||
for ([member, dep] of Object.entries(deps)) { | ||
if (member.startsWith("_")) { | ||
member = member.slice(1); | ||
element.setAttribute(member, currentValues[dep]); | ||
} | ||
else { | ||
element[member] = currentValues[dep]; | ||
} | ||
} | ||
} | ||
return [elements, values]; | ||
} | ||
/** | ||
* Correctly replace the specified nodes with corresponding values. | ||
* | ||
* This will materialize `elements` and `values` unless `lazy` | ||
* is supplied as a truthy value. | ||
* | ||
* @example | ||
* // Safely shuffle all the children of the first main element: | ||
* import { update } from 'deleight/queryoperator'; | ||
* import { uItems } from 'deleight/generational'; | ||
* const main = document.querySelector('main'); | ||
* update(main.children, uItems(main.children)) | ||
* | ||
* @param {Iterable<Node>} elements The nodes to replace. | ||
* @param {Iterable<Node>} values The replacement nodes. | ||
* @param { boolean } [lazy] | ||
*/ | ||
function update(elements, values, lazy) { | ||
let parentNode, tempNode; | ||
const template = document.createComment(""); // document.createElement('template'); | ||
const temps = []; | ||
if (!lazy) { | ||
elements = Array.from(elements); | ||
values = Array.from(values); | ||
} | ||
for (let element of elements) { | ||
parentNode = element.parentNode; | ||
tempNode = template.cloneNode(false); | ||
parentNode?.replaceChild(tempNode, element); | ||
temps.push([tempNode, parentNode]); | ||
} | ||
/* at this point we have replaced what we want to replace with temporary values */ | ||
const temps2 = temps.values(); | ||
let nextTemp; | ||
for (let value of values) { | ||
nextTemp = temps2.next(); | ||
if (!nextTemp.done) { | ||
[tempNode, parentNode] = nextTemp.value; | ||
parentNode?.replaceChild(value, tempNode); | ||
} | ||
else | ||
parentNode.appendChild(value); | ||
// this will allow us replace fewer nodes with more nodes if necessary. | ||
} | ||
while (!(nextTemp = temps2.next()).done) { | ||
[tempNode, parentNode] = nextTemp.value; | ||
parentNode.removeChild(tempNode); | ||
} // this will allow us to replace more nodes with fewer nodes if necessary: | ||
return [elements, values]; // we can, eg run cleanups or inits on either of these. | ||
} | ||
/** | ||
* Remove the elements from their parent nodes. | ||
* | ||
* This will materialize `elements` unless `lazy` | ||
* is supplied and its value is truthy. | ||
* | ||
* @example | ||
* import { update } from 'deleight/queryoperator'; | ||
* const main = document.querySelector('main'); | ||
* remoove(main.children); | ||
* | ||
* @param {Iterable<Node>} elements | ||
* @param { boolean } [lazy] | ||
*/ | ||
function remove(elements, lazy) { | ||
if (!lazy) | ||
elements = [...elements]; | ||
for (let element of elements) | ||
element.parentNode?.removeChild(element); | ||
return elements; // we can, eg run cleanups on these. | ||
} | ||
/** | ||
* notes: | ||
* 1. add tests for `undefinedIsEmpty` parameter in `set`. | ||
* 2. add tests for `update` using elements and values of different sizes. | ||
*/ | ||
export { insert, inserter, remove, set, update }; |
@@ -1,1 +0,242 @@ | ||
"use strict";const e=e=>{let t,s=0;for(let n=0,o=e.length;n<o;n++)t=e.charCodeAt(n),s=(s<<5)-s+t,s|=0;return s};class t{css;constructor(e){this.css=e}style(...e){let t;const s=[];for(let t of e)!(t instanceof ShadowRoot)&&t instanceof DocumentFragment?s.push(...Array.from(t.children)):s.push(t);for(let e of s){if(e instanceof Document||e instanceof ShadowRoot)t=e;else{const s=Array.from(e.childNodes);t=e.shadowRoot||e.attachShadow({mode:"open"}),e.innerHTML="",t.append(...s)}t.adoptedStyleSheets?.includes(this.css)||(t.adoptedStyleSheets=[...t.adoptedStyleSheets||[],this.css])}}remove(...e){let t;const s=[];for(let t of e)!(t instanceof ShadowRoot)&&t instanceof DocumentFragment?s.push(...Array.from(t.children)):s.push(t);for(let e of s)t=e.shadowRoot||e,(t instanceof ShadowRoot||t instanceof Document)&&t.adoptedStyleSheets.includes(this.css)&&t.adoptedStyleSheets.splice(t.adoptedStyleSheets.indexOf(this.css))}}exports.Sophistry=class{styles={};process(s,n){const o=[],l=[];if(s instanceof HTMLLinkElement&&"stylesheet"===s.getAttribute("rel")||s instanceof HTMLStyleElement){const h=s.getAttribute("s-ophistry")||s.getAttribute("href")?.split(".")[0]||e(s.outerHTML);if(this.styles.hasOwnProperty(h)&&!n)o.push(this.styles[h]);else{let e,n;this.styles.hasOwnProperty(h)?(n=this.styles[h],e=n.css):(s instanceof HTMLLinkElement&&"stylesheet"===s.getAttribute("rel")?(e=new CSSStyleSheet,l.push(fetch(s.getAttribute("href")).then((e=>e.text())).then((t=>e.replaceSync(t))))):s instanceof HTMLStyleElement&&(e=new CSSStyleSheet,e.replaceSync(s.textContent)),n=new t(e),this.styles[h]=n),o.push(n)}s.parentNode?.removeChild(s)}else{let e,t,h,r=s.children[0];for(;r;)e=r.nextElementSibling,[t,h]=this.process(r,n),o.push(...t),l.push(...h),r=e}return[o,l]}import(e,s){const n=new CSSStyleSheet,o=new t(n);this.styles[s||e.split(".")[0]]=o;return[o,fetch(e).then((e=>e.text())).then((e=>n.replaceSync(e)))]}set(e,s){if(this.styles.hasOwnProperty(e))this.styles[e].css.replaceSync(s);else{const n=new CSSStyleSheet;n.replaceSync(s),this.styles[e]=new t(n)}}},exports.StyleSheet=t; | ||
'use strict'; | ||
/** | ||
* This module supports CSS loading, caching and 'localised' reuse. | ||
* The article at https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#applying_styles_inside_the_shadow_dom | ||
* stated that programmatically creating stylesheets facilitates | ||
* selective reuse (as we would like when working with web components). Considering that CSS is | ||
* traditionally written declaratively, it is worthwile to try to reduce the | ||
* programming involved to use CSS with web components. | ||
* | ||
* The main export here is {@link Sophistry}. | ||
* | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* An instance of Sophistry can be used to obtain and cache CSS Stylesheets | ||
* which can be shared by multiple DOM elements. It can typically be very useful within | ||
* web components. | ||
* | ||
* | ||
*/ | ||
class Sophistry { | ||
/** | ||
* An cache for created SophistryStyleSheets. | ||
*/ | ||
styles = {}; | ||
/** | ||
* Processes and 'pops' all style tags within the root. | ||
* Ensures that the same CSSStyleSheet can be reused across document trees (maindocument | ||
* and shadow roots) instead of duplicated even when they have been | ||
* created declaratively. | ||
* | ||
* If `replace` is truthy, any cached stylesheets with the same name (or hash) as a | ||
* styleshhet within the root will be replaced (reactively). | ||
* | ||
* | ||
* @example | ||
* import { Sophistry } from 'deleight/sophistry'; | ||
* import { createFragment } from 'deleight/apriori'; | ||
* const mySophistry = new Sophistry(); | ||
* const element = createFragment(apriori.get('markup.html')); | ||
* const [styles, promises] = mySophistry.process(element); | ||
* document.body.append(element); | ||
* for (let style of styles) style.style(element, document.body.firstElementChild); | ||
* | ||
* @param {Element} root | ||
* @param {boolean} [replace] | ||
* @returns {[StyleSheet[], Promise<any>[]]} | ||
*/ | ||
process(root, replace) { | ||
const styleSheets = []; | ||
const promises = []; | ||
if ((root instanceof HTMLLinkElement && | ||
root.getAttribute("rel") === "stylesheet") || | ||
root instanceof HTMLStyleElement) { | ||
const name = root.getAttribute("s-ophistry") || | ||
root.getAttribute("href")?.split('.')[0] || | ||
hash(root.outerHTML); | ||
if (this.styles.hasOwnProperty(name) && !replace) | ||
styleSheets.push(this.styles[name]); | ||
else { | ||
let st, st2; | ||
if (this.styles.hasOwnProperty(name)) { | ||
st2 = this.styles[name]; | ||
st = st2.css; | ||
} | ||
else { | ||
if (root instanceof HTMLLinkElement && | ||
root.getAttribute("rel") === "stylesheet") { | ||
st = new CSSStyleSheet(); | ||
promises.push(fetch(root.getAttribute("href")) | ||
.then((r) => r.text()) | ||
.then((t) => st.replaceSync(t))); | ||
} | ||
else if (root instanceof HTMLStyleElement) { | ||
st = new CSSStyleSheet(); // root.sheet will not work if style has not been added to DOM!!! | ||
st.replaceSync(root.textContent); | ||
} | ||
st2 = new StyleSheet(st); | ||
this.styles[name] = st2; | ||
} | ||
styleSheets.push(st2); | ||
} | ||
root.parentNode?.removeChild(root); | ||
} | ||
else { | ||
let node = root.children[0], node2; | ||
let nodeStyleSheets, nodePromises; | ||
while (node) { | ||
node2 = node.nextElementSibling; | ||
[nodeStyleSheets, nodePromises] = this.process(node, replace); | ||
styleSheets.push(...nodeStyleSheets); | ||
promises.push(...nodePromises); | ||
node = node2; | ||
} | ||
} | ||
return [styleSheets, promises]; | ||
} | ||
/** | ||
* Import a stylesheet defined in an external CSS file. Optionally | ||
* specify a name for the imported style in the Sophystry cach ({@link Sophistry#styles}). | ||
* The name will default to the portion of the link before the first | ||
* apostrophe... | ||
* | ||
* @example | ||
* import { Sophistry } from 'deleight/sophistry'; | ||
* const mySophistry = new Sophistry(); | ||
* const [style, onImport] = mySophistry.import('style.css'); | ||
* | ||
* @param {string} link | ||
* @param {string} [name] | ||
* @returns {[StyleSheet, Promise<any>]} | ||
*/ | ||
import(link, name) { | ||
const st = new CSSStyleSheet(); | ||
const st2 = new StyleSheet(st); | ||
this.styles[name || link.split(".")[0]] = st2; | ||
const promise = fetch(link) | ||
.then((r) => r.text()) | ||
.then((t) => st.replaceSync(t)); | ||
return [st2, promise]; | ||
} | ||
/** | ||
* Replaces the text of an existing stylesheet in the cach. This is reactive. | ||
* | ||
* @example | ||
* import { Sophistry } from 'deleight/sophistry'; | ||
* const mySophistry = new Sophistry(); | ||
* mySophistry.set('style.css', await apriori.get('new-style.css')); // override everything. | ||
* | ||
* @param {string} name | ||
* @param {string} css | ||
* @returns | ||
*/ | ||
set(name, css) { | ||
if (this.styles.hasOwnProperty(name)) | ||
this.styles[name].css.replaceSync(css); | ||
else { | ||
const st = new CSSStyleSheet(); | ||
st.replaceSync(css); | ||
this.styles[name] = new StyleSheet(st); | ||
} | ||
} | ||
} | ||
const hash = (str) => { | ||
let newHash = 0, chr; | ||
for (let i = 0, len = str.length; i < len; i++) { | ||
chr = str.charCodeAt(i); | ||
newHash = (newHash << 5) - newHash + chr; | ||
newHash |= 0; // convert to 32 bit int. | ||
} | ||
return newHash; | ||
}; | ||
/** | ||
* This is used to wrap a CSSStyleSheet to provide convenient methods | ||
* for styling and 'unstyling' elements. | ||
* | ||
* @example | ||
* import { StyleSheet } from 'deleight/sophistry'; | ||
* const sss = new StyleSheet(css); | ||
* | ||
*/ | ||
class StyleSheet { | ||
/** | ||
* The wrapped CSS stylesheet. | ||
*/ | ||
css; | ||
/** | ||
* Creates a new Sophistry stylesheet. | ||
* | ||
* @param {CSSStyleSheet} cssStyleSheet | ||
* @constructor | ||
*/ | ||
constructor(cssStyleSheet) { | ||
this.css = cssStyleSheet; | ||
} | ||
/** | ||
* Styles the elements with the wrapped CSSStylesheets. | ||
* If an element is not the document or a shadow root, an open shadow | ||
* root is created for it and then the rrot is styled. | ||
* | ||
* @example | ||
* sss.style(...Array.from(document.body.children)) | ||
* | ||
* @param {...T} elements | ||
*/ | ||
style(...elements) { | ||
let root; | ||
const allElements = []; | ||
for (let element of elements) { | ||
if (!(element instanceof ShadowRoot) && element instanceof DocumentFragment) | ||
allElements.push(...Array.from(element.children)); | ||
else | ||
allElements.push(element); | ||
} | ||
for (let element of allElements) { | ||
if (!(element instanceof Document) && !(element instanceof ShadowRoot)) { | ||
const childNodes = Array.from(element.childNodes); | ||
root = element.shadowRoot || element.attachShadow({ mode: "open" }); | ||
element.innerHTML = ""; | ||
root.append(...childNodes); | ||
} | ||
else | ||
root = element; | ||
if (!root.adoptedStyleSheets?.includes(this.css)) | ||
root.adoptedStyleSheets = [ | ||
...(root.adoptedStyleSheets || []), | ||
this.css, | ||
]; | ||
} | ||
} | ||
/** | ||
* Removes the wrapped stylesheet from the elements (or their shadow roots). | ||
* | ||
* @example | ||
* sss.remove(...Array.from(document.body.children)) | ||
* | ||
* | ||
* @param {...T} elements | ||
*/ | ||
remove(...elements) { | ||
let root; | ||
const allElements = []; | ||
for (let element of elements) { | ||
if (!(element instanceof ShadowRoot) && element instanceof DocumentFragment) | ||
allElements.push(...Array.from(element.children)); | ||
else | ||
allElements.push(element); | ||
} | ||
for (let element of allElements) { | ||
root = element.shadowRoot || element; | ||
if (root instanceof ShadowRoot || root instanceof Document) { | ||
if (root.adoptedStyleSheets.includes(this.css)) | ||
root.adoptedStyleSheets.splice(root.adoptedStyleSheets.indexOf(this.css)); | ||
} | ||
} | ||
} | ||
} | ||
exports.Sophistry = Sophistry; | ||
exports.StyleSheet = StyleSheet; |
@@ -1,1 +0,239 @@ | ||
class e{styles={};process(e,n){const o=[],l=[];if(e instanceof HTMLLinkElement&&"stylesheet"===e.getAttribute("rel")||e instanceof HTMLStyleElement){const h=e.getAttribute("s-ophistry")||e.getAttribute("href")?.split(".")[0]||t(e.outerHTML);if(this.styles.hasOwnProperty(h)&&!n)o.push(this.styles[h]);else{let t,n;this.styles.hasOwnProperty(h)?(n=this.styles[h],t=n.css):(e instanceof HTMLLinkElement&&"stylesheet"===e.getAttribute("rel")?(t=new CSSStyleSheet,l.push(fetch(e.getAttribute("href")).then((e=>e.text())).then((e=>t.replaceSync(e))))):e instanceof HTMLStyleElement&&(t=new CSSStyleSheet,t.replaceSync(e.textContent)),n=new s(t),this.styles[h]=n),o.push(n)}e.parentNode?.removeChild(e)}else{let t,s,h,c=e.children[0];for(;c;)t=c.nextElementSibling,[s,h]=this.process(c,n),o.push(...s),l.push(...h),c=t}return[o,l]}import(e,t){const n=new CSSStyleSheet,o=new s(n);this.styles[t||e.split(".")[0]]=o;return[o,fetch(e).then((e=>e.text())).then((e=>n.replaceSync(e)))]}set(e,t){if(this.styles.hasOwnProperty(e))this.styles[e].css.replaceSync(t);else{const n=new CSSStyleSheet;n.replaceSync(t),this.styles[e]=new s(n)}}}const t=e=>{let t,s=0;for(let n=0,o=e.length;n<o;n++)t=e.charCodeAt(n),s=(s<<5)-s+t,s|=0;return s};class s{css;constructor(e){this.css=e}style(...e){let t;const s=[];for(let t of e)!(t instanceof ShadowRoot)&&t instanceof DocumentFragment?s.push(...Array.from(t.children)):s.push(t);for(let e of s){if(e instanceof Document||e instanceof ShadowRoot)t=e;else{const s=Array.from(e.childNodes);t=e.shadowRoot||e.attachShadow({mode:"open"}),e.innerHTML="",t.append(...s)}t.adoptedStyleSheets?.includes(this.css)||(t.adoptedStyleSheets=[...t.adoptedStyleSheets||[],this.css])}}remove(...e){let t;const s=[];for(let t of e)!(t instanceof ShadowRoot)&&t instanceof DocumentFragment?s.push(...Array.from(t.children)):s.push(t);for(let e of s)t=e.shadowRoot||e,(t instanceof ShadowRoot||t instanceof Document)&&t.adoptedStyleSheets.includes(this.css)&&t.adoptedStyleSheets.splice(t.adoptedStyleSheets.indexOf(this.css))}}export{e as Sophistry,s as StyleSheet}; | ||
/** | ||
* This module supports CSS loading, caching and 'localised' reuse. | ||
* The article at https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM#applying_styles_inside_the_shadow_dom | ||
* stated that programmatically creating stylesheets facilitates | ||
* selective reuse (as we would like when working with web components). Considering that CSS is | ||
* traditionally written declaratively, it is worthwile to try to reduce the | ||
* programming involved to use CSS with web components. | ||
* | ||
* The main export here is {@link Sophistry}. | ||
* | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* An instance of Sophistry can be used to obtain and cache CSS Stylesheets | ||
* which can be shared by multiple DOM elements. It can typically be very useful within | ||
* web components. | ||
* | ||
* | ||
*/ | ||
class Sophistry { | ||
/** | ||
* An cache for created SophistryStyleSheets. | ||
*/ | ||
styles = {}; | ||
/** | ||
* Processes and 'pops' all style tags within the root. | ||
* Ensures that the same CSSStyleSheet can be reused across document trees (maindocument | ||
* and shadow roots) instead of duplicated even when they have been | ||
* created declaratively. | ||
* | ||
* If `replace` is truthy, any cached stylesheets with the same name (or hash) as a | ||
* styleshhet within the root will be replaced (reactively). | ||
* | ||
* | ||
* @example | ||
* import { Sophistry } from 'deleight/sophistry'; | ||
* import { createFragment } from 'deleight/apriori'; | ||
* const mySophistry = new Sophistry(); | ||
* const element = createFragment(apriori.get('markup.html')); | ||
* const [styles, promises] = mySophistry.process(element); | ||
* document.body.append(element); | ||
* for (let style of styles) style.style(element, document.body.firstElementChild); | ||
* | ||
* @param {Element} root | ||
* @param {boolean} [replace] | ||
* @returns {[StyleSheet[], Promise<any>[]]} | ||
*/ | ||
process(root, replace) { | ||
const styleSheets = []; | ||
const promises = []; | ||
if ((root instanceof HTMLLinkElement && | ||
root.getAttribute("rel") === "stylesheet") || | ||
root instanceof HTMLStyleElement) { | ||
const name = root.getAttribute("s-ophistry") || | ||
root.getAttribute("href")?.split('.')[0] || | ||
hash(root.outerHTML); | ||
if (this.styles.hasOwnProperty(name) && !replace) | ||
styleSheets.push(this.styles[name]); | ||
else { | ||
let st, st2; | ||
if (this.styles.hasOwnProperty(name)) { | ||
st2 = this.styles[name]; | ||
st = st2.css; | ||
} | ||
else { | ||
if (root instanceof HTMLLinkElement && | ||
root.getAttribute("rel") === "stylesheet") { | ||
st = new CSSStyleSheet(); | ||
promises.push(fetch(root.getAttribute("href")) | ||
.then((r) => r.text()) | ||
.then((t) => st.replaceSync(t))); | ||
} | ||
else if (root instanceof HTMLStyleElement) { | ||
st = new CSSStyleSheet(); // root.sheet will not work if style has not been added to DOM!!! | ||
st.replaceSync(root.textContent); | ||
} | ||
st2 = new StyleSheet(st); | ||
this.styles[name] = st2; | ||
} | ||
styleSheets.push(st2); | ||
} | ||
root.parentNode?.removeChild(root); | ||
} | ||
else { | ||
let node = root.children[0], node2; | ||
let nodeStyleSheets, nodePromises; | ||
while (node) { | ||
node2 = node.nextElementSibling; | ||
[nodeStyleSheets, nodePromises] = this.process(node, replace); | ||
styleSheets.push(...nodeStyleSheets); | ||
promises.push(...nodePromises); | ||
node = node2; | ||
} | ||
} | ||
return [styleSheets, promises]; | ||
} | ||
/** | ||
* Import a stylesheet defined in an external CSS file. Optionally | ||
* specify a name for the imported style in the Sophystry cach ({@link Sophistry#styles}). | ||
* The name will default to the portion of the link before the first | ||
* apostrophe... | ||
* | ||
* @example | ||
* import { Sophistry } from 'deleight/sophistry'; | ||
* const mySophistry = new Sophistry(); | ||
* const [style, onImport] = mySophistry.import('style.css'); | ||
* | ||
* @param {string} link | ||
* @param {string} [name] | ||
* @returns {[StyleSheet, Promise<any>]} | ||
*/ | ||
import(link, name) { | ||
const st = new CSSStyleSheet(); | ||
const st2 = new StyleSheet(st); | ||
this.styles[name || link.split(".")[0]] = st2; | ||
const promise = fetch(link) | ||
.then((r) => r.text()) | ||
.then((t) => st.replaceSync(t)); | ||
return [st2, promise]; | ||
} | ||
/** | ||
* Replaces the text of an existing stylesheet in the cach. This is reactive. | ||
* | ||
* @example | ||
* import { Sophistry } from 'deleight/sophistry'; | ||
* const mySophistry = new Sophistry(); | ||
* mySophistry.set('style.css', await apriori.get('new-style.css')); // override everything. | ||
* | ||
* @param {string} name | ||
* @param {string} css | ||
* @returns | ||
*/ | ||
set(name, css) { | ||
if (this.styles.hasOwnProperty(name)) | ||
this.styles[name].css.replaceSync(css); | ||
else { | ||
const st = new CSSStyleSheet(); | ||
st.replaceSync(css); | ||
this.styles[name] = new StyleSheet(st); | ||
} | ||
} | ||
} | ||
const hash = (str) => { | ||
let newHash = 0, chr; | ||
for (let i = 0, len = str.length; i < len; i++) { | ||
chr = str.charCodeAt(i); | ||
newHash = (newHash << 5) - newHash + chr; | ||
newHash |= 0; // convert to 32 bit int. | ||
} | ||
return newHash; | ||
}; | ||
/** | ||
* This is used to wrap a CSSStyleSheet to provide convenient methods | ||
* for styling and 'unstyling' elements. | ||
* | ||
* @example | ||
* import { StyleSheet } from 'deleight/sophistry'; | ||
* const sss = new StyleSheet(css); | ||
* | ||
*/ | ||
class StyleSheet { | ||
/** | ||
* The wrapped CSS stylesheet. | ||
*/ | ||
css; | ||
/** | ||
* Creates a new Sophistry stylesheet. | ||
* | ||
* @param {CSSStyleSheet} cssStyleSheet | ||
* @constructor | ||
*/ | ||
constructor(cssStyleSheet) { | ||
this.css = cssStyleSheet; | ||
} | ||
/** | ||
* Styles the elements with the wrapped CSSStylesheets. | ||
* If an element is not the document or a shadow root, an open shadow | ||
* root is created for it and then the rrot is styled. | ||
* | ||
* @example | ||
* sss.style(...Array.from(document.body.children)) | ||
* | ||
* @param {...T} elements | ||
*/ | ||
style(...elements) { | ||
let root; | ||
const allElements = []; | ||
for (let element of elements) { | ||
if (!(element instanceof ShadowRoot) && element instanceof DocumentFragment) | ||
allElements.push(...Array.from(element.children)); | ||
else | ||
allElements.push(element); | ||
} | ||
for (let element of allElements) { | ||
if (!(element instanceof Document) && !(element instanceof ShadowRoot)) { | ||
const childNodes = Array.from(element.childNodes); | ||
root = element.shadowRoot || element.attachShadow({ mode: "open" }); | ||
element.innerHTML = ""; | ||
root.append(...childNodes); | ||
} | ||
else | ||
root = element; | ||
if (!root.adoptedStyleSheets?.includes(this.css)) | ||
root.adoptedStyleSheets = [ | ||
...(root.adoptedStyleSheets || []), | ||
this.css, | ||
]; | ||
} | ||
} | ||
/** | ||
* Removes the wrapped stylesheet from the elements (or their shadow roots). | ||
* | ||
* @example | ||
* sss.remove(...Array.from(document.body.children)) | ||
* | ||
* | ||
* @param {...T} elements | ||
*/ | ||
remove(...elements) { | ||
let root; | ||
const allElements = []; | ||
for (let element of elements) { | ||
if (!(element instanceof ShadowRoot) && element instanceof DocumentFragment) | ||
allElements.push(...Array.from(element.children)); | ||
else | ||
allElements.push(element); | ||
} | ||
for (let element of allElements) { | ||
root = element.shadowRoot || element; | ||
if (root instanceof ShadowRoot || root instanceof Document) { | ||
if (root.adoptedStyleSheets.includes(this.css)) | ||
root.adoptedStyleSheets.splice(root.adoptedStyleSheets.indexOf(this.css)); | ||
} | ||
} | ||
} | ||
} | ||
export { Sophistry, StyleSheet }; |
@@ -1,1 +0,100 @@ | ||
"use strict";const t=Symbol(),o=Symbol(),r=Symbol();function e(t){const o=Object.assign(((...o)=>{if(!o.length)return t;for(let t of o)t instanceof Function&&t(r);return r}),{obj:t}),r=new Proxy(o,n);return o.proxy=r,r}const n={get(n,s){if(s===r)return(...t)=>(Object.assign(n.obj,...t),n.proxy);if(s===o)return t=>{for(let[o,r]of Object.entries(t)){if(!n.obj.hasOwnProperty(o))throw new Error(`You cannot assign a new property (${o}) with this method.`);n.obj[o]=r}return n.proxy};if(s===t)return t=>{for(let[o,r]of Object.entries(t))r(e(n.obj[o]));return n.proxy};{const t=n.obj[s];return t instanceof Function?(...t)=>(n.obj[s](...t),n.proxy):t}}};exports.ASSIGN=r,exports.SET=o,exports.WITH=t,exports.With=e; | ||
'use strict'; | ||
/** | ||
* This module exports {@link With} function for creating more concise and structured code. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Used to obtain a context (Recursive object) around a property of | ||
* the currently wrapped object. This is to continue the chain inwards | ||
* to increase the concision even further. | ||
* | ||
* @example | ||
*With(obj)[WITH]({o1: o1 => {assert.equal(o1.c, 1);}, o2: o2 => {assert.equal(o2.c, 2);}}) | ||
*/ | ||
const WITH = Symbol(); | ||
/** | ||
* Used to set existing properties on the wrapped object and return the same object. | ||
* | ||
* @example | ||
* With(obj).set({knownA:1, knownB:2}).method1().method2('...')() // final call unwraps the object. | ||
*/ | ||
const SET = Symbol(); | ||
/** | ||
* Used to set any properties on the wrapped object and return the same object. | ||
* | ||
* @example | ||
* With(obj)[SET]({prop3: 5, prop4: 6}).inc().prop2 | ||
*/ | ||
const ASSIGN = Symbol(); | ||
/** | ||
* Behaves like the 'with' construct in many langusges. All method calls | ||
* with return the same object and we can also access the special [assign] | ||
* method to set properties without breaking the chain. Used for more | ||
* concise syntax in some scenarios. | ||
* | ||
* @example | ||
* import { With, ASSIGN } from 'deleight/withly'; | ||
* const el = With(document.createElement('div')).append().append()[ASSIGN]().append()().append(); | ||
* | ||
* @param obj | ||
* @returns | ||
*/ | ||
function With(obj) { | ||
const target = Object.assign((...args) => { | ||
if (!args.length) | ||
return obj; | ||
for (let arg of args) | ||
if (arg instanceof Function) | ||
arg(proxy); | ||
return proxy; | ||
}, { obj }); | ||
const proxy = new Proxy(target, trap); | ||
target.proxy = proxy; | ||
return proxy; | ||
} | ||
const trap = { | ||
get(target, p) { | ||
if (p === ASSIGN) { | ||
return (...objs) => { | ||
Object.assign(target.obj, ...objs); | ||
return target.proxy; | ||
}; | ||
} | ||
else if (p === SET) { | ||
return (arg) => { | ||
for (let [k, v] of Object.entries(arg)) { | ||
if (target.obj.hasOwnProperty(k)) | ||
target.obj[k] = v; | ||
else | ||
throw new Error(`You cannot assign a new property (${k}) with this method.`); | ||
} | ||
return target.proxy; | ||
}; | ||
} | ||
else if (p === WITH) { | ||
return (arg) => { | ||
for (let [k, v] of Object.entries(arg)) | ||
v(With(target.obj[k])); | ||
return target.proxy; | ||
}; | ||
} | ||
else { | ||
const res = target.obj[p]; | ||
if (!(res instanceof Function)) | ||
return res; | ||
return (...args) => { | ||
target.obj[p](...args); | ||
return target.proxy; | ||
}; | ||
} | ||
}, | ||
}; | ||
// const el1 = With(document.createElement('div')).append('').append()[SET]({className: 'cls1', textContent: 'Wow!'}).append('abc').append()(); | ||
// const el2 = With(document.createElement('div')).append().append()[WITH]({style: st => st[SET]({})}); | ||
exports.ASSIGN = ASSIGN; | ||
exports.SET = SET; | ||
exports.WITH = WITH; | ||
exports.With = With; |
@@ -1,1 +0,95 @@ | ||
const o=Symbol(),r=Symbol(),t=Symbol();function n(o){const r=Object.assign(((...r)=>{if(!r.length)return o;for(let o of r)o instanceof Function&&o(t);return t}),{obj:o}),t=new Proxy(r,e);return r.proxy=t,t}const e={get(e,i){if(i===t)return(...o)=>(Object.assign(e.obj,...o),e.proxy);if(i===r)return o=>{for(let[r,t]of Object.entries(o)){if(!e.obj.hasOwnProperty(r))throw new Error(`You cannot assign a new property (${r}) with this method.`);e.obj[r]=t}return e.proxy};if(i===o)return o=>{for(let[r,t]of Object.entries(o))t(n(e.obj[r]));return e.proxy};{const o=e.obj[i];return o instanceof Function?(...o)=>(e.obj[i](...o),e.proxy):o}}};export{t as ASSIGN,r as SET,o as WITH,n as With}; | ||
/** | ||
* This module exports {@link With} function for creating more concise and structured code. | ||
* | ||
* @module | ||
*/ | ||
/** | ||
* Used to obtain a context (Recursive object) around a property of | ||
* the currently wrapped object. This is to continue the chain inwards | ||
* to increase the concision even further. | ||
* | ||
* @example | ||
*With(obj)[WITH]({o1: o1 => {assert.equal(o1.c, 1);}, o2: o2 => {assert.equal(o2.c, 2);}}) | ||
*/ | ||
const WITH = Symbol(); | ||
/** | ||
* Used to set existing properties on the wrapped object and return the same object. | ||
* | ||
* @example | ||
* With(obj).set({knownA:1, knownB:2}).method1().method2('...')() // final call unwraps the object. | ||
*/ | ||
const SET = Symbol(); | ||
/** | ||
* Used to set any properties on the wrapped object and return the same object. | ||
* | ||
* @example | ||
* With(obj)[SET]({prop3: 5, prop4: 6}).inc().prop2 | ||
*/ | ||
const ASSIGN = Symbol(); | ||
/** | ||
* Behaves like the 'with' construct in many langusges. All method calls | ||
* with return the same object and we can also access the special [assign] | ||
* method to set properties without breaking the chain. Used for more | ||
* concise syntax in some scenarios. | ||
* | ||
* @example | ||
* import { With, ASSIGN } from 'deleight/withly'; | ||
* const el = With(document.createElement('div')).append().append()[ASSIGN]().append()().append(); | ||
* | ||
* @param obj | ||
* @returns | ||
*/ | ||
function With(obj) { | ||
const target = Object.assign((...args) => { | ||
if (!args.length) | ||
return obj; | ||
for (let arg of args) | ||
if (arg instanceof Function) | ||
arg(proxy); | ||
return proxy; | ||
}, { obj }); | ||
const proxy = new Proxy(target, trap); | ||
target.proxy = proxy; | ||
return proxy; | ||
} | ||
const trap = { | ||
get(target, p) { | ||
if (p === ASSIGN) { | ||
return (...objs) => { | ||
Object.assign(target.obj, ...objs); | ||
return target.proxy; | ||
}; | ||
} | ||
else if (p === SET) { | ||
return (arg) => { | ||
for (let [k, v] of Object.entries(arg)) { | ||
if (target.obj.hasOwnProperty(k)) | ||
target.obj[k] = v; | ||
else | ||
throw new Error(`You cannot assign a new property (${k}) with this method.`); | ||
} | ||
return target.proxy; | ||
}; | ||
} | ||
else if (p === WITH) { | ||
return (arg) => { | ||
for (let [k, v] of Object.entries(arg)) | ||
v(With(target.obj[k])); | ||
return target.proxy; | ||
}; | ||
} | ||
else { | ||
const res = target.obj[p]; | ||
if (!(res instanceof Function)) | ||
return res; | ||
return (...args) => { | ||
target.obj[p](...args); | ||
return target.proxy; | ||
}; | ||
} | ||
}, | ||
}; | ||
// const el1 = With(document.createElement('div')).append('').append()[SET]({className: 'cls1', textContent: 'Wow!'}).append('abc').append()(); | ||
// const el2 = With(document.createElement('div')).append().append()[WITH]({style: st => st[SET]({})}); | ||
export { ASSIGN, SET, WITH, With }; |
{ | ||
"name": "deleight", | ||
"version": "4.1.1", | ||
"version": "4.1.2", | ||
"description": "A group of 10 libraries for writing accessible and joyfully interactive web applications with traditional HTML, CSS and JavaScript.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
297964
7791