From e6581c7eb523b9a90ce5ba4d1ec1cc5672b92f3e Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Wed, 1 Nov 2023 19:00:53 +0100 Subject: [PATCH] chore: code tidy --- README.md | 25 +- source/constraints/abstract.mjs | 46 +- source/constraints/abstractoperator.mjs | 49 +- source/constraints/andoperator.mjs | 37 +- source/constraints/invalid.mjs | 34 +- source/constraints/isarray.mjs | 40 +- source/constraints/isobject.mjs | 40 +- source/constraints/oroperator.mjs | 90 +- source/constraints/valid.mjs | 34 +- source/data/buildmap.mjs | 212 ++- source/data/buildtree.mjs | 116 +- source/data/datasource.mjs | 347 ++-- source/data/datasource/dom.mjs | 164 +- source/data/datasource/server.mjs | 119 +- source/data/datasource/server/restapi.mjs | 359 ++-- .../server/restapi/data-fetch-error.mjs | 52 +- .../datasource/server/restapi/writeerror.mjs | 66 +- source/data/datasource/server/webconnect.mjs | 310 +-- source/data/datasource/storage.mjs | 148 +- .../data/datasource/storage/localstorage.mjs | 50 +- .../datasource/storage/sessionstorage.mjs | 54 +- source/data/diff.mjs | 195 +- source/data/extend.mjs | 84 +- source/data/pathfinder.mjs | 589 +++--- source/data/pipe.mjs | 72 +- source/data/transformer.mjs | 1468 +++++++-------- source/dom/assembler.mjs | 88 +- source/dom/attributes.mjs | 264 +-- source/dom/constants.mjs | 120 +- source/dom/customcontrol.mjs | 598 +++--- source/dom/customelement.mjs | 1658 +++++++++-------- source/dom/dimension.mjs | 102 +- source/dom/events.mjs | 143 +- source/dom/focusmanager.mjs | 350 ++-- source/dom/locale.mjs | 56 +- source/dom/ready.mjs | 26 +- source/dom/resource.mjs | 336 ++-- source/dom/resource/data.mjs | 232 +-- source/dom/resource/link.mjs | 190 +- source/dom/resource/link/stylesheet.mjs | 32 +- source/dom/resource/script.mjs | 146 +- source/dom/resourcemanager.mjs | 310 +-- source/dom/slotted.mjs | 160 +- source/dom/template.mjs | 238 +-- source/dom/theme.mjs | 70 +- source/dom/updater.mjs | 1325 ++++++------- source/dom/util.mjs | 175 +- source/dom/util/extract-keys.mjs | 56 +- .../dom/util/init-options-from-attributes.mjs | 84 +- source/dom/util/set-option-from-attribute.mjs | 64 +- source/dom/worker/factory.mjs | 166 +- source/i18n/formatter.mjs | 149 +- source/i18n/locale.mjs | 290 +-- source/i18n/provider.mjs | 134 +- source/i18n/providers/embed.mjs | 263 +-- source/i18n/providers/fetch.mjs | 160 +- source/i18n/translations.mjs | 400 ++-- source/logging/handler.mjs | 280 +-- source/logging/handler/console.mjs | 72 +- source/logging/logentry.mjs | 50 +- source/logging/logger.mjs | 352 ++-- source/math/random.mjs | 120 +- source/monster.mjs | 205 +- source/net/webconnect.mjs | 545 +++--- source/net/webconnect/message.mjs | 62 +- source/text/bracketed-key-value-hash.mjs | 361 ++-- source/text/formatter.mjs | 449 ++--- .../generate-range-comparison-expression.mjs | 90 +- source/types/base.mjs | 130 +- source/types/basewithoptions.mjs | 92 +- source/types/binary.mjs | 40 +- source/types/dataurl.mjs | 186 +- source/types/global.mjs | 84 +- source/types/id.mjs | 48 +- source/types/internal.mjs | 230 +-- source/types/is.mjs | 96 +- source/types/mediatype.mjs | 238 +-- source/types/node.mjs | 310 +-- source/types/nodelist.mjs | 192 +- source/types/noderecursiveiterator.mjs | 100 +- source/types/observablequeue.mjs | 146 +- source/types/observer.mjs | 208 +-- source/types/observerlist.mjs | 132 +- source/types/proxyobserver.mjs | 420 ++--- source/types/queue.mjs | 126 +- source/types/randomid.mjs | 26 +- source/types/regex.mjs | 4 +- source/types/stack.mjs | 128 +- source/types/tokenlist.mjs | 411 ++-- source/types/typeof.mjs | 22 +- source/types/uniquequeue.mjs | 96 +- source/types/uuid.mjs | 64 +- source/types/validate.mjs | 134 +- source/types/version.mjs | 220 +-- source/util/clone.mjs | 194 +- source/util/comparator.mjs | 194 +- source/util/deadmansswitch.mjs | 84 +- source/util/freeze.mjs | 19 +- source/util/processing.mjs | 211 +-- source/util/runtime.mjs | 100 +- source/util/trimspaces.mjs | 48 +- 101 files changed, 10494 insertions(+), 10010 deletions(-) diff --git a/README.md b/README.md index 331c9772e..2be7f483c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@ # Monster -Monster is perfectly suited for the creation of beautiful and fast -user interfaces and websites. +Monster is an ideal choice for building visually appealing and high-performance web interfaces +and websites using modern JavaScript web development techniques. -Monster relies on proven concepts mixed with many new JavaScript concepts such as -classes, WeakRef, WeakMaps, proxies or the MutationObserver interface, just to name a few. +Leveraging cutting-edge JavaScript features such as classes, WeakRef, WeakMaps, proxies, +and the MutationObserver interface, Monster offers a blend of tried-and-true methods and +innovative web components. -Monster integrates easily into your existing websites without taking over everything. +Designed for seamless integration, Monster complements your existing web projects +without dominating the entire architecture. -It is not the goal of Monster to pull in an entirely new abstraction -with its own language, but to combine the existing techniques of HTML, -CSS and JavaScript in a meaningful way. +Unlike solutions that introduce a whole new layer of abstraction and proprietary +languages, Monster focuses on enhancing the native capabilities of HTML, CSS, and +JavaScript for web development. -One design target is to reach the shiny sun with as little JavaScript as possible. +With a design objective to optimize performance and achieve stellar outcomes, +Monster aims to accomplish this using minimal JavaScript code. Monster was built with ES6 modules and uses [import](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/import) and [export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export). @@ -73,7 +76,7 @@ We do try to work around some browser bugs, but on the whole we don't use polyfi However, many functions can be mapped via [polyfill.io](https://polyfill.io/) and thus the compatibility can be increased. ```html -<script id="polyfill" src="https://polyfill.io/v3/polyfill.min.js?features=Array.from,Array.isArray,Array.prototype.entries,Array.prototype.fill,Array.prototype.filter,Array.prototype.forEach,Array.prototype.includes,Array.prototype.indexOf,Array.prototype.keys,Array.prototype.lastIndexOf,Array.prototype.map,Array.prototype.reduce,Array.prototype.sort,ArrayBuffer,atob,Blob,CustomEvent,DataView,document,Document,DocumentFragment,Element,Event,fetch,globalThis,HTMLDocument,HTMLTemplateElement,Intl,JSON,Map,Math.log2,MutationObserver,Number.isInteger,Object.assign,Object.defineProperty,Object.entries,Object.freeze,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.keys,Promise,Reflect,Reflect.defineProperty,Reflect.get,Reflect.getOwnPropertyDescriptor,Reflect.setPrototypeOf,Set,String.prototype.endsWith,String.prototype.includes,String.prototype.matchAll,String.prototype.padStart,String.prototype.startsWith,String.prototype.trim,Symbol,Symbol.for,Symbol.hasInstance,Symbol.iterator,Uint16Array,Uint8Array,URL,WeakMap,WeakSet" +<script id="polyfill" src="https://Set" crossorigin="anonymous" referrerpolicy="no-referrer"></script> ``` @@ -99,4 +102,4 @@ You can also purchase a commercial license. ## Changelog Detailed changes for each release are documented in -the [CHANGELOG](https://gitlab.schukai.com/oss/libraries/javascript/monster/-/blob/master/application/CHANGELOG). +the [CHANGELOG](https://gitlab.schukai.com/oss/libraries/javascript/monster/-/blob/master/application/CHANGELOG.md). diff --git a/source/constraints/abstract.mjs b/source/constraints/abstract.mjs index 319cd227b..01cf37e7d 100644 --- a/source/constraints/abstract.mjs +++ b/source/constraints/abstract.mjs @@ -26,29 +26,29 @@ export { AbstractConstraint }; * @summary The abstract constraint */ class AbstractConstraint extends Base { - /** - * - */ - constructor() { - super(); - } + /** + * + */ + constructor() { + super(); + } - /** - * this method must return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - return Promise.reject(value); - } + /** + * this method must return a promise containing the result of the check. + * + * @param {*} value + * @returns {Promise} + */ + isValid(value) { + return Promise.reject(value); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraints/abstract-constraint"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraints/abstract-constraint"); + } } diff --git a/source/constraints/abstractoperator.mjs b/source/constraints/abstractoperator.mjs index a35762a47..8c2f9c3c6 100644 --- a/source/constraints/abstractoperator.mjs +++ b/source/constraints/abstractoperator.mjs @@ -24,29 +24,34 @@ export { AbstractOperator }; * @summary The abstract operator constraint */ class AbstractOperator extends AbstractConstraint { - /** - * - * @param {AbstractConstraint} operantA - * @param {AbstractConstraint} operantB - * @throws {TypeError} "parameters must be from type AbstractConstraint" - */ - constructor(operantA, operantB) { - super(); + /** + * + * @param {AbstractConstraint} operantA + * @param {AbstractConstraint} operantB + * @throws {TypeError} "parameters must be from type AbstractConstraint" + */ + constructor(operantA, operantB) { + super(); - if (!(operantA instanceof AbstractConstraint && operantB instanceof AbstractConstraint)) { - throw new TypeError("parameters must be from type AbstractConstraint"); - } + if ( + !( + operantA instanceof AbstractConstraint && + operantB instanceof AbstractConstraint + ) + ) { + throw new TypeError("parameters must be from type AbstractConstraint"); + } - this.operantA = operantA; - this.operantB = operantB; - } + this.operantA = operantA; + this.operantB = operantB; + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraints/abstract-operator"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraints/abstract-operator"); + } } diff --git a/source/constraints/andoperator.mjs b/source/constraints/andoperator.mjs index cf8b3ea0c..d2f03788e 100644 --- a/source/constraints/andoperator.mjs +++ b/source/constraints/andoperator.mjs @@ -24,22 +24,25 @@ export { AndOperator }; * @summary A and operator constraint */ class AndOperator extends AbstractOperator { - /** - * this method return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - return Promise.all([this.operantA.isValid(value), this.operantB.isValid(value)]); - } + /** + * this method return a promise containing the result of the check. + * + * @param {*} value + * @returns {Promise} + */ + isValid(value) { + return Promise.all([ + this.operantA.isValid(value), + this.operantB.isValid(value), + ]); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraints/and-operator"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraints/and-operator"); + } } diff --git a/source/constraints/invalid.mjs b/source/constraints/invalid.mjs index a79faf373..2ca75ab90 100644 --- a/source/constraints/invalid.mjs +++ b/source/constraints/invalid.mjs @@ -24,22 +24,22 @@ export { Invalid }; * @summary A constraint that always invalid */ class Invalid extends AbstractConstraint { - /** - * this method return a rejected promise - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - return Promise.reject(value); - } + /** + * this method return a rejected promise + * + * @param {*} value + * @returns {Promise} + */ + isValid(value) { + return Promise.reject(value); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraints/invalid"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraints/invalid"); + } } diff --git a/source/constraints/isarray.mjs b/source/constraints/isarray.mjs index 5ccd4aad2..791728c18 100644 --- a/source/constraints/isarray.mjs +++ b/source/constraints/isarray.mjs @@ -23,26 +23,26 @@ export { IsArray }; * @summary A constraint to check if a value is an array */ class IsArray extends AbstractConstraint { - /** - * this method return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - if (isArray(value)) { - return Promise.resolve(value); - } + /** + * this method return a promise containing the result of the check. + * + * @param {*} value + * @returns {Promise} + */ + isValid(value) { + if (isArray(value)) { + return Promise.resolve(value); + } - return Promise.reject(value); - } + return Promise.reject(value); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraint/is-array"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraint/is-array"); + } } diff --git a/source/constraints/isobject.mjs b/source/constraints/isobject.mjs index 7c829f776..158685e32 100644 --- a/source/constraints/isobject.mjs +++ b/source/constraints/isobject.mjs @@ -23,26 +23,26 @@ export { IsObject }; * @summary A constraint to check if a value is an object */ class IsObject extends AbstractConstraint { - /** - * this method return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - if (isObject(value)) { - return Promise.resolve(value); - } + /** + * this method return a promise containing the result of the check. + * + * @param {*} value + * @returns {Promise} + */ + isValid(value) { + if (isObject(value)) { + return Promise.resolve(value); + } - return Promise.reject(value); - } + return Promise.reject(value); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraint/is-object"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraint/is-object"); + } } diff --git a/source/constraints/oroperator.mjs b/source/constraints/oroperator.mjs index f1de51399..df931873f 100644 --- a/source/constraints/oroperator.mjs +++ b/source/constraints/oroperator.mjs @@ -24,53 +24,53 @@ export { OrOperator }; * @summary A or operator */ class OrOperator extends AbstractOperator { - /** - * this method return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - var self = this; + /** + * this method return a promise containing the result of the check. + * + * @param {*} value + * @returns {Promise} + */ + isValid(value) { + var self = this; - return new Promise(function (resolve, reject) { - let a; - let b; + return new Promise(function (resolve, reject) { + let a; + let b; - self.operantA - .isValid(value) - .then(function () { - resolve(); - }) - .catch(function () { - a = false; - /** b has already been evaluated and was not true */ - if (b === false) { - reject(); - } - }); + self.operantA + .isValid(value) + .then(function () { + resolve(); + }) + .catch(function () { + a = false; + /** b has already been evaluated and was not true */ + if (b === false) { + reject(); + } + }); - self.operantB - .isValid(value) - .then(function () { - resolve(); - }) - .catch(function () { - b = false; - /** b has already been evaluated and was not true */ - if (a === false) { - reject(); - } - }); - }); - } + self.operantB + .isValid(value) + .then(function () { + resolve(); + }) + .catch(function () { + b = false; + /** b has already been evaluated and was not true */ + if (a === false) { + reject(); + } + }); + }); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraints/or-operator"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraints/or-operator"); + } } diff --git a/source/constraints/valid.mjs b/source/constraints/valid.mjs index cec2c287a..7ca7a2bf8 100644 --- a/source/constraints/valid.mjs +++ b/source/constraints/valid.mjs @@ -24,22 +24,22 @@ export { Valid }; * @summary A constraint that always valid */ class Valid extends AbstractConstraint { - /** - * this method return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - return Promise.resolve(value); - } + /** + * this method return a promise containing the result of the check. + * + * @param {*} value + * @returns {Promise} + */ + isValid(value) { + return Promise.resolve(value); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/constraints/valid"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/constraints/valid"); + } } diff --git a/source/data/buildmap.mjs b/source/data/buildmap.mjs index a6c4b9821..e39b69c97 100644 --- a/source/data/buildmap.mjs +++ b/source/data/buildmap.mjs @@ -40,11 +40,11 @@ const PARENT = "^"; * @throws {TypeError} - If the selector callback does not return a map. **/ function buildMap(subject, selector, valueTemplate, keyTemplate, filter) { - return assembleParts(subject, selector, filter, function (v, k, m) { - k = build(v, keyTemplate, k); - v = build(v, valueTemplate); - this.set(k, v); - }); + return assembleParts(subject, selector, filter, function (v, k, m) { + k = build(v, keyTemplate, k); + v = build(v, valueTemplate); + this.set(k, v); + }); } /** @@ -110,34 +110,34 @@ function buildMap(subject, selector, valueTemplate, keyTemplate, filter) { * @memberOf Monster.Data */ function assembleParts(subject, selector, filter, callback) { - const result = new Map(); - - let map; - if (isFunction(selector)) { - map = selector(subject); - if (!(map instanceof Map)) { - throw new TypeError("the selector callback must return a map"); - } - } else if (isString(selector)) { - map = new Map(); - buildFlatMap.call(map, subject, selector); - } else { - throw new TypeError("selector is neither a string nor a function"); - } - - if (!(map instanceof Map)) { - return result; - } - - map.forEach((v, k, m) => { - if (isFunction(filter)) { - if (filter.call(m, v, k) !== true) return; - } - - callback.call(result, v, k, m); - }); - - return result; + const result = new Map(); + + let map; + if (isFunction(selector)) { + map = selector(subject); + if (!(map instanceof Map)) { + throw new TypeError("the selector callback must return a map"); + } + } else if (isString(selector)) { + map = new Map(); + buildFlatMap.call(map, subject, selector); + } else { + throw new TypeError("selector is neither a string nor a function"); + } + + if (!(map instanceof Map)) { + return result; + } + + map.forEach((v, k, m) => { + if (isFunction(filter)) { + if (filter.call(m, v, k) !== true) return; + } + + callback.call(result, v, k, m); + }); + + return result; } /** @@ -149,58 +149,64 @@ function assembleParts(subject, selector, filter, callback) { * @return {*} */ function buildFlatMap(subject, selector, key, parentMap) { - const result = this; - const currentMap = new Map(); - - const resultLength = result.size; - - if (key === undefined) key = []; - - let parts = selector.split(DELIMITER); - let current = ""; - let currentPath = []; - do { - current = parts.shift(); - currentPath.push(current); - - if (current === WILDCARD) { - let finder = new Pathfinder(subject); - let map; - - try { - map = finder.getVia(currentPath.join(DELIMITER)); - } catch (e) { - let a = e; - map = new Map(); - } - - for (const [k, o] of map) { - let copyKey = clone(key); - - currentPath.map((a) => { - copyKey.push(a === WILDCARD ? k : a); - }); - - let kk = copyKey.join(DELIMITER); - let sub = buildFlatMap.call(result, o, parts.join(DELIMITER), copyKey, o); - - if (isObject(sub) && parentMap !== undefined) { - sub[PARENT] = parentMap; - } - - currentMap.set(kk, sub); - } - } - } while (parts.length > 0); - - // no set in child run - if (resultLength === result.size) { - for (const [k, o] of currentMap) { - result.set(k, o); - } - } - - return subject; + const result = this; + const currentMap = new Map(); + + const resultLength = result.size; + + if (key === undefined) key = []; + + let parts = selector.split(DELIMITER); + let current = ""; + let currentPath = []; + do { + current = parts.shift(); + currentPath.push(current); + + if (current === WILDCARD) { + let finder = new Pathfinder(subject); + let map; + + try { + map = finder.getVia(currentPath.join(DELIMITER)); + } catch (e) { + let a = e; + map = new Map(); + } + + for (const [k, o] of map) { + let copyKey = clone(key); + + currentPath.map((a) => { + copyKey.push(a === WILDCARD ? k : a); + }); + + let kk = copyKey.join(DELIMITER); + let sub = buildFlatMap.call( + result, + o, + parts.join(DELIMITER), + copyKey, + o, + ); + + if (isObject(sub) && parentMap !== undefined) { + sub[PARENT] = parentMap; + } + + currentMap.set(kk, sub); + } + } + } while (parts.length > 0); + + // no set in child run + if (resultLength === result.size) { + for (const [k, o] of currentMap) { + result.set(k, o); + } + } + + return subject; } /** @@ -354,30 +360,30 @@ function buildFlatMap(subject, selector, key, parentMap) { * @return {*} */ function build(subject, definition, defaultValue) { - if (definition === undefined) return defaultValue ? defaultValue : subject; - validateString(definition); + if (definition === undefined) return defaultValue ? defaultValue : subject; + validateString(definition); - const regexp = /(?<placeholder>\${(?<path>[a-z\^A-Z.\-_0-9]*)})/gm; - const array = [...definition.matchAll(regexp)]; + const regexp = /(?<placeholder>\${(?<path>[a-z\^A-Z.\-_0-9]*)})/gm; + const array = [...definition.matchAll(regexp)]; - let finder = new Pathfinder(subject); + let finder = new Pathfinder(subject); - if (array.length === 0) { - return finder.getVia(definition); - } + if (array.length === 0) { + return finder.getVia(definition); + } - array.forEach((a) => { - let groups = a?.["groups"]; - let placeholder = groups?.["placeholder"]; - if (placeholder === undefined) return; + array.forEach((a) => { + let groups = a?.["groups"]; + let placeholder = groups?.["placeholder"]; + if (placeholder === undefined) return; - let path = groups?.["path"]; + let path = groups?.["path"]; - let v = finder.getVia(path); - if (v === undefined) v = defaultValue; + let v = finder.getVia(path); + if (v === undefined) v = defaultValue; - definition = definition.replaceAll(placeholder, v); - }); + definition = definition.replaceAll(placeholder, v); + }); - return definition; + return definition; } diff --git a/source/data/buildtree.mjs b/source/data/buildtree.mjs index 3a36a9c10..a477e50f1 100644 --- a/source/data/buildtree.mjs +++ b/source/data/buildtree.mjs @@ -132,61 +132,63 @@ const rootSymbol = Symbol("root"); * @since 1.26.0 */ function buildTree(subject, selector, idKey, parentIDKey, options) { - const nodes = new Map(); - - if (!isObject(options)) { - options = {}; - } - - options = extend( - {}, - { - rootReferences: [null, undefined], - filter: undefined, - }, - options, - ); - - const filter = options?.filter; - let rootReferences = options.rootReferences; - if (!isArray(rootReferences)) { - rootReferences = [rootReferences]; - } - - const childMap = assembleParts(subject, selector, filter, function (o, k, m) { - const key = o?.[idKey]; - let ref = o?.[parentIDKey]; - if (rootReferences.indexOf(ref) !== -1) ref = rootSymbol; - - if (key === undefined) { - throw new Error("the object has no value for the specified id"); - } - - o[parentSymbol] = ref; - - const node = new Node(o); - this.has(ref) ? this.get(ref).add(node) : this.set(ref, new NodeList().add(node)); - nodes.set(key, node); - }); - - nodes.forEach((node) => { - let id = node?.["value"]?.[idKey]; - - if (childMap.has(id)) { - node.childNodes = childMap.get(id); - childMap.delete(id); - } - }); - - const list = new NodeList(); - - childMap.forEach((s) => { - if (s instanceof Set) { - s.forEach((n) => { - list.add(n); - }); - } - }); - - return list; + const nodes = new Map(); + + if (!isObject(options)) { + options = {}; + } + + options = extend( + {}, + { + rootReferences: [null, undefined], + filter: undefined, + }, + options, + ); + + const filter = options?.filter; + let rootReferences = options.rootReferences; + if (!isArray(rootReferences)) { + rootReferences = [rootReferences]; + } + + const childMap = assembleParts(subject, selector, filter, function (o, k, m) { + const key = o?.[idKey]; + let ref = o?.[parentIDKey]; + if (rootReferences.indexOf(ref) !== -1) ref = rootSymbol; + + if (key === undefined) { + throw new Error("the object has no value for the specified id"); + } + + o[parentSymbol] = ref; + + const node = new Node(o); + this.has(ref) + ? this.get(ref).add(node) + : this.set(ref, new NodeList().add(node)); + nodes.set(key, node); + }); + + nodes.forEach((node) => { + let id = node?.["value"]?.[idKey]; + + if (childMap.has(id)) { + node.childNodes = childMap.get(id); + childMap.delete(id); + } + }); + + const list = new NodeList(); + + childMap.forEach((s) => { + if (s instanceof Set) { + s.forEach((n) => { + list.add(n); + }); + } + }); + + return list; } diff --git a/source/data/datasource.mjs b/source/data/datasource.mjs index 31f4d20de..40bdea95f 100644 --- a/source/data/datasource.mjs +++ b/source/data/datasource.mjs @@ -34,7 +34,9 @@ export { Datasource }; * @license AGPLv3 * @since 1.24.0 */ -const internalDataSymbol = Symbol.for("@schukai/monster/data/datasource/@@data"); +const internalDataSymbol = Symbol.for( + "@schukai/monster/data/datasource/@@data", +); /** * The datasource class is the basis for dealing with different data sources. @@ -47,155 +49,164 @@ const internalDataSymbol = Symbol.for("@schukai/monster/data/datasource/@@data") * @summary The datasource class encapsulates the access to data objects. */ class Datasource extends Base { - /** - * creates a new datasource - * - */ - constructor() { - super(); - - this[internalSymbol] = new ProxyObserver({ - options: extend({}, this.defaults), - }); - - this[internalDataSymbol] = new ProxyObserver({}); - } - - /** - * attach a new observer - * - * @param {Observer} observer - * @returns {Datasource} - */ - attachObserver(observer) { - this[internalDataSymbol].attachObserver(observer); - return this; - } - - /** - * detach a observer - * - * @param {Observer} observer - * @returns {Datasource} - */ - detachObserver(observer) { - this[internalDataSymbol].detachObserver(observer); - return this; - } - - /** - * @param {Observer} observer - * @returns {boolean} - */ - containsObserver(observer) { - return this[internalDataSymbol].containsObserver(observer); - } - - /** - * Derived classes can override and extend this method as follows. - * - * ``` - * get defaults() { - * return Object.assign({}, super.defaults, { - * myValue:true - * }); - * } - * ``` - */ - get defaults() { - return {}; - } - - /** - * Set option - * - * @param {string} path - * @param {*} value - * @return {Datasource} - */ - setOption(path, value) { - new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(path, value); - return this; - } - - /** - * @param {string|object} options - * @return {Datasource} - * @throws {Error} the options does not contain a valid json definition - */ - setOptions(options) { - if (isString(options)) { - options = parseOptionsJSON(options); - } - - const self = this; - extend(self[internalSymbol].getSubject()["options"], self.defaults, options); - - return self; - } - - /** - * nested options can be specified by path `a.b.c` - * - * @param {string} path - * @param {*} defaultValue - * @return {*} - */ - getOption(path, defaultValue) { - let value; - - try { - value = new Pathfinder(this[internalSymbol].getRealSubject()["options"]).getVia(path); - } catch (e) {} - - if (value === undefined) return defaultValue; - return value; - } - - /** - * @throws {Error} this method must be implemented by derived classes. - * @return {Promise} - */ - read() { - throw new Error("this method must be implemented by derived classes"); - } - - /** - * @throws {Error} this method must be implemented by derived classes. - * @return {Promise} - */ - write() { - throw new Error("this method must be implemented by derived classes"); - } - - /** - * Returns real object - * - * @return {Object|Array} - */ - get() { - const self = this; - return self[internalDataSymbol].getRealSubject(); - } - - /** - * @param {Object|Array} data - * @return {Datasource} - */ - set(data) { - const self = this; - self[internalDataSymbol].setSubject(data); - return self; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource"); - } + /** + * creates a new datasource + * + */ + constructor() { + super(); + + this[internalSymbol] = new ProxyObserver({ + options: extend({}, this.defaults), + }); + + this[internalDataSymbol] = new ProxyObserver({}); + } + + /** + * attach a new observer + * + * @param {Observer} observer + * @returns {Datasource} + */ + attachObserver(observer) { + this[internalDataSymbol].attachObserver(observer); + return this; + } + + /** + * detach a observer + * + * @param {Observer} observer + * @returns {Datasource} + */ + detachObserver(observer) { + this[internalDataSymbol].detachObserver(observer); + return this; + } + + /** + * @param {Observer} observer + * @returns {boolean} + */ + containsObserver(observer) { + return this[internalDataSymbol].containsObserver(observer); + } + + /** + * Derived classes can override and extend this method as follows. + * + * ``` + * get defaults() { + * return Object.assign({}, super.defaults, { + * myValue:true + * }); + * } + * ``` + */ + get defaults() { + return {}; + } + + /** + * Set option + * + * @param {string} path + * @param {*} value + * @return {Datasource} + */ + setOption(path, value) { + new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia( + path, + value, + ); + return this; + } + + /** + * @param {string|object} options + * @return {Datasource} + * @throws {Error} the options does not contain a valid json definition + */ + setOptions(options) { + if (isString(options)) { + options = parseOptionsJSON(options); + } + + const self = this; + extend( + self[internalSymbol].getSubject()["options"], + self.defaults, + options, + ); + + return self; + } + + /** + * nested options can be specified by path `a.b.c` + * + * @param {string} path + * @param {*} defaultValue + * @return {*} + */ + getOption(path, defaultValue) { + let value; + + try { + value = new Pathfinder( + this[internalSymbol].getRealSubject()["options"], + ).getVia(path); + } catch (e) {} + + if (value === undefined) return defaultValue; + return value; + } + + /** + * @throws {Error} this method must be implemented by derived classes. + * @return {Promise} + */ + read() { + throw new Error("this method must be implemented by derived classes"); + } + + /** + * @throws {Error} this method must be implemented by derived classes. + * @return {Promise} + */ + write() { + throw new Error("this method must be implemented by derived classes"); + } + + /** + * Returns real object + * + * @return {Object|Array} + */ + get() { + const self = this; + return self[internalDataSymbol].getRealSubject(); + } + + /** + * @param {Object|Array} data + * @return {Datasource} + */ + set(data) { + const self = this; + self[internalDataSymbol].setSubject(data); + return self; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource"); + } } /** @@ -205,21 +216,23 @@ class Datasource extends Base { * @throws {Error} the options does not contain a valid json definition */ function parseOptionsJSON(data) { - if (isString(data)) { - // the configuration can be specified as a data url. - try { - let dataUrl = parseDataURL(data); - data = dataUrl.content; - } catch (e) {} - - try { - let obj = JSON.parse(data); - validateObject(obj); - return obj; - } catch (e) { - throw new Error(`the options does not contain a valid json definition (actual: ${data}).`); - } - } - - return {}; + if (isString(data)) { + // the configuration can be specified as a data url. + try { + let dataUrl = parseDataURL(data); + data = dataUrl.content; + } catch (e) {} + + try { + let obj = JSON.parse(data); + validateObject(obj); + return obj; + } catch (e) { + throw new Error( + `the options does not contain a valid json definition (actual: ${data}).`, + ); + } + } + + return {}; } diff --git a/source/data/datasource/dom.mjs b/source/data/datasource/dom.mjs index 749ab68f5..8dc566d88 100644 --- a/source/data/datasource/dom.mjs +++ b/source/data/datasource/dom.mjs @@ -19,97 +19,97 @@ export { DomStorage }; * @memberOf Monster.Data.Datasource */ class DomStorage extends Datasource { - /** - * @param {Object} [options] options contains definitions for the datasource. - */ - constructor(options) { - super(); + /** + * @param {Object} [options] options contains definitions for the datasource. + */ + constructor(options) { + super(); - if (isObject(options)) { - this.setOptions(options); - } - } + if (isObject(options)) { + this.setOptions(options); + } + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/storage/dom-storage"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/storage/dom-storage"); + } - /** - * @property {Object} defaults - * @property {Object} defaults.read - * @property {string} defaults.read.selector - * @property {Object} defaults.write - * @property {string} defaults.write.selector - */ - get defaults() { - return Object.assign({}, super.defaults, { - read: { - selector: undefined, - }, - write: { - selector: undefined, - }, - }); - } + /** + * @property {Object} defaults + * @property {Object} defaults.read + * @property {string} defaults.read.selector + * @property {Object} defaults.write + * @property {string} defaults.write.selector + */ + get defaults() { + return Object.assign({}, super.defaults, { + read: { + selector: undefined, + }, + write: { + selector: undefined, + }, + }); + } - /** - * @return {Promise} - * @throws {Error} The read selector is not defined - * @throws {Error} There are no storage element - */ - read() { - const self = this; + /** + * @return {Promise} + * @throws {Error} The read selector is not defined + * @throws {Error} There are no storage element + */ + read() { + const self = this; - let selector = self.getOption("read.selector", undefined); - if (!selector) { - throw new Error("The read selector is not defined"); - } + let selector = self.getOption("read.selector", undefined); + if (!selector) { + throw new Error("The read selector is not defined"); + } - let storage = document.querySelector(selector); - if (!storage) { - throw new Error("There are no storage element"); - } + let storage = document.querySelector(selector); + if (!storage) { + throw new Error("There are no storage element"); + } - return new Promise((resolve, reject) => { - try { - let data = JSON.parse(storage.innerHTML); - self.set(data); - resolve(data); - } catch (e) { - reject(e); - } - }); - } + return new Promise((resolve, reject) => { + try { + let data = JSON.parse(storage.innerHTML); + self.set(data); + resolve(data); + } catch (e) { + reject(e); + } + }); + } - /** - * @return {Promise} - * @throws {Error} The write selector is not defined - * @throws {Error} There are no storage element - */ - write() { - const self = this; + /** + * @return {Promise} + * @throws {Error} The write selector is not defined + * @throws {Error} There are no storage element + */ + write() { + const self = this; - let selector = self.getOption("write.selector"); - if (!selector) { - throw new Error("The write selector is not defined"); - } + let selector = self.getOption("write.selector"); + if (!selector) { + throw new Error("The write selector is not defined"); + } - let storage = document.querySelector(selector); - if (!storage) { - throw new Error("There are no storage element"); - } + let storage = document.querySelector(selector); + if (!storage) { + throw new Error("There are no storage element"); + } - return new Promise((resolve, reject) => { - try { - storage.innerHTML = JSON.stringify(self.get()); - resolve(storage); - } catch (e) { - reject(e); - } - }); - } + return new Promise((resolve, reject) => { + try { + storage.innerHTML = JSON.stringify(self.get()); + resolve(storage); + } catch (e) { + reject(e); + } + }); + } } diff --git a/source/data/datasource/server.mjs b/source/data/datasource/server.mjs index c0a2c11ee..29840708e 100644 --- a/source/data/datasource/server.mjs +++ b/source/data/datasource/server.mjs @@ -23,57 +23,57 @@ export { Server }; * @summary The Server class encapsulates the access to a server datasource */ class Server extends Datasource { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/server"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/server"); + } - /** - * This prepares the data that comes from the server. - * Should not be called directly. - * - * @private - * @param {Object} payload - * @returns {Object} - */ - transformServerPayload(payload) { - const self = this; - payload = doTransform.call(self, "read", payload); + /** + * This prepares the data that comes from the server. + * Should not be called directly. + * + * @private + * @param {Object} payload + * @returns {Object} + */ + transformServerPayload(payload) { + const self = this; + payload = doTransform.call(self, "read", payload); - const dataPath = self.getOption("read.path"); - if (dataPath) { - payload = new Pathfinder(payload).getVia(dataPath); - } + const dataPath = self.getOption("read.path"); + if (dataPath) { + payload = new Pathfinder(payload).getVia(dataPath); + } - return payload; - } + return payload; + } - /** - * This prepares the data for writing and should not be called directly. - * - * @private - * @param {Object} payload - * @returns {Object} - */ - prepareServerPayload(payload) { - const self = this; + /** + * This prepares the data for writing and should not be called directly. + * + * @private + * @param {Object} payload + * @returns {Object} + */ + prepareServerPayload(payload) { + const self = this; - payload = doTransform.call(self, "write", payload); + payload = doTransform.call(self, "write", payload); - let sheathingObject = self.getOption("write.sheathing.object"); - let sheathingPath = self.getOption("write.sheathing.path"); + let sheathingObject = self.getOption("write.sheathing.object"); + let sheathingPath = self.getOption("write.sheathing.path"); - if (sheathingObject && sheathingPath) { - const sub = payload; - payload = sheathingObject; - new Pathfinder(payload).setVia(sheathingPath, sub); - } + if (sheathingObject && sheathingPath) { + const sub = payload; + payload = sheathingObject; + new Pathfinder(payload).setVia(sheathingPath, sub); + } - return payload; - } + return payload; + } } /** @@ -83,22 +83,25 @@ class Server extends Datasource { * @returns {*} */ function doTransform(type, obj) { - const self = this; - let transformation = self.getOption(`${type}.mapping.transformer`); - if (transformation !== undefined) { - const pipe = new Pipe(transformation); - const callbacks = self.getOption(`${type}.mapping.callbacks`); + const self = this; + let transformation = self.getOption(`${type}.mapping.transformer`); + if (transformation !== undefined) { + const pipe = new Pipe(transformation); + const callbacks = self.getOption(`${type}.mapping.callbacks`); - if (isObject(callbacks)) { - for (const key in callbacks) { - if (callbacks.hasOwnProperty(key) && typeof callbacks[key] === "function") { - pipe.setCallback(key, callbacks[key]); - } - } - } + if (isObject(callbacks)) { + for (const key in callbacks) { + if ( + callbacks.hasOwnProperty(key) && + typeof callbacks[key] === "function" + ) { + pipe.setCallback(key, callbacks[key]); + } + } + } - obj = pipe.run(obj); - } + obj = pipe.run(obj); + } - return obj; + return obj; } diff --git a/source/data/datasource/server/restapi.mjs b/source/data/datasource/server/restapi.mjs index d476fbc96..5d8f10819 100644 --- a/source/data/datasource/server/restapi.mjs +++ b/source/data/datasource/server/restapi.mjs @@ -19,7 +19,9 @@ export { RestAPI }; * @license AGPLv3 * @since 3.12.0 */ -const rawDataSymbol = Symbol.for("@schukai/monster/data/datasource/server/restapi/rawdata"); +const rawDataSymbol = Symbol.for( + "@schukai/monster/data/datasource/server/restapi/rawdata", +); /** * The RestAPI is a class that enables a REST API server. @@ -32,143 +34,143 @@ const rawDataSymbol = Symbol.for("@schukai/monster/data/datasource/server/restap * @summary The RestAPI is a class that binds a REST API server. */ class RestAPI extends Server { - /** - * - * @param {Object} [options] options contains definitions for the datasource. - */ - constructor(options) { - super(); - - if (isObject(options)) { - this.setOptions(options); - } - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/server/restapi"); - } - - /** - * @property {Object} write={} Options - * @property {Object} write.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} - * @property {string} write.init.method=POST - * @property {Object} write.init.headers Object containing any custom headers that you want to apply to the request. - * @property {string} write.responseCallback Callback function to be executed after the request has been completed. - * @property {string} write.acceptedStatus=[200,201] - * @property {string} write.url URL - * @property {Object} write.mapping the mapping is applied before writing. - * @property {String} write.mapping.transformer Transformer to select the appropriate entries - * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. - * @property {Object} write.report - * @property {String} write.report.path Path to validations - * @property {Object} write.sheathing - * @property {Object} write.sheathing.object Object to be wrapped - * @property {string} write.sheathing.path Path to the data - * @property {Object} read={} Options - * @property {Object} read.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} - * @property {string} read.init.method=GET - * @property {string} read.acceptedStatus=[200] - * @property {string} read.url URL - * @property {Object} read.mapping the mapping is applied after reading. - * @property {String} read.mapping.transformer Transformer to select the appropriate entries - * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading. - */ - get defaults() { - return Object.assign({}, super.defaults, { - write: { - init: { - method: "POST", - }, - responseCallback: undefined, - acceptedStatus: [200, 201], - url: undefined, - mapping: { - transformer: undefined, - callbacks: [], - }, - sheathing: { - object: undefined, - path: undefined, - }, - report: { - path: undefined, - }, - }, - read: { - init: { - method: "GET", - }, - responseCallback: undefined, - acceptedStatus: [200], - url: undefined, - mapping: { - transformer: undefined, - callbacks: [], - }, - }, - }); - } - - /** - * @return {Promise} - * @throws {Error} the options does not contain a valid json definition - * @throws {TypeError} value is not a object - * @throws {Error} the data cannot be read - */ - read() { - const self = this; - - let init = self.getOption("read.init"); - if (!isObject(init)) init = {}; - if (!init["method"]) init["method"] = "GET"; - - let callback = self.getOption("read.responseCallback"); - if (!callback) - callback = (obj) => { - self.set(self.transformServerPayload.call(self, obj)); - }; - - return fetchData.call(this, init, "read", callback); - } - - /** - * @return {Promise} - * @throws {WriteError} the data cannot be written - */ - write() { - const self = this; - - let init = self.getOption("write.init"); - if (!isObject(init)) init = {}; - if (typeof init["headers"] !== "object") { - init["headers"] = { - "Content-Type": "application/json", - }; - } - if (!init["method"]) init["method"] = "POST"; - - let obj = self.prepareServerPayload(self.get()); - init["body"] = JSON.stringify(obj); - - let callback = self.getOption("write.responseCallback"); - return fetchData.call(this, init, "write", callback); - } - - /** - * @return {RestAPI} - */ - getClone() { - const self = this; - return new RestAPI( - self[internalSymbol].getRealSubject()["options"].read, - self[internalSymbol].getRealSubject()["options"].write, - ); - } + /** + * + * @param {Object} [options] options contains definitions for the datasource. + */ + constructor(options) { + super(); + + if (isObject(options)) { + this.setOptions(options); + } + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/server/restapi"); + } + + /** + * @property {Object} write={} Options + * @property {Object} write.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} + * @property {string} write.init.method=POST + * @property {Object} write.init.headers Object containing any custom headers that you want to apply to the request. + * @property {string} write.responseCallback Callback function to be executed after the request has been completed. + * @property {string} write.acceptedStatus=[200,201] + * @property {string} write.url URL + * @property {Object} write.mapping the mapping is applied before writing. + * @property {String} write.mapping.transformer Transformer to select the appropriate entries + * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. + * @property {Object} write.report + * @property {String} write.report.path Path to validations + * @property {Object} write.sheathing + * @property {Object} write.sheathing.object Object to be wrapped + * @property {string} write.sheathing.path Path to the data + * @property {Object} read={} Options + * @property {Object} read.init={} An options object containing any custom settings that you want to apply to the request. The parameters are identical to those of the {@link https://developer.mozilla.org/en-US/docs/Web/API/Request/Request|Request constructor} + * @property {string} read.init.method=GET + * @property {string} read.acceptedStatus=[200] + * @property {string} read.url URL + * @property {Object} read.mapping the mapping is applied after reading. + * @property {String} read.mapping.transformer Transformer to select the appropriate entries + * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading. + */ + get defaults() { + return Object.assign({}, super.defaults, { + write: { + init: { + method: "POST", + }, + responseCallback: undefined, + acceptedStatus: [200, 201], + url: undefined, + mapping: { + transformer: undefined, + callbacks: [], + }, + sheathing: { + object: undefined, + path: undefined, + }, + report: { + path: undefined, + }, + }, + read: { + init: { + method: "GET", + }, + responseCallback: undefined, + acceptedStatus: [200], + url: undefined, + mapping: { + transformer: undefined, + callbacks: [], + }, + }, + }); + } + + /** + * @return {Promise} + * @throws {Error} the options does not contain a valid json definition + * @throws {TypeError} value is not a object + * @throws {Error} the data cannot be read + */ + read() { + const self = this; + + let init = self.getOption("read.init"); + if (!isObject(init)) init = {}; + if (!init["method"]) init["method"] = "GET"; + + let callback = self.getOption("read.responseCallback"); + if (!callback) + callback = (obj) => { + self.set(self.transformServerPayload.call(self, obj)); + }; + + return fetchData.call(this, init, "read", callback); + } + + /** + * @return {Promise} + * @throws {WriteError} the data cannot be written + */ + write() { + const self = this; + + let init = self.getOption("write.init"); + if (!isObject(init)) init = {}; + if (typeof init["headers"] !== "object") { + init["headers"] = { + "Content-Type": "application/json", + }; + } + if (!init["method"]) init["method"] = "POST"; + + let obj = self.prepareServerPayload(self.get()); + init["body"] = JSON.stringify(obj); + + let callback = self.getOption("write.responseCallback"); + return fetchData.call(this, init, "write", callback); + } + + /** + * @return {RestAPI} + */ + getClone() { + const self = this; + return new RestAPI( + self[internalSymbol].getRealSubject()["options"].read, + self[internalSymbol].getRealSubject()["options"].write, + ); + } } /** @@ -179,43 +181,46 @@ class RestAPI extends Server { * @returns {Promise<string>} */ function fetchData(init, key, callback) { - const self = this; - let response; - - return fetch(self.getOption(`${key}.url`), init) - .then((resp) => { - response = resp; - - const acceptedStatus = self.getOption(`${key}.acceptedStatus`, [200]); - - if (acceptedStatus.indexOf(resp.status) === -1) { - throw new DataFetchError( - `the response does not contain a accepted status (actual: ${resp.status}).`, - response, - ); - } - - return resp.text(); - }) - .then((body) => { - let obj; - - try { - obj = JSON.parse(body); - - response[rawDataSymbol] = obj; - } catch (e) { - if (body.length > 100) { - body = `${body.substring(0, 97)}...`; - } - - throw new DataFetchError(`the response does not contain a valid json (actual: ${body}).`, response); - } - - if (callback && isFunction(callback)) { - callback(obj); - } - - return response; - }); + const self = this; + let response; + + return fetch(self.getOption(`${key}.url`), init) + .then((resp) => { + response = resp; + + const acceptedStatus = self.getOption(`${key}.acceptedStatus`, [200]); + + if (acceptedStatus.indexOf(resp.status) === -1) { + throw new DataFetchError( + `the response does not contain a accepted status (actual: ${resp.status}).`, + response, + ); + } + + return resp.text(); + }) + .then((body) => { + let obj; + + try { + obj = JSON.parse(body); + + response[rawDataSymbol] = obj; + } catch (e) { + if (body.length > 100) { + body = `${body.substring(0, 97)}...`; + } + + throw new DataFetchError( + `the response does not contain a valid json (actual: ${body}).`, + response, + ); + } + + if (callback && isFunction(callback)) { + callback(obj); + } + + return response; + }); } diff --git a/source/data/datasource/server/restapi/data-fetch-error.mjs b/source/data/datasource/server/restapi/data-fetch-error.mjs index 1e8b7c694..87c3eb27f 100644 --- a/source/data/datasource/server/restapi/data-fetch-error.mjs +++ b/source/data/datasource/server/restapi/data-fetch-error.mjs @@ -19,31 +19,33 @@ export { DataFetchError }; * @summary the error is thrown by the rest api in case of error */ class DataFetchError extends Error { - /** - * - * @param {string} message - * @param {Response} response - */ - constructor(message, response) { - super(message); - this[internalSymbol] = { - response: response, - }; - } + /** + * + * @param {string} message + * @param {Response} response + */ + constructor(message, response) { + super(message); + this[internalSymbol] = { + response: response, + }; + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/server/restapi/datafetcherror@@instance"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for( + "@schukai/monster/data/datasource/server/restapi/datafetcherror@@instance", + ); + } - /** - * @return {Response} - */ - getResponse() { - return this[internalSymbol]["response"]; - } + /** + * @return {Response} + */ + getResponse() { + return this[internalSymbol]["response"]; + } } diff --git a/source/data/datasource/server/restapi/writeerror.mjs b/source/data/datasource/server/restapi/writeerror.mjs index f4796b492..307308dc2 100644 --- a/source/data/datasource/server/restapi/writeerror.mjs +++ b/source/data/datasource/server/restapi/writeerror.mjs @@ -19,39 +19,41 @@ export { WriteError }; * @summary the error is thrown by the rest api in case of error */ class WriteError extends Error { - /** - * - * @param {string} message - * @param {Response} response - */ - constructor(message, response, validation) { - super(message); - this[internalSymbol] = { - response: response, - validation: validation, - }; - } + /** + * + * @param {string} message + * @param {Response} response + */ + constructor(message, response, validation) { + super(message); + this[internalSymbol] = { + response: response, + validation: validation, + }; + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/server/restapi/writeerror"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for( + "@schukai/monster/data/datasource/server/restapi/writeerror", + ); + } - /** - * @return {Response} - */ - getResponse() { - return this[internalSymbol]["response"]; - } + /** + * @return {Response} + */ + getResponse() { + return this[internalSymbol]["response"]; + } - /** - * @return {Object} - */ - getValidation() { - return this[internalSymbol]["validation"]; - } + /** + * @return {Object} + */ + getValidation() { + return this[internalSymbol]["validation"]; + } } diff --git a/source/data/datasource/server/webconnect.mjs b/source/data/datasource/server/webconnect.mjs index d866716d3..614c149b0 100644 --- a/source/data/datasource/server/webconnect.mjs +++ b/source/data/datasource/server/webconnect.mjs @@ -32,159 +32,159 @@ const webConnectSymbol = Symbol("connection"); * @summary The LocalStorage class encapsulates the access to data objects. */ class WebConnect extends Server { - /** - * - * @param {Object} [options] options contains definitions for the datasource. - */ - constructor(options) { - super(); - - const self = this; - - if (isString(options)) { - options = { url: options }; - } - - if (!isObject(options)) options = {}; - this.setOptions(options); - this[webConnectSymbol] = new NetWebConnect({ - url: self.getOption("url"), - connection: { - timeout: self.getOption("connection.timeout"), - reconnect: { - timeout: self.getOption("connection.reconnect.timeout"), - attempts: self.getOption("connection.reconnect.attempts"), - enabled: self.getOption("connection.reconnect.enabled"), - }, - }, - }); - } - - /** - * - * @returns {Promise} - */ - connect() { - return this[webConnectSymbol].connect(); - } - - /** - * @returns {boolean} - */ - isConnected() { - return this[webConnectSymbol].isConnected(); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/server/webconnect"); - } - - /** - * @property {string} url=undefined Defines the resource that you wish to fetch. - * @property {Object} connection - * @property {Object} connection.timeout=5000 Defines the timeout for the connection. - * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect. - * @property {Number} connection.reconnect.attempts The maximum number of reconnects. - * @property {Bool} connection.reconnect.enabled If the reconnect is enabled. - * @property {Object} write={} Options - * @property {Object} write.mapping the mapping is applied before writing. - * @property {String} write.mapping.transformer Transformer to select the appropriate entries - * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. - * @property {Object} write.sheathing - * @property {Object} write.sheathing.object Object to be wrapped - * @property {string} write.sheathing.path Path to the data - * @property {Object} read={} Options - * @property {String} read.path Path to data - * @property {Object} read.mapping the mapping is applied after reading. - * @property {String} read.mapping.transformer Transformer to select the appropriate entries - * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading. - */ - get defaults() { - return Object.assign({}, super.defaults, { - url: undefined, - write: { - mapping: { - transformer: undefined, - callbacks: {}, - }, - sheathing: { - object: undefined, - path: undefined, - }, - }, - read: { - mapping: { - transformer: undefined, - callbacks: {}, - }, - path: undefined, - }, - connection: { - timeout: 5000, - reconnect: { - timeout: 1000, - attempts: 1, - enabled: false, - }, - }, - }); - } - - /** - * This method closes the connection. - * - * @returns {Promise} - */ - close() { - return this[webConnectSymbol].close(); - } - - /** - * @return {Promise} - */ - read() { - const self = this; - - return new Promise((resolve, reject) => { - while (this[webConnectSymbol].dataReceived() === true) { - let obj = this[webConnectSymbol].poll(); - if (!isObject(obj)) { - reject(new Error("The received data is not an object.")); - return; - } - - if (!(obj instanceof Message)) { - reject(new Error("The received data is not a Message.")); - return; - } - - obj = obj.getData(); - obj = self.transformServerPayload.call(self, obj); - self.set(obj); - } - - resolve(self.get()); - }); - } - - /** - * @return {Promise} - */ - write() { - const self = this; - let obj = self.prepareServerPayload(self.get()); - return self[webConnectSymbol].send(obj); - } - - /** - * @return {RestAPI} - */ - getClone() { - const self = this; - return new WebConnect(self[internalSymbol].getRealSubject()["options"]); - } + /** + * + * @param {Object} [options] options contains definitions for the datasource. + */ + constructor(options) { + super(); + + const self = this; + + if (isString(options)) { + options = { url: options }; + } + + if (!isObject(options)) options = {}; + this.setOptions(options); + this[webConnectSymbol] = new NetWebConnect({ + url: self.getOption("url"), + connection: { + timeout: self.getOption("connection.timeout"), + reconnect: { + timeout: self.getOption("connection.reconnect.timeout"), + attempts: self.getOption("connection.reconnect.attempts"), + enabled: self.getOption("connection.reconnect.enabled"), + }, + }, + }); + } + + /** + * + * @returns {Promise} + */ + connect() { + return this[webConnectSymbol].connect(); + } + + /** + * @returns {boolean} + */ + isConnected() { + return this[webConnectSymbol].isConnected(); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/server/webconnect"); + } + + /** + * @property {string} url=undefined Defines the resource that you wish to fetch. + * @property {Object} connection + * @property {Object} connection.timeout=5000 Defines the timeout for the connection. + * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect. + * @property {Number} connection.reconnect.attempts The maximum number of reconnects. + * @property {Bool} connection.reconnect.enabled If the reconnect is enabled. + * @property {Object} write={} Options + * @property {Object} write.mapping the mapping is applied before writing. + * @property {String} write.mapping.transformer Transformer to select the appropriate entries + * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. + * @property {Object} write.sheathing + * @property {Object} write.sheathing.object Object to be wrapped + * @property {string} write.sheathing.path Path to the data + * @property {Object} read={} Options + * @property {String} read.path Path to data + * @property {Object} read.mapping the mapping is applied after reading. + * @property {String} read.mapping.transformer Transformer to select the appropriate entries + * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading. + */ + get defaults() { + return Object.assign({}, super.defaults, { + url: undefined, + write: { + mapping: { + transformer: undefined, + callbacks: {}, + }, + sheathing: { + object: undefined, + path: undefined, + }, + }, + read: { + mapping: { + transformer: undefined, + callbacks: {}, + }, + path: undefined, + }, + connection: { + timeout: 5000, + reconnect: { + timeout: 1000, + attempts: 1, + enabled: false, + }, + }, + }); + } + + /** + * This method closes the connection. + * + * @returns {Promise} + */ + close() { + return this[webConnectSymbol].close(); + } + + /** + * @return {Promise} + */ + read() { + const self = this; + + return new Promise((resolve, reject) => { + while (this[webConnectSymbol].dataReceived() === true) { + let obj = this[webConnectSymbol].poll(); + if (!isObject(obj)) { + reject(new Error("The received data is not an object.")); + return; + } + + if (!(obj instanceof Message)) { + reject(new Error("The received data is not a Message.")); + return; + } + + obj = obj.getData(); + obj = self.transformServerPayload.call(self, obj); + self.set(obj); + } + + resolve(self.get()); + }); + } + + /** + * @return {Promise} + */ + write() { + const self = this; + let obj = self.prepareServerPayload(self.get()); + return self[webConnectSymbol].send(obj); + } + + /** + * @return {RestAPI} + */ + getClone() { + const self = this; + return new WebConnect(self[internalSymbol].getRealSubject()["options"]); + } } diff --git a/source/data/datasource/storage.mjs b/source/data/datasource/storage.mjs index 4cd6a6135..e1f510257 100644 --- a/source/data/datasource/storage.mjs +++ b/source/data/datasource/storage.mjs @@ -15,7 +15,9 @@ export { Storage, storageObjectSymbol }; * @private * @type {symbol} */ -const storageObjectSymbol = Symbol.for("@schukai/monster/data/datasource/storage/@@storageObject"); +const storageObjectSymbol = Symbol.for( + "@schukai/monster/data/datasource/storage/@@storageObject", +); /** * The class represents a record. @@ -27,87 +29,87 @@ const storageObjectSymbol = Symbol.for("@schukai/monster/data/datasource/storage * @summary The Storage class encapsulates the access to data objects over WebStorageAPI. */ class Storage extends Datasource { - /** - * - * @param {string} key LocalStorage Key - * @throws {TypeError} value is not a string - */ - constructor(key) { - super(); - this.setOption("key", validateString(key)); - } + /** + * + * @param {string} key LocalStorage Key + * @throws {TypeError} value is not a string + */ + constructor(key) { + super(); + this.setOption("key", validateString(key)); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/storage"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/storage"); + } - /** - * @property {string} key=undefined LocalStorage Key - */ - get defaults() { - return Object.assign({}, super.defaults, { - key: undefined, - }); - } + /** + * @property {string} key=undefined LocalStorage Key + */ + get defaults() { + return Object.assign({}, super.defaults, { + key: undefined, + }); + } - /** - * @throws {Error} this method must be implemented by derived classes. - * @return {external:Storage} - * @private - */ - [storageObjectSymbol]() { - throw new Error("this method must be implemented by derived classes"); - } + /** + * @throws {Error} this method must be implemented by derived classes. + * @return {external:Storage} + * @private + */ + [storageObjectSymbol]() { + throw new Error("this method must be implemented by derived classes"); + } - /** - * @return {Promise} - * @throws {Error} the options does not contain a valid json definition - * @throws {TypeError} value is not a object - * @throws {Error} the data cannot be read - */ - read() { - const self = this; + /** + * @return {Promise} + * @throws {Error} the options does not contain a valid json definition + * @throws {TypeError} value is not a object + * @throws {Error} the data cannot be read + */ + read() { + const self = this; - const storage = self[storageObjectSymbol](); + const storage = self[storageObjectSymbol](); - return new Promise(function (resolve) { - const data = JSON.parse(storage.getItem(self.getOption("key"))); - self.set(data ?? {}); - resolve(); - }); - } + return new Promise(function (resolve) { + const data = JSON.parse(storage.getItem(self.getOption("key"))); + self.set(data ?? {}); + resolve(); + }); + } - /** - * @return {Storage} - * @throws {Error} the data cannot be written - */ - write() { - const self = this; + /** + * @return {Storage} + * @throws {Error} the data cannot be written + */ + write() { + const self = this; - const storage = self[storageObjectSymbol](); + const storage = self[storageObjectSymbol](); - return new Promise(function (resolve) { - const data = self.get(); - if (data === undefined) { - storage.removeItem(self.getOption("key")); - } else { - storage.setItem(self.getOption("key"), JSON.stringify(data)); - } + return new Promise(function (resolve) { + const data = self.get(); + if (data === undefined) { + storage.removeItem(self.getOption("key")); + } else { + storage.setItem(self.getOption("key"), JSON.stringify(data)); + } - resolve(); - }); - } + resolve(); + }); + } - /** - * @return {Storage} - */ - getClone() { - const self = this; - return new Storage(self[internalSymbol].getRealSubject()["options"].key); - } + /** + * @return {Storage} + */ + getClone() { + const self = this; + return new Storage(self[internalSymbol].getRealSubject()["options"].key); + } } diff --git a/source/data/datasource/storage/localstorage.mjs b/source/data/datasource/storage/localstorage.mjs index 842d27cb1..bf219dc97 100644 --- a/source/data/datasource/storage/localstorage.mjs +++ b/source/data/datasource/storage/localstorage.mjs @@ -22,30 +22,32 @@ export { LocalStorage }; * @summary The LocalStorage class encapsulates the access to data objects. */ class LocalStorage extends Storage { - /** - * @throws {Error} this method must be implemented by derived classes. - * @return {external:localStorage} - * @private - */ - [storageObjectSymbol]() { - return getGlobalObject("localStorage"); - } + /** + * @throws {Error} this method must be implemented by derived classes. + * @return {external:localStorage} + * @private + */ + [storageObjectSymbol]() { + return getGlobalObject("localStorage"); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/storage/localstorage"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/data/datasource/storage/localstorage"); + } - /** - * Create clone - * @return {LocalStorage} - */ - getClone() { - const self = this; - return new LocalStorage(self[internalSymbol].getRealSubject()["options"].key); - } + /** + * Create clone + * @return {LocalStorage} + */ + getClone() { + const self = this; + return new LocalStorage( + self[internalSymbol].getRealSubject()["options"].key, + ); + } } diff --git a/source/data/datasource/storage/sessionstorage.mjs b/source/data/datasource/storage/sessionstorage.mjs index c2928e9d7..91da76dfe 100644 --- a/source/data/datasource/storage/sessionstorage.mjs +++ b/source/data/datasource/storage/sessionstorage.mjs @@ -22,31 +22,35 @@ export { SessionStorage }; * @summary The LocalStorage class encapsulates the access to data objects. */ class SessionStorage extends Storage { - /** - * @throws {Error} this method must be implemented by derived classes. - * @return {external:sessionStorage} - * @private - */ - [storageObjectSymbol]() { - return getGlobalObject("sessionStorage"); - } + /** + * @throws {Error} this method must be implemented by derived classes. + * @return {external:sessionStorage} + * @private + */ + [storageObjectSymbol]() { + return getGlobalObject("sessionStorage"); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/data/datasource/storage/session-storage"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for( + "@schukai/monster/data/datasource/storage/session-storage", + ); + } - /** - * Create Clone - * - * @return {SessionStorage} - */ - getClone() { - const self = this; - return new SessionStorage(self[internalSymbol].getRealSubject()["options"].key); - } + /** + * Create Clone + * + * @return {SessionStorage} + */ + getClone() { + const self = this; + return new SessionStorage( + self[internalSymbol].getRealSubject()["options"].key, + ); + } } diff --git a/source/data/diff.mjs b/source/data/diff.mjs index 8b72b97e1..1600a0c93 100644 --- a/source/data/diff.mjs +++ b/source/data/diff.mjs @@ -25,7 +25,7 @@ export { diff }; * @memberOf Monster.Data */ function diff(first, second) { - return doDiff(first, second); + return doDiff(first, second); } /** @@ -36,13 +36,14 @@ function diff(first, second) { * @return {Set<string>|Set<number>} */ function getKeys(a, b, type) { - if (isArray(type)) { - const keys = a.length > b.length ? new Array(a.length) : new Array(b.length); - keys.fill(0); - return new Set(keys.map((_, i) => i)); - } - - return new Set(Object.keys(a).concat(Object.keys(b))); + if (isArray(type)) { + const keys = + a.length > b.length ? new Array(a.length) : new Array(b.length); + keys.fill(0); + return new Set(keys.map((_, i) => i)); + } + + return new Set(Object.keys(a).concat(Object.keys(b))); } /** @@ -54,30 +55,30 @@ function getKeys(a, b, type) { * @return {array} */ function doDiff(a, b, path, diff) { - let typeA = typeOf(a); - let typeB = typeOf(b); - - const currPath = path || []; - const currDiff = diff || []; - - if (typeA === typeB && (typeA === "object" || typeA === "array")) { - getKeys(a, b, typeA).forEach((v) => { - if (!Object.prototype.hasOwnProperty.call(a, v)) { - currDiff.push(buildResult(a[v], b[v], "add", currPath.concat(v))); - } else if (!Object.prototype.hasOwnProperty.call(b, v)) { - currDiff.push(buildResult(a[v], b[v], "delete", currPath.concat(v))); - } else { - doDiff(a[v], b[v], currPath.concat(v), currDiff); - } - }); - } else { - const o = getOperator(a, b, typeA, typeB); - if (o !== undefined) { - currDiff.push(buildResult(a, b, o, path)); - } - } - - return currDiff; + let typeA = typeOf(a); + let typeB = typeOf(b); + + const currPath = path || []; + const currDiff = diff || []; + + if (typeA === typeB && (typeA === "object" || typeA === "array")) { + getKeys(a, b, typeA).forEach((v) => { + if (!Object.prototype.hasOwnProperty.call(a, v)) { + currDiff.push(buildResult(a[v], b[v], "add", currPath.concat(v))); + } else if (!Object.prototype.hasOwnProperty.call(b, v)) { + currDiff.push(buildResult(a[v], b[v], "delete", currPath.concat(v))); + } else { + doDiff(a[v], b[v], currPath.concat(v), currDiff); + } + }); + } else { + const o = getOperator(a, b, typeA, typeB); + if (o !== undefined) { + currDiff.push(buildResult(a, b, o, path)); + } + } + + return currDiff; } /** @@ -90,40 +91,40 @@ function doDiff(a, b, path, diff) { * @private */ function buildResult(a, b, operator, path) { - const result = { - operator, - path, - }; - - if (operator !== "add") { - result.first = { - value: a, - type: typeof a, - }; - - if (isObject(a)) { - const name = Object.getPrototypeOf(a)?.constructor?.name; - if (name !== undefined) { - result.first.instance = name; - } - } - } - - if (operator === "add" || operator === "update") { - result.second = { - value: b, - type: typeof b, - }; - - if (isObject(b)) { - const name = Object.getPrototypeOf(b)?.constructor?.name; - if (name !== undefined) { - result.second.instance = name; - } - } - } - - return result; + const result = { + operator, + path, + }; + + if (operator !== "add") { + result.first = { + value: a, + type: typeof a, + }; + + if (isObject(a)) { + const name = Object.getPrototypeOf(a)?.constructor?.name; + if (name !== undefined) { + result.first.instance = name; + } + } + } + + if (operator === "add" || operator === "update") { + result.second = { + value: b, + type: typeof b, + }; + + if (isObject(b)) { + const name = Object.getPrototypeOf(b)?.constructor?.name; + if (name !== undefined) { + result.second.instance = name; + } + } + } + + return result; } /** @@ -133,15 +134,15 @@ function buildResult(a, b, operator, path) { * @return {boolean} */ function isNotEqual(a, b) { - if (typeof a !== typeof b) { - return true; - } + if (typeof a !== typeof b) { + return true; + } - if (a instanceof Date && b instanceof Date) { - return a.getTime() !== b.getTime(); - } + if (a instanceof Date && b instanceof Date) { + return a.getTime() !== b.getTime(); + } - return a !== b; + return a !== b; } /** @@ -151,28 +152,28 @@ function isNotEqual(a, b) { * @return {string|undefined} */ function getOperator(a, b) { - /** - * @type {string|undefined} - */ - let operator; - - /** - * @type {string} - */ - let typeA = typeof a; - - /** - * @type {string} - */ - let typeB = typeof b; - - if (typeA === "undefined" && typeB !== "undefined") { - operator = "add"; - } else if (typeA !== "undefined" && typeB === "undefined") { - operator = "delete"; - } else if (isNotEqual(a, b)) { - operator = "update"; - } - - return operator; + /** + * @type {string|undefined} + */ + let operator; + + /** + * @type {string} + */ + let typeA = typeof a; + + /** + * @type {string} + */ + let typeB = typeof b; + + if (typeA === "undefined" && typeB !== "undefined") { + operator = "add"; + } else if (typeA !== "undefined" && typeB === "undefined") { + operator = "delete"; + } else if (isNotEqual(a, b)) { + operator = "update"; + } + + return operator; } diff --git a/source/data/extend.mjs b/source/data/extend.mjs index 649f24142..5b1f70d7d 100644 --- a/source/data/extend.mjs +++ b/source/data/extend.mjs @@ -26,55 +26,55 @@ export { extend }; * @throws {Error} unsupported argument */ function extend(...args) { - let o; - let i; + let o; + let i; - if (typeof args !== "object" || args[0] === null) { - throw new Error(`unsupported argument ${JSON.stringify(args[0])}`); - } + if (typeof args !== "object" || args[0] === null) { + throw new Error(`unsupported argument ${JSON.stringify(args[0])}`); + } - for (i = 0; i < args.length; i++) { - let a = args[i]; + for (i = 0; i < args.length; i++) { + let a = args[i]; - if (!(isObject(a) || isArray(a))) { - throw new Error(`unsupported argument ${JSON.stringify(a)}`); - } + if (!(isObject(a) || isArray(a))) { + throw new Error(`unsupported argument ${JSON.stringify(a)}`); + } - if (o === undefined) { - o = a; - continue; - } + if (o === undefined) { + o = a; + continue; + } - for (let k in a) { - let v = a?.[k]; + for (let k in a) { + let v = a?.[k]; - if (v === o?.[k]) { - continue; - } + if (v === o?.[k]) { + continue; + } - if ((isObject(v) && typeOf(v) === "object") || isArray(v)) { - if (o[k] === undefined) { - if (isArray(v)) { - o[k] = []; - } else { - o[k] = {}; - } - } else { - if (typeOf(o[k]) !== typeOf(v)) { - throw new Error( - `type mismatch: ${JSON.stringify(o[k])}(${typeOf(o[k])}) != ${JSON.stringify(v)}(${typeOf( - v, - )})`, - ); - } - } + if ((isObject(v) && typeOf(v) === "object") || isArray(v)) { + if (o[k] === undefined) { + if (isArray(v)) { + o[k] = []; + } else { + o[k] = {}; + } + } else { + if (typeOf(o[k]) !== typeOf(v)) { + throw new Error( + `type mismatch: ${JSON.stringify(o[k])}(${typeOf( + o[k], + )}) != ${JSON.stringify(v)}(${typeOf(v)})`, + ); + } + } - o[k] = extend(o[k], v); - } else { - o[k] = v; - } - } - } + o[k] = extend(o[k], v); + } else { + o[k] = v; + } + } + } - return o; + return o; } diff --git a/source/data/pathfinder.mjs b/source/data/pathfinder.mjs index 8673210dd..602bad7cb 100644 --- a/source/data/pathfinder.mjs +++ b/source/data/pathfinder.mjs @@ -6,9 +6,19 @@ */ import { Base } from "../types/base.mjs"; -import { isArray, isInteger, isObject, isPrimitive, isString } from "../types/is.mjs"; +import { + isArray, + isInteger, + isObject, + isPrimitive, + isString, +} from "../types/is.mjs"; import { Stack } from "../types/stack.mjs"; -import { validateInteger, validateBoolean, validateString } from "../types/validate.mjs"; +import { + validateInteger, + validateBoolean, + validateString, +} from "../types/validate.mjs"; export { Pathfinder, DELIMITER, WILDCARD }; @@ -72,99 +82,99 @@ const WILDCARD = "*"; * @memberOf Monster.Data */ class Pathfinder extends Base { - /** - * @param {array|object|Map|Set} value - * @since 1.4.0 - * @throws {Error} the parameter must not be a simple type - **/ - constructor(object) { - super(); - - if (isPrimitive(object)) { - throw new Error("the parameter must not be a simple type"); - } - - this.object = object; - this.wildCard = WILDCARD; - } - - /** - * set wildcard - * - * @param {string} wildcard - * @return {Pathfinder} - * @since 1.7.0 - */ - setWildCard(wildcard) { - validateString(wildcard); - this.wildCard = wildcard; - return this; - } - - /** - * - * @param {string|array} path - * @since 1.4.0 - * @returns {*} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @throws {Error} unsupported action for this data type - */ - getVia(path) { - return getValueViaPath.call(this, this.object, path); - } - - /** - * - * @param {string|array} path - * @param {*} value - * @returns {Pathfinder} - * @since 1.4.0 - * @throws {TypeError} unsupported type - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @throws {Error} unsupported action for this data type - */ - setVia(path, value) { - setValueViaPath.call(this, this.object, path, value); - return this; - } - - /** - * Delete Via Path - * - * @param {string|array} path - * @returns {Pathfinder} - * @since 1.6.0 - * @throws {TypeError} unsupported type - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @throws {Error} unsupported action for this data type - */ - deleteVia(path) { - deleteValueViaPath.call(this, this.object, path); - return this; - } - - /** - * - * @param {string|array} path - * @return {bool} - * @throws {TypeError} unsupported type - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @since 1.4.0 - */ - exists(path) { - try { - getValueViaPath.call(this, this.object, path, true); - return true; - } catch (e) {} - - return false; - } + /** + * @param {array|object|Map|Set} value + * @since 1.4.0 + * @throws {Error} the parameter must not be a simple type + **/ + constructor(object) { + super(); + + if (isPrimitive(object)) { + throw new Error("the parameter must not be a simple type"); + } + + this.object = object; + this.wildCard = WILDCARD; + } + + /** + * set wildcard + * + * @param {string} wildcard + * @return {Pathfinder} + * @since 1.7.0 + */ + setWildCard(wildcard) { + validateString(wildcard); + this.wildCard = wildcard; + return this; + } + + /** + * + * @param {string|array} path + * @since 1.4.0 + * @returns {*} + * @throws {TypeError} unsupported type + * @throws {Error} the journey is not at its end + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @throws {Error} unsupported action for this data type + */ + getVia(path) { + return getValueViaPath.call(this, this.object, path); + } + + /** + * + * @param {string|array} path + * @param {*} value + * @returns {Pathfinder} + * @since 1.4.0 + * @throws {TypeError} unsupported type + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @throws {Error} unsupported action for this data type + */ + setVia(path, value) { + setValueViaPath.call(this, this.object, path, value); + return this; + } + + /** + * Delete Via Path + * + * @param {string|array} path + * @returns {Pathfinder} + * @since 1.6.0 + * @throws {TypeError} unsupported type + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @throws {Error} unsupported action for this data type + */ + deleteVia(path) { + deleteValueViaPath.call(this, this.object, path); + return this; + } + + /** + * + * @param {string|array} path + * @return {bool} + * @throws {TypeError} unsupported type + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @since 1.4.0 + */ + exists(path) { + try { + getValueViaPath.call(this, this.object, path, true); + return true; + } catch (e) {} + + return false; + } } /** @@ -179,27 +189,27 @@ class Pathfinder extends Base { * @private */ function iterate(subject, path, check) { - if (check === undefined) { - check = false; - } - validateBoolean(check); - - const result = new Map(); - - if (isArray(path)) { - path = path.join(DELIMITER); - } - - if (isObject(subject) || isArray(subject)) { - for (const [key, value] of Object.entries(subject)) { - result.set(key, getValueViaPath.call(this, value, path, check)); - } - } else { - let key = path.split(DELIMITER).shift(); - result.set(key, getValueViaPath.call(this, subject, path, check)); - } - - return result; + if (check === undefined) { + check = false; + } + validateBoolean(check); + + const result = new Map(); + + if (isArray(path)) { + path = path.join(DELIMITER); + } + + if (isObject(subject) || isArray(subject)) { + for (const [key, value] of Object.entries(subject)) { + result.set(key, getValueViaPath.call(this, value, path, check)); + } + } else { + let key = path.split(DELIMITER).shift(); + result.set(key, getValueViaPath.call(this, subject, path, check)); + } + + return result; } /** @@ -214,68 +224,71 @@ function iterate(subject, path, check) { * @private */ function getValueViaPath(subject, path, check) { - if (check === undefined) { - check = false; - } - validateBoolean(check); - - if (!(isArray(path) || isString(path))) { - throw new Error("type error: path must be a string or an array"); - } - - let parts; - if (isString(path)) { - if (path === "") { - return subject; - } - - parts = path.split(DELIMITER); - } - - let current = parts.shift(); - - if (current === this.wildCard) { - return iterate.call(this, subject, parts.join(DELIMITER), check); - } - - if (isObject(subject) || isArray(subject)) { - let anchor; - if (subject instanceof Map || subject instanceof WeakMap) { - anchor = subject.get(current); - } else if (subject instanceof Set || subject instanceof WeakSet) { - current = parseInt(current); - validateInteger(current); - anchor = [...subject]?.[current]; - } else if (typeof WeakRef === "function" && subject instanceof WeakRef) { - throw Error("unsupported action for this data type"); - } else if (isArray(subject)) { - current = parseInt(current); - validateInteger(current); - anchor = subject?.[current]; - } else { - anchor = subject?.[current]; - } - - if (isObject(anchor) || isArray(anchor)) { - return getValueViaPath.call(this, anchor, parts.join(DELIMITER), check); - } - - if (parts.length > 0) { - throw Error(`the journey is not at its end (${parts.join(DELIMITER)})`); - } - - if (check === true) { - const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(subject), current); - - if (!subject.hasOwnProperty(current) && descriptor === undefined) { - throw Error("unknown value"); - } - } - - return anchor; - } - - throw TypeError(`unsupported type ${typeof subject}`); + if (check === undefined) { + check = false; + } + validateBoolean(check); + + if (!(isArray(path) || isString(path))) { + throw new Error("type error: path must be a string or an array"); + } + + let parts; + if (isString(path)) { + if (path === "") { + return subject; + } + + parts = path.split(DELIMITER); + } + + let current = parts.shift(); + + if (current === this.wildCard) { + return iterate.call(this, subject, parts.join(DELIMITER), check); + } + + if (isObject(subject) || isArray(subject)) { + let anchor; + if (subject instanceof Map || subject instanceof WeakMap) { + anchor = subject.get(current); + } else if (subject instanceof Set || subject instanceof WeakSet) { + current = parseInt(current); + validateInteger(current); + anchor = [...subject]?.[current]; + } else if (typeof WeakRef === "function" && subject instanceof WeakRef) { + throw Error("unsupported action for this data type"); + } else if (isArray(subject)) { + current = parseInt(current); + validateInteger(current); + anchor = subject?.[current]; + } else { + anchor = subject?.[current]; + } + + if (isObject(anchor) || isArray(anchor)) { + return getValueViaPath.call(this, anchor, parts.join(DELIMITER), check); + } + + if (parts.length > 0) { + throw Error(`the journey is not at its end (${parts.join(DELIMITER)})`); + } + + if (check === true) { + const descriptor = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(subject), + current, + ); + + if (!subject.hasOwnProperty(current) && descriptor === undefined) { + throw Error("unknown value"); + } + } + + return anchor; + } + + throw TypeError(`unsupported type ${typeof subject}`); } /** @@ -291,72 +304,72 @@ function getValueViaPath(subject, path, check) { * @private */ function setValueViaPath(subject, path, value) { - if (!(isArray(path) || isString(path))) { - throw new Error("type error: path must be a string or an array"); - } - - let parts; - if (isArray(path)) { - if (path.length === 0) { - return subject; - } - - parts = path; - } else { - parts = path.split(DELIMITER); - } - - let last = parts.pop(); - let subpath = parts.join(DELIMITER); - - let stack = new Stack(); - let current = subpath; - while (true) { - try { - getValueViaPath.call(this, subject, current, true); - break; - } catch (e) {} - - stack.push(current); - parts.pop(); - current = parts.join(DELIMITER); - - if (current === "") break; - } - - while (!stack.isEmpty()) { - current = stack.pop(); - let obj = {}; - - if (!stack.isEmpty()) { - let n = stack.peek().split(DELIMITER).pop(); - if (isInteger(parseInt(n))) { - obj = []; - } - } - - setValueViaPath.call(this, subject, current, obj); - } - - let anchor = getValueViaPath.call(this, subject, subpath); - - if (!(isObject(subject) || isArray(subject))) { - throw TypeError(`unsupported type: ${typeof subject}`); - } - - if (anchor instanceof Map || anchor instanceof WeakMap) { - anchor.set(last, value); - } else if (anchor instanceof Set || anchor instanceof WeakSet) { - anchor.append(value); - } else if (typeof WeakRef === "function" && anchor instanceof WeakRef) { - throw Error("unsupported action for this data type"); - } else if (isArray(anchor)) { - last = parseInt(last); - validateInteger(last); - assignProperty(anchor, last, value); - } else { - assignProperty(anchor, last, value); - } + if (!(isArray(path) || isString(path))) { + throw new Error("type error: path must be a string or an array"); + } + + let parts; + if (isArray(path)) { + if (path.length === 0) { + return subject; + } + + parts = path; + } else { + parts = path.split(DELIMITER); + } + + let last = parts.pop(); + let subpath = parts.join(DELIMITER); + + let stack = new Stack(); + let current = subpath; + while (true) { + try { + getValueViaPath.call(this, subject, current, true); + break; + } catch (e) {} + + stack.push(current); + parts.pop(); + current = parts.join(DELIMITER); + + if (current === "") break; + } + + while (!stack.isEmpty()) { + current = stack.pop(); + let obj = {}; + + if (!stack.isEmpty()) { + let n = stack.peek().split(DELIMITER).pop(); + if (isInteger(parseInt(n))) { + obj = []; + } + } + + setValueViaPath.call(this, subject, current, obj); + } + + let anchor = getValueViaPath.call(this, subject, subpath); + + if (!(isObject(subject) || isArray(subject))) { + throw TypeError(`unsupported type: ${typeof subject}`); + } + + if (anchor instanceof Map || anchor instanceof WeakMap) { + anchor.set(last, value); + } else if (anchor instanceof Set || anchor instanceof WeakSet) { + anchor.append(value); + } else if (typeof WeakRef === "function" && anchor instanceof WeakRef) { + throw Error("unsupported action for this data type"); + } else if (isArray(anchor)) { + last = parseInt(last); + validateInteger(last); + assignProperty(anchor, last, value); + } else { + assignProperty(anchor, last, value); + } } /** @@ -366,16 +379,16 @@ function setValueViaPath(subject, path, value) { * @param {*} value */ function assignProperty(object, key, value) { - if (!object.hasOwnProperty(key)) { - object[key] = value; - return; - } + if (!object.hasOwnProperty(key)) { + object[key] = value; + return; + } - if (value === undefined) { - delete object[key]; - } + if (value === undefined) { + delete object[key]; + } - object[key] = value; + object[key] = value; } /** @@ -392,40 +405,40 @@ function assignProperty(object, key, value) { * @private */ function deleteValueViaPath(subject, path) { - if (!(isArray(path) || isString(path))) { - throw new Error("type error: path must be a string or an array"); - } - - let parts; - if (isArray(path)) { - if (path.length === 0) { - return subject; - } - - parts = path; - } else { - parts = path.split(DELIMITER); - } - - let last = parts.pop(); - const subpath = parts.join(DELIMITER); - - const anchor = getValueViaPath.call(this, subject, subpath); - - if (anchor instanceof Map) { - anchor.delete(last); - } else if ( - anchor instanceof Set || - anchor instanceof WeakMap || - anchor instanceof WeakSet || - (typeof WeakRef === "function" && anchor instanceof WeakRef) - ) { - throw Error("unsupported action for this data type"); - } else if (isArray(anchor)) { - last = parseInt(last); - validateInteger(last); - delete anchor[last]; - } else { - delete anchor[last]; - } + if (!(isArray(path) || isString(path))) { + throw new Error("type error: path must be a string or an array"); + } + + let parts; + if (isArray(path)) { + if (path.length === 0) { + return subject; + } + + parts = path; + } else { + parts = path.split(DELIMITER); + } + + let last = parts.pop(); + const subpath = parts.join(DELIMITER); + + const anchor = getValueViaPath.call(this, subject, subpath); + + if (anchor instanceof Map) { + anchor.delete(last); + } else if ( + anchor instanceof Set || + anchor instanceof WeakMap || + anchor instanceof WeakSet || + (typeof WeakRef === "function" && anchor instanceof WeakRef) + ) { + throw Error("unsupported action for this data type"); + } else if (isArray(anchor)) { + last = parseInt(last); + validateInteger(last); + delete anchor[last]; + } else { + delete anchor[last]; + } } diff --git a/source/data/pipe.mjs b/source/data/pipe.mjs index e78a29011..a97b59073 100644 --- a/source/data/pipe.mjs +++ b/source/data/pipe.mjs @@ -32,44 +32,44 @@ const DELIMITER = "|"; * @memberOf Monster.Data */ class Pipe extends Base { - /** - * @param {string} pipe a pipe consists of commands whose input and output are connected with the pipe symbol `|`. - * @throws {TypeError} - */ - constructor(pipe) { - super(); - validateString(pipe); + /** + * @param {string} pipe a pipe consists of commands whose input and output are connected with the pipe symbol `|`. + * @throws {TypeError} + */ + constructor(pipe) { + super(); + validateString(pipe); - this.pipe = pipe.split(DELIMITER).map((v) => { - return new Transformer(v); - }); - } + this.pipe = pipe.split(DELIMITER).map((v) => { + return new Transformer(v); + }); + } - /** - * @param {string} name - * @param {function} callback - * @param {object} context - * @returns {Transformer} - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not a function - */ - setCallback(name, callback, context) { - for (const [, t] of Object.entries(this.pipe)) { - t.setCallback(name, callback, context); - } + /** + * @param {string} name + * @param {function} callback + * @param {object} context + * @returns {Transformer} + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not a function + */ + setCallback(name, callback, context) { + for (const [, t] of Object.entries(this.pipe)) { + t.setCallback(name, callback, context); + } - return this; - } + return this; + } - /** - * run a pipe - * - * @param {*} value - * @returns {*} - */ - run(value) { - return this.pipe.reduce((accumulator, transformer, currentIndex, array) => { - return transformer.run(accumulator); - }, value); - } + /** + * run a pipe + * + * @param {*} value + * @returns {*} + */ + run(value) { + return this.pipe.reduce((accumulator, transformer, currentIndex, array) => { + return transformer.run(accumulator); + }, value); + } } diff --git a/source/data/transformer.mjs b/source/data/transformer.mjs index 72f7d3119..bc9d55541 100644 --- a/source/data/transformer.mjs +++ b/source/data/transformer.mjs @@ -10,14 +10,17 @@ import { Base } from "../types/base.mjs"; import { getGlobal, getGlobalObject } from "../types/global.mjs"; import { ID } from "../types/id.mjs"; import { isArray, isObject, isString, isPrimitive } from "../types/is.mjs"; -import { getDocumentTranslations, Translations } from "../i18n/translations.mjs"; import { - validateFunction, - validateInteger, - validateObject, - validatePrimitive, - validateString, - validateBoolean, + getDocumentTranslations, + Translations, +} from "../i18n/translations.mjs"; +import { + validateFunction, + validateInteger, + validateObject, + validatePrimitive, + validateString, + validateBoolean, } from "../types/validate.mjs"; import { clone } from "../util/clone.mjs"; import { Pathfinder } from "./pathfinder.mjs"; @@ -42,53 +45,53 @@ export { Transformer }; * @memberOf Monster.Data */ class Transformer extends Base { - /** - * - * @param {string} definition - */ - constructor(definition) { - super(); - this.args = disassemble(definition); - this.command = this.args.shift(); - this.callbacks = new Map(); - } - - /** - * - * @param {string} name - * @param {function} callback - * @param {object} context - * @returns {Transformer} - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not a function - */ - setCallback(name, callback, context) { - validateString(name); - validateFunction(callback); - - if (context !== undefined) { - validateObject(context); - } - - this.callbacks.set(name, { - callback: callback, - context: context, - }); - - return this; - } - - /** - * - * @param {*} value - * @returns {*} - * @throws {Error} unknown command - * @throws {TypeError} unsupported type - * @throws {Error} type not supported - */ - run(value) { - return transform.apply(this, [value]); - } + /** + * + * @param {string} definition + */ + constructor(definition) { + super(); + this.args = disassemble(definition); + this.command = this.args.shift(); + this.callbacks = new Map(); + } + + /** + * + * @param {string} name + * @param {function} callback + * @param {object} context + * @returns {Transformer} + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not a function + */ + setCallback(name, callback, context) { + validateString(name); + validateFunction(callback); + + if (context !== undefined) { + validateObject(context); + } + + this.callbacks.set(name, { + callback: callback, + context: context, + }); + + return this; + } + + /** + * + * @param {*} value + * @returns {*} + * @throws {Error} unknown command + * @throws {TypeError} unsupported type + * @throws {Error} type not supported + */ + run(value) { + return transform.apply(this, [value]); + } } /** @@ -98,41 +101,41 @@ class Transformer extends Base { * @private */ function disassemble(command) { - validateString(command); - - let placeholder = new Map(); - const regex = /((?<pattern>\\(?<char>.)){1})/gim; - - // The separator for args must be escaped - // undefined string which should not occur normally and is also not a regex - let result = command.matchAll(regex); - - for (let m of result) { - let g = m?.["groups"]; - if (!isObject(g)) { - continue; - } - - let p = g?.["pattern"]; - let c = g?.["char"]; - - if (p && c) { - let r = `__${new ID().toString()}__`; - placeholder.set(r, c); - command = command.replace(p, r); - } - } - let parts = command.split(":"); - - parts = parts.map(function (value) { - let v = value.trim(); - for (let k of placeholder) { - v = v.replace(k[0], k[1]); - } - return v; - }); - - return parts; + validateString(command); + + let placeholder = new Map(); + const regex = /((?<pattern>\\(?<char>.)){1})/gim; + + // The separator for args must be escaped + // undefined string which should not occur normally and is also not a regex + let result = command.matchAll(regex); + + for (let m of result) { + let g = m?.["groups"]; + if (!isObject(g)) { + continue; + } + + let p = g?.["pattern"]; + let c = g?.["char"]; + + if (p && c) { + let r = `__${new ID().toString()}__`; + placeholder.set(r, c); + command = command.replace(p, r); + } + } + let parts = command.split(":"); + + parts = parts.map(function (value) { + let v = value.trim(); + for (let k of placeholder) { + v = v.replace(k[0], k[1]); + } + return v; + }); + + return parts; } /** @@ -143,12 +146,12 @@ function disassemble(command) { * @private */ function convertToString(value) { - if (isObject(value) && value.hasOwnProperty("toString")) { - value = value.toString(); - } + if (isObject(value) && value.hasOwnProperty("toString")) { + value = value.toString(); + } - validateString(value); - return value; + validateString(value); + return value; } /** @@ -162,636 +165,649 @@ function convertToString(value) { * @throws {Error} missing key parameter */ function transform(value) { - const console = getGlobalObject("console"); - - let args = clone(this.args); - let key; - let defaultValue; - - let translations; - let date; - let locale; - let timestamp; - let map; - let keyValue; - - switch (this.command) { - case "static": - return this.args.join(":"); - - case "tolower": - case "strtolower": - case "tolowercase": - validateString(value); - return value.toLowerCase(); - - case "contains": - if (isString(value)) { - return value.includes(args[0]); - } - - if (isArray(value)) { - return value.includes(args[0]); - } - - if (isObject(value)) { - return value.hasOwnProperty(args[0]); - } - - return false; - - case "has-entries": - case "hasentries": - if (isObject(value)) { - return Object.keys(value).length > 0; - } - - if (isArray(value)) { - return value.length > 0; - } - - return false; - - case "isundefined": - case "is-undefined": - return value === undefined; - - case "isnull": - case "is-null": - return value === null; - - case "isset": - case "is-set": - return value !== undefined && value !== null; - - case "isnumber": - case "is-number": - return isPrimitive(value) && !isNaN(value); - - case "isinteger": - case "is-integer": - return isPrimitive(value) && !isNaN(value) && value % 1 === 0; - - case "isfloat": - case "is-float": - return isPrimitive(value) && !isNaN(value) && value % 1 !== 0; - - case "isobject": - case "is-object": - return isObject(value); - - case "isarray": - case "is-array": - return Array.isArray(value); - - case "not": - validateBoolean(value); - return !value; - - case "toupper": - case "strtoupper": - case "touppercase": - validateString(value); - return value.toUpperCase(); - - case "tostring": - return `${value}`; - - case "tointeger": - let n = parseInt(value); - validateInteger(n); - return n; - - case "to-json": - case "tojson": - return JSON.stringify(value); - - case "from-json": - case "fromjson": - return JSON.parse(value); - - case "trim": - validateString(value); - return value.trim(); - - case "rawurlencode": - validateString(value); - return encodeURIComponent(value) - .replace(/!/g, "%21") - .replace(/'/g, "%27") - .replace(/\(/g, "%28") - .replace(/\)/g, "%29") - .replace(/\*/g, "%2A"); - - case "call": - /** - * callback-definition - * function callback(value, ...args) { - * return value; - * } - */ - - let callback; - let callbackName = args.shift(); - let context = getGlobal(); - - if (isObject(value) && value.hasOwnProperty(callbackName)) { - callback = value[callbackName]; - } else if (this.callbacks.has(callbackName)) { - let s = this.callbacks.get(callbackName); - callback = s?.["callback"]; - context = s?.["context"]; - } else if (typeof window === "object" && window.hasOwnProperty(callbackName)) { - callback = window[callbackName]; - } - validateFunction(callback); - - args.unshift(value); - return callback.call(context, ...args); - - case "plain": - case "plaintext": - validateString(value); - let doc = new DOMParser().parseFromString(value, "text/html"); - return doc.body.textContent || ""; - - case "if": - case "?": - validatePrimitive(value); - - let trueStatement = args.shift() || undefined; - let falseStatement = args.shift() || undefined; - - if (trueStatement === "value") { - trueStatement = value; - } - if (trueStatement === "\\value") { - trueStatement = "value"; - } - - if (trueStatement === "\\undefined") { - trueStatement = undefined; - } - - if (trueStatement === "\\null") { - trueStatement = null; - } - - if (falseStatement === "value") { - falseStatement = value; - } - if (falseStatement === "\\value") { - falseStatement = "value"; - } - - if (falseStatement === "\\undefined") { - falseStatement = undefined; - } - - if (falseStatement === "\\null") { - falseStatement = null; - } - - let condition = - (value !== undefined && value !== "" && value !== "off" && value !== "false" && value !== false) || - value === "on" || - value === "true" || - value === true; - return condition ? trueStatement : falseStatement; - - case "ucfirst": - validateString(value); - - let firstchar = value.charAt(0).toUpperCase(); - return firstchar + value.substr(1); - case "ucwords": - validateString(value); - - return value.replace(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, function (v) { - return v.toUpperCase(); - }); - - case "count": - case "length": - if ((isString(value) || isObject(value) || isArray(value)) && value.hasOwnProperty("length")) { - return value.length; - } - - throw new TypeError(`unsupported type ${typeof value}`); - - case "to-base64": - case "btoa": - case "base64": - return btoa(convertToString(value)); - - case "atob": - case "from-base64": - return atob(convertToString(value)); - - case "empty": - return ""; - - case "undefined": - return undefined; - - case "debug": - if (isObject(console)) { - console.log(value); - } - - return value; - - case "prefix": - validateString(value); - let prefix = args?.[0]; - return prefix + value; - - case "suffix": - validateString(value); - let suffix = args?.[0]; - return value + suffix; - - case "uniqid": - return new ID().toString(); - - case "first-key": - case "last-key": - case "nth-last-key": - case "nth-key": - if (!isObject(value)) { - throw new Error("type not supported"); - } - - const keys = Object.keys(value).sort(); - - if (this.command === "first-key") { - key = 0; - } else if (this.command === "last-key") { - key = keys.length - 1; - } else { - key = validateInteger(parseInt(args.shift())); - - if (this.command === "nth-last-key") { - key = keys.length - key - 1; - } - } - - defaultValue = args.shift() || ""; - - let useKey = keys?.[key]; - - if (value?.[useKey]) { - return value?.[useKey]; - } - - return defaultValue; - - case "key": - case "property": - case "index": - key = args.shift() || undefined; - - if (key === undefined) { - throw new Error("missing key parameter"); - } - - defaultValue = args.shift() || undefined; - - if (value instanceof Map) { - if (!value.has(key)) { - return defaultValue; - } - return value.get(key); - } - - if (isObject(value) || isArray(value)) { - if (value?.[key]) { - return value?.[key]; - } - - return defaultValue; - } - - throw new Error("type not supported"); - - case "path-exists": - key = args.shift(); - if (key === undefined) { - throw new Error("missing key parameter"); - } - - return new Pathfinder(value).exists(key); - - case "concat": - let pf2 = new Pathfinder(value); - let concat = ""; - while (args.length > 0) { - key = args.shift(); - if (key === undefined) { - throw new Error("missing key parameter"); - } - - // add empty strings - if (isString(key) && key.trim() === "") { - concat += key; - continue; - } - - if (!pf2.exists(key)) { - concat += key; - continue; - } - let v = pf2.getVia(key); - if (!isPrimitive(v)) { - throw new Error("value is not primitive"); - } - - concat += v; - } - - return concat; - case "path": - key = args.shift(); - if (key === undefined) { - throw new Error("missing key parameter"); - } - - let pf = new Pathfinder(value); - - if (!pf.exists(key)) { - return undefined; - } - - return pf.getVia(key); - - case "substring": - validateString(value); - - let start = parseInt(args[0]) || 0; - let end = (parseInt(args[1]) || 0) + start; - - return value.substring(start, end); - - case "nop": - return value; - - case "??": - case "default": - if (value !== undefined && value !== null) { - return value; - } - - defaultValue = args.shift(); - let defaultType = args.shift(); - if (defaultType === undefined) { - defaultType = "string"; - } - - switch (defaultType) { - case "int": - case "integer": - return parseInt(defaultValue); - case "float": - return parseFloat(defaultValue); - case "undefined": - return undefined; - case "bool": - case "boolean": - defaultValue = defaultValue.toLowerCase(); - return ( - (defaultValue !== "undefined" && - defaultValue !== "" && - defaultValue !== "off" && - defaultValue !== "false" && - defaultValue !== "false") || - defaultValue === "on" || - defaultValue === "true" || - defaultValue === "true" - ); - case "string": - return `${defaultValue}`; - case "object": - return JSON.parse(atob(defaultValue)); - } - - throw new Error("type not supported"); - - case "map": - map = new Map(); - while (args.length > 0) { - keyValue = args.shift(); - if (keyValue === undefined) { - throw new Error("missing key parameter"); - } - - keyValue = keyValue.split("="); - map.set(keyValue[0], keyValue[1]); - } - - return map.get(value); - - case "equals": - if (args.length === 0) { - throw new Error("missing value parameter"); - } - - validatePrimitive(value); - - const equalsValue = args.shift(); - - /** - * The history of “typeof null” - * https://2ality.com/2013/10/typeof-null.html - * In JavaScript, typeof null is 'object', which incorrectly suggests - * that null is an object. - */ - if (value === null) { - if (equalsValue === "null") { - return true; - } - return false; - } - - const typeOfValue = typeof value; - - switch (typeOfValue) { - case "string": - return value === equalsValue; - case "number": - return value === parseFloat(equalsValue); - case "boolean": - return value === (equalsValue === "true" || equalsValue === "on"); - case "undefined": - return equalsValue === "undefined"; - default: - throw new Error("type not supported"); - } - - case "money": - case "currency": - try { - locale = getLocaleOfDocument(); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - const currency = value.substring(0, 3); - if (!currency) { - throw new Error("missing currency parameter"); - } - - const maximumFractionDigits = args?.[0] || 2; - const roundingIncrement = args?.[1] || 5; - - const nf = new Intl.NumberFormat(locale, { - style: "currency", - currency: currency, - maximumFractionDigits: maximumFractionDigits, - roundingIncrement: roundingIncrement, - }); - - return nf.format(value.substring(3)); - - case "timestamp": - date = new Date(value); - timestamp = date.getTime(); - if (isNaN(timestamp)) { - throw new Error("invalid date"); - } - return timestamp; - - case "time": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - try { - locale = getLocaleOfDocument(); - return date.toLocaleTimeString(locale); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "datetimeformat": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - const options = { - dateStyle: args.shift() || "medium", - timeStyle: args.shift() || "medium", - }; - - try { - locale = getLocaleOfDocument(); - return new Intl.DateTimeFormat(locale, options).format(date); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "datetime": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - try { - locale = getLocaleOfDocument(); - return date.toLocaleString(locale); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "date": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - try { - locale = getLocaleOfDocument(); - return date.toLocaleDateString(locale); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "year": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getFullYear(); - - case "month": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getMonth() + 1; - - case "day": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getDate(); - - case "weekday": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getDay(); - - case "hour": - case "hours": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getHours(); - - case "minute": - case "minutes": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getMinutes(); - - case "second": - case "seconds": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getSeconds(); - - case "i18n": - case "translation": - translations = getDocumentTranslations(); - if (!(translations instanceof Translations)) { - throw new Error("missing translations"); - } - - key = args.shift() || undefined; - if (key === undefined) { - key = value; - } - - defaultValue = args.shift() || undefined; - return translations.getText(key, defaultValue); - - default: - throw new Error(`unknown command ${this.command}`); - } + const console = getGlobalObject("console"); + + let args = clone(this.args); + let key; + let defaultValue; + + let translations; + let date; + let locale; + let timestamp; + let map; + let keyValue; + + switch (this.command) { + case "static": + return this.args.join(":"); + + case "tolower": + case "strtolower": + case "tolowercase": + validateString(value); + return value.toLowerCase(); + + case "contains": + if (isString(value)) { + return value.includes(args[0]); + } + + if (isArray(value)) { + return value.includes(args[0]); + } + + if (isObject(value)) { + return value.hasOwnProperty(args[0]); + } + + return false; + + case "has-entries": + case "hasentries": + if (isObject(value)) { + return Object.keys(value).length > 0; + } + + if (isArray(value)) { + return value.length > 0; + } + + return false; + + case "isundefined": + case "is-undefined": + return value === undefined; + + case "isnull": + case "is-null": + return value === null; + + case "isset": + case "is-set": + return value !== undefined && value !== null; + + case "isnumber": + case "is-number": + return isPrimitive(value) && !isNaN(value); + + case "isinteger": + case "is-integer": + return isPrimitive(value) && !isNaN(value) && value % 1 === 0; + + case "isfloat": + case "is-float": + return isPrimitive(value) && !isNaN(value) && value % 1 !== 0; + + case "isobject": + case "is-object": + return isObject(value); + + case "isarray": + case "is-array": + return Array.isArray(value); + + case "not": + validateBoolean(value); + return !value; + + case "toupper": + case "strtoupper": + case "touppercase": + validateString(value); + return value.toUpperCase(); + + case "tostring": + return `${value}`; + + case "tointeger": + let n = parseInt(value); + validateInteger(n); + return n; + + case "to-json": + case "tojson": + return JSON.stringify(value); + + case "from-json": + case "fromjson": + return JSON.parse(value); + + case "trim": + validateString(value); + return value.trim(); + + case "rawurlencode": + validateString(value); + return encodeURIComponent(value) + .replace(/!/g, "%21") + .replace(/'/g, "%27") + .replace(/\(/g, "%28") + .replace(/\)/g, "%29") + .replace(/\*/g, "%2A"); + + case "call": + /** + * callback-definition + * function callback(value, ...args) { + * return value; + * } + */ + + let callback; + let callbackName = args.shift(); + let context = getGlobal(); + + if (isObject(value) && value.hasOwnProperty(callbackName)) { + callback = value[callbackName]; + } else if (this.callbacks.has(callbackName)) { + let s = this.callbacks.get(callbackName); + callback = s?.["callback"]; + context = s?.["context"]; + } else if ( + typeof window === "object" && + window.hasOwnProperty(callbackName) + ) { + callback = window[callbackName]; + } + validateFunction(callback); + + args.unshift(value); + return callback.call(context, ...args); + + case "plain": + case "plaintext": + validateString(value); + let doc = new DOMParser().parseFromString(value, "text/html"); + return doc.body.textContent || ""; + + case "if": + case "?": + validatePrimitive(value); + + let trueStatement = args.shift() || undefined; + let falseStatement = args.shift() || undefined; + + if (trueStatement === "value") { + trueStatement = value; + } + if (trueStatement === "\\value") { + trueStatement = "value"; + } + + if (trueStatement === "\\undefined") { + trueStatement = undefined; + } + + if (trueStatement === "\\null") { + trueStatement = null; + } + + if (falseStatement === "value") { + falseStatement = value; + } + if (falseStatement === "\\value") { + falseStatement = "value"; + } + + if (falseStatement === "\\undefined") { + falseStatement = undefined; + } + + if (falseStatement === "\\null") { + falseStatement = null; + } + + let condition = + (value !== undefined && + value !== "" && + value !== "off" && + value !== "false" && + value !== false) || + value === "on" || + value === "true" || + value === true; + return condition ? trueStatement : falseStatement; + + case "ucfirst": + validateString(value); + + let firstchar = value.charAt(0).toUpperCase(); + return firstchar + value.substr(1); + case "ucwords": + validateString(value); + + return value.replace( + /^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, + function (v) { + return v.toUpperCase(); + }, + ); + + case "count": + case "length": + if ( + (isString(value) || isObject(value) || isArray(value)) && + value.hasOwnProperty("length") + ) { + return value.length; + } + + throw new TypeError(`unsupported type ${typeof value}`); + + case "to-base64": + case "btoa": + case "base64": + return btoa(convertToString(value)); + + case "atob": + case "from-base64": + return atob(convertToString(value)); + + case "empty": + return ""; + + case "undefined": + return undefined; + + case "debug": + if (isObject(console)) { + console.log(value); + } + + return value; + + case "prefix": + validateString(value); + let prefix = args?.[0]; + return prefix + value; + + case "suffix": + validateString(value); + let suffix = args?.[0]; + return value + suffix; + + case "uniqid": + return new ID().toString(); + + case "first-key": + case "last-key": + case "nth-last-key": + case "nth-key": + if (!isObject(value)) { + throw new Error("type not supported"); + } + + const keys = Object.keys(value).sort(); + + if (this.command === "first-key") { + key = 0; + } else if (this.command === "last-key") { + key = keys.length - 1; + } else { + key = validateInteger(parseInt(args.shift())); + + if (this.command === "nth-last-key") { + key = keys.length - key - 1; + } + } + + defaultValue = args.shift() || ""; + + let useKey = keys?.[key]; + + if (value?.[useKey]) { + return value?.[useKey]; + } + + return defaultValue; + + case "key": + case "property": + case "index": + key = args.shift() || undefined; + + if (key === undefined) { + throw new Error("missing key parameter"); + } + + defaultValue = args.shift() || undefined; + + if (value instanceof Map) { + if (!value.has(key)) { + return defaultValue; + } + return value.get(key); + } + + if (isObject(value) || isArray(value)) { + if (value?.[key]) { + return value?.[key]; + } + + return defaultValue; + } + + throw new Error("type not supported"); + + case "path-exists": + key = args.shift(); + if (key === undefined) { + throw new Error("missing key parameter"); + } + + return new Pathfinder(value).exists(key); + + case "concat": + let pf2 = new Pathfinder(value); + let concat = ""; + while (args.length > 0) { + key = args.shift(); + if (key === undefined) { + throw new Error("missing key parameter"); + } + + // add empty strings + if (isString(key) && key.trim() === "") { + concat += key; + continue; + } + + if (!pf2.exists(key)) { + concat += key; + continue; + } + let v = pf2.getVia(key); + if (!isPrimitive(v)) { + throw new Error("value is not primitive"); + } + + concat += v; + } + + return concat; + case "path": + key = args.shift(); + if (key === undefined) { + throw new Error("missing key parameter"); + } + + let pf = new Pathfinder(value); + + if (!pf.exists(key)) { + return undefined; + } + + return pf.getVia(key); + + case "substring": + validateString(value); + + let start = parseInt(args[0]) || 0; + let end = (parseInt(args[1]) || 0) + start; + + return value.substring(start, end); + + case "nop": + return value; + + case "??": + case "default": + if (value !== undefined && value !== null) { + return value; + } + + defaultValue = args.shift(); + let defaultType = args.shift(); + if (defaultType === undefined) { + defaultType = "string"; + } + + switch (defaultType) { + case "int": + case "integer": + return parseInt(defaultValue); + case "float": + return parseFloat(defaultValue); + case "undefined": + return undefined; + case "bool": + case "boolean": + defaultValue = defaultValue.toLowerCase(); + return ( + (defaultValue !== "undefined" && + defaultValue !== "" && + defaultValue !== "off" && + defaultValue !== "false" && + defaultValue !== "false") || + defaultValue === "on" || + defaultValue === "true" || + defaultValue === "true" + ); + case "string": + return `${defaultValue}`; + case "object": + return JSON.parse(atob(defaultValue)); + } + + throw new Error("type not supported"); + + case "map": + map = new Map(); + while (args.length > 0) { + keyValue = args.shift(); + if (keyValue === undefined) { + throw new Error("missing key parameter"); + } + + keyValue = keyValue.split("="); + map.set(keyValue[0], keyValue[1]); + } + + return map.get(value); + + case "equals": + if (args.length === 0) { + throw new Error("missing value parameter"); + } + + validatePrimitive(value); + + const equalsValue = args.shift(); + + /** + * The history of “typeof null” + * https://2ality.com/2013/10/typeof-null.html + * In JavaScript, typeof null is 'object', which incorrectly suggests + * that null is an object. + */ + if (value === null) { + if (equalsValue === "null") { + return true; + } + return false; + } + + const typeOfValue = typeof value; + + switch (typeOfValue) { + case "string": + return value === equalsValue; + case "number": + return value === parseFloat(equalsValue); + case "boolean": + return value === (equalsValue === "true" || equalsValue === "on"); + case "undefined": + return equalsValue === "undefined"; + default: + throw new Error("type not supported"); + } + + case "money": + case "currency": + try { + locale = getLocaleOfDocument(); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + const currency = value.substring(0, 3); + if (!currency) { + throw new Error("missing currency parameter"); + } + + const maximumFractionDigits = args?.[0] || 2; + const roundingIncrement = args?.[1] || 5; + + const nf = new Intl.NumberFormat(locale, { + style: "currency", + currency: currency, + maximumFractionDigits: maximumFractionDigits, + roundingIncrement: roundingIncrement, + }); + + return nf.format(value.substring(3)); + + case "timestamp": + date = new Date(value); + timestamp = date.getTime(); + if (isNaN(timestamp)) { + throw new Error("invalid date"); + } + return timestamp; + + case "time": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + try { + locale = getLocaleOfDocument(); + return date.toLocaleTimeString(locale); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "datetimeformat": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + const options = { + dateStyle: args.shift() || "medium", + timeStyle: args.shift() || "medium", + }; + + try { + locale = getLocaleOfDocument(); + return new Intl.DateTimeFormat(locale, options).format(date); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "datetime": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + try { + locale = getLocaleOfDocument(); + return date.toLocaleString(locale); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "date": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + try { + locale = getLocaleOfDocument(); + return date.toLocaleDateString(locale); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "year": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getFullYear(); + + case "month": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getMonth() + 1; + + case "day": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getDate(); + + case "weekday": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getDay(); + + case "hour": + case "hours": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getHours(); + + case "minute": + case "minutes": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getMinutes(); + + case "second": + case "seconds": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getSeconds(); + + case "i18n": + case "translation": + translations = getDocumentTranslations(); + if (!(translations instanceof Translations)) { + throw new Error("missing translations"); + } + + key = args.shift() || undefined; + if (key === undefined) { + key = value; + } + + defaultValue = args.shift() || undefined; + return translations.getText(key, defaultValue); + + default: + throw new Error(`unknown command ${this.command}`); + } } diff --git a/source/dom/assembler.mjs b/source/dom/assembler.mjs index 146702aea..ee512cac9 100644 --- a/source/dom/assembler.mjs +++ b/source/dom/assembler.mjs @@ -30,52 +30,52 @@ const ATTRIBUTEPREFIX = "data-monster-"; * @summary Allows you to build an html fragment */ class Assembler extends Base { - /** - * @param {DocumentFragment} fragment - * @throws {TypeError} value is not an instance of - * @throws {TypeError} value is not a function - * @throws {Error} the function is not defined - */ - constructor(fragment) { - super(); - this.attributePrefix = ATTRIBUTEPREFIX; - validateInstance(fragment, getGlobalFunction("DocumentFragment")); - this.fragment = fragment; - } + /** + * @param {DocumentFragment} fragment + * @throws {TypeError} value is not an instance of + * @throws {TypeError} value is not a function + * @throws {Error} the function is not defined + */ + constructor(fragment) { + super(); + this.attributePrefix = ATTRIBUTEPREFIX; + validateInstance(fragment, getGlobalFunction("DocumentFragment")); + this.fragment = fragment; + } - /** - * - * @param {string} prefix - * @returns {Assembler} - * @throws {TypeError} value is not a string - */ - setAttributePrefix(prefix) { - validateString(prefix); - this.attributePrefix = prefix; - return this; - } + /** + * + * @param {string} prefix + * @returns {Assembler} + * @throws {TypeError} value is not a string + */ + setAttributePrefix(prefix) { + validateString(prefix); + this.attributePrefix = prefix; + return this; + } - /** - * - * @returns {string} - */ - getAttributePrefix() { - return this.attributePrefix; - } + /** + * + * @returns {string} + */ + getAttributePrefix() { + return this.attributePrefix; + } - /** - * - * @param {ProxyObserver|undefined} data - * @return {DocumentFragment} - * @throws {TypeError} value is not an instance of - */ - createDocumentFragment(data) { - if (data === undefined) { - data = new ProxyObserver({}); - } + /** + * + * @param {ProxyObserver|undefined} data + * @return {DocumentFragment} + * @throws {TypeError} value is not an instance of + */ + createDocumentFragment(data) { + if (data === undefined) { + data = new ProxyObserver({}); + } - validateInstance(data, ProxyObserver); - let fragment = this.fragment.cloneNode(true); - return fragment; - } + validateInstance(data, ProxyObserver); + let fragment = this.fragment.cloneNode(true); + return fragment; + } } diff --git a/source/dom/attributes.mjs b/source/dom/attributes.mjs index ff4232861..c1ceacb64 100644 --- a/source/dom/attributes.mjs +++ b/source/dom/attributes.mjs @@ -7,23 +7,27 @@ import { getGlobalFunction } from "../types/global.mjs"; import { TokenList } from "../types/tokenlist.mjs"; -import { validateInstance, validateString, validateSymbol } from "../types/validate.mjs"; +import { + validateInstance, + validateString, + validateSymbol, +} from "../types/validate.mjs"; import { ATTRIBUTE_OBJECTLINK } from "./constants.mjs"; export { - findClosestObjectLink, - addToObjectLink, - removeObjectLink, - hasObjectLink, - getLinkedObjects, - toggleAttributeToken, - addAttributeToken, - removeAttributeToken, - containsAttributeToken, - replaceAttributeToken, - clearAttributeTokens, - findClosestByAttribute, - findClosestByClass, + findClosestObjectLink, + addToObjectLink, + removeObjectLink, + hasObjectLink, + getLinkedObjects, + toggleAttributeToken, + addAttributeToken, + removeAttributeToken, + containsAttributeToken, + replaceAttributeToken, + clearAttributeTokens, + findClosestByAttribute, + findClosestByClass, }; /** @@ -41,7 +45,7 @@ export { * @throws {TypeError} value is not an instance of HTMLElement */ function findClosestObjectLink(element) { - return findClosestByAttribute(element, ATTRIBUTE_OBJECTLINK); + return findClosestByAttribute(element, ATTRIBUTE_OBJECTLINK); } /** @@ -57,16 +61,16 @@ function findClosestObjectLink(element) { * @return {boolean} */ function addToObjectLink(element, symbol, object) { - validateInstance(element, HTMLElement); - validateSymbol(symbol); + validateInstance(element, HTMLElement); + validateSymbol(symbol); - if (element?.[symbol] === undefined) { - element[symbol] = new Set(); - } + if (element?.[symbol] === undefined) { + element[symbol] = new Set(); + } - addAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - element[symbol].add(object); - return element; + addAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); + element[symbol].add(object); + return element; } /** @@ -81,16 +85,16 @@ function addToObjectLink(element, symbol, object) { * @return {boolean} */ function removeObjectLink(element, symbol) { - validateInstance(element, HTMLElement); - validateSymbol(symbol); + validateInstance(element, HTMLElement); + validateSymbol(symbol); - if (element?.[symbol] === undefined) { - return element; - } + if (element?.[symbol] === undefined) { + return element; + } - removeAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - delete element[symbol]; - return element; + removeAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); + delete element[symbol]; + return element; } /** @@ -105,14 +109,18 @@ function removeObjectLink(element, symbol) { * @return {boolean} */ function hasObjectLink(element, symbol) { - validateInstance(element, HTMLElement); - validateSymbol(symbol); - - if (element?.[symbol] === undefined) { - return false; - } - - return containsAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); + validateInstance(element, HTMLElement); + validateSymbol(symbol); + + if (element?.[symbol] === undefined) { + return false; + } + + return containsAttributeToken( + element, + ATTRIBUTE_OBJECTLINK, + symbol.toString(), + ); } /** @@ -133,14 +141,14 @@ function hasObjectLink(element, symbol) { * @throws {Error} there is no object link for symbol */ function getLinkedObjects(element, symbol) { - validateInstance(element, HTMLElement); - validateSymbol(symbol); + validateInstance(element, HTMLElement); + validateSymbol(symbol); - if (element?.[symbol] === undefined) { - throw new Error(`there is no object link for ${symbol.toString()}`); - } + if (element?.[symbol] === undefined) { + throw new Error(`there is no object link for ${symbol.toString()}`); + } - return element?.[symbol][Symbol.iterator](); + return element?.[symbol][Symbol.iterator](); } /** @@ -158,18 +166,21 @@ function getLinkedObjects(element, symbol) { * @return {HTMLElement} */ function toggleAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token); - validateString(key); + validateInstance(element, HTMLElement); + validateString(token); + validateString(key); - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } + if (!element.hasAttribute(key)) { + element.setAttribute(key, token); + return element; + } - element.setAttribute(key, new TokenList(element.getAttribute(key)).toggle(token).toString()); + element.setAttribute( + key, + new TokenList(element.getAttribute(key)).toggle(token).toString(), + ); - return element; + return element; } /** @@ -185,18 +196,21 @@ function toggleAttributeToken(element, key, token) { * @return {HTMLElement} */ function addAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token); - validateString(key); + validateInstance(element, HTMLElement); + validateString(token); + validateString(key); - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } + if (!element.hasAttribute(key)) { + element.setAttribute(key, token); + return element; + } - element.setAttribute(key, new TokenList(element.getAttribute(key)).add(token).toString()); + element.setAttribute( + key, + new TokenList(element.getAttribute(key)).add(token).toString(), + ); - return element; + return element; } /** @@ -214,17 +228,20 @@ function addAttributeToken(element, key, token) { * @return {HTMLElement} */ function removeAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token); - validateString(key); + validateInstance(element, HTMLElement); + validateString(token); + validateString(key); - if (!element.hasAttribute(key)) { - return element; - } + if (!element.hasAttribute(key)) { + return element; + } - element.setAttribute(key, new TokenList(element.getAttribute(key)).remove(token).toString()); + element.setAttribute( + key, + new TokenList(element.getAttribute(key)).remove(token).toString(), + ); - return element; + return element; } /** @@ -242,15 +259,15 @@ function removeAttributeToken(element, key, token) { * @return {boolean} */ function containsAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token); - validateString(key); + validateInstance(element, HTMLElement); + validateString(token); + validateString(key); - if (!element.hasAttribute(key)) { - return false; - } + if (!element.hasAttribute(key)) { + return false; + } - return new TokenList(element.getAttribute(key)).contains(token); + return new TokenList(element.getAttribute(key)).contains(token); } /** @@ -267,18 +284,21 @@ function containsAttributeToken(element, key, token) { * @return {HTMLElement} */ function replaceAttributeToken(element, key, from, to) { - validateInstance(element, HTMLElement); - validateString(from); - validateString(to); - validateString(key); + validateInstance(element, HTMLElement); + validateString(from); + validateString(to); + validateString(key); - if (!element.hasAttribute(key)) { - return element; - } + if (!element.hasAttribute(key)) { + return element; + } - element.setAttribute(key, new TokenList(element.getAttribute(key)).replace(from, to).toString()); + element.setAttribute( + key, + new TokenList(element.getAttribute(key)).replace(from, to).toString(), + ); - return element; + return element; } /** @@ -293,16 +313,16 @@ function replaceAttributeToken(element, key, from, to) { * @return {HTMLElement} */ function clearAttributeTokens(element, key) { - validateInstance(element, HTMLElement); - validateString(key); + validateInstance(element, HTMLElement); + validateString(key); - if (!element.hasAttribute(key)) { - return element; - } + if (!element.hasAttribute(key)) { + return element; + } - element.setAttribute(key, ""); + element.setAttribute(key, ""); - return element; + return element; } /** @@ -335,25 +355,25 @@ function clearAttributeTokens(element, key) { * @summary find closest node */ function findClosestByAttribute(element, key, value) { - validateInstance(element, getGlobalFunction("HTMLElement")); - - if (element.hasAttribute(key)) { - if (value === undefined) { - return element; - } - - if (element.getAttribute(key) === value) { - return element; - } - } - - let selector = validateString(key); - if (value !== undefined) selector += `=${validateString(value)}`; - let result = element.closest(`[${selector}]`); - if (result instanceof HTMLElement) { - return result; - } - return undefined; + validateInstance(element, getGlobalFunction("HTMLElement")); + + if (element.hasAttribute(key)) { + if (value === undefined) { + return element; + } + + if (element.getAttribute(key) === value) { + return element; + } + } + + let selector = validateString(key); + if (value !== undefined) selector += `=${validateString(value)}`; + let result = element.closest(`[${selector}]`); + if (result instanceof HTMLElement) { + return result; + } + return undefined; } /** @@ -388,16 +408,16 @@ function findClosestByAttribute(element, key, value) { * @summary find closest node */ function findClosestByClass(element, className) { - validateInstance(element, getGlobalFunction("HTMLElement")); + validateInstance(element, getGlobalFunction("HTMLElement")); - if (element?.classList?.contains(validateString(className))) { - return element; - } + if (element?.classList?.contains(validateString(className))) { + return element; + } - let result = element.closest(`.${className}`); - if (result instanceof HTMLElement) { - return result; - } + let result = element.closest(`.${className}`); + if (result instanceof HTMLElement) { + return result; + } - return undefined; + return undefined; } diff --git a/source/dom/constants.mjs b/source/dom/constants.mjs index a3fe1b992..0c7aae9e1 100644 --- a/source/dom/constants.mjs +++ b/source/dom/constants.mjs @@ -6,62 +6,62 @@ */ export { - DEFAULT_THEME, - ATTRIBUTE_PREFIX, - ATTRIBUTE_OPTIONS, - ATTRIBUTE_OPTIONS_SELECTOR, - ATTRIBUTE_THEME_PREFIX, - ATTRIBUTE_THEME_NAME, - ATTRIBUTE_UPDATER_ATTRIBUTES, - ATTRIBUTE_UPDATER_SELECT_THIS, - ATTRIBUTE_UPDATER_REPLACE, - ATTRIBUTE_UPDATER_INSERT, - ATTRIBUTE_UPDATER_INSERT_REFERENCE, - ATTRIBUTE_UPDATER_REMOVE, - ATTRIBUTE_UPDATER_BIND, - ATTRIBUTE_TEMPLATE_PREFIX, - ATTRIBUTE_ROLE, - ATTRIBUTE_DISABLED, - ATTRIBUTE_VALUE, - ATTRIBUTE_OBJECTLINK, - ATTRIBUTE_ERRORMESSAGE, - TAG_SCRIPT, - TAG_STYLE, - TAG_LINK, - ATTRIBUTE_ID, - ATTRIBUTE_CLASS, - ATTRIBUTE_TITLE, - ATTRIBUTE_SRC, - ATTRIBUTE_HREF, - ATTRIBUTE_TYPE, - ATTRIBUTE_NONCE, - ATTRIBUTE_TRANSLATE, - ATTRIBUTE_TABINDEX, - ATTRIBUTE_SPELLCHECK, - ATTRIBUTE_SLOT, - ATTRIBUTE_PART, - ATTRIBUTE_LANG, - ATTRIBUTE_ITEMTYPE, - ATTRIBUTE_ITEMSCOPE, - ATTRIBUTE_ITEMREF, - ATTRIBUTE_ITEMID, - ATTRIBUTE_ITEMPROP, - ATTRIBUTE_IS, - ATTRIBUTE_INPUTMODE, - ATTRIBUTE_ACCESSKEY, - ATTRIBUTE_AUTOCAPITALIZE, - ATTRIBUTE_AUTOFOCUS, - ATTRIBUTE_CONTENTEDITABLE, - ATTRIBUTE_DIR, - ATTRIBUTE_DRAGGABLE, - ATTRIBUTE_ENTERKEYHINT, - ATTRIBUTE_EXPORTPARTS, - ATTRIBUTE_HIDDEN, - objectUpdaterLinkSymbol, - customElementUpdaterLinkSymbol, - initControlCallbackName, - ATTRIBUTE_SCRIPT_HOST, - ATTRIBUTE_INIT_CALLBACK, + DEFAULT_THEME, + ATTRIBUTE_PREFIX, + ATTRIBUTE_OPTIONS, + ATTRIBUTE_OPTIONS_SELECTOR, + ATTRIBUTE_THEME_PREFIX, + ATTRIBUTE_THEME_NAME, + ATTRIBUTE_UPDATER_ATTRIBUTES, + ATTRIBUTE_UPDATER_SELECT_THIS, + ATTRIBUTE_UPDATER_REPLACE, + ATTRIBUTE_UPDATER_INSERT, + ATTRIBUTE_UPDATER_INSERT_REFERENCE, + ATTRIBUTE_UPDATER_REMOVE, + ATTRIBUTE_UPDATER_BIND, + ATTRIBUTE_TEMPLATE_PREFIX, + ATTRIBUTE_ROLE, + ATTRIBUTE_DISABLED, + ATTRIBUTE_VALUE, + ATTRIBUTE_OBJECTLINK, + ATTRIBUTE_ERRORMESSAGE, + TAG_SCRIPT, + TAG_STYLE, + TAG_LINK, + ATTRIBUTE_ID, + ATTRIBUTE_CLASS, + ATTRIBUTE_TITLE, + ATTRIBUTE_SRC, + ATTRIBUTE_HREF, + ATTRIBUTE_TYPE, + ATTRIBUTE_NONCE, + ATTRIBUTE_TRANSLATE, + ATTRIBUTE_TABINDEX, + ATTRIBUTE_SPELLCHECK, + ATTRIBUTE_SLOT, + ATTRIBUTE_PART, + ATTRIBUTE_LANG, + ATTRIBUTE_ITEMTYPE, + ATTRIBUTE_ITEMSCOPE, + ATTRIBUTE_ITEMREF, + ATTRIBUTE_ITEMID, + ATTRIBUTE_ITEMPROP, + ATTRIBUTE_IS, + ATTRIBUTE_INPUTMODE, + ATTRIBUTE_ACCESSKEY, + ATTRIBUTE_AUTOCAPITALIZE, + ATTRIBUTE_AUTOFOCUS, + ATTRIBUTE_CONTENTEDITABLE, + ATTRIBUTE_DIR, + ATTRIBUTE_DRAGGABLE, + ATTRIBUTE_ENTERKEYHINT, + ATTRIBUTE_EXPORTPARTS, + ATTRIBUTE_HIDDEN, + objectUpdaterLinkSymbol, + customElementUpdaterLinkSymbol, + initControlCallbackName, + ATTRIBUTE_SCRIPT_HOST, + ATTRIBUTE_INIT_CALLBACK, }; /** @@ -253,7 +253,9 @@ const ATTRIBUTE_ERRORMESSAGE = `${ATTRIBUTE_PREFIX}error`; * @license AGPLv3 * @since 1.24.0 */ -const objectUpdaterLinkSymbol = Symbol.for("@schukai/monster/dom/@@object-updater-link"); +const objectUpdaterLinkSymbol = Symbol.for( + "@schukai/monster/dom/@@object-updater-link", +); /** * @memberOf Monster.DOM @@ -261,7 +263,9 @@ const objectUpdaterLinkSymbol = Symbol.for("@schukai/monster/dom/@@object-update * @license AGPLv3 * @since 1.24.0 */ -const customElementUpdaterLinkSymbol = Symbol.for("@schukai/monster/dom/custom-element@@options-updater-link"); +const customElementUpdaterLinkSymbol = Symbol.for( + "@schukai/monster/dom/custom-element@@options-updater-link", +); /** * @memberOf Monster.DOM diff --git a/source/dom/customcontrol.mjs b/source/dom/customcontrol.mjs index d853f1c92..d4ae13816 100644 --- a/source/dom/customcontrol.mjs +++ b/source/dom/customcontrol.mjs @@ -49,293 +49,295 @@ const attachedInternalSymbol = Symbol("attachedInternal"); * @extends Monster.DOM.CustomElement */ class CustomControl extends CustomElement { - /** - * The constructor method of CustomControl, which is called when creating a new instance. - * It checks whether the element supports `attachInternals()` and initializes an internal form-associated element - * if supported. Additionally, it initializes a MutationObserver to watch for attribute changes. - * - * See the links below for more information: - * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define|CustomElementRegistry.define()} - * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get|CustomElementRegistry.get()} - * and {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals|ElementInternals} - * - * @inheritdoc - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @since 1.7.0 - */ - constructor() { - super(); - - // check if element supports `attachInternals()` - if (typeof this["attachInternals"] === "function") { - this[attachedInternalSymbol] = this.attachInternals(); - } else { - // `attachInternals()` is not supported, so a polyfill is necessary - throw Error("the ElementInternals is not supported and a polyfill is necessary"); - } - - // initialize a MutationObserver to watch for attribute changes - initObserver.call(this); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/custom-control@@instance"); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - return super.observedAttributes; - } - - /** - * Adding a static `formAssociated` property, with a true value, makes an autonomous custom element a form-associated custom element. - * - * @see [attachInternals()]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @see [Custom Elements Face Example]{@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example} - * @since 1.14.0 - * @return {boolean} - */ - static formAssociated = true; - - /** - * @inheritdoc - * @since 1.14.0 - **/ - get defaults() { - return extend({}, super.defaults); - } - - /** - * Must be overridden by a derived class and return the value of the control. - * - * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. - * - * @since 1.14.0 - * @throws {Error} the value getter must be overwritten by the derived class - */ - get value() { - throw Error("the value getter must be overwritten by the derived class"); - } - - /** - * Must be overridden by a derived class and set the value of the control. - * - * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. - * - * @param {*} value The value to set. - * @since 1.14.0 - * @throws {Error} the value setter must be overwritten by the derived class - */ - set value(value) { - throw Error("the value setter must be overwritten by the derived class"); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {NodeList} - * @since 1.14.0 - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/labels} - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get labels() { - return getInternal.call(this)?.labels; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {string|null} - */ - get name() { - return this.getAttribute("name"); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {string} - */ - get type() { - return this.constructor.getTag(); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {ValidityState} - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @see [ValidityState]{@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState} - * @see [validity]{@link https://developer.mozilla.org/en-US/docs/Web/API/validity} - */ - get validity() { - return getInternal.call(this)?.validity; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {string} - * @since 1.14.0 - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/validationMessage - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get validationMessage() { - return getInternal.call(this)?.validationMessage; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {boolean} - * @since 1.14.0 - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/willValidate - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get willValidate() { - return getInternal.call(this)?.willValidate; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {boolean} - * @since 1.14.0 - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get states() { - return getInternal.call(this)?.states; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {HTMLFontElement|null} - * @since 1.14.0 - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/form - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get form() { - return getInternal.call(this)?.form; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * ``` - * // Use the control's name as the base name for submitted data - * const n = this.getAttribute('name'); - * const entries = new FormData(); - * entries.append(n + '-first-name', this.firstName_); - * entries.append(n + '-last-name', this.lastName_); - * this.setFormValue(entries); - * ``` - * - * @param {File|string|FormData} value - * @param {File|string|FormData} state - * @since 1.14.0 - * @return {undefined} - * @throws {DOMException} NotSupportedError - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setFormValue - */ - setFormValue(value, state) { - getInternal.call(this).setFormValue(value, state); - } - - /** - * - * @param {object} flags - * @param {string|undefined} message - * @param {HTMLElement} anchor - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setValidity - * @since 1.14.0 - * @return {undefined} - * @throws {DOMException} NotSupportedError - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - setValidity(flags, message, anchor) { - getInternal.call(this).setValidity(flags, message, anchor); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/checkValidity - * @since 1.14.0 - * @return {boolean} - * @throws {DOMException} NotSupportedError - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - checkValidity() { - return getInternal.call(this)?.checkValidity(); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {boolean} - * @since 1.14.0 - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/reportValidity - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @throws {DOMException} NotSupportedError - */ - reportValidity() { - return getInternal.call(this)?.reportValidity(); - } - - /** - * Sets the `form` attribute of the custom control to the `id` of the passed form element. - * If no form element is passed, removes the `form` attribute. - * - * @param {HTMLFormElement} form - The form element to associate with the control - */ - formAssociatedCallback(form) { - if (form) { - if (form.id) { - this.setAttribute("form", form.id); - } - } else { - this.removeAttribute("form"); - } - } - - /** - * Sets or removes the `disabled` attribute of the custom control based on the passed value. - * - * @param {boolean} disabled - Whether or not the control should be disabled - */ - formDisabledCallback(disabled) { - if (disabled) { - this.setAttribute("disabled", ""); - } else { - this.removeAttribute("disabled"); - } - } - - /** - * @param {string} state - * @param {string} mode - */ - formStateRestoreCallback(state, mode) {} - - /** - * - */ - formResetCallback() { - this.value = ""; - } + /** + * The constructor method of CustomControl, which is called when creating a new instance. + * It checks whether the element supports `attachInternals()` and initializes an internal form-associated element + * if supported. Additionally, it initializes a MutationObserver to watch for attribute changes. + * + * See the links below for more information: + * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define|CustomElementRegistry.define()} + * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get|CustomElementRegistry.get()} + * and {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals|ElementInternals} + * + * @inheritdoc + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + * @since 1.7.0 + */ + constructor() { + super(); + + // check if element supports `attachInternals()` + if (typeof this["attachInternals"] === "function") { + this[attachedInternalSymbol] = this.attachInternals(); + } else { + // `attachInternals()` is not supported, so a polyfill is necessary + throw Error( + "the ElementInternals is not supported and a polyfill is necessary", + ); + } + + // initialize a MutationObserver to watch for attribute changes + initObserver.call(this); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/custom-control@@instance"); + } + + /** + * This method determines which attributes are to be monitored by `attributeChangedCallback()`. + * + * @return {string[]} + * @since 1.15.0 + */ + static get observedAttributes() { + return super.observedAttributes; + } + + /** + * Adding a static `formAssociated` property, with a true value, makes an autonomous custom element a form-associated custom element. + * + * @see [attachInternals()]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} + * @see [Custom Elements Face Example]{@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example} + * @since 1.14.0 + * @return {boolean} + */ + static formAssociated = true; + + /** + * @inheritdoc + * @since 1.14.0 + **/ + get defaults() { + return extend({}, super.defaults); + } + + /** + * Must be overridden by a derived class and return the value of the control. + * + * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. + * + * @since 1.14.0 + * @throws {Error} the value getter must be overwritten by the derived class + */ + get value() { + throw Error("the value getter must be overwritten by the derived class"); + } + + /** + * Must be overridden by a derived class and set the value of the control. + * + * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. + * + * @param {*} value The value to set. + * @since 1.14.0 + * @throws {Error} the value setter must be overwritten by the derived class + */ + set value(value) { + throw Error("the value setter must be overwritten by the derived class"); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {NodeList} + * @since 1.14.0 + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/labels} + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get labels() { + return getInternal.call(this)?.labels; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {string|null} + */ + get name() { + return this.getAttribute("name"); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {string} + */ + get type() { + return this.constructor.getTag(); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {ValidityState} + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + * @see [ValidityState]{@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState} + * @see [validity]{@link https://developer.mozilla.org/en-US/docs/Web/API/validity} + */ + get validity() { + return getInternal.call(this)?.validity; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {string} + * @since 1.14.0 + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/validationMessage + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get validationMessage() { + return getInternal.call(this)?.validationMessage; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {boolean} + * @since 1.14.0 + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/willValidate + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get willValidate() { + return getInternal.call(this)?.willValidate; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {boolean} + * @since 1.14.0 + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get states() { + return getInternal.call(this)?.states; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {HTMLFontElement|null} + * @since 1.14.0 + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/form + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get form() { + return getInternal.call(this)?.form; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * ``` + * // Use the control's name as the base name for submitted data + * const n = this.getAttribute('name'); + * const entries = new FormData(); + * entries.append(n + '-first-name', this.firstName_); + * entries.append(n + '-last-name', this.lastName_); + * this.setFormValue(entries); + * ``` + * + * @param {File|string|FormData} value + * @param {File|string|FormData} state + * @since 1.14.0 + * @return {undefined} + * @throws {DOMException} NotSupportedError + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setFormValue + */ + setFormValue(value, state) { + getInternal.call(this).setFormValue(value, state); + } + + /** + * + * @param {object} flags + * @param {string|undefined} message + * @param {HTMLElement} anchor + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setValidity + * @since 1.14.0 + * @return {undefined} + * @throws {DOMException} NotSupportedError + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + setValidity(flags, message, anchor) { + getInternal.call(this).setValidity(flags, message, anchor); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/checkValidity + * @since 1.14.0 + * @return {boolean} + * @throws {DOMException} NotSupportedError + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + checkValidity() { + return getInternal.call(this)?.checkValidity(); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {boolean} + * @since 1.14.0 + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/reportValidity + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + * @throws {DOMException} NotSupportedError + */ + reportValidity() { + return getInternal.call(this)?.reportValidity(); + } + + /** + * Sets the `form` attribute of the custom control to the `id` of the passed form element. + * If no form element is passed, removes the `form` attribute. + * + * @param {HTMLFormElement} form - The form element to associate with the control + */ + formAssociatedCallback(form) { + if (form) { + if (form.id) { + this.setAttribute("form", form.id); + } + } else { + this.removeAttribute("form"); + } + } + + /** + * Sets or removes the `disabled` attribute of the custom control based on the passed value. + * + * @param {boolean} disabled - Whether or not the control should be disabled + */ + formDisabledCallback(disabled) { + if (disabled) { + this.setAttribute("disabled", ""); + } else { + this.removeAttribute("disabled"); + } + } + + /** + * @param {string} state + * @param {string} mode + */ + formStateRestoreCallback(state, mode) {} + + /** + * + */ + formResetCallback() { + this.value = ""; + } } /** @@ -345,13 +347,15 @@ class CustomControl extends CustomElement { * @this CustomControl */ function getInternal() { - const self = this; + const self = this; - if (!(attachedInternalSymbol in this)) { - throw new Error("ElementInternals is not supported and a polyfill is necessary"); - } + if (!(attachedInternalSymbol in this)) { + throw new Error( + "ElementInternals is not supported and a polyfill is necessary", + ); + } - return self[attachedInternalSymbol]; + return self[attachedInternalSymbol]; } /** @@ -360,10 +364,10 @@ function getInternal() { * @this CustomControl */ function initObserver() { - const self = this; + const self = this; - // value - self[attributeObserverSymbol]["value"] = () => { - self.setOption("value", self.getAttribute("value")); - }; + // value + self[attributeObserverSymbol]["value"] = () => { + self.setOption("value", self.getAttribute("value")); + }; } diff --git a/source/dom/customelement.mjs b/source/dom/customelement.mjs index ba2afd730..be1f12afe 100644 --- a/source/dom/customelement.mjs +++ b/source/dom/customelement.mjs @@ -13,37 +13,55 @@ import { Formatter } from "../text/formatter.mjs"; import { parseDataURL } from "../types/dataurl.mjs"; import { getGlobalObject } from "../types/global.mjs"; -import { isArray, isFunction, isIterable, isObject, isString } from "../types/is.mjs"; +import { + isArray, + isFunction, + isIterable, + isObject, + isString, +} from "../types/is.mjs"; import { Observer } from "../types/observer.mjs"; import { ProxyObserver } from "../types/proxyobserver.mjs"; -import { validateFunction, validateInstance, validateObject, validateString } from "../types/validate.mjs"; +import { + validateFunction, + validateInstance, + validateObject, + validateString, +} from "../types/validate.mjs"; import { clone } from "../util/clone.mjs"; -import { addAttributeToken, getLinkedObjects, hasObjectLink } from "./attributes.mjs"; import { - ATTRIBUTE_DISABLED, - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_OPTIONS, - ATTRIBUTE_INIT_CALLBACK, - ATTRIBUTE_OPTIONS_SELECTOR, - ATTRIBUTE_SCRIPT_HOST, - customElementUpdaterLinkSymbol, - initControlCallbackName, + addAttributeToken, + getLinkedObjects, + hasObjectLink, +} from "./attributes.mjs"; +import { + ATTRIBUTE_DISABLED, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_OPTIONS, + ATTRIBUTE_INIT_CALLBACK, + ATTRIBUTE_OPTIONS_SELECTOR, + ATTRIBUTE_SCRIPT_HOST, + customElementUpdaterLinkSymbol, + initControlCallbackName, } from "./constants.mjs"; import { findDocumentTemplate, Template } from "./template.mjs"; import { addObjectWithUpdaterToElement } from "./updater.mjs"; import { instanceSymbol } from "../constants.mjs"; -import { getDocumentTranslations, Translations } from "../i18n/translations.mjs"; +import { + getDocumentTranslations, + Translations, +} from "../i18n/translations.mjs"; import { getSlottedElements } from "./slotted.mjs"; import { initOptionsFromAttributes } from "./util/init-options-from-attributes.mjs"; import { setOptionFromAttribute } from "./util/set-option-from-attribute.mjs"; export { - CustomElement, - initMethodSymbol, - assembleMethodSymbol, - attributeObserverSymbol, - registerCustomElement, - getSlottedElements, + CustomElement, + initMethodSymbol, + assembleMethodSymbol, + attributeObserverSymbol, + registerCustomElement, + getSlottedElements, }; /** @@ -56,20 +74,26 @@ const initMethodSymbol = Symbol.for("@schukai/monster/dom/@@initMethodSymbol"); * @memberOf Monster.DOM * @type {symbol} */ -const assembleMethodSymbol = Symbol.for("@schukai/monster/dom/@@assembleMethodSymbol"); +const assembleMethodSymbol = Symbol.for( + "@schukai/monster/dom/@@assembleMethodSymbol", +); /** * this symbol holds the attribute observer callbacks. The key is the attribute name. * @memberOf Monster.DOM * @type {symbol} */ -const attributeObserverSymbol = Symbol.for("@schukai/monster/dom/@@attributeObserver"); +const attributeObserverSymbol = Symbol.for( + "@schukai/monster/dom/@@attributeObserver", +); /** * @private * @type {symbol} */ -const attributeMutationObserverSymbol = Symbol("@schukai/monster/dom/@@mutationObserver"); +const attributeMutationObserverSymbol = Symbol( + "@schukai/monster/dom/@@mutationObserver", +); /** * @private @@ -201,458 +225,476 @@ const scriptHostElementSymbol = Symbol("scriptHostElement"); * @summary A base class for HTML5 custom controls. */ class CustomElement extends HTMLElement { - /** - * A new object is created. First the `initOptions` method is called. Here the - * options can be defined in derived classes. Subsequently, the shadowRoot is initialized. - * - * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>. - * - * @throws {Error} the options attribute does not contain a valid json definition. - * @since 1.7.0 - */ - constructor() { - super(); - - this[attributeObserverSymbol] = {}; - this[internalSymbol] = new ProxyObserver({ - options: initOptionsFromAttributes(this, extend({}, this.defaults)), - }); - this[initMethodSymbol](); - initOptionObserver.call(this); - this[scriptHostElementSymbol] = []; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/custom-element@@instance"); - } - - /** - * This method determines which attributes are to be - * monitored by `attributeChangedCallback()`. Unfortunately, this method is static. - * Therefore, the `observedAttributes` property cannot be changed during runtime. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - return []; - } - - /** - * - * @param attribute - * @param callback - * @returns {Monster.DOM.CustomElement} - */ - addAttributeObserver(attribute, callback) { - validateFunction(callback); - this[attributeObserverSymbol][attribute] = callback; - return this; - } - - /** - * - * @param attribute - * @returns {Monster.DOM.CustomElement} - */ - removeAttributeObserver(attribute) { - delete this[attributeObserverSymbol][attribute]; - return this; - } - - /** - * The `defaults` property defines the default values for a control. If you want to override these, - * you can use various methods, which are described in the documentation available at - * {@link https://monsterjs.orgendocconfigurate-a-monster-control}. - * - * The individual configuration values are listed below: - * - * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow), - * in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements). - * - * More information about the template element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template). - * - * More information about the slot element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot). - * - * @property {boolean} disabled=false Specifies whether the control is disabled. When present, it makes the element non-mutable, non-focusable, and non-submittable with the form. - * @property {string} shadowMode=open Specifies the mode of the shadow root. When set to `open`, elements in the shadow root are accessible from JavaScript outside the root, while setting it to `closed` denies access to the root's nodes from JavaScript outside it. - * @property {Boolean} delegatesFocus=true Specifies the behavior of the control with respect to focusability. When set to `true`, it mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling. - * @property {Object} templates Specifies the templates used by the control. - * @property {string} templates.main=undefined Specifies the main template used by the control. - * @property {Object} templateMapping Specifies the mapping of templates. - * @since 1.8.0 - */ - get defaults() { - return { - disabled: false, - shadowMode: "open", - delegatesFocus: true, - templates: { - main: undefined, - }, - templateMapping: {}, - }; - } - - /** - * This method updates the labels of the element. - * The labels are defined in the options object. - * The key of the label is used to retrieve the translation from the document. - * If the translation is different from the label, the label is updated. - * - * Before you can use this method, you must have loaded the translations. - * - * @returns {Monster.DOM.CustomElement} - * @throws {Error} Cannot find element with translations. Add a translations object to the document. - */ - updateI18n() { - const translations = getDocumentTranslations(); - if (!translations) { - return this; - } - - let labels = this.getOption("labels"); - if (!(isObject(labels) || isIterable(labels))) { - return this; - } - - for (const key in labels) { - const def = labels[key]; - - if (isString(def)) { - const text = translations.getText(key, def); - if (text !== def) { - this.setOption(`labels.${key}`, text); - } - continue; - } else if (isObject(def)) { - for (const k in def) { - const d = def[k]; - - const text = translations.getPluralRuleText(key, k, d); - if (!isString(text)) { - throw new Error("Invalid labels definition"); - } - if (text !== d) { - this.setOption(`labels.${key}.${k}`, text); - } - } - continue; - } - - throw new Error("Invalid labels definition"); - } - return this; - } - - /** - * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten - * by the derived class. - * - * Note that there is no check on the name of the tag in this class. It is the responsibility of - * the developer to assign an appropriate tag name. If the name is not valid, the - * `registerCustomElement()` method will issue an error. - * - * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name - * @throws {Error} This method must be overridden by the derived class. - * @return {string} The tag name associated with the custom element. - * @since 1.7.0 - */ - static getTag() { - throw new Error("The method `getTag()` must be overridden by the derived class."); - } - - /** - * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element. - * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour. - * - * If `undefined` is returned, then the shadow root does not receive a stylesheet. - * - * Example usage: - * - * ```js - * static getCSSStyleSheet() { - * const sheet = new CSSStyleSheet(); - * sheet.replaceSync("p { color: red; }"); - * return sheet; - * } - * ``` - * - * If the environment does not support the `CSSStyleSheet` constructor, - * you can use the following workaround to create the stylesheet: - * - * ```js - * const doc = document.implementation.createHTMLDocument('title'); - * let style = doc.createElement("style"); - * style.innerHTML = "p { color: red; }"; - * style.appendChild(document.createTextNode("")); - * doc.head.appendChild(style); - * return doc.styleSheets[0]; - * ``` - * - * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} A `CSSStyleSheet` object or an array of such objects that define the styles for the custom element, or `undefined` if no stylesheet should be applied. - */ - static getCSSStyleSheet() { - return undefined; - } - - /** - * attach a new observer - * - * @param {Observer} observer - * @returns {CustomElement} - */ - attachObserver(observer) { - this[internalSymbol].attachObserver(observer); - return this; - } - - /** - * detach a observer - * - * @param {Observer} observer - * @returns {CustomElement} - */ - detachObserver(observer) { - this[internalSymbol].detachObserver(observer); - return this; - } - - /** - * @param {Observer} observer - * @returns {ProxyObserver} - */ - containsObserver(observer) { - return this[internalSymbol].containsObserver(observer); - } - - /** - * nested options can be specified by path `a.b.c` - * - * @param {string} path - * @param {*} defaultValue - * @return {*} - * @since 1.10.0 - */ - getOption(path, defaultValue) { - let value; - - try { - value = new Pathfinder(this[internalSymbol].getRealSubject()["options"]).getVia(path); - } catch (e) {} - - if (value === undefined) return defaultValue; - return value; - } - - /** - * Set option and inform elements - * - * @param {string} path - * @param {*} value - * @return {CustomElement} - * @since 1.14.0 - */ - setOption(path, value) { - new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia(path, value); - return this; - } - - /** - * @since 1.15.0 - * @param {string|object} options - * @return {CustomElement} - */ - setOptions(options) { - if (isString(options)) { - options = parseOptionsJSON.call(this, options); - } - - const self = this; - extend(self[internalSymbol].getSubject()["options"], self.defaults, options); - - return self; - } - - /** - * Is called once via the constructor - * - * @return {CustomElement} - * @since 1.8.0 - */ - [initMethodSymbol]() { - return this; - } - - /** - * This method is called once when the object is included in the DOM for the first time. It performs the following actions: - * 1. Extracts the options from the attributes and the script tag of the element and sets them. - * 2. Initializes the shadow root and its CSS stylesheet (if specified). - * 3. Initializes the HTML content of the element. - * 4. Initializes the custom elements inside the shadow root and the slotted elements. - * 5. Attaches a mutation observer to observe changes to the attributes of the element. - * - * @return {CustomElement} - The updated custom element. - * @since 1.8.0 - */ - [assembleMethodSymbol]() { - const self = this; - let elements; - let nodeList; - - // Extract options from attributes and set them - const AttributeOptions = getOptionsFromAttributes.call(self); - if (isObject(AttributeOptions) && Object.keys(AttributeOptions).length > 0) { - self.setOptions(AttributeOptions); - } - - // Extract options from script tag and set them - const ScriptOptions = getOptionsFromScriptTag.call(self); - if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) { - self.setOptions(ScriptOptions); - } - - // Initialize the shadow root and its CSS stylesheet - if (self.getOption("shadowMode", false) !== false) { - try { - initShadowRoot.call(self); - elements = self.shadowRoot.childNodes; - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - - try { - initCSSStylesheet.call(this); - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - - // If the elements are not found inside the shadow root, initialize the HTML content of the element - if (!(elements instanceof NodeList)) { - initHtmlContent.call(this); - elements = this.childNodes; - } - - // Initialize the custom elements inside the shadow root and the slotted elements - initFromCallbackHost.call(this); - try { - nodeList = new Set([...elements, ...getSlottedElements.call(self)]); - } catch (e) { - nodeList = elements; - } - addObjectWithUpdaterToElement.call( - self, - nodeList, - customElementUpdaterLinkSymbol, - clone(self[internalSymbol].getRealSubject()["options"]), - ); - - // Attach a mutation observer to observe changes to the attributes of the element - attachAttributeChangeMutationObserver.call(this); - - return self; - } - - /** - * This method is called every time the element is inserted into the DOM. It checks if the custom element - * has already been initialized and if not, calls the assembleMethod to initialize it. - * - * @return {void} - * @since 1.7.0 - * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback - */ - connectedCallback() { - const self = this; - - // Check if the object has already been initialized - if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) { - // If not, call the assembleMethod to initialize the object - self[assembleMethodSymbol](); - } - } - - /** - * Called every time the element is removed from the DOM. Useful for running clean up code. - * - * @return {void} - * @since 1.7.0 - */ - disconnectedCallback() {} - - /** - * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)). - * - * @return {void} - * @since 1.7.0 - */ - adoptedCallback() {} - - /** - * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial - * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes - * property will receive this callback. - * - * @param {string} attrName - * @param {string} oldVal - * @param {string} newVal - * @return {void} - * @since 1.15.0 - */ - attributeChangedCallback(attrName, oldVal, newVal) { - const self = this; - - if (attrName.startsWith("data-monster-option-")) { - setOptionFromAttribute(self, attrName, this[internalSymbol].getSubject()["options"]); - } - - const callback = self[attributeObserverSymbol]?.[attrName]; - if (isFunction(callback)) { - try { - callback.call(self, newVal, oldVal); - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - } - - /** - * - * @param {Node} node - * @return {boolean} - * @throws {TypeError} value is not an instance of - * @since 1.19.0 - */ - hasNode(node) { - const self = this; - - if (containChildNode.call(self, validateInstance(node, Node))) { - return true; - } - - if (!(self.shadowRoot instanceof ShadowRoot)) { - return false; - } - - return containChildNode.call(self.shadowRoot, node); - } - - /** - * Calls a callback function if it exists. - * - * @param {string} name - * @param {*} args - * @returns {*} - */ - callCallback(name, args) { - const self = this; - return callControlCallback.call(self, name, ...args); - } + /** + * A new object is created. First the `initOptions` method is called. Here the + * options can be defined in derived classes. Subsequently, the shadowRoot is initialized. + * + * IMPORTANT: CustomControls instances are not created via the constructor, but either via a tag in the HTML or via <code>document.createElement()</code>. + * + * @throws {Error} the options attribute does not contain a valid json definition. + * @since 1.7.0 + */ + constructor() { + super(); + + this[attributeObserverSymbol] = {}; + this[internalSymbol] = new ProxyObserver({ + options: initOptionsFromAttributes(this, extend({}, this.defaults)), + }); + this[initMethodSymbol](); + initOptionObserver.call(this); + this[scriptHostElementSymbol] = []; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/custom-element@@instance"); + } + + /** + * This method determines which attributes are to be + * monitored by `attributeChangedCallback()`. Unfortunately, this method is static. + * Therefore, the `observedAttributes` property cannot be changed during runtime. + * + * @return {string[]} + * @since 1.15.0 + */ + static get observedAttributes() { + return []; + } + + /** + * + * @param attribute + * @param callback + * @returns {Monster.DOM.CustomElement} + */ + addAttributeObserver(attribute, callback) { + validateFunction(callback); + this[attributeObserverSymbol][attribute] = callback; + return this; + } + + /** + * + * @param attribute + * @returns {Monster.DOM.CustomElement} + */ + removeAttributeObserver(attribute) { + delete this[attributeObserverSymbol][attribute]; + return this; + } + + /** + * The `defaults` property defines the default values for a control. If you want to override these, + * you can use various methods, which are described in the documentation available at + * {@link https://monsterjs.orgendocconfigurate-a-monster-control}. + * + * The individual configuration values are listed below: + * + * More information about the shadowRoot can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow), + * in the [HTML Standard](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements) or in the [WHATWG Wiki](https://wiki.whatwg.org/wiki/Custom_Elements). + * + * More information about the template element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template). + * + * More information about the slot element can be found in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot). + * + * @property {boolean} disabled=false Specifies whether the control is disabled. When present, it makes the element non-mutable, non-focusable, and non-submittable with the form. + * @property {string} shadowMode=open Specifies the mode of the shadow root. When set to `open`, elements in the shadow root are accessible from JavaScript outside the root, while setting it to `closed` denies access to the root's nodes from JavaScript outside it. + * @property {Boolean} delegatesFocus=true Specifies the behavior of the control with respect to focusability. When set to `true`, it mitigates custom element issues around focusability. When a non-focusable part of the shadow DOM is clicked, the first focusable part is given focus, and the shadow host is given any available :focus styling. + * @property {Object} templates Specifies the templates used by the control. + * @property {string} templates.main=undefined Specifies the main template used by the control. + * @property {Object} templateMapping Specifies the mapping of templates. + * @since 1.8.0 + */ + get defaults() { + return { + disabled: false, + shadowMode: "open", + delegatesFocus: true, + templates: { + main: undefined, + }, + templateMapping: {}, + }; + } + + /** + * This method updates the labels of the element. + * The labels are defined in the options object. + * The key of the label is used to retrieve the translation from the document. + * If the translation is different from the label, the label is updated. + * + * Before you can use this method, you must have loaded the translations. + * + * @returns {Monster.DOM.CustomElement} + * @throws {Error} Cannot find element with translations. Add a translations object to the document. + */ + updateI18n() { + const translations = getDocumentTranslations(); + if (!translations) { + return this; + } + + let labels = this.getOption("labels"); + if (!(isObject(labels) || isIterable(labels))) { + return this; + } + + for (const key in labels) { + const def = labels[key]; + + if (isString(def)) { + const text = translations.getText(key, def); + if (text !== def) { + this.setOption(`labels.${key}`, text); + } + continue; + } else if (isObject(def)) { + for (const k in def) { + const d = def[k]; + + const text = translations.getPluralRuleText(key, k, d); + if (!isString(text)) { + throw new Error("Invalid labels definition"); + } + if (text !== d) { + this.setOption(`labels.${key}.${k}`, text); + } + } + continue; + } + + throw new Error("Invalid labels definition"); + } + return this; + } + + /** + * The `getTag()` method returns the tag name associated with the custom element. This method should be overwritten + * by the derived class. + * + * Note that there is no check on the name of the tag in this class. It is the responsibility of + * the developer to assign an appropriate tag name. If the name is not valid, the + * `registerCustomElement()` method will issue an error. + * + * @see https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name + * @throws {Error} This method must be overridden by the derived class. + * @return {string} The tag name associated with the custom element. + * @since 1.7.0 + */ + static getTag() { + throw new Error( + "The method `getTag()` must be overridden by the derived class.", + ); + } + + /** + * The `getCSSStyleSheet()` method returns a `CSSStyleSheet` object that defines the styles for the custom element. + * If the environment does not support the `CSSStyleSheet` constructor, then an object can be built using the provided detour. + * + * If `undefined` is returned, then the shadow root does not receive a stylesheet. + * + * Example usage: + * + * ```js + * static getCSSStyleSheet() { + * const sheet = new CSSStyleSheet(); + * sheet.replaceSync("p { color: red; }"); + * return sheet; + * } + * ``` + * + * If the environment does not support the `CSSStyleSheet` constructor, + * you can use the following workaround to create the stylesheet: + * + * ```js + * const doc = document.implementation.createHTMLDocument('title'); + * let style = doc.createElement("style"); + * style.innerHTML = "p { color: red; }"; + * style.appendChild(document.createTextNode("")); + * doc.head.appendChild(style); + * return doc.styleSheets[0]; + * ``` + * + * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} A `CSSStyleSheet` object or an array of such objects that define the styles for the custom element, or `undefined` if no stylesheet should be applied. + */ + static getCSSStyleSheet() { + return undefined; + } + + /** + * attach a new observer + * + * @param {Observer} observer + * @returns {CustomElement} + */ + attachObserver(observer) { + this[internalSymbol].attachObserver(observer); + return this; + } + + /** + * detach a observer + * + * @param {Observer} observer + * @returns {CustomElement} + */ + detachObserver(observer) { + this[internalSymbol].detachObserver(observer); + return this; + } + + /** + * @param {Observer} observer + * @returns {ProxyObserver} + */ + containsObserver(observer) { + return this[internalSymbol].containsObserver(observer); + } + + /** + * nested options can be specified by path `a.b.c` + * + * @param {string} path + * @param {*} defaultValue + * @return {*} + * @since 1.10.0 + */ + getOption(path, defaultValue) { + let value; + + try { + value = new Pathfinder( + this[internalSymbol].getRealSubject()["options"], + ).getVia(path); + } catch (e) {} + + if (value === undefined) return defaultValue; + return value; + } + + /** + * Set option and inform elements + * + * @param {string} path + * @param {*} value + * @return {CustomElement} + * @since 1.14.0 + */ + setOption(path, value) { + new Pathfinder(this[internalSymbol].getSubject()["options"]).setVia( + path, + value, + ); + return this; + } + + /** + * @since 1.15.0 + * @param {string|object} options + * @return {CustomElement} + */ + setOptions(options) { + if (isString(options)) { + options = parseOptionsJSON.call(this, options); + } + + const self = this; + extend( + self[internalSymbol].getSubject()["options"], + self.defaults, + options, + ); + + return self; + } + + /** + * Is called once via the constructor + * + * @return {CustomElement} + * @since 1.8.0 + */ + [initMethodSymbol]() { + return this; + } + + /** + * This method is called once when the object is included in the DOM for the first time. It performs the following actions: + * 1. Extracts the options from the attributes and the script tag of the element and sets them. + * 2. Initializes the shadow root and its CSS stylesheet (if specified). + * 3. Initializes the HTML content of the element. + * 4. Initializes the custom elements inside the shadow root and the slotted elements. + * 5. Attaches a mutation observer to observe changes to the attributes of the element. + * + * @return {CustomElement} - The updated custom element. + * @since 1.8.0 + */ + [assembleMethodSymbol]() { + const self = this; + let elements; + let nodeList; + + // Extract options from attributes and set them + const AttributeOptions = getOptionsFromAttributes.call(self); + if ( + isObject(AttributeOptions) && + Object.keys(AttributeOptions).length > 0 + ) { + self.setOptions(AttributeOptions); + } + + // Extract options from script tag and set them + const ScriptOptions = getOptionsFromScriptTag.call(self); + if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) { + self.setOptions(ScriptOptions); + } + + // Initialize the shadow root and its CSS stylesheet + if (self.getOption("shadowMode", false) !== false) { + try { + initShadowRoot.call(self); + elements = self.shadowRoot.childNodes; + } catch (e) { + addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); + } + + try { + initCSSStylesheet.call(this); + } catch (e) { + addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); + } + } + + // If the elements are not found inside the shadow root, initialize the HTML content of the element + if (!(elements instanceof NodeList)) { + initHtmlContent.call(this); + elements = this.childNodes; + } + + // Initialize the custom elements inside the shadow root and the slotted elements + initFromCallbackHost.call(this); + try { + nodeList = new Set([...elements, ...getSlottedElements.call(self)]); + } catch (e) { + nodeList = elements; + } + addObjectWithUpdaterToElement.call( + self, + nodeList, + customElementUpdaterLinkSymbol, + clone(self[internalSymbol].getRealSubject()["options"]), + ); + + // Attach a mutation observer to observe changes to the attributes of the element + attachAttributeChangeMutationObserver.call(this); + + return self; + } + + /** + * This method is called every time the element is inserted into the DOM. It checks if the custom element + * has already been initialized and if not, calls the assembleMethod to initialize it. + * + * @return {void} + * @since 1.7.0 + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/connectedCallback + */ + connectedCallback() { + const self = this; + + // Check if the object has already been initialized + if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) { + // If not, call the assembleMethod to initialize the object + self[assembleMethodSymbol](); + } + } + + /** + * Called every time the element is removed from the DOM. Useful for running clean up code. + * + * @return {void} + * @since 1.7.0 + */ + disconnectedCallback() {} + + /** + * The custom element has been moved into a new document (e.g. someone called document.adoptNode(el)). + * + * @return {void} + * @since 1.7.0 + */ + adoptedCallback() {} + + /** + * Called when an observed attribute has been added, removed, updated, or replaced. Also called for initial + * values when an element is created by the parser, or upgraded. Note: only attributes listed in the observedAttributes + * property will receive this callback. + * + * @param {string} attrName + * @param {string} oldVal + * @param {string} newVal + * @return {void} + * @since 1.15.0 + */ + attributeChangedCallback(attrName, oldVal, newVal) { + const self = this; + + if (attrName.startsWith("data-monster-option-")) { + setOptionFromAttribute( + self, + attrName, + this[internalSymbol].getSubject()["options"], + ); + } + + const callback = self[attributeObserverSymbol]?.[attrName]; + if (isFunction(callback)) { + try { + callback.call(self, newVal, oldVal); + } catch (e) { + addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); + } + } + } + + /** + * + * @param {Node} node + * @return {boolean} + * @throws {TypeError} value is not an instance of + * @since 1.19.0 + */ + hasNode(node) { + const self = this; + + if (containChildNode.call(self, validateInstance(node, Node))) { + return true; + } + + if (!(self.shadowRoot instanceof ShadowRoot)) { + return false; + } + + return containChildNode.call(self.shadowRoot, node); + } + + /** + * Calls a callback function if it exists. + * + * @param {string} name + * @param {*} args + * @returns {*} + */ + callCallback(name, args) { + const self = this; + return callControlCallback.call(self, name, ...args); + } } /** @@ -661,48 +703,52 @@ class CustomElement extends HTMLElement { * @return {any} */ function callControlCallback(callBackFunctionName, ...args) { - const self = this; - - if (!isString(callBackFunctionName) || callBackFunctionName === "") { - return; - } - - if (callBackFunctionName in self) { - return self[callBackFunctionName](self, ...args); - } - - if (!self.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) { - return; - } - - if (self[scriptHostElementSymbol].length === 0) { - const targetId = self.getAttribute(ATTRIBUTE_SCRIPT_HOST); - if (!targetId) { - return; - } - - const list = targetId.split(","); - for (const id of list) { - const host = findElementWithIdUpwards(self, targetId); - if (!(host instanceof HTMLElement)) { - continue; - } - - self[scriptHostElementSymbol].push(host); - } - } - - for (const host of self[scriptHostElementSymbol]) { - if (callBackFunctionName in host) { - try { - return host[callBackFunctionName](self, ...args); - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - } - - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, `callback ${callBackFunctionName} not found`); + const self = this; + + if (!isString(callBackFunctionName) || callBackFunctionName === "") { + return; + } + + if (callBackFunctionName in self) { + return self[callBackFunctionName](self, ...args); + } + + if (!self.hasAttribute(ATTRIBUTE_SCRIPT_HOST)) { + return; + } + + if (self[scriptHostElementSymbol].length === 0) { + const targetId = self.getAttribute(ATTRIBUTE_SCRIPT_HOST); + if (!targetId) { + return; + } + + const list = targetId.split(","); + for (const id of list) { + const host = findElementWithIdUpwards(self, targetId); + if (!(host instanceof HTMLElement)) { + continue; + } + + self[scriptHostElementSymbol].push(host); + } + } + + for (const host of self[scriptHostElementSymbol]) { + if (callBackFunctionName in host) { + try { + return host[callBackFunctionName](self, ...args); + } catch (e) { + addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); + } + } + } + + addAttributeToken( + self, + ATTRIBUTE_ERRORMESSAGE, + `callback ${callBackFunctionName} not found`, + ); } /** @@ -719,18 +765,18 @@ function callControlCallback(callBackFunctionName, ...args) { * @since 1.8.0 */ function initFromCallbackHost() { - const self = this; + const self = this; - // Set the default callback function name - let callBackFunctionName = initControlCallbackName; + // Set the default callback function name + let callBackFunctionName = initControlCallbackName; - // If the `data-monster-option-callback` attribute is set, use its value as the callback function name - if (self.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) { - callBackFunctionName = self.getAttribute(ATTRIBUTE_INIT_CALLBACK); - } + // If the `data-monster-option-callback` attribute is set, use its value as the callback function name + if (self.hasAttribute(ATTRIBUTE_INIT_CALLBACK)) { + callBackFunctionName = self.getAttribute(ATTRIBUTE_INIT_CALLBACK); + } - // Call the callback function with the element as a parameter if it exists - callControlCallback.call(self, callBackFunctionName); + // Call the callback function with the element as a parameter if it exists + callControlCallback.call(self, callBackFunctionName); } /** @@ -740,32 +786,35 @@ function initFromCallbackHost() { * @this CustomElement */ function attachAttributeChangeMutationObserver() { - const self = this; - - if (typeof self[attributeMutationObserverSymbol] !== "undefined") { - return; - } - - self[attributeMutationObserverSymbol] = new MutationObserver(function (mutations, observer) { - for (const mutation of mutations) { - if (mutation.type === "attributes") { - self.attributeChangedCallback( - mutation.attributeName, - mutation.oldValue, - mutation.target.getAttribute(mutation.attributeName), - ); - } - } - }); - - try { - self[attributeMutationObserverSymbol].observe(self, { - attributes: true, - attributeOldValue: true, - }); - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } + const self = this; + + if (typeof self[attributeMutationObserverSymbol] !== "undefined") { + return; + } + + self[attributeMutationObserverSymbol] = new MutationObserver(function ( + mutations, + observer, + ) { + for (const mutation of mutations) { + if (mutation.type === "attributes") { + self.attributeChangedCallback( + mutation.attributeName, + mutation.oldValue, + mutation.target.getAttribute(mutation.attributeName), + ); + } + } + }); + + try { + self[attributeMutationObserverSymbol].observe(self, { + attributes: true, + attributeOldValue: true, + }); + } catch (e) { + addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); + } } /** @@ -775,21 +824,21 @@ function attachAttributeChangeMutationObserver() { * @return {boolean} */ function containChildNode(node) { - const self = this; + const self = this; - if (self.contains(node)) { - return true; - } + if (self.contains(node)) { + return true; + } - for (const [, e] of Object.entries(self.childNodes)) { - if (e.contains(node)) { - return true; - } + for (const [, e] of Object.entries(self.childNodes)) { + if (e.contains(node)) { + return true; + } - containChildNode.call(e, node); - } + containChildNode.call(e, node); + } - return false; + return false; } /** @@ -799,86 +848,89 @@ function containChildNode(node) { * @this CustomElement */ function initOptionObserver() { - const self = this; - - let lastDisabledValue = undefined; - self.attachObserver( - new Observer(function () { - const flag = self.getOption("disabled"); - - if (flag === lastDisabledValue) { - return; - } - - lastDisabledValue = flag; - - if (!(self.shadowRoot instanceof ShadowRoot)) { - return; - } - - const query = - "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]"; - const elements = self.shadowRoot.querySelectorAll(query); - - let nodeList; - try { - nodeList = new Set([...elements, ...getSlottedElements.call(self, query)]); - } catch (e) { - nodeList = elements; - } - - for (const element of [...nodeList]) { - if (flag === true) { - element.setAttribute(ATTRIBUTE_DISABLED, ""); - } else { - element.removeAttribute(ATTRIBUTE_DISABLED); - } - } - }), - ); - - self.attachObserver( - new Observer(function () { - // not initialised - if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) { - return; - } - // inform every element - const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol); - - for (const list of updaters) { - for (const updater of list) { - let d = clone(self[internalSymbol].getRealSubject()["options"]); - Object.assign(updater.getSubject(), d); - } - } - }), - ); - - // disabled - self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => { - if (self.hasAttribute(ATTRIBUTE_DISABLED)) { - self.setOption(ATTRIBUTE_DISABLED, true); - } else { - self.setOption(ATTRIBUTE_DISABLED, undefined); - } - }; - - // data-monster-options - self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => { - const options = getOptionsFromAttributes.call(self); - if (isObject(options) && Object.keys(options).length > 0) { - self.setOptions(options); - } - }; - - // data-monster-options-selector - self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => { - const options = getOptionsFromScriptTag.call(self); - if (isObject(options) && Object.keys(options).length > 0) { - self.setOptions(options); - } - }; + const self = this; + + let lastDisabledValue = undefined; + self.attachObserver( + new Observer(function () { + const flag = self.getOption("disabled"); + + if (flag === lastDisabledValue) { + return; + } + + lastDisabledValue = flag; + + if (!(self.shadowRoot instanceof ShadowRoot)) { + return; + } + + const query = + "button, command, fieldset, keygen, optgroup, option, select, textarea, input, [data-monster-objectlink]"; + const elements = self.shadowRoot.querySelectorAll(query); + + let nodeList; + try { + nodeList = new Set([ + ...elements, + ...getSlottedElements.call(self, query), + ]); + } catch (e) { + nodeList = elements; + } + + for (const element of [...nodeList]) { + if (flag === true) { + element.setAttribute(ATTRIBUTE_DISABLED, ""); + } else { + element.removeAttribute(ATTRIBUTE_DISABLED); + } + } + }), + ); + + self.attachObserver( + new Observer(function () { + // not initialised + if (!hasObjectLink(self, customElementUpdaterLinkSymbol)) { + return; + } + // inform every element + const updaters = getLinkedObjects(self, customElementUpdaterLinkSymbol); + + for (const list of updaters) { + for (const updater of list) { + let d = clone(self[internalSymbol].getRealSubject()["options"]); + Object.assign(updater.getSubject(), d); + } + } + }), + ); + + // disabled + self[attributeObserverSymbol][ATTRIBUTE_DISABLED] = () => { + if (self.hasAttribute(ATTRIBUTE_DISABLED)) { + self.setOption(ATTRIBUTE_DISABLED, true); + } else { + self.setOption(ATTRIBUTE_DISABLED, undefined); + } + }; + + // data-monster-options + self[attributeObserverSymbol][ATTRIBUTE_OPTIONS] = () => { + const options = getOptionsFromAttributes.call(self); + if (isObject(options) && Object.keys(options).length > 0) { + self.setOptions(options); + } + }; + + // data-monster-options-selector + self[attributeObserverSymbol][ATTRIBUTE_OPTIONS_SELECTOR] = () => { + const options = getOptionsFromScriptTag.call(self); + if (isObject(options) && Object.keys(options).length > 0) { + self.setOptions(options); + } + }; } /** @@ -887,37 +939,39 @@ function initOptionObserver() { * @throws {TypeError} value is not a object */ function getOptionsFromScriptTag() { - const self = this; - - if (!self.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) { - return {}; - } - - const node = document.querySelector(self.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR)); - if (!(node instanceof HTMLScriptElement)) { - addAttributeToken( - self, - ATTRIBUTE_ERRORMESSAGE, - `the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${self.getAttribute( - ATTRIBUTE_OPTIONS_SELECTOR, - )}) but not found.`, - ); - return {}; - } - - let obj = {}; - - try { - obj = parseOptionsJSON.call(this, node.textContent.trim()); - } catch (e) { - addAttributeToken( - self, - ATTRIBUTE_ERRORMESSAGE, - `when analyzing the configuration from the script tag there was an error. ${e}`, - ); - } - - return obj; + const self = this; + + if (!self.hasAttribute(ATTRIBUTE_OPTIONS_SELECTOR)) { + return {}; + } + + const node = document.querySelector( + self.getAttribute(ATTRIBUTE_OPTIONS_SELECTOR), + ); + if (!(node instanceof HTMLScriptElement)) { + addAttributeToken( + self, + ATTRIBUTE_ERRORMESSAGE, + `the selector ${ATTRIBUTE_OPTIONS_SELECTOR} for options was specified (${self.getAttribute( + ATTRIBUTE_OPTIONS_SELECTOR, + )}) but not found.`, + ); + return {}; + } + + let obj = {}; + + try { + obj = parseOptionsJSON.call(this, node.textContent.trim()); + } catch (e) { + addAttributeToken( + self, + ATTRIBUTE_ERRORMESSAGE, + `when analyzing the configuration from the script tag there was an error. ${e}`, + ); + } + + return obj; } /** @@ -925,23 +979,23 @@ function getOptionsFromScriptTag() { * @return {object} */ function getOptionsFromAttributes() { - const self = this; - - if (this.hasAttribute(ATTRIBUTE_OPTIONS)) { - try { - return parseOptionsJSON.call(self, this.getAttribute(ATTRIBUTE_OPTIONS)); - } catch (e) { - addAttributeToken( - self, - ATTRIBUTE_ERRORMESSAGE, - `the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute( - ATTRIBUTE_OPTIONS, - )}).${e}`, - ); - } - } - - return {}; + const self = this; + + if (this.hasAttribute(ATTRIBUTE_OPTIONS)) { + try { + return parseOptionsJSON.call(self, this.getAttribute(ATTRIBUTE_OPTIONS)); + } catch (e) { + addAttributeToken( + self, + ATTRIBUTE_ERRORMESSAGE, + `the options attribute ${ATTRIBUTE_OPTIONS} does not contain a valid json definition (actual: ${this.getAttribute( + ATTRIBUTE_OPTIONS, + )}).${e}`, + ); + } + } + + return {}; } /** @@ -950,25 +1004,25 @@ function getOptionsFromAttributes() { * @return {Object} */ function parseOptionsJSON(data) { - let obj = {}; + let obj = {}; - if (!isString(data)) { - return obj; - } + if (!isString(data)) { + return obj; + } - // the configuration can be specified as a data url. - try { - let dataUrl = parseDataURL(data); - data = dataUrl.content; - } catch (e) {} + // the configuration can be specified as a data url. + try { + let dataUrl = parseDataURL(data); + data = dataUrl.content; + } catch (e) {} - try { - obj = JSON.parse(data); - } catch (e) { - throw e; - } + try { + obj = JSON.parse(data); + } catch (e) { + throw e; + } - return validateObject(obj); + return validateObject(obj); } /** @@ -976,21 +1030,21 @@ function parseOptionsJSON(data) { * @return {initHtmlContent} */ function initHtmlContent() { - try { - let template = findDocumentTemplate(this.constructor.getTag()); - this.appendChild(template.createDocumentFragment()); - } catch (e) { - let html = this.getOption("templates.main", ""); - if (isString(html) && html.length > 0) { - const mapping = this.getOption("templateMapping", {}); - if (isObject(mapping)) { - html = new Formatter(mapping).format(html); - } - this.innerHTML = html; - } - } - - return this; + try { + let template = findDocumentTemplate(this.constructor.getTag()); + this.appendChild(template.createDocumentFragment()); + } catch (e) { + let html = this.getOption("templates.main", ""); + if (isString(html) && html.length > 0) { + const mapping = this.getOption("templateMapping", {}); + if (isObject(mapping)) { + html = new Formatter(mapping).format(html); + } + this.innerHTML = html; + } + } + + return this; } /** @@ -1003,51 +1057,51 @@ function initHtmlContent() { * @throws {TypeError} value is not an instance of */ function initCSSStylesheet() { - const self = this; - - if (!(this.shadowRoot instanceof ShadowRoot)) { - return self; - } - - const styleSheet = this.constructor.getCSSStyleSheet(); - - if (styleSheet instanceof CSSStyleSheet) { - if (styleSheet.cssRules.length > 0) { - this.shadowRoot.adoptedStyleSheets = [styleSheet]; - } - } else if (isArray(styleSheet)) { - const assign = []; - for (let s of styleSheet) { - if (isString(s)) { - let trimedStyleSheet = s.trim(); - if (trimedStyleSheet !== "") { - const style = document.createElement("style"); - style.innerHTML = trimedStyleSheet; - self.shadowRoot.prepend(style); - } - continue; - } - - validateInstance(s, CSSStyleSheet); - - if (s.cssRules.length > 0) { - assign.push(s); - } - } - - if (assign.length > 0) { - this.shadowRoot.adoptedStyleSheets = assign; - } - } else if (isString(styleSheet)) { - let trimedStyleSheet = styleSheet.trim(); - if (trimedStyleSheet !== "") { - const style = document.createElement("style"); - style.innerHTML = styleSheet; - self.shadowRoot.prepend(style); - } - } - - return self; + const self = this; + + if (!(this.shadowRoot instanceof ShadowRoot)) { + return self; + } + + const styleSheet = this.constructor.getCSSStyleSheet(); + + if (styleSheet instanceof CSSStyleSheet) { + if (styleSheet.cssRules.length > 0) { + this.shadowRoot.adoptedStyleSheets = [styleSheet]; + } + } else if (isArray(styleSheet)) { + const assign = []; + for (let s of styleSheet) { + if (isString(s)) { + let trimedStyleSheet = s.trim(); + if (trimedStyleSheet !== "") { + const style = document.createElement("style"); + style.innerHTML = trimedStyleSheet; + self.shadowRoot.prepend(style); + } + continue; + } + + validateInstance(s, CSSStyleSheet); + + if (s.cssRules.length > 0) { + assign.push(s); + } + } + + if (assign.length > 0) { + this.shadowRoot.adoptedStyleSheets = assign; + } + } else if (isString(styleSheet)) { + let trimedStyleSheet = styleSheet.trim(); + if (trimedStyleSheet !== "") { + const style = document.createElement("style"); + style.innerHTML = styleSheet; + self.shadowRoot.prepend(style); + } + } + + return self; } /** @@ -1060,35 +1114,35 @@ function initCSSStylesheet() { * @since 1.8.0 */ function initShadowRoot() { - let template; - let html; - - try { - template = findDocumentTemplate(this.constructor.getTag()); - } catch (e) { - html = this.getOption("templates.main", ""); - if (!isString(html) || html === undefined || html === "") { - throw new Error("html is not set."); - } - } - - this.attachShadow({ - mode: this.getOption("shadowMode", "open"), - delegatesFocus: this.getOption("delegatesFocus", true), - }); - - if (template instanceof Template) { - this.shadowRoot.appendChild(template.createDocumentFragment()); - return this; - } - - const mapping = this.getOption("templateMapping", {}); - if (isObject(mapping)) { - html = new Formatter(mapping).format(html); - } - - this.shadowRoot.innerHTML = html; - return this; + let template; + let html; + + try { + template = findDocumentTemplate(this.constructor.getTag()); + } catch (e) { + html = this.getOption("templates.main", ""); + if (!isString(html) || html === undefined || html === "") { + throw new Error("html is not set."); + } + } + + this.attachShadow({ + mode: this.getOption("shadowMode", "open"), + delegatesFocus: this.getOption("delegatesFocus", true), + }); + + if (template instanceof Template) { + this.shadowRoot.appendChild(template.createDocumentFragment()); + return this; + } + + const mapping = this.getOption("templateMapping", {}); + if (isObject(mapping)) { + html = new Formatter(mapping).format(html); + } + + this.shadowRoot.innerHTML = html; + return this; } /** @@ -1103,15 +1157,15 @@ function initShadowRoot() { * @throws {DOMException} Failed to execute 'define' on 'CustomElementRegistry': is not a valid custom element name */ function registerCustomElement(element) { - validateFunction(element); - const customElements = getGlobalObject("customElements"); - if (customElements === undefined) { - throw new Error("customElements is not supported."); - } + validateFunction(element); + const customElements = getGlobalObject("customElements"); + if (customElements === undefined) { + throw new Error("customElements is not supported."); + } - if (customElements.get(element.getTag()) !== undefined) { - return; - } + if (customElements.get(element.getTag()) !== undefined) { + return; + } - customElements.define(element.getTag(), element); + customElements.define(element.getTag(), element); } diff --git a/source/dom/dimension.mjs b/source/dom/dimension.mjs index 07ef3c5da..cd3eac78f 100644 --- a/source/dom/dimension.mjs +++ b/source/dom/dimension.mjs @@ -19,13 +19,13 @@ export { convertToPixels, getDeviceDPI }; * @type {number|function} */ let CURRENT_DEVICE_DPI = function () { - let i = 0; - for (i = 56; i < 2000; i++) { - if (getWindow().matchMedia(`(max-resolution: ${i}dpi)`).matches === true) { - return i; - } - } - return i; + let i = 0; + for (i = 56; i < 2000; i++) { + if (getWindow().matchMedia(`(max-resolution: ${i}dpi)`).matches === true) { + return i; + } + } + return i; }; /** @@ -36,12 +36,12 @@ let CURRENT_DEVICE_DPI = function () { * @returns {number} */ function getDeviceDPI() { - // only call the function once - if (typeof CURRENT_DEVICE_DPI === "function") { - CURRENT_DEVICE_DPI = CURRENT_DEVICE_DPI(); - } + // only call the function once + if (typeof CURRENT_DEVICE_DPI === "function") { + CURRENT_DEVICE_DPI = CURRENT_DEVICE_DPI(); + } - return getWindow().devicePixelRatio * CURRENT_DEVICE_DPI; + return getWindow().devicePixelRatio * CURRENT_DEVICE_DPI; } /** @@ -74,42 +74,52 @@ function getDeviceDPI() { * @throws {Error} Invalid value format */ -function convertToPixels(value, parentElement = document.documentElement, fontSizeElement = document.documentElement) { - validateString(value); +function convertToPixels( + value, + parentElement = document.documentElement, + fontSizeElement = document.documentElement, +) { + validateString(value); - const regex = /^(-?[\d.]+)(.*)$/; - const matchResult = value.match(regex); + const regex = /^(-?[\d.]+)(.*)$/; + const matchResult = value.match(regex); - if (!matchResult) { - throw new Error(`Invalid value format: ${value}`); - } + if (!matchResult) { + throw new Error(`Invalid value format: ${value}`); + } - const [, num, unit] = matchResult; - const number = parseFloat(num); - const dpi = getDeviceDPI(); + const [, num, unit] = matchResult; + const number = parseFloat(num); + const dpi = getDeviceDPI(); - if (unit === "px") { - return number; - } else if (unit === "em") { - const fontSize = parseFloat(window.getComputedStyle(fontSizeElement).fontSize); - return number * fontSize; - } else if (unit === "rem") { - const rootFontSize = parseFloat(window.getComputedStyle(parentElement).fontSize); - return number * rootFontSize; - } else if (unit === "%") { - const parentWidth = parseFloat(window.getComputedStyle(parentElement).width); - return (number * parentWidth) / 100; - } else if (unit === "in") { - return number * dpi; - } else if (unit === "cm") { - return (number * dpi) / 2.54; - } else if (unit === "mm") { - return (number * dpi) / 25.4; - } else if (unit === "pt") { - return (number * dpi) / 72; - } else if (unit === "pc") { - return (number * dpi) / 6; - } else { - throw new Error(`Unsupported unit: ${unit}`); - } + if (unit === "px") { + return number; + } else if (unit === "em") { + const fontSize = parseFloat( + window.getComputedStyle(fontSizeElement).fontSize, + ); + return number * fontSize; + } else if (unit === "rem") { + const rootFontSize = parseFloat( + window.getComputedStyle(parentElement).fontSize, + ); + return number * rootFontSize; + } else if (unit === "%") { + const parentWidth = parseFloat( + window.getComputedStyle(parentElement).width, + ); + return (number * parentWidth) / 100; + } else if (unit === "in") { + return number * dpi; + } else if (unit === "cm") { + return (number * dpi) / 2.54; + } else if (unit === "mm") { + return (number * dpi) / 25.4; + } else if (unit === "pt") { + return (number * dpi) / 72; + } else if (unit === "pc") { + return (number * dpi) / 6; + } else { + throw new Error(`Unsupported unit: ${unit}`); + } } diff --git a/source/dom/events.mjs b/source/dom/events.mjs index cdc3bd077..c173b00c3 100644 --- a/source/dom/events.mjs +++ b/source/dom/events.mjs @@ -25,29 +25,31 @@ export { fireEvent, fireCustomEvent, findTargetElementFromEvent }; * @summary Construct and send and event */ function fireEvent(element, type) { - const document = getDocument(); - - if (element instanceof HTMLElement) { - if (type === "click") { - element.click(); - return; - } - - // https://developer.mozilla.org/en-US/docs/Web/API/Event/Event - let event = new Event(validateString(type), { - bubbles: true, - cancelable: true, - composed: true, - }); - - element.dispatchEvent(event); - } else if (element instanceof HTMLCollection || element instanceof NodeList) { - for (let e of element) { - fireEvent(e, type); - } - } else { - throw new TypeError("value is not an instance of HTMLElement or HTMLCollection"); - } + const document = getDocument(); + + if (element instanceof HTMLElement) { + if (type === "click") { + element.click(); + return; + } + + // https://developer.mozilla.org/en-US/docs/Web/API/Event/Event + let event = new Event(validateString(type), { + bubbles: true, + cancelable: true, + composed: true, + }); + + element.dispatchEvent(event); + } else if (element instanceof HTMLCollection || element instanceof NodeList) { + for (let e of element) { + fireEvent(e, type); + } + } else { + throw new TypeError( + "value is not an instance of HTMLElement or HTMLCollection", + ); + } } /** @@ -64,28 +66,30 @@ function fireEvent(element, type) { * @summary Construct and send and event */ function fireCustomEvent(element, type, detail) { - const document = getDocument(); - - if (element instanceof HTMLElement) { - if (!isObject(detail)) { - detail = { detail }; - } - - let event = new CustomEvent(validateString(type), { - bubbles: true, - cancelable: true, - composed: true, - detail, - }); - - element.dispatchEvent(event); - } else if (element instanceof HTMLCollection || element instanceof NodeList) { - for (let e of element) { - fireCustomEvent(e, type, detail); - } - } else { - throw new TypeError("value is not an instance of HTMLElement or HTMLCollection"); - } + const document = getDocument(); + + if (element instanceof HTMLElement) { + if (!isObject(detail)) { + detail = { detail }; + } + + let event = new CustomEvent(validateString(type), { + bubbles: true, + cancelable: true, + composed: true, + detail, + }); + + element.dispatchEvent(event); + } else if (element instanceof HTMLCollection || element instanceof NodeList) { + for (let e of element) { + fireCustomEvent(e, type, detail); + } + } else { + throw new TypeError( + "value is not an instance of HTMLElement or HTMLCollection", + ); + } } /** @@ -105,28 +109,29 @@ function fireCustomEvent(element, type, detail) { * @summary Help function to find the appropriate control */ function findTargetElementFromEvent(event, attributeName, attributeValue) { - validateInstance(event, Event); - - if (typeof event.composedPath !== "function") { - throw new Error("unsupported event"); - } - - const path = event.composedPath(); - - // closest cannot be used here, because closest is not correct for slotted elements - if (isArray(path)) { - for (let i = 0; i < path.length; i++) { - const o = path[i]; - - if ( - o instanceof HTMLElement && - o.hasAttribute(attributeName) && - (attributeValue === undefined || o.getAttribute(attributeName) === attributeValue) - ) { - return o; - } - } - } - - return undefined; + validateInstance(event, Event); + + if (typeof event.composedPath !== "function") { + throw new Error("unsupported event"); + } + + const path = event.composedPath(); + + // closest cannot be used here, because closest is not correct for slotted elements + if (isArray(path)) { + for (let i = 0; i < path.length; i++) { + const o = path[i]; + + if ( + o instanceof HTMLElement && + o.hasAttribute(attributeName) && + (attributeValue === undefined || + o.getAttribute(attributeName) === attributeValue) + ) { + return o; + } + } + } + + return undefined; } diff --git a/source/dom/focusmanager.mjs b/source/dom/focusmanager.mjs index ef43920d8..1686dd3da 100644 --- a/source/dom/focusmanager.mjs +++ b/source/dom/focusmanager.mjs @@ -43,179 +43,179 @@ const stackSymbol = Symbol("stack"); * @summary Handle the focus */ class FocusManager extends BaseWithOptions { - /** - * - * @param {Object|undefined} options - */ - constructor(options) { - super(options); - validateInstance(this.getOption(KEY_DOCUMENT), HTMLDocument); - - this[stackSymbol] = new Stack(); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/focusmanager"); - } - - /** - * @property {HTMLDocument} document the document object into which the node is to be appended - */ - get defaults() { - return extend({}, super.defaults, { - [KEY_DOCUMENT]: getGlobalObject("document"), - [KEY_CONTEXT]: undefined, - }); - } - - /** - * Remembers the current focus on a stack. - * Several focus can be stored. - * - * @return {Monster.DOM.FocusManager} - */ - storeFocus() { - const active = this.getActive(); - if (active instanceof Node) { - this[stackSymbol].push(active); - } - return this; - } - - /** - * The last focus on the stack is set again - * - * @return {Monster.DOM.FocusManager} - */ - restoreFocus() { - const last = this[stackSymbol].pop(); - if (last instanceof Node) { - this.focus(last); - } - - return this; - } - - /** - * - * @param {Node} element - * @param {boolean} preventScroll - * @throws {TypeError} value is not an instance of - * @return {Monster.DOM.FocusManager} - */ - focus(element, preventScroll) { - validateInstance(element, Node); - - element.focus({ - preventScroll: preventScroll ?? false, - }); - - return this; - } - - /** - * - * @return {Element} - */ - getActive() { - return this.getOption(KEY_DOCUMENT).activeElement; - } - - /** - * Select all elements that can be focused - * - * @param {string|undefined} query - * @return {array} - * @throws {TypeError} value is not an instance of - */ - getFocusable(query) { - let contextElement = this.getOption(KEY_CONTEXT); - if (contextElement === undefined) { - contextElement = this.getOption(KEY_DOCUMENT); - } - - validateInstance(contextElement, Node); - - if (query !== undefined) { - validateString(query); - } - - return [ - ...contextElement.querySelectorAll( - 'details, button, input, [tabindex]:not([tabindex="-1"]), select, textarea, a[href], body', - ), - ].filter((element) => { - if (query !== undefined && !element.matches(query)) { - return false; - } - - if (element.hasAttribute("disabled")) return false; - if (element.getAttribute("aria-hidden") === "true") return false; - - const rect = element.getBoundingClientRect(); - if (rect.width === 0) return false; - if (rect.height === 0) return false; - - return true; - }); - } - - /** - * @param {string} query - * @return {Monster.DOM.FocusManager} - */ - focusNext(query) { - const current = this.getActive(); - const focusable = this.getFocusable(query); - - if (!isArray(focusable) || focusable.length === 0) { - return this; - } - - if (current instanceof Node) { - let index = focusable.indexOf(current); - - if (index > -1) { - this.focus(focusable[index + 1] || focusable[0]); - } else { - this.focus(focusable[0]); - } - } else { - this.focus(focusable[0]); - } - - return this; - } - - /** - * @param {string} query - * @return {Monster.DOM.FocusManager} - */ - focusPrev(query) { - const current = this.getActive(); - const focusable = this.getFocusable(query); - - if (!isArray(focusable) || focusable.length === 0) { - return this; - } - - if (current instanceof Node) { - let index = focusable.indexOf(current); - - if (index > -1) { - this.focus(focusable[index - 1] || focusable[focusable.length - 1]); - } else { - this.focus(focusable[focusable.length - 1]); - } - } else { - this.focus(focusable[focusable.length - 1]); - } - - return this; - } + /** + * + * @param {Object|undefined} options + */ + constructor(options) { + super(options); + validateInstance(this.getOption(KEY_DOCUMENT), HTMLDocument); + + this[stackSymbol] = new Stack(); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/focusmanager"); + } + + /** + * @property {HTMLDocument} document the document object into which the node is to be appended + */ + get defaults() { + return extend({}, super.defaults, { + [KEY_DOCUMENT]: getGlobalObject("document"), + [KEY_CONTEXT]: undefined, + }); + } + + /** + * Remembers the current focus on a stack. + * Several focus can be stored. + * + * @return {Monster.DOM.FocusManager} + */ + storeFocus() { + const active = this.getActive(); + if (active instanceof Node) { + this[stackSymbol].push(active); + } + return this; + } + + /** + * The last focus on the stack is set again + * + * @return {Monster.DOM.FocusManager} + */ + restoreFocus() { + const last = this[stackSymbol].pop(); + if (last instanceof Node) { + this.focus(last); + } + + return this; + } + + /** + * + * @param {Node} element + * @param {boolean} preventScroll + * @throws {TypeError} value is not an instance of + * @return {Monster.DOM.FocusManager} + */ + focus(element, preventScroll) { + validateInstance(element, Node); + + element.focus({ + preventScroll: preventScroll ?? false, + }); + + return this; + } + + /** + * + * @return {Element} + */ + getActive() { + return this.getOption(KEY_DOCUMENT).activeElement; + } + + /** + * Select all elements that can be focused + * + * @param {string|undefined} query + * @return {array} + * @throws {TypeError} value is not an instance of + */ + getFocusable(query) { + let contextElement = this.getOption(KEY_CONTEXT); + if (contextElement === undefined) { + contextElement = this.getOption(KEY_DOCUMENT); + } + + validateInstance(contextElement, Node); + + if (query !== undefined) { + validateString(query); + } + + return [ + ...contextElement.querySelectorAll( + 'details, button, input, [tabindex]:not([tabindex="-1"]), select, textarea, a[href], body', + ), + ].filter((element) => { + if (query !== undefined && !element.matches(query)) { + return false; + } + + if (element.hasAttribute("disabled")) return false; + if (element.getAttribute("aria-hidden") === "true") return false; + + const rect = element.getBoundingClientRect(); + if (rect.width === 0) return false; + if (rect.height === 0) return false; + + return true; + }); + } + + /** + * @param {string} query + * @return {Monster.DOM.FocusManager} + */ + focusNext(query) { + const current = this.getActive(); + const focusable = this.getFocusable(query); + + if (!isArray(focusable) || focusable.length === 0) { + return this; + } + + if (current instanceof Node) { + let index = focusable.indexOf(current); + + if (index > -1) { + this.focus(focusable[index + 1] || focusable[0]); + } else { + this.focus(focusable[0]); + } + } else { + this.focus(focusable[0]); + } + + return this; + } + + /** + * @param {string} query + * @return {Monster.DOM.FocusManager} + */ + focusPrev(query) { + const current = this.getActive(); + const focusable = this.getFocusable(query); + + if (!isArray(focusable) || focusable.length === 0) { + return this; + } + + if (current instanceof Node) { + let index = focusable.indexOf(current); + + if (index > -1) { + this.focus(focusable[index - 1] || focusable[focusable.length - 1]); + } else { + this.focus(focusable[focusable.length - 1]); + } + } else { + this.focus(focusable[focusable.length - 1]); + } + + return this; + } } diff --git a/source/dom/locale.mjs b/source/dom/locale.mjs index ffc4382bf..f2e07c932 100644 --- a/source/dom/locale.mjs +++ b/source/dom/locale.mjs @@ -37,22 +37,22 @@ const DEFAULT_LANGUAGE = "en"; * @summary Tries to determine the locale used */ function getLocaleOfDocument() { - const document = getDocument(); + const document = getDocument(); - let html = document.querySelector("html"); - if (html instanceof HTMLElement && html.hasAttribute("lang")) { - let locale = html.getAttribute("lang"); - if (locale) { - return new parseLocale(locale); - } - } + let html = document.querySelector("html"); + if (html instanceof HTMLElement && html.hasAttribute("lang")) { + let locale = html.getAttribute("lang"); + if (locale) { + return new parseLocale(locale); + } + } - let navigatorLanguage = getNavigatorLanguage(); - if (navigatorLanguage) { - return parseLocale(navigatorLanguage); - } + let navigatorLanguage = getNavigatorLanguage(); + if (navigatorLanguage) { + return parseLocale(navigatorLanguage); + } - return parseLocale(DEFAULT_LANGUAGE); + return parseLocale(DEFAULT_LANGUAGE); } /** @@ -62,22 +62,22 @@ function getLocaleOfDocument() { * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages */ const getNavigatorLanguage = () => { - const navigator = getGlobalObject("navigator"); - if (navigator === undefined) { - return undefined; - } + const navigator = getGlobalObject("navigator"); + if (navigator === undefined) { + return undefined; + } - if (navigator.hasOwnProperty("language")) { - const language = navigator.language; - if (typeof language === "string" && language.length > 0) { - return language; - } - } + if (navigator.hasOwnProperty("language")) { + const language = navigator.language; + if (typeof language === "string" && language.length > 0) { + return language; + } + } - const languages = navigator?.languages; - if (Array.isArray(languages) && languages.length > 0) { - return languages[0]; - } + const languages = navigator?.languages; + if (Array.isArray(languages) && languages.length > 0) { + return languages[0]; + } - return undefined; + return undefined; }; diff --git a/source/dom/ready.mjs b/source/dom/ready.mjs index ef03e22c6..1465554f3 100644 --- a/source/dom/ready.mjs +++ b/source/dom/ready.mjs @@ -29,13 +29,13 @@ export { domReady, windowReady }; * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState */ const domReady = new Promise((resolve) => { - const document = getDocument(); + const document = getDocument(); - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", resolve); - } else { - resolve(); - } + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", resolve); + } else { + resolve(); + } }); /** @@ -54,12 +54,12 @@ const domReady = new Promise((resolve) => { * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState */ const windowReady = new Promise((resolve) => { - const document = getDocument(); - const window = getWindow(); + const document = getDocument(); + const window = getWindow(); - if (document.readyState === "complete") { - resolve(); - } else { - window.addEventListener("load", resolve); - } + if (document.readyState === "complete") { + resolve(); + } else { + window.addEventListener("load", resolve); + } }); diff --git a/source/dom/resource.mjs b/source/dom/resource.mjs index 57500a21f..d53793aac 100644 --- a/source/dom/resource.mjs +++ b/source/dom/resource.mjs @@ -13,7 +13,11 @@ import { ID } from "../types/id.mjs"; import { isString } from "../types/is.mjs"; import { Observer } from "../types/observer.mjs"; import { ProxyObserver } from "../types/proxyobserver.mjs"; -import { ATTRIBUTE_CLASS, ATTRIBUTE_ID, ATTRIBUTE_TITLE } from "./constants.mjs"; +import { + ATTRIBUTE_CLASS, + ATTRIBUTE_ID, + ATTRIBUTE_TITLE, +} from "./constants.mjs"; import { instanceSymbol } from "../constants.mjs"; export { Resource, KEY_DOCUMENT, KEY_QUERY, referenceSymbol }; @@ -52,143 +56,143 @@ const referenceSymbol = Symbol("reference"); * @summary A Resource class */ class Resource extends BaseWithOptions { - /** - * - * @param {Object|undefined} options - */ - constructor(options) { - super(options); - - let uri = this.getOption(this.constructor.getURLAttribute()); - - if (uri === undefined) { - throw new Error("missing source"); - } else if (uri instanceof URL) { - uri = uri.toString(); - } else if (!isString(uri)) { - throw new Error("unsupported url type"); - } - - this[internalSymbol][this.constructor.getURLAttribute()] = uri; - this[internalStateSymbol] = new ProxyObserver({ - loaded: false, - error: undefined, - }); - - this[referenceSymbol] = undefined; - } - - /** - * @return {boolean} - */ - isConnected() { - if (this[referenceSymbol] instanceof HTMLElement) { - return this[referenceSymbol].isConnected; - } - - return false; - } - - /** - * This method is overridden by the special classes and creates the DOM object. - * This method is also called implicitly, if not yet done explicitly, by calling `connect()`. - * - * @throws {Error} this method must be implemented by derived classes - * @return {Monster.DOM.Resource} - */ - create() { - throw new Error("this method must be implemented by derived classes"); - } - - /** - * This method appends the HTMLElement to the specified document. - * If the element has not yet been created, `create()` is called implicitly. - * - * throws {Error} target not found - * @return {Monster.DOM.Resource} - */ - connect() { - if (!(this[referenceSymbol] instanceof HTMLElement)) { - this.create(); - } - - appendToDocument.call(this); - return this; - } - - /** - * @property {Document} document the document object into which the node is to be appended - * @property {string} src/href url to the corresponding resource - * @property {string} query defines the location where the resource is to be hooked into the dom. - * @property {string} id element attribute id - * @property {string} title element attribute title - * @property {string} class element attribute class - * @property {int} timeout timeout - */ - get defaults() { - return extend({}, super.defaults, { - [this.constructor.getURLAttribute()]: undefined, - [KEY_DOCUMENT]: getGlobalObject("document"), - [KEY_QUERY]: "head", - [KEY_TIMEOUT]: 10000, - [ATTRIBUTE_ID]: new ID("resource").toString(), - [ATTRIBUTE_CLASS]: undefined, - [ATTRIBUTE_TITLE]: undefined, - }); - } - - /** - * With `available()` you can check if a resource is available. - * This is the case when the tag is included and the resource is loaded. - * - * @return {Promise} - */ - available() { - const self = this; - if (!(self[referenceSymbol] instanceof HTMLElement)) { - return Promise.reject("no element"); - } - - if (!self.isConnected()) { - return Promise.reject("element not connected"); - } - - if (self[internalStateSymbol].getSubject()["loaded"] === true) { - if (self[internalStateSymbol].getSubject()["error"] !== undefined) { - return Promise.reject(self[internalStateSymbol].getSubject()["error"]); - } - - return Promise.resolve(); - } - - return new Promise(function (resolve, reject) { - const timeout = setTimeout(() => { - reject("timeout"); - }, self.getOption("timeout")); - - const observer = new Observer(() => { - clearTimeout(timeout); - self[internalStateSymbol].detachObserver(observer); - resolve(); - }); - - self[internalStateSymbol].attachObserver(observer); - }); - } - - /** - * @return {string} - */ - static getURLAttribute() { - throw new Error("this method must be implemented by derived classes"); - } - - /** - * @return {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/resource"); - } + /** + * + * @param {Object|undefined} options + */ + constructor(options) { + super(options); + + let uri = this.getOption(this.constructor.getURLAttribute()); + + if (uri === undefined) { + throw new Error("missing source"); + } else if (uri instanceof URL) { + uri = uri.toString(); + } else if (!isString(uri)) { + throw new Error("unsupported url type"); + } + + this[internalSymbol][this.constructor.getURLAttribute()] = uri; + this[internalStateSymbol] = new ProxyObserver({ + loaded: false, + error: undefined, + }); + + this[referenceSymbol] = undefined; + } + + /** + * @return {boolean} + */ + isConnected() { + if (this[referenceSymbol] instanceof HTMLElement) { + return this[referenceSymbol].isConnected; + } + + return false; + } + + /** + * This method is overridden by the special classes and creates the DOM object. + * This method is also called implicitly, if not yet done explicitly, by calling `connect()`. + * + * @throws {Error} this method must be implemented by derived classes + * @return {Monster.DOM.Resource} + */ + create() { + throw new Error("this method must be implemented by derived classes"); + } + + /** + * This method appends the HTMLElement to the specified document. + * If the element has not yet been created, `create()` is called implicitly. + * + * throws {Error} target not found + * @return {Monster.DOM.Resource} + */ + connect() { + if (!(this[referenceSymbol] instanceof HTMLElement)) { + this.create(); + } + + appendToDocument.call(this); + return this; + } + + /** + * @property {Document} document the document object into which the node is to be appended + * @property {string} src/href url to the corresponding resource + * @property {string} query defines the location where the resource is to be hooked into the dom. + * @property {string} id element attribute id + * @property {string} title element attribute title + * @property {string} class element attribute class + * @property {int} timeout timeout + */ + get defaults() { + return extend({}, super.defaults, { + [this.constructor.getURLAttribute()]: undefined, + [KEY_DOCUMENT]: getGlobalObject("document"), + [KEY_QUERY]: "head", + [KEY_TIMEOUT]: 10000, + [ATTRIBUTE_ID]: new ID("resource").toString(), + [ATTRIBUTE_CLASS]: undefined, + [ATTRIBUTE_TITLE]: undefined, + }); + } + + /** + * With `available()` you can check if a resource is available. + * This is the case when the tag is included and the resource is loaded. + * + * @return {Promise} + */ + available() { + const self = this; + if (!(self[referenceSymbol] instanceof HTMLElement)) { + return Promise.reject("no element"); + } + + if (!self.isConnected()) { + return Promise.reject("element not connected"); + } + + if (self[internalStateSymbol].getSubject()["loaded"] === true) { + if (self[internalStateSymbol].getSubject()["error"] !== undefined) { + return Promise.reject(self[internalStateSymbol].getSubject()["error"]); + } + + return Promise.resolve(); + } + + return new Promise(function (resolve, reject) { + const timeout = setTimeout(() => { + reject("timeout"); + }, self.getOption("timeout")); + + const observer = new Observer(() => { + clearTimeout(timeout); + self[internalStateSymbol].detachObserver(observer); + resolve(); + }); + + self[internalStateSymbol].attachObserver(observer); + }); + } + + /** + * @return {string} + */ + static getURLAttribute() { + throw new Error("this method must be implemented by derived classes"); + } + + /** + * @return {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/resource"); + } } /** @@ -197,17 +201,17 @@ class Resource extends BaseWithOptions { * throws {Error} target not found */ function appendToDocument() { - const self = this; + const self = this; - const targetNode = document.querySelector(self.getOption(KEY_QUERY, "head")); - if (!(targetNode instanceof HTMLElement)) { - throw new Error("target not found"); - } + const targetNode = document.querySelector(self.getOption(KEY_QUERY, "head")); + if (!(targetNode instanceof HTMLElement)) { + throw new Error("target not found"); + } - addEvents.call(self); - targetNode.appendChild(self[referenceSymbol]); + addEvents.call(self); + targetNode.appendChild(self[referenceSymbol]); - return self; + return self; } /** @@ -215,29 +219,31 @@ function appendToDocument() { * @return {addEvents} */ function addEvents() { - const self = this; + const self = this; - const onError = () => { - self[referenceSymbol].removeEventListener("error", onError); - self[referenceSymbol].removeEventListener("load", onLoad); + const onError = () => { + self[referenceSymbol].removeEventListener("error", onError); + self[referenceSymbol].removeEventListener("load", onLoad); - self[internalStateSymbol].setSubject({ - loaded: true, - error: `${self[referenceSymbol][self.constructor.getURLAttribute()]} is not available`, - }); + self[internalStateSymbol].setSubject({ + loaded: true, + error: `${ + self[referenceSymbol][self.constructor.getURLAttribute()] + } is not available`, + }); - return; - }; + return; + }; - const onLoad = () => { - self[referenceSymbol].removeEventListener("error", onError); - self[referenceSymbol].removeEventListener("load", onLoad); - self[internalStateSymbol].getSubject()["loaded"] = true; - return; - }; + const onLoad = () => { + self[referenceSymbol].removeEventListener("error", onError); + self[referenceSymbol].removeEventListener("load", onLoad); + self[internalStateSymbol].getSubject()["loaded"] = true; + return; + }; - self[referenceSymbol].addEventListener("load", onLoad, false); - self[referenceSymbol].addEventListener("error", onError, false); + self[referenceSymbol].addEventListener("load", onLoad, false); + self[referenceSymbol].addEventListener("error", onError, false); - return self; + return self; } diff --git a/source/dom/resource/data.mjs b/source/dom/resource/data.mjs index 7475dbe21..4d73d3406 100644 --- a/source/dom/resource/data.mjs +++ b/source/dom/resource/data.mjs @@ -9,15 +9,20 @@ import { internalStateSymbol } from "../../constants.mjs"; import { extend } from "../../data/extend.mjs"; import { getGlobalFunction } from "../../types/global.mjs"; import { - ATTRIBUTE_CLASS, - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_ID, - ATTRIBUTE_SRC, - ATTRIBUTE_TITLE, - ATTRIBUTE_TYPE, - TAG_SCRIPT, + ATTRIBUTE_CLASS, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_ID, + ATTRIBUTE_SRC, + ATTRIBUTE_TITLE, + ATTRIBUTE_TYPE, + TAG_SCRIPT, } from "../constants.mjs"; -import { KEY_DOCUMENT, KEY_QUERY, referenceSymbol, Resource } from "../resource.mjs"; +import { + KEY_DOCUMENT, + KEY_QUERY, + referenceSymbol, + Resource, +} from "../resource.mjs"; import { instanceSymbol } from "../../constants.mjs"; export { Data }; @@ -31,58 +36,58 @@ export { Data }; * @summary A Data Resource class */ class Data extends Resource { - /** - * @property {string} mode=cors https://developer.mozilla.org/en-US/docs/Web/API/fetch - * @property {string} credentials=same-origin https://developer.mozilla.org/en-US/docs/Web/API/fetch - * @property {string} type=application/json {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type} - */ - get defaults() { - return extend({}, super.defaults, { - mode: "cors", - credentials: "same-origin", - type: "application/json", - }); - } - - /** - * - * @return {Monster.DOM.Resource.Data} - */ - create() { - createElement.call(this); - return this; - } - - /** - * This method appends the HTMLElement to the specified document - * - * throws {Error} target not found - * @return {Monster.DOM.Resource} - */ - connect() { - if (!(this[referenceSymbol] instanceof HTMLElement)) { - this.create(); - } - - appendToDocument.call(this); - return this; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/resource/data"); - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC; - } + /** + * @property {string} mode=cors https://developer.mozilla.org/en-US/docs/Web/API/fetch + * @property {string} credentials=same-origin https://developer.mozilla.org/en-US/docs/Web/API/fetch + * @property {string} type=application/json {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type} + */ + get defaults() { + return extend({}, super.defaults, { + mode: "cors", + credentials: "same-origin", + type: "application/json", + }); + } + + /** + * + * @return {Monster.DOM.Resource.Data} + */ + create() { + createElement.call(this); + return this; + } + + /** + * This method appends the HTMLElement to the specified document + * + * throws {Error} target not found + * @return {Monster.DOM.Resource} + */ + connect() { + if (!(this[referenceSymbol] instanceof HTMLElement)) { + this.create(); + } + + appendToDocument.call(this); + return this; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/resource/data"); + } + + /** + * @return {string} + */ + static getURLAttribute() { + return ATTRIBUTE_SRC; + } } /** @@ -90,18 +95,23 @@ class Data extends Resource { * @return {Monster.DOM.Resource.Data} */ function createElement() { - const self = this; - - const document = self.getOption(KEY_DOCUMENT); - self[referenceSymbol] = document.createElement(TAG_SCRIPT); - - for (let key of [ATTRIBUTE_TYPE, ATTRIBUTE_ID, ATTRIBUTE_CLASS, ATTRIBUTE_TITLE]) { - if (self.getOption(key) !== undefined) { - self[referenceSymbol][key] = self.getOption(key); - } - } - - return self; + const self = this; + + const document = self.getOption(KEY_DOCUMENT); + self[referenceSymbol] = document.createElement(TAG_SCRIPT); + + for (let key of [ + ATTRIBUTE_TYPE, + ATTRIBUTE_ID, + ATTRIBUTE_CLASS, + ATTRIBUTE_TITLE, + ]) { + if (self.getOption(key) !== undefined) { + self[referenceSymbol][key] = self.getOption(key); + } + } + + return self; } /** @@ -110,43 +120,43 @@ function createElement() { * throws {Error} target not found */ function appendToDocument() { - const self = this; - - const targetNode = document.querySelector(self.getOption(KEY_QUERY, "head")); - if (!(targetNode instanceof HTMLElement)) { - throw new Error("target not found"); - } - - targetNode.appendChild(self[referenceSymbol]); - - getGlobalFunction("fetch")(self.getOption(ATTRIBUTE_SRC), { - method: "GET", // *GET, POST, PUT, DELETE, etc. - mode: self.getOption("mode", "cors"), // no-cors, *cors, same-origin - cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached - credentials: self.getOption("credentials", "same-origin"), // include, *same-origin, omit - headers: { - Accept: self.getOption("type", "application/json"), - }, - redirect: "follow", // manual, *follow, error - referrerPolicy: "no-referrer", // no-referrer, - }) - .then((response) => { - return response.text(); - }) - .then((text) => { - const textNode = document.createTextNode(text); - self[referenceSymbol].appendChild(textNode); - - self[internalStateSymbol].getSubject()["loaded"] = true; - }) - .catch((e) => { - self[internalStateSymbol].setSubject({ - loaded: true, - error: e.toString(), - }); - - targetNode.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString()); - }); - - return self; + const self = this; + + const targetNode = document.querySelector(self.getOption(KEY_QUERY, "head")); + if (!(targetNode instanceof HTMLElement)) { + throw new Error("target not found"); + } + + targetNode.appendChild(self[referenceSymbol]); + + getGlobalFunction("fetch")(self.getOption(ATTRIBUTE_SRC), { + method: "GET", // *GET, POST, PUT, DELETE, etc. + mode: self.getOption("mode", "cors"), // no-cors, *cors, same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + credentials: self.getOption("credentials", "same-origin"), // include, *same-origin, omit + headers: { + Accept: self.getOption("type", "application/json"), + }, + redirect: "follow", // manual, *follow, error + referrerPolicy: "no-referrer", // no-referrer, + }) + .then((response) => { + return response.text(); + }) + .then((text) => { + const textNode = document.createTextNode(text); + self[referenceSymbol].appendChild(textNode); + + self[internalStateSymbol].getSubject()["loaded"] = true; + }) + .catch((e) => { + self[internalStateSymbol].setSubject({ + loaded: true, + error: e.toString(), + }); + + targetNode.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString()); + }); + + return self; } diff --git a/source/dom/resource/link.mjs b/source/dom/resource/link.mjs index abc069670..c76a4f3e4 100644 --- a/source/dom/resource/link.mjs +++ b/source/dom/resource/link.mjs @@ -7,14 +7,14 @@ import { extend } from "../../data/extend.mjs"; import { - ATTRIBUTE_CLASS, - ATTRIBUTE_HREF, - ATTRIBUTE_ID, - ATTRIBUTE_NONCE, - ATTRIBUTE_SRC, - ATTRIBUTE_TITLE, - ATTRIBUTE_TYPE, - TAG_LINK, + ATTRIBUTE_CLASS, + ATTRIBUTE_HREF, + ATTRIBUTE_ID, + ATTRIBUTE_NONCE, + ATTRIBUTE_SRC, + ATTRIBUTE_TITLE, + ATTRIBUTE_TYPE, + TAG_LINK, } from "../constants.mjs"; import { KEY_DOCUMENT, referenceSymbol, Resource } from "../resource.mjs"; import { instanceSymbol } from "../../constants.mjs"; @@ -31,67 +31,67 @@ export { Link }; * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link */ class Link extends Resource { - /** - * @property {string} as {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as} - * @property {string} crossOrigin=anonymous {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin} - * @property {boolean} disabled - * @property {string} href {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-href} - * @property {string} hreflang {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-hreflang} - * @property {string} imagesizes {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesizes} - * @property {string} imagesrcset {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesrcset} - * @property {string} integrity {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-integrity} - * @property {string} media {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-media} - * @property {string} prefetch {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-prefetch} - * @property {string} referrerpolicy {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-referrerpolicy} - * @property {string} rel {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel} - * @property {string} type {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-type} - * @property {string} sizes {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-sizes} - * @property {string} nonce {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce} - */ - get defaults() { - return extend({}, super.defaults, { - as: undefined, - crossOrigin: "anonymous", - disabled: undefined, - href: undefined, - hreflang: undefined, - imagesizes: undefined, - imagesrcset: undefined, - integrity: undefined, - media: undefined, - prefetch: undefined, - referrerpolicy: undefined, - rel: undefined, - sizes: undefined, - type: undefined, - nonce: undefined, - }); - } + /** + * @property {string} as {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as} + * @property {string} crossOrigin=anonymous {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin} + * @property {boolean} disabled + * @property {string} href {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-href} + * @property {string} hreflang {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-hreflang} + * @property {string} imagesizes {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesizes} + * @property {string} imagesrcset {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-imagesrcset} + * @property {string} integrity {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-integrity} + * @property {string} media {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-media} + * @property {string} prefetch {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-prefetch} + * @property {string} referrerpolicy {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-referrerpolicy} + * @property {string} rel {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel} + * @property {string} type {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-type} + * @property {string} sizes {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-sizes} + * @property {string} nonce {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce} + */ + get defaults() { + return extend({}, super.defaults, { + as: undefined, + crossOrigin: "anonymous", + disabled: undefined, + href: undefined, + hreflang: undefined, + imagesizes: undefined, + imagesrcset: undefined, + integrity: undefined, + media: undefined, + prefetch: undefined, + referrerpolicy: undefined, + rel: undefined, + sizes: undefined, + type: undefined, + nonce: undefined, + }); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/resource/link"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/resource/link"); + } - /** - * - * @return {Monster.DOM.Resource.Link} - */ - create() { - createElement.call(this); - return this; - } + /** + * + * @return {Monster.DOM.Resource.Link} + */ + create() { + createElement.call(this); + return this; + } - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_HREF; - } + /** + * @return {string} + */ + static getURLAttribute() { + return ATTRIBUTE_HREF; + } } /** @@ -99,36 +99,36 @@ class Link extends Resource { * @return {Monster.DOM.Resource.Link} */ function createElement() { - const self = this; + const self = this; - const document = self.getOption(KEY_DOCUMENT); - self[referenceSymbol] = document.createElement(TAG_LINK); + const document = self.getOption(KEY_DOCUMENT); + self[referenceSymbol] = document.createElement(TAG_LINK); - for (let key of [ - "as", - "crossOrigin", - "disabled", - "href", - "hreflang", - "imagesizes", - "imagesrcset", - "integrity", - "media", - "prefetch", - "referrerpolicy", - "sizes", - "rel", - "type", - ATTRIBUTE_HREF, - ATTRIBUTE_ID, - ATTRIBUTE_CLASS, - ATTRIBUTE_TITLE, - ATTRIBUTE_NONCE, - ]) { - if (self.getOption(key) !== undefined) { - self[referenceSymbol][key] = self.getOption(key); - } - } + for (let key of [ + "as", + "crossOrigin", + "disabled", + "href", + "hreflang", + "imagesizes", + "imagesrcset", + "integrity", + "media", + "prefetch", + "referrerpolicy", + "sizes", + "rel", + "type", + ATTRIBUTE_HREF, + ATTRIBUTE_ID, + ATTRIBUTE_CLASS, + ATTRIBUTE_TITLE, + ATTRIBUTE_NONCE, + ]) { + if (self.getOption(key) !== undefined) { + self[referenceSymbol][key] = self.getOption(key); + } + } - return self; + return self; } diff --git a/source/dom/resource/link/stylesheet.mjs b/source/dom/resource/link/stylesheet.mjs index 7fdd77d58..c9eeec2fd 100644 --- a/source/dom/resource/link/stylesheet.mjs +++ b/source/dom/resource/link/stylesheet.mjs @@ -21,21 +21,21 @@ export { Stylesheet }; * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link */ class Stylesheet extends Link { - /** - * @property {string} rel {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel} - */ - get defaults() { - return extend({}, super.defaults, { - rel: "stylesheet", - }); - } + /** + * @property {string} rel {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-rel} + */ + get defaults() { + return extend({}, super.defaults, { + rel: "stylesheet", + }); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/resource/link/stylesheet"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/resource/link/stylesheet"); + } } diff --git a/source/dom/resource/script.mjs b/source/dom/resource/script.mjs index 7b70287d7..c62aa2184 100644 --- a/source/dom/resource/script.mjs +++ b/source/dom/resource/script.mjs @@ -7,13 +7,13 @@ import { extend } from "../../data/extend.mjs"; import { - ATTRIBUTE_CLASS, - ATTRIBUTE_ID, - ATTRIBUTE_NONCE, - ATTRIBUTE_SRC, - ATTRIBUTE_TITLE, - ATTRIBUTE_TYPE, - TAG_SCRIPT, + ATTRIBUTE_CLASS, + ATTRIBUTE_ID, + ATTRIBUTE_NONCE, + ATTRIBUTE_SRC, + ATTRIBUTE_TITLE, + ATTRIBUTE_TYPE, + TAG_SCRIPT, } from "../constants.mjs"; import { KEY_DOCUMENT, referenceSymbol, Resource } from "../resource.mjs"; import { instanceSymbol } from "../../constants.mjs"; @@ -29,53 +29,53 @@ export { Script }; * @summary A Resource class */ class Script extends Resource { - /** - * @property {boolean} async=true {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async} - * @property {string} crossOrigin=anonymous {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin} - * @property {boolean} defer=false {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer} - * @property {string} integrity {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-integrity} - * @property {boolean} nomodule {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nomodule} - * @property {string} nonce {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce} - * @property {string} referrerpolicy {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-referrerpolicy} - * @property {string} type {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type} - */ - get defaults() { - return extend({}, super.defaults, { - async: true, - crossOrigin: "anonymous", - defer: false, - integrity: undefined, - nomodule: false, - nonce: undefined, - referrerpolicy: undefined, - type: "text/javascript", - }); - } + /** + * @property {boolean} async=true {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async} + * @property {string} crossOrigin=anonymous {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-crossorigin} + * @property {boolean} defer=false {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-defer} + * @property {string} integrity {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-integrity} + * @property {boolean} nomodule {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nomodule} + * @property {string} nonce {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce} + * @property {string} referrerpolicy {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-referrerpolicy} + * @property {string} type {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type} + */ + get defaults() { + return extend({}, super.defaults, { + async: true, + crossOrigin: "anonymous", + defer: false, + integrity: undefined, + nomodule: false, + nonce: undefined, + referrerpolicy: undefined, + type: "text/javascript", + }); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/resource/script"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/resource/script"); + } - /** - * - * @return {Monster.DOM.Resource.Script} - */ - create() { - createElement.call(this); - return this; - } + /** + * + * @return {Monster.DOM.Resource.Script} + */ + create() { + createElement.call(this); + return this; + } - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC; - } + /** + * @return {string} + */ + static getURLAttribute() { + return ATTRIBUTE_SRC; + } } /** @@ -83,29 +83,29 @@ class Script extends Resource { * @return {Monster.DOM.Resource.Script} */ function createElement() { - const self = this; + const self = this; - const document = self.getOption(KEY_DOCUMENT); - self[referenceSymbol] = document.createElement(TAG_SCRIPT); + const document = self.getOption(KEY_DOCUMENT); + self[referenceSymbol] = document.createElement(TAG_SCRIPT); - for (let key of [ - "crossOrigin", - "defer", - "async", - "integrity", - "nomodule", - ATTRIBUTE_NONCE, - "referrerpolicy", - ATTRIBUTE_TYPE, - ATTRIBUTE_SRC, - ATTRIBUTE_ID, - ATTRIBUTE_CLASS, - ATTRIBUTE_TITLE, - ]) { - if (self.getOption(key) !== undefined) { - self[referenceSymbol][key] = self.getOption(key); - } - } + for (let key of [ + "crossOrigin", + "defer", + "async", + "integrity", + "nomodule", + ATTRIBUTE_NONCE, + "referrerpolicy", + ATTRIBUTE_TYPE, + ATTRIBUTE_SRC, + ATTRIBUTE_ID, + ATTRIBUTE_CLASS, + ATTRIBUTE_TITLE, + ]) { + if (self.getOption(key) !== undefined) { + self[referenceSymbol][key] = self.getOption(key); + } + } - return self; + return self; } diff --git a/source/dom/resourcemanager.mjs b/source/dom/resourcemanager.mjs index 3457de5a3..d355a4b7b 100644 --- a/source/dom/resourcemanager.mjs +++ b/source/dom/resourcemanager.mjs @@ -28,121 +28,121 @@ export { ResourceManager }; * @summary A Resource class */ class ResourceManager extends Base { - /** - * - * @param {Object} options - * throw {Error} unsupported document type - */ - constructor(options) { - super(options); - equipWithInternal.call(this); - - if (!(this.getOption("document") instanceof Document)) { - throw new Error("unsupported document type"); - } - } - - /** - * @deprecated since 3.15.0 use getInternal instead - * @property {string} baseurl - */ - getOption(key) { - return this.getInternal(key); - } - - /** - * @property {string} baseurl - */ - getBaseURL() { - this.getOption("document")?.baseURL; - } - - /** - * @property {string} baseurl - * @deprecated since 3.15.0 use internalDefaults instead - */ - get defaults() { - return this.internalDefaults; - } - - /** - * - * @property {HTMLDocument} document=document Document - * @property {Object} resources - * @property {Array} resources.scripts=[] array with {@link Monster.DOM.Resource.Script} objects - * @property {Array} resources.stylesheets=[] array with {@link Monster.DOM.Resource.Link.Stylesheet} objects - * @property {Array} resources.data=[] array with {@link Monster.DOM.Resource.Data} objects - */ - get internalDefaults() { - return Object.assign( - {}, - { - document: getGlobalObject("document"), - resources: { - scripts: [], - stylesheets: [], - data: [], - }, - }, - ); - } - - /** - * Append Tags to DOM - * - * @return {Monster.DOM.ResourceManager} - * @throws {Error} unsupported resource definition - */ - connect() { - runResourceMethod.call(this, "connect"); - return this; - } - - /** - * Check if available - * - * @return {Promise} - * @throws {Error} unsupported resource definition - */ - available() { - return Promise.all(runResourceMethod.call(this, "available")); - } - - /** - * Add a script - * - * @param {string|URL} url - * @param [Object|undefined} options - * @return {Monster.DOM.ResourceManager} - * @see Monster.DOM.Resource.Script - */ - addScript(url, options) { - return addResource.call(this, "scripts", url, options); - } - - /** - * Add Stylesheet - * - * @param {string|URL} url - * @param [Object|undefined} options - * @return {Monster.DOM.ResourceManager} - * @see Monster.DOM.Resource.Link.Stylesheet - */ - addStylesheet(url, options) { - return addResource.call(this, "stylesheets", url, options); - } - - /** - * Add Data Tag - * - * @param {string|URL} url - * @param [Object|undefined} options - * @return {Monster.DOM.ResourceManager} - * @see Monster.DOM.Resource.Data - */ - addData(url, options) { - return addResource.call(this, "data", url, options); - } + /** + * + * @param {Object} options + * throw {Error} unsupported document type + */ + constructor(options) { + super(options); + equipWithInternal.call(this); + + if (!(this.getOption("document") instanceof Document)) { + throw new Error("unsupported document type"); + } + } + + /** + * @deprecated since 3.15.0 use getInternal instead + * @property {string} baseurl + */ + getOption(key) { + return this.getInternal(key); + } + + /** + * @property {string} baseurl + */ + getBaseURL() { + this.getOption("document")?.baseURL; + } + + /** + * @property {string} baseurl + * @deprecated since 3.15.0 use internalDefaults instead + */ + get defaults() { + return this.internalDefaults; + } + + /** + * + * @property {HTMLDocument} document=document Document + * @property {Object} resources + * @property {Array} resources.scripts=[] array with {@link Monster.DOM.Resource.Script} objects + * @property {Array} resources.stylesheets=[] array with {@link Monster.DOM.Resource.Link.Stylesheet} objects + * @property {Array} resources.data=[] array with {@link Monster.DOM.Resource.Data} objects + */ + get internalDefaults() { + return Object.assign( + {}, + { + document: getGlobalObject("document"), + resources: { + scripts: [], + stylesheets: [], + data: [], + }, + }, + ); + } + + /** + * Append Tags to DOM + * + * @return {Monster.DOM.ResourceManager} + * @throws {Error} unsupported resource definition + */ + connect() { + runResourceMethod.call(this, "connect"); + return this; + } + + /** + * Check if available + * + * @return {Promise} + * @throws {Error} unsupported resource definition + */ + available() { + return Promise.all(runResourceMethod.call(this, "available")); + } + + /** + * Add a script + * + * @param {string|URL} url + * @param [Object|undefined} options + * @return {Monster.DOM.ResourceManager} + * @see Monster.DOM.Resource.Script + */ + addScript(url, options) { + return addResource.call(this, "scripts", url, options); + } + + /** + * Add Stylesheet + * + * @param {string|URL} url + * @param [Object|undefined} options + * @return {Monster.DOM.ResourceManager} + * @see Monster.DOM.Resource.Link.Stylesheet + */ + addStylesheet(url, options) { + return addResource.call(this, "stylesheets", url, options); + } + + /** + * Add Data Tag + * + * @param {string|URL} url + * @param [Object|undefined} options + * @return {Monster.DOM.ResourceManager} + * @see Monster.DOM.Resource.Data + */ + addData(url, options) { + return addResource.call(this, "data", url, options); + } } /** @@ -151,26 +151,26 @@ class ResourceManager extends Base { * @return {Array} */ function runResourceMethod(method) { - const self = this; + const self = this; - const result = []; + const result = []; - for (const type of ["scripts", "stylesheets", "data"]) { - const resources = self.getOption(`resources.${type}`); - if (!isArray(resources)) { - continue; - } + for (const type of ["scripts", "stylesheets", "data"]) { + const resources = self.getOption(`resources.${type}`); + if (!isArray(resources)) { + continue; + } - for (const resource of resources) { - if (!(resource instanceof Resource)) { - throw new Error("unsupported resource definition"); - } + for (const resource of resources) { + if (!(resource instanceof Resource)) { + throw new Error("unsupported resource definition"); + } - result.push(resource[method]()); - } - } + result.push(resource[method]()); + } + } - return result; + return result; } /** @@ -182,29 +182,29 @@ function runResourceMethod(method) { * @private */ function addResource(type, url, options) { - const self = this; - - if (url instanceof URL) { - url = url.toString(); - } - - options = options || {}; - - let resource; - switch (type) { - case "scripts": - resource = new Script(extend({}, options, { [ATTRIBUTE_SRC]: url })); - break; - case "stylesheets": - resource = new Stylesheet(extend({}, options, { [ATTRIBUTE_HREF]: url })); - break; - case "data": - resource = new Data(extend({}, options, { [ATTRIBUTE_SRC]: url })); - break; - default: - throw new Error(`unsupported type ${type}`); - } - - self.getOption("resources")?.[type].push(resource); - return self; + const self = this; + + if (url instanceof URL) { + url = url.toString(); + } + + options = options || {}; + + let resource; + switch (type) { + case "scripts": + resource = new Script(extend({}, options, { [ATTRIBUTE_SRC]: url })); + break; + case "stylesheets": + resource = new Stylesheet(extend({}, options, { [ATTRIBUTE_HREF]: url })); + break; + case "data": + resource = new Data(extend({}, options, { [ATTRIBUTE_SRC]: url })); + break; + default: + throw new Error(`unsupported type ${type}`); + } + + self.getOption("resources")?.[type].push(resource); + return self; } diff --git a/source/dom/slotted.mjs b/source/dom/slotted.mjs index 53f8047bc..3a7445594 100644 --- a/source/dom/slotted.mjs +++ b/source/dom/slotted.mjs @@ -14,47 +14,47 @@ export { getSlottedElements, getSlottedNodes }; * @throws {Error} query must be a string */ function getSlottedNodes(query, name) { - const self = this; - const result = new Set(); - - if (!self.shadowRoot) { - return result; - } - - let selector = "slot"; - if (name !== undefined) { - if (name === null) { - selector += ":not([name])"; - } else { - selector += `[name=${validateString(name)}]`; - } - } - - const slots = self.shadowRoot.querySelectorAll(selector); - - for (const [, slot] of Object.entries(slots)) { - slot.assignedNodes().forEach(function (node) { - if (node === null || node === undefined) { - return; - } - - if (isString(query)) { - node.querySelectorAll(query).forEach(function (n) { - result.add(n); - }); - - if (node.matches(query)) { - result.add(node); - } - } else if (query !== undefined) { - throw new Error("query must be a string"); - } else { - result.add(node); - } - }); - } - - return result; + const self = this; + const result = new Set(); + + if (!self.shadowRoot) { + return result; + } + + let selector = "slot"; + if (name !== undefined) { + if (name === null) { + selector += ":not([name])"; + } else { + selector += `[name=${validateString(name)}]`; + } + } + + const slots = self.shadowRoot.querySelectorAll(selector); + + for (const [, slot] of Object.entries(slots)) { + slot.assignedNodes().forEach(function (node) { + if (node === null || node === undefined) { + return; + } + + if (isString(query)) { + node.querySelectorAll(query).forEach(function (n) { + result.add(n); + }); + + if (node.matches(query)) { + result.add(node); + } + } else if (query !== undefined) { + throw new Error("query must be a string"); + } else { + result.add(node); + } + }); + } + + return result; } /** @@ -68,43 +68,43 @@ function getSlottedNodes(query, name) { * @throws {Error} query must be a string */ function getSlottedElements(query, name) { - const self = this; - const result = new Set(); - - if (!(self.shadowRoot instanceof ShadowRoot)) { - return result; - } - - let selector = "slot"; - if (name !== undefined) { - if (name === null) { - selector += ":not([name])"; - } else { - selector += `[name=${validateString(name)}]`; - } - } - - const slots = self.shadowRoot.querySelectorAll(selector); - - for (const [, slot] of Object.entries(slots)) { - slot.assignedElements().forEach(function (node) { - if (!(node instanceof HTMLElement)) return; - - if (isString(query)) { - node.querySelectorAll(query).forEach(function (n) { - result.add(n); - }); - - if (node.matches(query)) { - result.add(node); - } - } else if (query !== undefined) { - throw new Error("query must be a string"); - } else { - result.add(node); - } - }); - } - - return result; + const self = this; + const result = new Set(); + + if (!(self.shadowRoot instanceof ShadowRoot)) { + return result; + } + + let selector = "slot"; + if (name !== undefined) { + if (name === null) { + selector += ":not([name])"; + } else { + selector += `[name=${validateString(name)}]`; + } + } + + const slots = self.shadowRoot.querySelectorAll(selector); + + for (const [, slot] of Object.entries(slots)) { + slot.assignedElements().forEach(function (node) { + if (!(node instanceof HTMLElement)) return; + + if (isString(query)) { + node.querySelectorAll(query).forEach(function (n) { + result.add(n); + }); + + if (node.matches(query)) { + result.add(node); + } + } else if (query !== undefined) { + throw new Error("query must be a string"); + } else { + result.add(node); + } + }); + } + + return result; } diff --git a/source/dom/template.mjs b/source/dom/template.mjs index e2ea3e880..5f10e0b73 100644 --- a/source/dom/template.mjs +++ b/source/dom/template.mjs @@ -23,45 +23,45 @@ export { Template }; * @summary A template class */ class Template extends Base { - /** - * - * @param {HTMLTemplateElement} template - * @throws {TypeError} value is not an instance of - * @throws {TypeError} value is not a function - * @throws {Error} the function is not defined - */ - constructor(template) { - super(); - const HTMLTemplateElement = getGlobalFunction("HTMLTemplateElement"); - validateInstance(template, HTMLTemplateElement); - this.template = template; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/resource/template"); - } - - /** - * - * @returns {HTMLTemplateElement} - */ - getTemplateElement() { - return this.template; - } - - /** - * - * @return {DocumentFragment} - * @throws {TypeError} value is not an instance of - */ - createDocumentFragment() { - return this.template.content.cloneNode(true); - } + /** + * + * @param {HTMLTemplateElement} template + * @throws {TypeError} value is not an instance of + * @throws {TypeError} value is not a function + * @throws {Error} the function is not defined + */ + constructor(template) { + super(); + const HTMLTemplateElement = getGlobalFunction("HTMLTemplateElement"); + validateInstance(template, HTMLTemplateElement); + this.template = template; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/resource/template"); + } + + /** + * + * @returns {HTMLTemplateElement} + */ + getTemplateElement() { + return this.template; + } + + /** + * + * @return {DocumentFragment} + * @throws {TypeError} value is not an instance of + */ + createDocumentFragment() { + return this.template.content.cloneNode(true); + } } /** @@ -128,77 +128,91 @@ class Template extends Base { * @throws {TypeError} value is not a string */ export function findDocumentTemplate(id, currentNode) { - validateString(id); - - const document = getGlobalObject("document"); - const HTMLTemplateElement = getGlobalFunction("HTMLTemplateElement"); - const DocumentFragment = getGlobalFunction("DocumentFragment"); - const Document = getGlobalFunction("Document"); - - let prefixID; - - if (!(currentNode instanceof Document || currentNode instanceof DocumentFragment)) { - if (currentNode instanceof Node) { - if (currentNode.hasAttribute(ATTRIBUTE_TEMPLATE_PREFIX)) { - prefixID = currentNode.getAttribute(ATTRIBUTE_TEMPLATE_PREFIX); - } - - currentNode = currentNode.getRootNode(); - - if (!(currentNode instanceof Document || currentNode instanceof DocumentFragment)) { - currentNode = currentNode.ownerDocument; - } - } - - if (!(currentNode instanceof Document || currentNode instanceof DocumentFragment)) { - currentNode = document; - } - } - - let template; - let theme = getDocumentTheme(); - - if (prefixID) { - let themedPrefixID = `${prefixID}-${id}-${theme.getName()}`; - - // current + themedPrefixID - template = currentNode.getElementById(themedPrefixID); - if (template instanceof HTMLTemplateElement) { - return new Template(template); - } - - // document + themedPrefixID - template = document.getElementById(themedPrefixID); - if (template instanceof HTMLTemplateElement) { - return new Template(template); - } - } - - let themedID = `${id}-${theme.getName()}`; - - // current + themedID - template = currentNode.getElementById(themedID); - if (template instanceof HTMLTemplateElement) { - return new Template(template); - } - - // document + themedID - template = document.getElementById(themedID); - if (template instanceof HTMLTemplateElement) { - return new Template(template); - } - - // current + ID - template = currentNode.getElementById(id); - if (template instanceof HTMLTemplateElement) { - return new Template(template); - } - - // document + ID - template = document.getElementById(id); - if (template instanceof HTMLTemplateElement) { - return new Template(template); - } - - throw new Error(`template ${id} not found.`); + validateString(id); + + const document = getGlobalObject("document"); + const HTMLTemplateElement = getGlobalFunction("HTMLTemplateElement"); + const DocumentFragment = getGlobalFunction("DocumentFragment"); + const Document = getGlobalFunction("Document"); + + let prefixID; + + if ( + !( + currentNode instanceof Document || currentNode instanceof DocumentFragment + ) + ) { + if (currentNode instanceof Node) { + if (currentNode.hasAttribute(ATTRIBUTE_TEMPLATE_PREFIX)) { + prefixID = currentNode.getAttribute(ATTRIBUTE_TEMPLATE_PREFIX); + } + + currentNode = currentNode.getRootNode(); + + if ( + !( + currentNode instanceof Document || + currentNode instanceof DocumentFragment + ) + ) { + currentNode = currentNode.ownerDocument; + } + } + + if ( + !( + currentNode instanceof Document || + currentNode instanceof DocumentFragment + ) + ) { + currentNode = document; + } + } + + let template; + let theme = getDocumentTheme(); + + if (prefixID) { + let themedPrefixID = `${prefixID}-${id}-${theme.getName()}`; + + // current + themedPrefixID + template = currentNode.getElementById(themedPrefixID); + if (template instanceof HTMLTemplateElement) { + return new Template(template); + } + + // document + themedPrefixID + template = document.getElementById(themedPrefixID); + if (template instanceof HTMLTemplateElement) { + return new Template(template); + } + } + + let themedID = `${id}-${theme.getName()}`; + + // current + themedID + template = currentNode.getElementById(themedID); + if (template instanceof HTMLTemplateElement) { + return new Template(template); + } + + // document + themedID + template = document.getElementById(themedID); + if (template instanceof HTMLTemplateElement) { + return new Template(template); + } + + // current + ID + template = currentNode.getElementById(id); + if (template instanceof HTMLTemplateElement) { + return new Template(template); + } + + // document + ID + template = document.getElementById(id); + if (template instanceof HTMLTemplateElement) { + return new Template(template); + } + + throw new Error(`template ${id} not found.`); } diff --git a/source/dom/theme.mjs b/source/dom/theme.mjs index 9d2b4fce9..4cfa897fe 100644 --- a/source/dom/theme.mjs +++ b/source/dom/theme.mjs @@ -23,33 +23,33 @@ export { Theme, getDocumentTheme }; * @summary A theme class */ class Theme extends Base { - /** - * - * @param name - * @throws {TypeError} value is not a string - */ - constructor(name) { - super(); - validateString(name); - this.name = name; - } + /** + * + * @param name + * @throws {TypeError} value is not a string + */ + constructor(name) { + super(); + validateString(name); + this.name = name; + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/theme"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/theme"); + } - /** - * - * @returns {string} - */ - getName() { - return this.name; - } + /** + * + * @returns {string} + */ + getName() { + return this.name; + } } /** @@ -70,16 +70,16 @@ class Theme extends Base { * @since 1.7.0 */ function getDocumentTheme() { - let document = getGlobalObject("document"); - let name = DEFAULT_THEME; + let document = getGlobalObject("document"); + let name = DEFAULT_THEME; - let element = document.querySelector("html"); - if (element instanceof HTMLElement) { - let theme = element.getAttribute(ATTRIBUTE_THEME_NAME); - if (theme) { - name = theme; - } - } + let element = document.querySelector("html"); + if (element instanceof HTMLElement) { + let theme = element.getAttribute(ATTRIBUTE_THEME_NAME); + if (theme) { + name = theme; + } + } - return new Theme(name); + return new Theme(name); } diff --git a/source/dom/updater.mjs b/source/dom/updater.mjs index 40867df61..8fa8a2d66 100644 --- a/source/dom/updater.mjs +++ b/source/dom/updater.mjs @@ -10,14 +10,14 @@ import { diff } from "../data/diff.mjs"; import { Pathfinder } from "../data/pathfinder.mjs"; import { Pipe } from "../data/pipe.mjs"; import { - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_UPDATER_ATTRIBUTES, - ATTRIBUTE_UPDATER_BIND, - ATTRIBUTE_UPDATER_INSERT, - ATTRIBUTE_UPDATER_INSERT_REFERENCE, - ATTRIBUTE_UPDATER_REMOVE, - ATTRIBUTE_UPDATER_REPLACE, - ATTRIBUTE_UPDATER_SELECT_THIS, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_UPDATER_ATTRIBUTES, + ATTRIBUTE_UPDATER_BIND, + ATTRIBUTE_UPDATER_INSERT, + ATTRIBUTE_UPDATER_INSERT_REFERENCE, + ATTRIBUTE_UPDATER_REMOVE, + ATTRIBUTE_UPDATER_REPLACE, + ATTRIBUTE_UPDATER_SELECT_THIS, } from "./constants.mjs"; import { Base } from "../types/base.mjs"; @@ -56,164 +56,174 @@ export { Updater, addObjectWithUpdaterToElement }; * @summary The updater class connects an object with the dom */ class Updater extends Base { - /** - * @since 1.8.0 - * @param {HTMLElement} element - * @param {object|ProxyObserver|undefined} subject - * @throws {TypeError} value is not a object - * @throws {TypeError} value is not an instance of HTMLElement - * @see {@link Monster.DOM.findDocumentTemplate} - */ - constructor(element, subject) { - super(); - - /** - * @type {HTMLElement} - */ - if (subject === undefined) subject = {}; - if (!isInstance(subject, ProxyObserver)) { - subject = new ProxyObserver(subject); - } - - this[internalSymbol] = { - element: validateInstance(element, HTMLElement), - last: {}, - callbacks: new Map(), - eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"], - subject: subject, - }; - - this[internalSymbol].callbacks.set("checkstate", getCheckStateCallback.call(this)); - - this[internalSymbol].subject.attachObserver( - new Observer(() => { - const s = this[internalSymbol].subject.getRealSubject(); - - const diffResult = diff(this[internalSymbol].last, s); - this[internalSymbol].last = clone(s); - - for (const [, change] of Object.entries(diffResult)) { - removeElement.call(this, change); - insertElement.call(this, change); - updateContent.call(this, change); - updateAttributes.call(this, change); - } - }), - ); - } - - /** - * Defaults: 'keyup', 'click', 'change', 'drop', 'touchend' - * - * @see {@link https://developer.mozilla.org/de/docs/Web/Events} - * @since 1.9.0 - * @param {Array} types - * @return {Updater} - */ - setEventTypes(types) { - this[internalSymbol].eventTypes = validateArray(types); - return this; - } - - /** - * With this method, the eventlisteners are hooked in and the magic begins. - * - * ``` - * updater.run().then(() => { - * updater.enableEventProcessing(); - * }); - * ``` - * - * @since 1.9.0 - * @return {Updater} - * @throws {Error} the bind argument must start as a value with a path - */ - enableEventProcessing() { - this.disableEventProcessing(); - - for (const type of this[internalSymbol].eventTypes) { - // @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - this[internalSymbol].element.addEventListener(type, getControlEventHandler.call(this), { - capture: true, - passive: true, - }); - } - - return this; - } - - /** - * This method turns off the magic or who loves it more profane it removes the eventListener. - * - * @since 1.9.0 - * @return {Updater} - */ - disableEventProcessing() { - for (const type of this[internalSymbol].eventTypes) { - this[internalSymbol].element.removeEventListener(type, getControlEventHandler.call(this)); - } - - return this; - } - - /** - * The run method must be called for the update to start working. - * The method ensures that changes are detected. - * - * ``` - * updater.run().then(() => { - * updater.enableEventProcessing(); - * }); - * ``` - * - * @summary Let the magic begin - * @return {Promise} - */ - run() { - // the key __init__has no further meaning and is only - // used to create the diff for empty objects. - this[internalSymbol].last = { __init__: true }; - return this[internalSymbol].subject.notifyObservers(); - } - - /** - * Gets the values of bound elements and changes them in subject - * - * @since 1.27.0 - * @return {Monster.DOM.Updater} - */ - retrieve() { - retrieveFromBindings.call(this); - return this; - } - - /** - * If you have passed a ProxyObserver in the constructor, you will get the object that the ProxyObserver manages here. - * However, if you passed a simple object, here you will get a proxy for that object. - * - * For changes the ProxyObserver must be used. - * - * @since 1.8.0 - * @return {Proxy} - */ - getSubject() { - return this[internalSymbol].subject.getSubject(); - } - - /** - * This method can be used to register commands that can be called via call: instruction. - * This can be used to provide a pipe with its own functionality. - * - * @param {string} name - * @param {function} callback - * @returns {Transformer} - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not a function - */ - setCallback(name, callback) { - this[internalSymbol].callbacks.set(name, callback); - return this; - } + /** + * @since 1.8.0 + * @param {HTMLElement} element + * @param {object|ProxyObserver|undefined} subject + * @throws {TypeError} value is not a object + * @throws {TypeError} value is not an instance of HTMLElement + * @see {@link Monster.DOM.findDocumentTemplate} + */ + constructor(element, subject) { + super(); + + /** + * @type {HTMLElement} + */ + if (subject === undefined) subject = {}; + if (!isInstance(subject, ProxyObserver)) { + subject = new ProxyObserver(subject); + } + + this[internalSymbol] = { + element: validateInstance(element, HTMLElement), + last: {}, + callbacks: new Map(), + eventTypes: ["keyup", "click", "change", "drop", "touchend", "input"], + subject: subject, + }; + + this[internalSymbol].callbacks.set( + "checkstate", + getCheckStateCallback.call(this), + ); + + this[internalSymbol].subject.attachObserver( + new Observer(() => { + const s = this[internalSymbol].subject.getRealSubject(); + + const diffResult = diff(this[internalSymbol].last, s); + this[internalSymbol].last = clone(s); + + for (const [, change] of Object.entries(diffResult)) { + removeElement.call(this, change); + insertElement.call(this, change); + updateContent.call(this, change); + updateAttributes.call(this, change); + } + }), + ); + } + + /** + * Defaults: 'keyup', 'click', 'change', 'drop', 'touchend' + * + * @see {@link https://developer.mozilla.org/de/docs/Web/Events} + * @since 1.9.0 + * @param {Array} types + * @return {Updater} + */ + setEventTypes(types) { + this[internalSymbol].eventTypes = validateArray(types); + return this; + } + + /** + * With this method, the eventlisteners are hooked in and the magic begins. + * + * ``` + * updater.run().then(() => { + * updater.enableEventProcessing(); + * }); + * ``` + * + * @since 1.9.0 + * @return {Updater} + * @throws {Error} the bind argument must start as a value with a path + */ + enableEventProcessing() { + this.disableEventProcessing(); + + for (const type of this[internalSymbol].eventTypes) { + // @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + this[internalSymbol].element.addEventListener( + type, + getControlEventHandler.call(this), + { + capture: true, + passive: true, + }, + ); + } + + return this; + } + + /** + * This method turns off the magic or who loves it more profane it removes the eventListener. + * + * @since 1.9.0 + * @return {Updater} + */ + disableEventProcessing() { + for (const type of this[internalSymbol].eventTypes) { + this[internalSymbol].element.removeEventListener( + type, + getControlEventHandler.call(this), + ); + } + + return this; + } + + /** + * The run method must be called for the update to start working. + * The method ensures that changes are detected. + * + * ``` + * updater.run().then(() => { + * updater.enableEventProcessing(); + * }); + * ``` + * + * @summary Let the magic begin + * @return {Promise} + */ + run() { + // the key __init__has no further meaning and is only + // used to create the diff for empty objects. + this[internalSymbol].last = { __init__: true }; + return this[internalSymbol].subject.notifyObservers(); + } + + /** + * Gets the values of bound elements and changes them in subject + * + * @since 1.27.0 + * @return {Monster.DOM.Updater} + */ + retrieve() { + retrieveFromBindings.call(this); + return this; + } + + /** + * If you have passed a ProxyObserver in the constructor, you will get the object that the ProxyObserver manages here. + * However, if you passed a simple object, here you will get a proxy for that object. + * + * For changes the ProxyObserver must be used. + * + * @since 1.8.0 + * @return {Proxy} + */ + getSubject() { + return this[internalSymbol].subject.getSubject(); + } + + /** + * This method can be used to register commands that can be called via call: instruction. + * This can be used to provide a pipe with its own functionality. + * + * @param {string} name + * @param {function} callback + * @returns {Transformer} + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not a function + */ + setCallback(name, callback) { + this[internalSymbol].callbacks.set(name, callback); + return this; + } } /** @@ -224,22 +234,22 @@ class Updater extends Base { * @this Updater */ function getCheckStateCallback() { - const self = this; - - return function (current) { - // this is a reference to the current object (therefore no array function here) - if (this instanceof HTMLInputElement) { - if (["radio", "checkbox"].indexOf(this.type) !== -1) { - return `${this.value}` === `${current}` ? "true" : undefined; - } - } else if (this instanceof HTMLOptionElement) { - if (isArray(current) && current.indexOf(this.value) !== -1) { - return "true"; - } - - return undefined; - } - }; + const self = this; + + return function (current) { + // this is a reference to the current object (therefore no array function here) + if (this instanceof HTMLInputElement) { + if (["radio", "checkbox"].indexOf(this.type) !== -1) { + return `${this.value}` === `${current}` ? "true" : undefined; + } + } else if (this instanceof HTMLOptionElement) { + if (isArray(current) && current.indexOf(this.value) !== -1) { + return "true"; + } + + return undefined; + } + }; } /** @@ -254,28 +264,28 @@ const symbol = Symbol("@schukai/monster/updater@@EventHandler"); * @throws {Error} the bind argument must start as a value with a path */ function getControlEventHandler() { - const self = this; + const self = this; - if (self[symbol]) { - return self[symbol]; - } + if (self[symbol]) { + return self[symbol]; + } - /** - * @throws {Error} the bind argument must start as a value with a path. - * @throws {Error} unsupported object - * @param {Event} event - */ - self[symbol] = (event) => { - const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND); + /** + * @throws {Error} the bind argument must start as a value with a path. + * @throws {Error} unsupported object + * @param {Event} event + */ + self[symbol] = (event) => { + const element = findTargetElementFromEvent(event, ATTRIBUTE_UPDATER_BIND); - if (element === undefined) { - return; - } + if (element === undefined) { + return; + } - retrieveAndSetValue.call(self, element); - }; + retrieveAndSetValue.call(self, element); + }; - return self[symbol]; + return self[symbol]; } /** @@ -286,67 +296,72 @@ function getControlEventHandler() { * @private */ function retrieveAndSetValue(element) { - const self = this; - - const pathfinder = new Pathfinder(self[internalSymbol].subject.getSubject()); - - let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND); - if (path === null) throw new Error("the bind argument must start as a value with a path"); - - if (path.indexOf("path:") !== 0) { - throw new Error("the bind argument must start as a value with a path"); - } - - path = path.substring(5); - - let value; - - if (element instanceof HTMLInputElement) { - switch (element.type) { - case "checkbox": - value = element.checked ? element.value : undefined; - break; - default: - value = element.value; - break; - } - } else if (element instanceof HTMLTextAreaElement) { - value = element.value; - } else if (element instanceof HTMLSelectElement) { - switch (element.type) { - case "select-one": - value = element.value; - break; - case "select-multiple": - value = element.value; - - let options = element?.selectedOptions; - if (options === undefined) options = element.querySelectorAll(":scope option:checked"); - value = Array.from(options).map(({ value }) => value); - - break; - } - - // values from customelements - } else if ( - (element?.constructor?.prototype && - !!Object.getOwnPropertyDescriptor(element.constructor.prototype, "value")?.["get"]) || - element.hasOwnProperty("value") - ) { - value = element?.["value"]; - } else { - throw new Error("unsupported object"); - } - - const copy = clone(self[internalSymbol].subject.getRealSubject()); - const pf = new Pathfinder(copy); - pf.setVia(path, value); - - const diffResult = diff(copy, self[internalSymbol].subject.getRealSubject()); - - if (diffResult.length > 0) { - pathfinder.setVia(path, value); - } + const self = this; + + const pathfinder = new Pathfinder(self[internalSymbol].subject.getSubject()); + + let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND); + if (path === null) + throw new Error("the bind argument must start as a value with a path"); + + if (path.indexOf("path:") !== 0) { + throw new Error("the bind argument must start as a value with a path"); + } + + path = path.substring(5); + + let value; + + if (element instanceof HTMLInputElement) { + switch (element.type) { + case "checkbox": + value = element.checked ? element.value : undefined; + break; + default: + value = element.value; + break; + } + } else if (element instanceof HTMLTextAreaElement) { + value = element.value; + } else if (element instanceof HTMLSelectElement) { + switch (element.type) { + case "select-one": + value = element.value; + break; + case "select-multiple": + value = element.value; + + let options = element?.selectedOptions; + if (options === undefined) + options = element.querySelectorAll(":scope option:checked"); + value = Array.from(options).map(({ value }) => value); + + break; + } + + // values from customelements + } else if ( + (element?.constructor?.prototype && + !!Object.getOwnPropertyDescriptor( + element.constructor.prototype, + "value", + )?.["get"]) || + element.hasOwnProperty("value") + ) { + value = element?.["value"]; + } else { + throw new Error("unsupported object"); + } + + const copy = clone(self[internalSymbol].subject.getRealSubject()); + const pf = new Pathfinder(copy); + pf.setVia(path, value); + + const diffResult = diff(copy, self[internalSymbol].subject.getRealSubject()); + + if (diffResult.length > 0) { + pathfinder.setVia(path, value); + } } /** @@ -356,15 +371,17 @@ function retrieveAndSetValue(element) { * @private */ function retrieveFromBindings() { - const self = this; + const self = this; - if (self[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`)) { - retrieveAndSetValue.call(self, self[internalSymbol].element); - } + if (self[internalSymbol].element.matches(`[${ATTRIBUTE_UPDATER_BIND}]`)) { + retrieveAndSetValue.call(self, self[internalSymbol].element); + } - for (const [, element] of self[internalSymbol].element.querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`).entries()) { - retrieveAndSetValue.call(self, element); - } + for (const [, element] of self[internalSymbol].element + .querySelectorAll(`[${ATTRIBUTE_UPDATER_BIND}]`) + .entries()) { + retrieveAndSetValue.call(self, element); + } } /** @@ -375,13 +392,13 @@ function retrieveFromBindings() { * @return {void} */ function removeElement(change) { - const self = this; + const self = this; - for (const [, element] of self[internalSymbol].element - .querySelectorAll(`:scope [${ATTRIBUTE_UPDATER_REMOVE}]`) - .entries()) { - element.parentNode.removeChild(element); - } + for (const [, element] of self[internalSymbol].element + .querySelectorAll(`:scope [${ATTRIBUTE_UPDATER_REMOVE}]`) + .entries()) { + element.parentNode.removeChild(element); + } } /** @@ -397,124 +414,134 @@ function removeElement(change) { * @this Updater */ function insertElement(change) { - const self = this; - const subject = self[internalSymbol].subject.getRealSubject(); + const self = this; + const subject = self[internalSymbol].subject.getRealSubject(); - let mem = new WeakSet(); - let wd = 0; + let mem = new WeakSet(); + let wd = 0; - const container = self[internalSymbol].element; - - while (true) { - let found = false; - wd++; - - let p = clone(change?.["path"]); - if (!isArray(p)) return self; - - while (p.length > 0) { - const current = p.join("."); - - let iterator = new Set(); - const query = `[${ATTRIBUTE_UPDATER_INSERT}*="path:${current}"]`; - - const e = container.querySelectorAll(query); - - if (e.length > 0) { - iterator = new Set([...e]); - } - - if (container.matches(query)) { - iterator.add(container); - } - - for (const [, containerElement] of iterator.entries()) { - if (mem.has(containerElement)) continue; - mem.add(containerElement); - - found = true; - - const attributes = containerElement.getAttribute(ATTRIBUTE_UPDATER_INSERT); - if (attributes === null) continue; - - let def = trimSpaces(attributes); - let i = def.indexOf(" "); - let key = trimSpaces(def.substr(0, i)); - let refPrefix = `${key}-`; - let cmd = trimSpaces(def.substr(i)); - - // this case is actually excluded by the query but is nevertheless checked again here - if (cmd.indexOf("|") > 0) { - throw new Error("pipes are not allowed when cloning a node."); - } - - let pipe = new Pipe(cmd); - self[internalSymbol].callbacks.forEach((f, n) => { - pipe.setCallback(n, f); - }); - - let value; - try { - containerElement.removeAttribute(ATTRIBUTE_ERRORMESSAGE); - value = pipe.run(subject); - } catch (e) { - containerElement.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); - } - - let dataPath = cmd.split(":").pop(); - - let insertPoint; - if (containerElement.hasChildNodes()) { - insertPoint = containerElement.lastChild; - } - - if (!isIterable(value)) { - throw new Error("the value is not iterable"); - } - - let available = new Set(); - - for (const [i, obj] of Object.entries(value)) { - let ref = refPrefix + i; - let currentPath = `${dataPath}.${i}`; - - available.add(ref); - let refElement = containerElement.querySelector(`[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`); - - if (refElement instanceof HTMLElement) { - insertPoint = refElement; - continue; - } - - appendNewDocumentFragment(containerElement, key, ref, currentPath); - } - - let nodes = containerElement.querySelectorAll( - `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}*="${refPrefix}"]`, - ); - - for (const [, node] of Object.entries(nodes)) { - if (!available.has(node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE))) { - try { - containerElement.removeChild(node); - } catch (e) { - containerElement.setAttribute( - ATTRIBUTE_ERRORMESSAGE, - `${containerElement.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${e.message}`.trim(), - ); - } - } - } - } - - p.pop(); - } - - if (found === false) break; - if (wd++ > 200) { - throw new Error("the maximum depth for the recursion is reached."); - } - } + const container = self[internalSymbol].element; + + while (true) { + let found = false; + wd++; + + let p = clone(change?.["path"]); + if (!isArray(p)) return self; + + while (p.length > 0) { + const current = p.join("."); + + let iterator = new Set(); + const query = `[${ATTRIBUTE_UPDATER_INSERT}*="path:${current}"]`; + + const e = container.querySelectorAll(query); + + if (e.length > 0) { + iterator = new Set([...e]); + } + + if (container.matches(query)) { + iterator.add(container); + } + + for (const [, containerElement] of iterator.entries()) { + if (mem.has(containerElement)) continue; + mem.add(containerElement); + + found = true; + + const attributes = containerElement.getAttribute( + ATTRIBUTE_UPDATER_INSERT, + ); + if (attributes === null) continue; + + let def = trimSpaces(attributes); + let i = def.indexOf(" "); + let key = trimSpaces(def.substr(0, i)); + let refPrefix = `${key}-`; + let cmd = trimSpaces(def.substr(i)); + + // this case is actually excluded by the query but is nevertheless checked again here + if (cmd.indexOf("|") > 0) { + throw new Error("pipes are not allowed when cloning a node."); + } + + let pipe = new Pipe(cmd); + self[internalSymbol].callbacks.forEach((f, n) => { + pipe.setCallback(n, f); + }); + + let value; + try { + containerElement.removeAttribute(ATTRIBUTE_ERRORMESSAGE); + value = pipe.run(subject); + } catch (e) { + containerElement.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); + } + + let dataPath = cmd.split(":").pop(); + + let insertPoint; + if (containerElement.hasChildNodes()) { + insertPoint = containerElement.lastChild; + } + + if (!isIterable(value)) { + throw new Error("the value is not iterable"); + } + + let available = new Set(); + + for (const [i, obj] of Object.entries(value)) { + let ref = refPrefix + i; + let currentPath = `${dataPath}.${i}`; + + available.add(ref); + let refElement = containerElement.querySelector( + `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}="${ref}"]`, + ); + + if (refElement instanceof HTMLElement) { + insertPoint = refElement; + continue; + } + + appendNewDocumentFragment(containerElement, key, ref, currentPath); + } + + let nodes = containerElement.querySelectorAll( + `[${ATTRIBUTE_UPDATER_INSERT_REFERENCE}*="${refPrefix}"]`, + ); + + for (const [, node] of Object.entries(nodes)) { + if ( + !available.has( + node.getAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE), + ) + ) { + try { + containerElement.removeChild(node); + } catch (e) { + containerElement.setAttribute( + ATTRIBUTE_ERRORMESSAGE, + `${containerElement.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${ + e.message + }`.trim(), + ); + } + } + } + } + + p.pop(); + } + + if (found === false) break; + if (wd++ > 200) { + throw new Error("the maximum depth for the recursion is reached."); + } + } } /** @@ -529,17 +556,17 @@ function insertElement(change) { * @throws {Error} no template was found with the specified key. */ function appendNewDocumentFragment(container, key, ref, path) { - let template = findDocumentTemplate(key, container); + let template = findDocumentTemplate(key, container); - let nodes = template.createDocumentFragment(); - for (const [, node] of Object.entries(nodes.childNodes)) { - if (node instanceof HTMLElement) { - applyRecursive(node, key, path); - node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref); - } + let nodes = template.createDocumentFragment(); + for (const [, node] of Object.entries(nodes.childNodes)) { + if (node instanceof HTMLElement) { + applyRecursive(node, key, path); + node.setAttribute(ATTRIBUTE_UPDATER_INSERT_REFERENCE, ref); + } - container.appendChild(node); - } + container.appendChild(node); + } } /** @@ -552,21 +579,27 @@ function appendNewDocumentFragment(container, key, ref, path) { * @return {void} */ function applyRecursive(node, key, path) { - if (node instanceof HTMLElement) { - if (node.hasAttribute(ATTRIBUTE_UPDATER_REPLACE)) { - let value = node.getAttribute(ATTRIBUTE_UPDATER_REPLACE); - node.setAttribute(ATTRIBUTE_UPDATER_REPLACE, value.replaceAll(`path:${key}`, `path:${path}`)); - } - - if (node.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { - let value = node.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); - node.setAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES, value.replaceAll(`path:${key}`, `path:${path}`)); - } - - for (const [, child] of Object.entries(node.childNodes)) { - applyRecursive(child, key, path); - } - } + if (node instanceof HTMLElement) { + if (node.hasAttribute(ATTRIBUTE_UPDATER_REPLACE)) { + let value = node.getAttribute(ATTRIBUTE_UPDATER_REPLACE); + node.setAttribute( + ATTRIBUTE_UPDATER_REPLACE, + value.replaceAll(`path:${key}`, `path:${path}`), + ); + } + + if (node.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { + let value = node.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); + node.setAttribute( + ATTRIBUTE_UPDATER_ATTRIBUTES, + value.replaceAll(`path:${key}`, `path:${path}`), + ); + } + + for (const [, child] of Object.entries(node.childNodes)) { + applyRecursive(child, key, path); + } + } } /** @@ -578,20 +611,20 @@ function applyRecursive(node, key, path) { * @this Updater */ function updateContent(change) { - const self = this; - const subject = self[internalSymbol].subject.getRealSubject(); - - let p = clone(change?.["path"]); - runUpdateContent.call(this, this[internalSymbol].element, p, subject); - - const slots = this[internalSymbol].element.querySelectorAll("slot"); - if (slots.length > 0) { - for (const [, slot] of Object.entries(slots)) { - for (const [, element] of Object.entries(slot.assignedNodes())) { - runUpdateContent.call(this, element, p, subject); - } - } - } + const self = this; + const subject = self[internalSymbol].subject.getRealSubject(); + + let p = clone(change?.["path"]); + runUpdateContent.call(this, this[internalSymbol].element, p, subject); + + const slots = this[internalSymbol].element.querySelectorAll("slot"); + if (slots.length > 0) { + for (const [, slot] of Object.entries(slots)) { + for (const [, element] of Object.entries(slot.assignedNodes())) { + runUpdateContent.call(this, element, p, subject); + } + } + } } /** @@ -604,67 +637,69 @@ function updateContent(change) { * @return {void} */ function runUpdateContent(container, parts, subject) { - if (!isArray(parts)) return; - if (!(container instanceof HTMLElement)) return; - parts = clone(parts); - - let mem = new WeakSet(); - - while (parts.length > 0) { - const current = parts.join("."); - parts.pop(); - - // Unfortunately, static data is always changed as well, since it is not possible to react to changes here. - const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`; - const e = container.querySelectorAll(`${query}`); - - const iterator = new Set([...e]); - - if (container.matches(query)) { - iterator.add(container); - } - - /** - * @type {HTMLElement} - */ - for (const [element] of iterator.entries()) { - if (mem.has(element)) return; - mem.add(element); - - const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE); - let cmd = trimSpaces(attributes); - - let pipe = new Pipe(cmd); - this[internalSymbol].callbacks.forEach((f, n) => { - pipe.setCallback(n, f); - }); - - let value; - try { - element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); - value = pipe.run(subject); - } catch (e) { - element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); - } - - if (value instanceof HTMLElement) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - - try { - element.appendChild(value); - } catch (e) { - element.setAttribute( - ATTRIBUTE_ERRORMESSAGE, - `${element.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${e.message}`.trim(), - ); - } - } else { - element.innerHTML = value; - } - } - } + if (!isArray(parts)) return; + if (!(container instanceof HTMLElement)) return; + parts = clone(parts); + + let mem = new WeakSet(); + + while (parts.length > 0) { + const current = parts.join("."); + parts.pop(); + + // Unfortunately, static data is always changed as well, since it is not possible to react to changes here. + const query = `[${ATTRIBUTE_UPDATER_REPLACE}^="path:${current}"], [${ATTRIBUTE_UPDATER_REPLACE}^="static:"], [${ATTRIBUTE_UPDATER_REPLACE}^="i18n:"]`; + const e = container.querySelectorAll(`${query}`); + + const iterator = new Set([...e]); + + if (container.matches(query)) { + iterator.add(container); + } + + /** + * @type {HTMLElement} + */ + for (const [element] of iterator.entries()) { + if (mem.has(element)) return; + mem.add(element); + + const attributes = element.getAttribute(ATTRIBUTE_UPDATER_REPLACE); + let cmd = trimSpaces(attributes); + + let pipe = new Pipe(cmd); + this[internalSymbol].callbacks.forEach((f, n) => { + pipe.setCallback(n, f); + }); + + let value; + try { + element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); + value = pipe.run(subject); + } catch (e) { + element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); + } + + if (value instanceof HTMLElement) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + + try { + element.appendChild(value); + } catch (e) { + element.setAttribute( + ATTRIBUTE_ERRORMESSAGE, + `${element.getAttribute(ATTRIBUTE_ERRORMESSAGE)}, ${ + e.message + }`.trim(), + ); + } + } else { + element.innerHTML = value; + } + } + } } /** @@ -676,9 +711,9 @@ function runUpdateContent(container, parts, subject) { * @return {void} */ function updateAttributes(change) { - const subject = this[internalSymbol].subject.getRealSubject(); - let p = clone(change?.["path"]); - runUpdateAttributes.call(this, this[internalSymbol].element, p, subject); + const subject = this[internalSymbol].subject.getRealSubject(); + let p = clone(change?.["path"]); + runUpdateAttributes.call(this, this[internalSymbol].element, p, subject); } /** @@ -690,72 +725,72 @@ function updateAttributes(change) { * @this Updater */ function runUpdateAttributes(container, parts, subject) { - const self = this; + const self = this; - if (!isArray(parts)) return; - parts = clone(parts); + if (!isArray(parts)) return; + parts = clone(parts); - let mem = new WeakSet(); + let mem = new WeakSet(); - while (parts.length > 0) { - const current = parts.join("."); - parts.pop(); + while (parts.length > 0) { + const current = parts.join("."); + parts.pop(); - let iterator = new Set(); + let iterator = new Set(); - const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`; + const query = `[${ATTRIBUTE_UPDATER_SELECT_THIS}][${ATTRIBUTE_UPDATER_ATTRIBUTES}], [${ATTRIBUTE_UPDATER_ATTRIBUTES}*="path:${current}"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="static:"], [${ATTRIBUTE_UPDATER_ATTRIBUTES}^="i18n:"]`; - const e = container.querySelectorAll(query); + const e = container.querySelectorAll(query); - if (e.length > 0) { - iterator = new Set([...e]); - } + if (e.length > 0) { + iterator = new Set([...e]); + } - if (container.matches(query)) { - iterator.add(container); - } + if (container.matches(query)) { + iterator.add(container); + } - for (const [element] of iterator.entries()) { - if (mem.has(element)) return; - mem.add(element); + for (const [element] of iterator.entries()) { + if (mem.has(element)) return; + mem.add(element); - // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set - if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { - continue; - } + // this case occurs when the ATTRIBUTE_UPDATER_SELECT_THIS attribute is set + if (!element.hasAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES)) { + continue; + } - const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); + const attributes = element.getAttribute(ATTRIBUTE_UPDATER_ATTRIBUTES); - for (let [, def] of Object.entries(attributes.split(","))) { - def = trimSpaces(def); - let i = def.indexOf(" "); - let name = trimSpaces(def.substr(0, i)); - let cmd = trimSpaces(def.substr(i)); + for (let [, def] of Object.entries(attributes.split(","))) { + def = trimSpaces(def); + let i = def.indexOf(" "); + let name = trimSpaces(def.substr(0, i)); + let cmd = trimSpaces(def.substr(i)); - let pipe = new Pipe(cmd); + let pipe = new Pipe(cmd); - self[internalSymbol].callbacks.forEach((f, n) => { - pipe.setCallback(n, f, element); - }); + self[internalSymbol].callbacks.forEach((f, n) => { + pipe.setCallback(n, f, element); + }); - let value; - try { - element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); - value = pipe.run(subject); - } catch (e) { - element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); - } + let value; + try { + element.removeAttribute(ATTRIBUTE_ERRORMESSAGE); + value = pipe.run(subject); + } catch (e) { + element.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.message); + } - if (value === undefined) { - element.removeAttribute(name); - } else if (element.getAttribute(name) !== value) { - element.setAttribute(name, value); - } + if (value === undefined) { + element.removeAttribute(name); + } else if (element.getAttribute(name) !== value) { + element.setAttribute(name, value); + } - handleInputControlAttributeUpdate.call(this, element, name, value); - } - } - } + handleInputControlAttributeUpdate.call(this, element, name, value); + } + } + } } /** @@ -768,68 +803,68 @@ function runUpdateAttributes(container, parts, subject) { */ function handleInputControlAttributeUpdate(element, name, value) { - const self = this; - - if (element instanceof HTMLSelectElement) { - switch (element.type) { - case "select-multiple": - for (const [index, opt] of Object.entries(element.options)) { - if (value.indexOf(opt.value) !== -1) { - opt.selected = true; - } else { - opt.selected = false; - } - } - - break; - case "select-one": - // Only one value may be selected - - for (const [index, opt] of Object.entries(element.options)) { - if (opt.value === value) { - element.selectedIndex = index; - break; - } - } - - break; - } - } else if (element instanceof HTMLInputElement) { - switch (element.type) { - case "radio": - if (name === "checked") { - if (value !== undefined) { - element.checked = true; - } else { - element.checked = false; - } - } - - break; - - case "checkbox": - if (name === "checked") { - if (value !== undefined) { - element.checked = true; - } else { - element.checked = false; - } - } - - break; - case "text": - default: - if (name === "value") { - element.value = value === undefined ? "" : value; - } - - break; - } - } else if (element instanceof HTMLTextAreaElement) { - if (name === "value") { - element.value = value === undefined ? "" : value; - } - } + const self = this; + + if (element instanceof HTMLSelectElement) { + switch (element.type) { + case "select-multiple": + for (const [index, opt] of Object.entries(element.options)) { + if (value.indexOf(opt.value) !== -1) { + opt.selected = true; + } else { + opt.selected = false; + } + } + + break; + case "select-one": + // Only one value may be selected + + for (const [index, opt] of Object.entries(element.options)) { + if (opt.value === value) { + element.selectedIndex = index; + break; + } + } + + break; + } + } else if (element instanceof HTMLInputElement) { + switch (element.type) { + case "radio": + if (name === "checked") { + if (value !== undefined) { + element.checked = true; + } else { + element.checked = false; + } + } + + break; + + case "checkbox": + if (name === "checked") { + if (value !== undefined) { + element.checked = true; + } else { + element.checked = false; + } + } + + break; + case "text": + default: + if (name === "value") { + element.value = value === undefined ? "" : value; + } + + break; + } + } else if (element instanceof HTMLTextAreaElement) { + if (name === "value") { + element.value = value === undefined ? "" : value; + } + } } /** @@ -845,45 +880,49 @@ function handleInputControlAttributeUpdate(element, name, value) { * @throws {TypeError} symbol must be an instance of Symbol */ function addObjectWithUpdaterToElement(elements, symbol, object) { - const self = this; - if (!(self instanceof HTMLElement)) { - throw new TypeError("the context of this function must be an instance of HTMLElement"); - } - - if (!(typeof symbol === "symbol")) { - throw new TypeError("symbol must be an instance of Symbol"); - } - - const updaters = new Set(); - - if (elements instanceof NodeList) { - elements = new Set([...elements]); - } else if (elements instanceof HTMLElement) { - elements = new Set([elements]); - } else if (elements instanceof Set) { - } else { - throw new TypeError(`elements is not a valid type. (actual: ${typeof elements})`); - } - - let result = []; - - elements.forEach((element) => { - if (!(element instanceof HTMLElement)) return; - if (element instanceof HTMLTemplateElement) return; - - const u = new Updater(element, object); - updaters.add(u); - - result.push( - u.run().then(() => { - return u.enableEventProcessing(); - }), - ); - }); - - if (updaters.size > 0) { - addToObjectLink(self, symbol, updaters); - } - - return result; + const self = this; + if (!(self instanceof HTMLElement)) { + throw new TypeError( + "the context of this function must be an instance of HTMLElement", + ); + } + + if (!(typeof symbol === "symbol")) { + throw new TypeError("symbol must be an instance of Symbol"); + } + + const updaters = new Set(); + + if (elements instanceof NodeList) { + elements = new Set([...elements]); + } else if (elements instanceof HTMLElement) { + elements = new Set([elements]); + } else if (elements instanceof Set) { + } else { + throw new TypeError( + `elements is not a valid type. (actual: ${typeof elements})`, + ); + } + + let result = []; + + elements.forEach((element) => { + if (!(element instanceof HTMLElement)) return; + if (element instanceof HTMLTemplateElement) return; + + const u = new Updater(element, object); + updaters.add(u); + + result.push( + u.run().then(() => { + return u.enableEventProcessing(); + }), + ); + }); + + if (updaters.size > 0) { + addToObjectLink(self, symbol, updaters); + } + + return result; } diff --git a/source/dom/util.mjs b/source/dom/util.mjs index 7e7c2ff2f..d32d22520 100644 --- a/source/dom/util.mjs +++ b/source/dom/util.mjs @@ -8,7 +8,13 @@ import { getGlobal } from "../types/global.mjs"; import { validateString } from "../types/validate.mjs"; -export { getDocument, getWindow, getDocumentFragmentFromString, findElementWithIdUpwards, getContainingDocument }; +export { + getDocument, + getWindow, + getDocumentFragmentFromString, + findElementWithIdUpwards, + getContainingDocument, +}; /** * This method fetches the document object @@ -48,12 +54,12 @@ export { getDocument, getWindow, getDocumentFragmentFromString, findElementWithI * @throws {Error} not supported environment */ function getDocument() { - let document = getGlobal()?.["document"]; - if (typeof document !== "object") { - throw new Error("not supported environment"); - } + let document = getGlobal()?.["document"]; + if (typeof document !== "object") { + throw new Error("not supported environment"); + } - return document; + return document; } /** @@ -96,12 +102,12 @@ function getDocument() { * @throws {Error} not supported environment */ function getWindow() { - let window = getGlobal()?.["window"]; - if (typeof window !== "object") { - throw new Error("not supported environment"); - } + let window = getGlobal()?.["window"]; + if (typeof window !== "object") { + throw new Error("not supported environment"); + } - return window; + return window; } /** @@ -143,13 +149,13 @@ function getWindow() { * @throws {TypeError} value is not a string */ function getDocumentFragmentFromString(html) { - validateString(html); + validateString(html); - const document = getDocument(); - const template = document.createElement("template"); - template.innerHTML = html; + const document = getDocument(); + const template = document.createElement("template"); + template.innerHTML = html; - return template.content; + return template.content; } /** @@ -165,39 +171,39 @@ function getDocumentFragmentFromString(html) { * @copyright schukai GmbH */ function findElementWithIdUpwards(element, targetId) { - if (!element) { - return null; - } - - // Check if the current element has the target ID - if (element.id === targetId) { - return element; - } - - // Search within the current element's shadow root, if it exists - if (element.shadowRoot) { - const target = element.shadowRoot.getElementById(targetId); - if (target) { - return target; - } - } - - // If the current element is the document.documentElement, search within the main document - if (element === document.documentElement) { - const target = document.getElementById(targetId); - if (target) { - return target; - } - } - - // If the current element is inside a shadow root, search its host's ancestors - const rootNode = element.getRootNode(); - if (rootNode && rootNode instanceof ShadowRoot) { - return findElementWithIdUpwards(rootNode.host, targetId); - } - - // Otherwise, search the current element's parent - return findElementWithIdUpwards(element.parentElement, targetId); + if (!element) { + return null; + } + + // Check if the current element has the target ID + if (element.id === targetId) { + return element; + } + + // Search within the current element's shadow root, if it exists + if (element.shadowRoot) { + const target = element.shadowRoot.getElementById(targetId); + if (target) { + return target; + } + } + + // If the current element is the document.documentElement, search within the main document + if (element === document.documentElement) { + const target = document.getElementById(targetId); + if (target) { + return target; + } + } + + // If the current element is inside a shadow root, search its host's ancestors + const rootNode = element.getRootNode(); + if (rootNode && rootNode instanceof ShadowRoot) { + return findElementWithIdUpwards(rootNode.host, targetId); + } + + // Otherwise, search the current element's parent + return findElementWithIdUpwards(element.parentElement, targetId); } /** @@ -206,30 +212,34 @@ function findElementWithIdUpwards(element, targetId) { * @returns {HTMLElement|null} */ function traverseShadowRoots(element) { - let currentRoot = element.shadowRoot; - let currentParent = element.parentNode; - - while ( - currentParent && - currentParent.nodeType !== Node.DOCUMENT_NODE && - currentParent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE - ) { - if (currentRoot && currentRoot.parentNode) { - currentParent = currentRoot.parentNode; - currentRoot = currentParent.shadowRoot; - } else if (currentParent.parentNode) { - currentParent = currentParent.parentNode; - currentRoot = null; - } else if (currentRoot && currentRoot.host && currentRoot.host.nodeType === Node.DOCUMENT_NODE) { - currentParent = currentRoot.host; - currentRoot = null; - } else { - currentParent = null; - currentRoot = null; - } - } - - return currentParent; + let currentRoot = element.shadowRoot; + let currentParent = element.parentNode; + + while ( + currentParent && + currentParent.nodeType !== Node.DOCUMENT_NODE && + currentParent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE + ) { + if (currentRoot && currentRoot.parentNode) { + currentParent = currentRoot.parentNode; + currentRoot = currentParent.shadowRoot; + } else if (currentParent.parentNode) { + currentParent = currentParent.parentNode; + currentRoot = null; + } else if ( + currentRoot && + currentRoot.host && + currentRoot.host.nodeType === Node.DOCUMENT_NODE + ) { + currentParent = currentRoot.host; + currentRoot = null; + } else { + currentParent = null; + currentRoot = null; + } + } + + return currentParent; } /** @@ -242,12 +252,15 @@ function traverseShadowRoots(element) { * @since 3.36.0 */ function getContainingDocument(element) { - if ( - !element || - !(element instanceof HTMLElement || element instanceof element.ownerDocument.defaultView.HTMLElement) - ) { - throw new Error("Invalid argument. Expected an HTMLElement."); - } - - return traverseShadowRoots(element) || null; + if ( + !element || + !( + element instanceof HTMLElement || + element instanceof element.ownerDocument.defaultView.HTMLElement + ) + ) { + throw new Error("Invalid argument. Expected an HTMLElement."); + } + + return traverseShadowRoots(element) || null; } diff --git a/source/dom/util/extract-keys.mjs b/source/dom/util/extract-keys.mjs index 976be3a30..399945438 100644 --- a/source/dom/util/extract-keys.mjs +++ b/source/dom/util/extract-keys.mjs @@ -17,28 +17,40 @@ export { extractKeys }; * @param {string} valueSeparator * @returns {Map<any, any>} */ -function extractKeys(obj, keyPrefix = "", keySeparator = "-", valueSeparator = ".") { - const resultMap = new Map(); +function extractKeys( + obj, + keyPrefix = "", + keySeparator = "-", + valueSeparator = ".", +) { + const resultMap = new Map(); - function helper(currentObj, currentKeyPrefix, currentValuePrefix) { - for (const key in currentObj) { - if (currentObj[key] !== null && typeof currentObj[key] === "object" && !Array.isArray(currentObj[key])) { - const newKeyPrefix = currentKeyPrefix - ? currentKeyPrefix + keySeparator + key.toLowerCase() - : key.toLowerCase(); - const newValuePrefix = currentValuePrefix ? currentValuePrefix + valueSeparator + key : key; - helper(currentObj[key], newKeyPrefix, newValuePrefix); - } else { - const finalKey = currentKeyPrefix - ? currentKeyPrefix + keySeparator + key.toLowerCase() - : key.toLowerCase(); - const finalValue = currentValuePrefix ? currentValuePrefix + valueSeparator + key : key; - resultMap.set(finalKey, finalValue); - } - } - } + function helper(currentObj, currentKeyPrefix, currentValuePrefix) { + for (const key in currentObj) { + if ( + currentObj[key] !== null && + typeof currentObj[key] === "object" && + !Array.isArray(currentObj[key]) + ) { + const newKeyPrefix = currentKeyPrefix + ? currentKeyPrefix + keySeparator + key.toLowerCase() + : key.toLowerCase(); + const newValuePrefix = currentValuePrefix + ? currentValuePrefix + valueSeparator + key + : key; + helper(currentObj[key], newKeyPrefix, newValuePrefix); + } else { + const finalKey = currentKeyPrefix + ? currentKeyPrefix + keySeparator + key.toLowerCase() + : key.toLowerCase(); + const finalValue = currentValuePrefix + ? currentValuePrefix + valueSeparator + key + : key; + resultMap.set(finalKey, finalValue); + } + } + } - helper(obj, keyPrefix, keyPrefix); - return resultMap; + helper(obj, keyPrefix, keyPrefix); + return resultMap; } - diff --git a/source/dom/util/init-options-from-attributes.mjs b/source/dom/util/init-options-from-attributes.mjs index e428dbceb..6286f4c9f 100644 --- a/source/dom/util/init-options-from-attributes.mjs +++ b/source/dom/util/init-options-from-attributes.mjs @@ -41,50 +41,58 @@ export { initOptionsFromAttributes }; * @returns {Object} - The initialized options object. * @this HTMLElement - The context of the DOM element. */ -function initOptionsFromAttributes(element, options, mapping = {}, prefix = "data-monster-option-") { - if (!(element instanceof HTMLElement)) return options; - if (!element.hasAttributes()) return options; +function initOptionsFromAttributes( + element, + options, + mapping = {}, + prefix = "data-monster-option-", +) { + if (!(element instanceof HTMLElement)) return options; + if (!element.hasAttributes()) return options; - const keyMap = extractKeys(options); + const keyMap = extractKeys(options); - const finder = new Pathfinder(options); + const finder = new Pathfinder(options); - element.getAttributeNames().forEach((name) => { - if (!name.startsWith(prefix)) return; + element.getAttributeNames().forEach((name) => { + if (!name.startsWith(prefix)) return; - // check if the attribute name is a valid option. - // the mapping between the attribute is simple. The dash is replaced by a dot. - // e.g. data-monster-url => url - const optionName = keyMap.get(name.substring(prefix.length).toLowerCase()); - if (!finder.exists(optionName)) return; + // check if the attribute name is a valid option. + // the mapping between the attribute is simple. The dash is replaced by a dot. + // e.g. data-monster-url => url + const optionName = keyMap.get(name.substring(prefix.length).toLowerCase()); + if (!finder.exists(optionName)) return; - if (element.hasAttribute(name)) { - let value = element.getAttribute(name); - if (mapping.hasOwnProperty(optionName) && isFunction(mapping[optionName])) { - value = mapping[optionName](value); - } + if (element.hasAttribute(name)) { + let value = element.getAttribute(name); + if ( + mapping.hasOwnProperty(optionName) && + isFunction(mapping[optionName]) + ) { + value = mapping[optionName](value); + } - let optionValue = finder.getVia(optionName); - if (optionValue === null || optionValue === undefined) { - optionValue = value; - } - - const typeOfOptionValue = typeof optionValue; - if (optionValue === null || optionValue === undefined) { - value = null; - } else if (typeOfOptionValue === "boolean") { - value = value === "true"; - } else if (typeOfOptionValue === "number") { - value = Number(value); - } else if (typeOfOptionValue === "string") { - value = String(value); - } else if (typeOfOptionValue === "object") { - value = JSON.parse(value); - } + let optionValue = finder.getVia(optionName); + if (optionValue === null || optionValue === undefined) { + optionValue = value; + } - finder.setVia(optionName, value); - } - }); + const typeOfOptionValue = typeof optionValue; + if (optionValue === null || optionValue === undefined) { + value = null; + } else if (typeOfOptionValue === "boolean") { + value = value === "true"; + } else if (typeOfOptionValue === "number") { + value = Number(value); + } else if (typeOfOptionValue === "string") { + value = String(value); + } else if (typeOfOptionValue === "object") { + value = JSON.parse(value); + } - return options; + finder.setVia(optionName, value); + } + }); + + return options; } diff --git a/source/dom/util/set-option-from-attribute.mjs b/source/dom/util/set-option-from-attribute.mjs index 21448b1a8..dd717e1ee 100644 --- a/source/dom/util/set-option-from-attribute.mjs +++ b/source/dom/util/set-option-from-attribute.mjs @@ -42,40 +42,46 @@ export { setOptionFromAttribute }; * @returns {Object} - The initialized options object. * @this HTMLElement - The context of the DOM element. */ -function setOptionFromAttribute(element, name, options, mapping = {}, prefix = "data-monster-option-") { - if (!(element instanceof HTMLElement)) return options; - if (!element.hasAttributes()) return options; +function setOptionFromAttribute( + element, + name, + options, + mapping = {}, + prefix = "data-monster-option-", +) { + if (!(element instanceof HTMLElement)) return options; + if (!element.hasAttributes()) return options; - const keyMap = extractKeys(options); - const finder = new Pathfinder(options); + const keyMap = extractKeys(options); + const finder = new Pathfinder(options); - // check if the attribute name is a valid option. - // the mapping between the attribute is simple. The dash is replaced by a dot. - // e.g. data-monster-url => url - const optionName = keyMap.get(name.substring(prefix.length).toLowerCase()); - if (!finder.exists(optionName)) return; + // check if the attribute name is a valid option. + // the mapping between the attribute is simple. The dash is replaced by a dot. + // e.g. data-monster-url => url + const optionName = keyMap.get(name.substring(prefix.length).toLowerCase()); + if (!finder.exists(optionName)) return; - if (!element.hasAttribute(name)) { - return options; - } + if (!element.hasAttribute(name)) { + return options; + } - let value = element.getAttribute(name); - if (mapping.hasOwnProperty(optionName) && isFunction(mapping[optionName])) { - value = mapping[optionName](value); - } + let value = element.getAttribute(name); + if (mapping.hasOwnProperty(optionName) && isFunction(mapping[optionName])) { + value = mapping[optionName](value); + } - const typeOfOptionValue = typeof finder.getVia(optionName); - if (typeOfOptionValue === "boolean") { - value = value === "true"; - } else if (typeOfOptionValue === "number") { - value = Number(value); - } else if (typeOfOptionValue === "string") { - value = String(value); - } else if (typeOfOptionValue === "object") { - value = JSON.parse(value); - } + const typeOfOptionValue = typeof finder.getVia(optionName); + if (typeOfOptionValue === "boolean") { + value = value === "true"; + } else if (typeOfOptionValue === "number") { + value = Number(value); + } else if (typeOfOptionValue === "string") { + value = String(value); + } else if (typeOfOptionValue === "object") { + value = JSON.parse(value); + } - finder.setVia(optionName, value); + finder.setVia(optionName, value); - return options; + return options; } diff --git a/source/dom/worker/factory.mjs b/source/dom/worker/factory.mjs index 85b1fad0f..3fb46982d 100644 --- a/source/dom/worker/factory.mjs +++ b/source/dom/worker/factory.mjs @@ -23,87 +23,87 @@ export { Factory }; * @summary A small factory to create worker */ class Factory extends Base { - /** - * - */ - constructor() { - super(); - this[internalSymbol] = { - worker: new WeakMap(), - }; - } - - /** - * Creates a worker from a URL - * - * @param {string|URL} url - * @param {function} messageHandler - * @param {function} errorHandler - * @return {Worker} - */ - createFromURL = function (url, messageHandler, errorHandler) { - if (url instanceof URL) { - url = url.toString(); - } - - const workerClass = getGlobalFunction("Worker"); - var worker = new workerClass(validateString(url)); - - if (isFunction(messageHandler)) { - worker.onmessage = (event) => { - messageHandler.call(worker, event); - }; - } - - if (isFunction(errorHandler)) { - worker.onerror = (event) => { - errorHandler.call(worker, event); - }; - } - - return worker; - }; - - /** - * Creates a worker from a script - * - * @param {string} content - * @param {function} messageHandler - * @param {function} errorHandler - * @return {Worker} - * @see https://developer.mozilla.org/de/docs/Web/API/URL/createObjectURL - */ - createFromScript = function (content, messageHandler, errorHandler) { - const blobFunction = new getGlobalFunction("Blob"); - const blob = new blobFunction([validateString(content)], { - type: "script/javascript", - }); - - const url = getGlobalFunction("URL").createObjectURL(blob); - const worker = this.createFromURL(url, messageHandler, errorHandler); - - this[internalSymbol]["worker"].set(worker, url); - - return worker; - }; - - /** - * Terminate the worker and call revokeObjectURL if necessary. - * - * @param worker - * @return {Monster.DOM.Worker.Factory} - */ - terminate(worker) { - const workerClass = getGlobalFunction("Worker"); - validateInstance(worker, workerClass); - - worker.terminate(); - - if (this[internalSymbol]["worker"].has(worker)) { - const url = this[internalSymbol]["worker"].get(worker); - URL.revokeObjectURL(url); - } - - return this; - } + /** + * + */ + constructor() { + super(); + this[internalSymbol] = { + worker: new WeakMap(), + }; + } + + /** + * Creates a worker from a URL + * + * @param {string|URL} url + * @param {function} messageHandler + * @param {function} errorHandler + * @return {Worker} + */ + createFromURL = function (url, messageHandler, errorHandler) { + if (url instanceof URL) { + url = url.toString(); + } + + const workerClass = getGlobalFunction("Worker"); + var worker = new workerClass(validateString(url)); + + if (isFunction(messageHandler)) { + worker.onmessage = (event) => { + messageHandler.call(worker, event); + }; + } + + if (isFunction(errorHandler)) { + worker.onerror = (event) => { + errorHandler.call(worker, event); + }; + } + + return worker; + }; + + /** + * Creates a worker from a script + * + * @param {string} content + * @param {function} messageHandler + * @param {function} errorHandler + * @return {Worker} + * @see https://developer.mozilla.org/de/docs/Web/API/URL/createObjectURL + */ + createFromScript = function (content, messageHandler, errorHandler) { + const blobFunction = new getGlobalFunction("Blob"); + const blob = new blobFunction([validateString(content)], { + type: "script/javascript", + }); + + const url = getGlobalFunction("URL").createObjectURL(blob); + const worker = this.createFromURL(url, messageHandler, errorHandler); + + this[internalSymbol]["worker"].set(worker, url); + + return worker; + }; + + /** + * Terminate the worker and call revokeObjectURL if necessary. + * + * @param worker + * @return {Monster.DOM.Worker.Factory} + */ + terminate(worker) { + const workerClass = getGlobalFunction("Worker"); + validateInstance(worker, workerClass); + + worker.terminate(); + + if (this[internalSymbol]["worker"].has(worker)) { + const url = this[internalSymbol]["worker"].get(worker); + URL.revokeObjectURL(url); + } + + return this; + } } diff --git a/source/i18n/formatter.mjs b/source/i18n/formatter.mjs index 2ea43acb0..e659698ca 100644 --- a/source/i18n/formatter.mjs +++ b/source/i18n/formatter.mjs @@ -30,85 +30,88 @@ const internalTranslationSymbol = Symbol("internalTranslation"); * @memberOf Monster.I18n */ class Formatter extends TextFormatter { - /** - * Default values for the markers are `${` and `}` - * - * @param {object} object - * @throws {TypeError} value is not a object - */ - constructor(object, translation, options) { - super(object, options); - this[internalTranslationSymbol] = validateInstance(translation, Translations); - } + /** + * Default values for the markers are `${` and `}` + * + * @param {object} object + * @throws {TypeError} value is not a object + */ + constructor(object, translation, options) { + super(object, options); + this[internalTranslationSymbol] = validateInstance( + translation, + Translations, + ); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 3.27.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/i18n/formatter@@instance"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 3.27.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/i18n/formatter@@instance"); + } - /** - * @property {object} marker - * @property {array} marker.open=["i18n{","${"] - * @property {array} marker.close=["${"] - * @property {object} parameter - * @property {string} parameter.delimiter="::" - * @property {string} parameter.assignment="=" - * @property {object} callbacks - * @property {function} callbacks.i18n=()=>{} - */ - get defaults() { - const self = this; - return extend({}, super.defaults, { - callbacks: { - i18n: (value) => { - return self[internalTranslationSymbol].getText(validateString(value)); - }, - }, - marker: { - open: ["i18n{", "${"], - close: ["}"], - }, - }); - } + /** + * @property {object} marker + * @property {array} marker.open=["i18n{","${"] + * @property {array} marker.close=["${"] + * @property {object} parameter + * @property {string} parameter.delimiter="::" + * @property {string} parameter.assignment="=" + * @property {object} callbacks + * @property {function} callbacks.i18n=()=>{} + */ + get defaults() { + const self = this; + return extend({}, super.defaults, { + callbacks: { + i18n: (value) => { + return self[internalTranslationSymbol].getText(validateString(value)); + }, + }, + marker: { + open: ["i18n{", "${"], + close: ["}"], + }, + }); + } - /** - * - * @param {string} text - * @return {string} - * @throws {TypeError} value is not a string - * @throws {Error} too deep nesting - * @throws {Error} key not found - * @throws {Error} the closing marker is missing - */ - format(text) { - validateString(text); + /** + * + * @param {string} text + * @return {string} + * @throws {TypeError} value is not a string + * @throws {Error} too deep nesting + * @throws {Error} key not found + * @throws {Error} the closing marker is missing + */ + format(text) { + validateString(text); - const openMarker = this[internalSymbol]["marker"]["open"]?.[0]; - const closeMarker = this[internalSymbol]["marker"]["close"]?.[0]; + const openMarker = this[internalSymbol]["marker"]["open"]?.[0]; + const closeMarker = this[internalSymbol]["marker"]["close"]?.[0]; - if (text.indexOf(openMarker) === 0) { - text = text.substring(openMarker.length); + if (text.indexOf(openMarker) === 0) { + text = text.substring(openMarker.length); - if (text.indexOf(closeMarker) === text.length - closeMarker.length) { - text = text.substring(0, text.length - closeMarker.length); - } else { - throw new Error("the closing marker is missing"); - } - } + if (text.indexOf(closeMarker) === text.length - closeMarker.length) { + text = text.substring(0, text.length - closeMarker.length); + } else { + throw new Error("the closing marker is missing"); + } + } - const parts = validateString(text).split("::"); - const translationKey = parts.shift().trim(); // key value delimiter - const parameter = parts.join("::").trim(); + const parts = validateString(text).split("::"); + const translationKey = parts.shift().trim(); // key value delimiter + const parameter = parts.join("::").trim(); - let assembledText = `${openMarker}static:${translationKey} | call:i18n`; - if (parameter.length > 0) { - assembledText += `::${parameter}`; - } - assembledText += closeMarker; - return super.format(assembledText); - } + let assembledText = `${openMarker}static:${translationKey} | call:i18n`; + if (parameter.length > 0) { + assembledText += `::${parameter}`; + } + assembledText += closeMarker; + return super.format(assembledText); + } } diff --git a/source/i18n/locale.mjs b/source/i18n/locale.mjs index 2511ba9c5..3f9adc9d1 100644 --- a/source/i18n/locale.mjs +++ b/source/i18n/locale.mjs @@ -65,115 +65,116 @@ const localeStringSymbol = Symbol("localeString"); * @see https://datatracker.ietf.org/doc/html/rfc3066 */ class Locale extends Base { - /** - * @param {string} language - * @param {string} [region] - * @param {string} [script] - * @param {string} [variants] - * @param {string} [extlang] - * @param {string} [privateUse] - * @throws {Error} unsupported locale - */ - constructor(language, region, script, variants, extlang, privateUse) { - super(); + /** + * @param {string} language + * @param {string} [region] + * @param {string} [script] + * @param {string} [variants] + * @param {string} [extlang] + * @param {string} [privateUse] + * @throws {Error} unsupported locale + */ + constructor(language, region, script, variants, extlang, privateUse) { + super(); - this[propertiesSymbol] = { - language: language === undefined ? undefined : validateString(language), - script: script === undefined ? undefined : validateString(script), - region: region === undefined ? undefined : validateString(region), - variants: variants === undefined ? undefined : validateString(variants), - extlang: extlang === undefined ? undefined : validateString(extlang), - privateUse: privateUse === undefined ? undefined : validateString(privateUse), - }; + this[propertiesSymbol] = { + language: language === undefined ? undefined : validateString(language), + script: script === undefined ? undefined : validateString(script), + region: region === undefined ? undefined : validateString(region), + variants: variants === undefined ? undefined : validateString(variants), + extlang: extlang === undefined ? undefined : validateString(extlang), + privateUse: + privateUse === undefined ? undefined : validateString(privateUse), + }; - let s = []; - if (language !== undefined) s.push(language); - if (script !== undefined) s.push(script); - if (region !== undefined) s.push(region); - if (variants !== undefined) s.push(variants); - if (extlang !== undefined) s.push(extlang); - if (privateUse !== undefined) s.push(privateUse); + let s = []; + if (language !== undefined) s.push(language); + if (script !== undefined) s.push(script); + if (region !== undefined) s.push(region); + if (variants !== undefined) s.push(variants); + if (extlang !== undefined) s.push(extlang); + if (privateUse !== undefined) s.push(privateUse); - if (s.length === 0) { - throw new Error("unsupported locale"); - } + if (s.length === 0) { + throw new Error("unsupported locale"); + } - this[localeStringSymbol] = s.join("-"); - } + this[localeStringSymbol] = s.join("-"); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 3.27.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/i18n/locale@@instance"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 3.27.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/i18n/locale@@instance"); + } - /** - * @return {string} - */ - get localeString() { - return this[localeStringSymbol]; - } + /** + * @return {string} + */ + get localeString() { + return this[localeStringSymbol]; + } - /** - * @return {string|undefined} - */ - get language() { - return this[propertiesSymbol].language; - } + /** + * @return {string|undefined} + */ + get language() { + return this[propertiesSymbol].language; + } - /** - * @return {string|undefined} - */ - get region() { - return this[propertiesSymbol].region; - } + /** + * @return {string|undefined} + */ + get region() { + return this[propertiesSymbol].region; + } - /** - * @return {string|undefined} - */ - get script() { - return this[propertiesSymbol].script; - } + /** + * @return {string|undefined} + */ + get script() { + return this[propertiesSymbol].script; + } - /** - * @return {string|undefined} - */ - get variants() { - return this[propertiesSymbol].variants; - } + /** + * @return {string|undefined} + */ + get variants() { + return this[propertiesSymbol].variants; + } - /** - * @return {string|undefined} - */ - get extlang() { - return this[propertiesSymbol].extlang; - } + /** + * @return {string|undefined} + */ + get extlang() { + return this[propertiesSymbol].extlang; + } - /** - * @return {string|undefined} - */ - get privateUse() { - return this[propertiesSymbol].privateValue; - } + /** + * @return {string|undefined} + */ + get privateUse() { + return this[propertiesSymbol].privateValue; + } - /** - * @return {string} - */ - toString() { - return `${this.localeString}`; - } + /** + * @return {string} + */ + toString() { + return `${this.localeString}`; + } - /** - * The structure has the following: language, script, region, variants, extlang, privateUse - * - * @return {Monster.I18n.LocaleMap} - */ - getMap() { - return clone(this[propertiesSymbol]); - } + /** + * The structure has the following: language, script, region, variants, extlang, privateUse + * + * @return {Monster.I18n.LocaleMap} + */ + getMap() { + return clone(this[propertiesSymbol]); + } } /** @@ -257,62 +258,63 @@ class Locale extends Base { * @throws {Error} unsupported locale */ function parseLocale(locale) { - locale = validateString(locale).replace(/_/g, "-"); + locale = validateString(locale).replace(/_/g, "-"); - let language; - let region; - let variants; - let parts; - let script; - let extlang; - let regexRegular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)"; - let regexIrregular = - "(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)"; - let regexGrandfathered = `(${regexIrregular}|${regexRegular})`; - let regexPrivateUse = "(x(-[A-Za-z0-9]{1,8})+)"; - let regexSingleton = "[0-9A-WY-Za-wy-z]"; - let regexExtension = `(${regexSingleton}(-[A-Za-z0-9]{2,8})+)`; - let regexVariant = "([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})"; - let regexRegion = "([A-Za-z]{2}|[0-9]{3})"; - let regexScript = "([A-Za-z]{4})"; - let regexExtlang = "([A-Za-z]{3}(-[A-Za-z]{3}){0,2})"; - let regexLanguage = `(([A-Za-z]{2,3}(-${regexExtlang})?)|[A-Za-z]{4}|[A-Za-z]{5,8})`; - let regexLangtag = `(${regexLanguage}(-${regexScript})?(-${regexRegion})?(-${regexVariant})*(-${regexExtension})*(-${regexPrivateUse})?)`; - let regexLanguageTag = `^(${regexGrandfathered}|${regexLangtag}|${regexPrivateUse})$`; - let regex = new RegExp(regexLanguageTag); - let match; + let language; + let region; + let variants; + let parts; + let script; + let extlang; + let regexRegular = + "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)"; + let regexIrregular = + "(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)"; + let regexGrandfathered = `(${regexIrregular}|${regexRegular})`; + let regexPrivateUse = "(x(-[A-Za-z0-9]{1,8})+)"; + let regexSingleton = "[0-9A-WY-Za-wy-z]"; + let regexExtension = `(${regexSingleton}(-[A-Za-z0-9]{2,8})+)`; + let regexVariant = "([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})"; + let regexRegion = "([A-Za-z]{2}|[0-9]{3})"; + let regexScript = "([A-Za-z]{4})"; + let regexExtlang = "([A-Za-z]{3}(-[A-Za-z]{3}){0,2})"; + let regexLanguage = `(([A-Za-z]{2,3}(-${regexExtlang})?)|[A-Za-z]{4}|[A-Za-z]{5,8})`; + let regexLangtag = `(${regexLanguage}(-${regexScript})?(-${regexRegion})?(-${regexVariant})*(-${regexExtension})*(-${regexPrivateUse})?)`; + let regexLanguageTag = `^(${regexGrandfathered}|${regexLangtag}|${regexPrivateUse})$`; + let regex = new RegExp(regexLanguageTag); + let match; - if ((match = regex.exec(locale)) !== null) { - if (match.index === regex.lastIndex) { - regex.lastIndex++; - } - } + if ((match = regex.exec(locale)) !== null) { + if (match.index === regex.lastIndex) { + regex.lastIndex++; + } + } - if (match === undefined || match === null) { - throw new Error("unsupported locale"); - } + if (match === undefined || match === null) { + throw new Error("unsupported locale"); + } - if (match[6] !== undefined) { - language = match[6]; + if (match[6] !== undefined) { + language = match[6]; - parts = language.split("-"); - if (parts.length > 1) { - language = parts[0]; - extlang = parts[1]; - } - } + parts = language.split("-"); + if (parts.length > 1) { + language = parts[0]; + extlang = parts[1]; + } + } - if (match[14] !== undefined) { - region = match[14]; - } + if (match[14] !== undefined) { + region = match[14]; + } - if (match[12] !== undefined) { - script = match[12]; - } + if (match[12] !== undefined) { + script = match[12]; + } - if (match[16] !== undefined) { - variants = match[16]; - } + if (match[16] !== undefined) { + variants = match[16]; + } - return new Locale(language, region, script, variants, extlang); + return new Locale(language, region, script, variants, extlang); } diff --git a/source/i18n/provider.mjs b/source/i18n/provider.mjs index 8a5207d63..a33e787a1 100644 --- a/source/i18n/provider.mjs +++ b/source/i18n/provider.mjs @@ -6,7 +6,11 @@ */ import { instanceSymbol } from "../constants.mjs"; -import { hasObjectLink, getLinkedObjects, addToObjectLink } from "../dom/attributes.mjs"; +import { + hasObjectLink, + getLinkedObjects, + addToObjectLink, +} from "../dom/attributes.mjs"; import { getLocaleOfDocument } from "../dom/locale.mjs"; import { BaseWithOptions } from "../types/basewithoptions.mjs"; import { Locale } from "./locale.mjs"; @@ -21,7 +25,9 @@ export { Provider, translationsLinkSymbol }; * @since 3.9.0 * @private */ -const translationsLinkSymbol = Symbol.for("@schukai/monster/i18n/translations@@link"); +const translationsLinkSymbol = Symbol.for( + "@schukai/monster/i18n/translations@@link", +); /** * A provider makes a translation object available. @@ -33,76 +39,76 @@ const translationsLinkSymbol = Symbol.for("@schukai/monster/i18n/translations@@l * @see {@link https://datatracker.ietf.org/doc/html/rfc3066} */ class Provider extends BaseWithOptions { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 3.27.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/i18n/provider@@instance"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 3.27.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/i18n/provider@@instance"); + } - /** - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - if (locale === undefined) { - locale = getLocaleOfDocument(); - } + /** + * @param {Locale|string} locale + * @return {Promise} + */ + getTranslations(locale) { + if (locale === undefined) { + locale = getLocaleOfDocument(); + } - return new Promise((resolve, reject) => { - try { - resolve(new Translations(locale)); - } catch (e) { - reject(e); - } - }); - } + return new Promise((resolve, reject) => { + try { + resolve(new Translations(locale)); + } catch (e) { + reject(e); + } + }); + } - /** - * @param {Locale|string} locale - * @param {HTMLElement} element - * @return {Provider} - */ - assignToElement(locale, element) { - if (locale === undefined) { - locale = getLocaleOfDocument(); - } + /** + * @param {Locale|string} locale + * @param {HTMLElement} element + * @return {Provider} + */ + assignToElement(locale, element) { + if (locale === undefined) { + locale = getLocaleOfDocument(); + } - if (!(locale instanceof Locale)) { - throw new Error("Locale is not an instance of Locale"); - } + if (!(locale instanceof Locale)) { + throw new Error("Locale is not an instance of Locale"); + } - if (!(element instanceof HTMLElement)) { - element = document.querySelector("body"); - } + if (!(element instanceof HTMLElement)) { + element = document.querySelector("body"); + } - if (!(element instanceof HTMLElement)) { - throw new Error("Element is not an HTMLElement"); - } + if (!(element instanceof HTMLElement)) { + throw new Error("Element is not an HTMLElement"); + } - return this.getTranslations(locale).then((obj) => { - let translations = null; - if (hasObjectLink(element, translationsLinkSymbol)) { - const objects = getLinkedObjects(element, translationsLinkSymbol); - for (const o of objects) { - if (o instanceof Translations) { - translations = o; - break; - } - } + return this.getTranslations(locale).then((obj) => { + let translations = null; + if (hasObjectLink(element, translationsLinkSymbol)) { + const objects = getLinkedObjects(element, translationsLinkSymbol); + for (const o of objects) { + if (o instanceof Translations) { + translations = o; + break; + } + } - if (!(translations instanceof Translations)) { - throw new Error("Object is not an instance of Translations"); - } + if (!(translations instanceof Translations)) { + throw new Error("Object is not an instance of Translations"); + } - translations.assignTranslations(obj); - } else { - addToObjectLink(element, translationsLinkSymbol, obj); - } + translations.assignTranslations(obj); + } else { + addToObjectLink(element, translationsLinkSymbol, obj); + } - return obj; - }); - } + return obj; + }); + } } diff --git a/source/i18n/providers/embed.mjs b/source/i18n/providers/embed.mjs index 00744830d..79e49ea54 100644 --- a/source/i18n/providers/embed.mjs +++ b/source/i18n/providers/embed.mjs @@ -28,131 +28,140 @@ export { Embed }; * @tutorial i18n-locale-and-formatter */ class Embed extends Provider { - /** - * ```html - * <script id="translations" type="application/json"> - * { - * "hello": "Hallo" - * } - * </script> - * ``` - * - * - * ```javascript - * new Embed('translations') - * ``` - * - * @param {HTMLElement|string} elementOrId - * @param {Object} options - */ - constructor(elementOrId, options) { - super(options); - - if (options === undefined) { - options = {}; - } - - if (elementOrId instanceof HTMLElement) { - /** - * @property {HTMLElement|string} - */ - this.translateElement = elementOrId; - } else { - /** - * @property {HTMLElement|string} - */ - this.translateElement = getDocument().getElementById(validateString(elementOrId)); - } - - /** - * @private - * @property {Object} options - */ - this[internalSymbol] = extend({}, super.defaults, this.defaults, validateObject(options)); - } - - /** - * Defaults - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} - */ - get defaults() { - return extend({}, super.defaults); - } - - /** - * - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - if (isString(locale)) { - locale = parseLocale(locale); - } - - return new Promise((resolve, reject) => { - if (this.translateElement === null) { - reject(new Error("Text not found")); - return; - } - - if (!(this.translateElement instanceof HTMLScriptElement)) { - reject(new Error("Element is not a script tag")); - return; - } - - if (this.translateElement.type !== "application/json") { - reject(new Error("Element is not a script tag with type application/json")); - return; - } - - let translations = null; - try { - translations = JSON.parse(this.translateElement.innerHTML.trim()); - } catch (e) { - reject(e); - return; - } - - if (translations === null) { - reject(new Error("Translations not found or invalid")); - return; - } - - const t = new Translations(locale); - t.assignTranslations(translations); - - resolve(t); - }); - } - - /** - * Initializes the translations for the current document. - * - * `script[data-monster-role=translations]` is searched for and the translations are assigned to the element. - * - * @param element - * @returns {Promise<unknown[]>} - */ - static assignTranslationsToElement(element) { - const d = getDocument(); - - if (!(element instanceof HTMLElement)) { - element = d.querySelector("body"); - } - - const list = d.querySelectorAll("script[data-monster-role=translations]"); - if (list === null) { - return; - } - - const promises = []; - - list.forEach((translationElement) => { - const p = new Embed(translationElement); - promises.push(p.assignToElement(undefined, element)); - }); - - return Promise.all(promises); - } + /** + * ```html + * <script id="translations" type="application/json"> + * { + * "hello": "Hallo" + * } + * </script> + * ``` + * + * + * ```javascript + * new Embed('translations') + * ``` + * + * @param {HTMLElement|string} elementOrId + * @param {Object} options + */ + constructor(elementOrId, options) { + super(options); + + if (options === undefined) { + options = {}; + } + + if (elementOrId instanceof HTMLElement) { + /** + * @property {HTMLElement|string} + */ + this.translateElement = elementOrId; + } else { + /** + * @property {HTMLElement|string} + */ + this.translateElement = getDocument().getElementById( + validateString(elementOrId), + ); + } + + /** + * @private + * @property {Object} options + */ + this[internalSymbol] = extend( + {}, + super.defaults, + this.defaults, + validateObject(options), + ); + } + + /** + * Defaults + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} + */ + get defaults() { + return extend({}, super.defaults); + } + + /** + * + * @param {Locale|string} locale + * @return {Promise} + */ + getTranslations(locale) { + if (isString(locale)) { + locale = parseLocale(locale); + } + + return new Promise((resolve, reject) => { + if (this.translateElement === null) { + reject(new Error("Text not found")); + return; + } + + if (!(this.translateElement instanceof HTMLScriptElement)) { + reject(new Error("Element is not a script tag")); + return; + } + + if (this.translateElement.type !== "application/json") { + reject( + new Error("Element is not a script tag with type application/json"), + ); + return; + } + + let translations = null; + try { + translations = JSON.parse(this.translateElement.innerHTML.trim()); + } catch (e) { + reject(e); + return; + } + + if (translations === null) { + reject(new Error("Translations not found or invalid")); + return; + } + + const t = new Translations(locale); + t.assignTranslations(translations); + + resolve(t); + }); + } + + /** + * Initializes the translations for the current document. + * + * `script[data-monster-role=translations]` is searched for and the translations are assigned to the element. + * + * @param element + * @returns {Promise<unknown[]>} + */ + static assignTranslationsToElement(element) { + const d = getDocument(); + + if (!(element instanceof HTMLElement)) { + element = d.querySelector("body"); + } + + const list = d.querySelectorAll("script[data-monster-role=translations]"); + if (list === null) { + return; + } + + const promises = []; + + list.forEach((translationElement) => { + const p = new Embed(translationElement); + promises.push(p.assignToElement(undefined, element)); + }); + + return Promise.all(promises); + } } diff --git a/source/i18n/providers/fetch.mjs b/source/i18n/providers/fetch.mjs index d0e4163e9..266e51b7a 100644 --- a/source/i18n/providers/fetch.mjs +++ b/source/i18n/providers/fetch.mjs @@ -29,89 +29,97 @@ export { Fetch }; * @tutorial i18n-locale-and-formatter */ class Fetch extends Provider { - /** - * As options the key `fetch` can be passed. This config object is passed to the fetch method as init. - * - * The url may contain placeholders (language, script, region, variants, extlang, privateUse), so you can specify one url for all translations. - * - * ``` - * new Fetch('https://www.example.com/assets/${language}.json') - * ``` - * - * @param {string|URL} url - * @param {Object} options see {@link Monster.I18n.Providers.Fetch#defaults} - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/fetch} - */ - constructor(url, options) { - super(options); + /** + * As options the key `fetch` can be passed. This config object is passed to the fetch method as init. + * + * The url may contain placeholders (language, script, region, variants, extlang, privateUse), so you can specify one url for all translations. + * + * ``` + * new Fetch('https://www.example.com/assets/${language}.json') + * ``` + * + * @param {string|URL} url + * @param {Object} options see {@link Monster.I18n.Providers.Fetch#defaults} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/fetch} + */ + constructor(url, options) { + super(options); - if (isInstance(url, URL)) { - url = url.toString(); - } + if (isInstance(url, URL)) { + url = url.toString(); + } - if (options === undefined) { - options = {}; - } + if (options === undefined) { + options = {}; + } - validateString(url); + validateString(url); - /** - * @property {string} - */ - this.url = url; + /** + * @property {string} + */ + this.url = url; - /** - * @private - * @property {Object} options - */ - this[internalSymbol] = extend({}, super.defaults, this.defaults, validateObject(options)); - } + /** + * @private + * @property {Object} options + */ + this[internalSymbol] = extend( + {}, + super.defaults, + this.defaults, + validateObject(options), + ); + } - /** - * Defaults - * - * @property {Object} fetch - * @property {String} fetch.method=GET - * @property {String} fetch.mode=cors - * @property {String} fetch.cache=no-cache - * @property {String} fetch.credentials=omit - * @property {String} fetch.redirect=follow - * @property {String} fetch.referrerPolicy=no-referrer - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} - */ - get defaults() { - return extend( - { - fetch: { - method: "GET", // *GET, POST, PUT, DELETE, etc. - mode: "cors", // no-cors, *cors, same-origin - cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached - credentials: "omit", // include, *same-origin, omit - redirect: "follow", // manual, *follow, error - referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url - }, - }, - super.defaults, - ); - } + /** + * Defaults + * + * @property {Object} fetch + * @property {String} fetch.method=GET + * @property {String} fetch.mode=cors + * @property {String} fetch.cache=no-cache + * @property {String} fetch.credentials=omit + * @property {String} fetch.redirect=follow + * @property {String} fetch.referrerPolicy=no-referrer + * + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} + */ + get defaults() { + return extend( + { + fetch: { + method: "GET", // *GET, POST, PUT, DELETE, etc. + mode: "cors", // no-cors, *cors, same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + credentials: "omit", // include, *same-origin, omit + redirect: "follow", // manual, *follow, error + referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + }, + }, + super.defaults, + ); + } - /** - * - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - if (isString(locale)) { - locale = parseLocale(locale); - } + /** + * + * @param {Locale|string} locale + * @return {Promise} + */ + getTranslations(locale) { + if (isString(locale)) { + locale = parseLocale(locale); + } - let formatter = new Formatter(locale.getMap()); + let formatter = new Formatter(locale.getMap()); - return getGlobalFunction("fetch")(formatter.format(this.url), this.getOption("fetch", {})) - .then((response) => response.json()) - .then((data) => { - return new Translations(locale).assignTranslations(data); - }); - } + return getGlobalFunction("fetch")( + formatter.format(this.url), + this.getOption("fetch", {}), + ) + .then((response) => response.json()) + .then((data) => { + return new Translations(locale).assignTranslations(data); + }); + } } diff --git a/source/i18n/translations.mjs b/source/i18n/translations.mjs index e7589ef21..8c2281b15 100644 --- a/source/i18n/translations.mjs +++ b/source/i18n/translations.mjs @@ -11,7 +11,11 @@ import { ATTRIBUTE_OBJECTLINK } from "../dom/constants.mjs"; import { getDocument } from "../dom/util.mjs"; import { Base } from "../types/base.mjs"; import { isObject, isString } from "../types/is.mjs"; -import { validateInteger, validateObject, validateString } from "../types/validate.mjs"; +import { + validateInteger, + validateObject, + validateString, +} from "../types/validate.mjs"; import { Locale, parseLocale } from "./locale.mjs"; import { translationsLinkSymbol } from "./provider.mjs"; @@ -28,174 +32,176 @@ export { Translations, getDocumentTranslations }; * @see https://datatracker.ietf.org/doc/html/rfc3066 */ class Translations extends Base { - /** - * - * @param {Locale} locale - */ - constructor(locale) { - super(); - - if (locale instanceof Locale) { - this.locale = locale; - } else { - this.locale = parseLocale(validateString(locale)); - } - - this.storage = new Map(); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 3.27.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/i18n/translations@@instance"); - } - - /** - * Fetches a text using the specified key. - * If no suitable key is found, `defaultText` is taken. - * - * @param {string} key - * @param {string|undefined} defaultText - * @return {string} - * @throws {Error} key not found - */ - getText(key, defaultText) { - if (!this.storage.has(key)) { - if (defaultText === undefined) { - throw new Error(`key ${key} not found`); - } - - return validateString(defaultText); - } - - let r = this.storage.get(key); - if (isObject(r)) { - return this.getPluralRuleText(key, "other", defaultText); - } - - return this.storage.get(key); - } - - /** - * A number `count` can be passed to this method. In addition to a number, one of the keywords can also be passed directly. - * "zero", "one", "two", "few", "many" and "other". Remember: not every language has all rules. - * - * The appropriate text for this number is then selected. If no suitable key is found, `defaultText` is taken. - * - * @param {string} key - * @param {integer|count} count - * @param {string|undefined} defaultText - * @return {string} - */ - getPluralRuleText(key, count, defaultText) { - if (!this.storage.has(key)) { - return validateString(defaultText); - } - - let r = validateObject(this.storage.get(key)); - - let keyword; - if (isString(count)) { - keyword = count.toLocaleString(); - } else { - count = validateInteger(count); - if (count === 0) { - // special handling for zero count - if (r.hasOwnProperty("zero")) { - return validateString(r["zero"]); - } - } - - keyword = new Intl.PluralRules(this.locale.toString()).select(validateInteger(count)); - } - - if (r.hasOwnProperty(keyword)) { - return validateString(r[keyword]); - } - - // @deprecated since 2023-03-14 - // DEFAULT_KEY is undefined - // if (r.hasOwnProperty(DEFAULT_KEY)) { - // return validateString(r[DEFAULT_KEY]); - // } - - return validateString(defaultText); - } - - /** - * Set a text for a key - * - * ``` - * translations.setText("text1", "Make my day!"); - * // plural rules - * translations.setText("text6", { - * "zero": "There are no files on Disk.", - * "one": "There is one file on Disk.", - * "other": "There are files on Disk." - * "default": "There are files on Disk." - * }); - * ``` - * - * @param {string} key - * @param {string|object} text - * @return {Translations} - * @throws {TypeError} value is not a string or object - */ - setText(key, text) { - if (isString(text) || isObject(text)) { - this.storage.set(validateString(key), text); - return this; - } - - throw new TypeError("value is not a string or object"); - } - - /** - * This method can be used to transfer overlays from an object. The keys are transferred, and the values are entered - * as text. - * - * The values can either be character strings or, in the case of texts with plural forms, objects. The plural forms - * must be stored as text via a standard key "zero", "one", "two", "few", "many" and "other". - * - * Additionally, the key default can be specified, which will be used if no other key fits. - * - * In some languages, like for example in German, there is no own more number at the value 0. In these languages, - * the function applies additionally zero. - * - * ``` - * translations.assignTranslations({ - * "text1": "Make my day!", - * "text2": "I'll be back!", - * "text6": { - * "zero": "There are no files on Disk.", - * "one": "There is one file on Disk.", - * "other": "There are files on Disk." - * "default": "There are files on Disk." - * }); - * ``` - * - * @param {object} translations - * @return {Translations} - */ - assignTranslations(translations) { - validateObject(translations); - - if (translations instanceof Translations) { - translations.storage.forEach((v, k) => { - this.setText(k, v); - }); - return this; - } - - for (const [k, v] of Object.entries(translations)) { - this.setText(k, v); - } - - return this; - } + /** + * + * @param {Locale} locale + */ + constructor(locale) { + super(); + + if (locale instanceof Locale) { + this.locale = locale; + } else { + this.locale = parseLocale(validateString(locale)); + } + + this.storage = new Map(); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 3.27.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/i18n/translations@@instance"); + } + + /** + * Fetches a text using the specified key. + * If no suitable key is found, `defaultText` is taken. + * + * @param {string} key + * @param {string|undefined} defaultText + * @return {string} + * @throws {Error} key not found + */ + getText(key, defaultText) { + if (!this.storage.has(key)) { + if (defaultText === undefined) { + throw new Error(`key ${key} not found`); + } + + return validateString(defaultText); + } + + let r = this.storage.get(key); + if (isObject(r)) { + return this.getPluralRuleText(key, "other", defaultText); + } + + return this.storage.get(key); + } + + /** + * A number `count` can be passed to this method. In addition to a number, one of the keywords can also be passed directly. + * "zero", "one", "two", "few", "many" and "other". Remember: not every language has all rules. + * + * The appropriate text for this number is then selected. If no suitable key is found, `defaultText` is taken. + * + * @param {string} key + * @param {integer|count} count + * @param {string|undefined} defaultText + * @return {string} + */ + getPluralRuleText(key, count, defaultText) { + if (!this.storage.has(key)) { + return validateString(defaultText); + } + + let r = validateObject(this.storage.get(key)); + + let keyword; + if (isString(count)) { + keyword = count.toLocaleString(); + } else { + count = validateInteger(count); + if (count === 0) { + // special handling for zero count + if (r.hasOwnProperty("zero")) { + return validateString(r["zero"]); + } + } + + keyword = new Intl.PluralRules(this.locale.toString()).select( + validateInteger(count), + ); + } + + if (r.hasOwnProperty(keyword)) { + return validateString(r[keyword]); + } + + // @deprecated since 2023-03-14 + // DEFAULT_KEY is undefined + // if (r.hasOwnProperty(DEFAULT_KEY)) { + // return validateString(r[DEFAULT_KEY]); + // } + + return validateString(defaultText); + } + + /** + * Set a text for a key + * + * ``` + * translations.setText("text1", "Make my day!"); + * // plural rules + * translations.setText("text6", { + * "zero": "There are no files on Disk.", + * "one": "There is one file on Disk.", + * "other": "There are files on Disk." + * "default": "There are files on Disk." + * }); + * ``` + * + * @param {string} key + * @param {string|object} text + * @return {Translations} + * @throws {TypeError} value is not a string or object + */ + setText(key, text) { + if (isString(text) || isObject(text)) { + this.storage.set(validateString(key), text); + return this; + } + + throw new TypeError("value is not a string or object"); + } + + /** + * This method can be used to transfer overlays from an object. The keys are transferred, and the values are entered + * as text. + * + * The values can either be character strings or, in the case of texts with plural forms, objects. The plural forms + * must be stored as text via a standard key "zero", "one", "two", "few", "many" and "other". + * + * Additionally, the key default can be specified, which will be used if no other key fits. + * + * In some languages, like for example in German, there is no own more number at the value 0. In these languages, + * the function applies additionally zero. + * + * ``` + * translations.assignTranslations({ + * "text1": "Make my day!", + * "text2": "I'll be back!", + * "text6": { + * "zero": "There are no files on Disk.", + * "one": "There is one file on Disk.", + * "other": "There are files on Disk." + * "default": "There are files on Disk." + * }); + * ``` + * + * @param {object} translations + * @return {Translations} + */ + assignTranslations(translations) { + validateObject(translations); + + if (translations instanceof Translations) { + translations.storage.forEach((v, k) => { + this.setText(k, v); + }); + return this; + } + + for (const [k, v] of Object.entries(translations)) { + this.setText(k, v); + } + + return this; + } } /** @@ -210,30 +216,34 @@ class Translations extends Base { * @memberOf Monster.I18n */ function getDocumentTranslations(element) { - const d = getDocument(); - - if (!(element instanceof HTMLElement)) { - element = d.querySelector(`[${ATTRIBUTE_OBJECTLINK}~="${translationsLinkSymbol.toString()}"]`); - if (element === null) { - throw new Error("Cannot find element with translations. Add a translations object to the document."); - } - } - - if (!(element instanceof HTMLElement)) { - throw new Error("Element is not an HTMLElement."); - } - - if (!hasObjectLink(element, translationsLinkSymbol)) { - throw new Error("This element has no translations."); - } - - let obj = getLinkedObjects(element, translationsLinkSymbol); - - for (const t of obj) { - if (t instanceof Translations) { - return t; - } - } - - throw new Error("Missing translations."); + const d = getDocument(); + + if (!(element instanceof HTMLElement)) { + element = d.querySelector( + `[${ATTRIBUTE_OBJECTLINK}~="${translationsLinkSymbol.toString()}"]`, + ); + if (element === null) { + throw new Error( + "Cannot find element with translations. Add a translations object to the document.", + ); + } + } + + if (!(element instanceof HTMLElement)) { + throw new Error("Element is not an HTMLElement."); + } + + if (!hasObjectLink(element, translationsLinkSymbol)) { + throw new Error("This element has no translations."); + } + + let obj = getLinkedObjects(element, translationsLinkSymbol); + + for (const t of obj) { + if (t instanceof Translations) { + return t; + } + } + + throw new Error("Missing translations."); } diff --git a/source/logging/handler.mjs b/source/logging/handler.mjs index 7d92bc23b..4737ca748 100644 --- a/source/logging/handler.mjs +++ b/source/logging/handler.mjs @@ -21,144 +21,144 @@ export { Handler }; * @memberOf Monster.Logging */ class Handler extends Base { - constructor() { - super(); - - /** - * Loglevel - * - * @type {integer} - */ - this.loglevel = OFF; - } - - /** - * This is the central log function. this method must be - * overwritten by derived handlers with their own logic. - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * @param {LogEntry} entry - * @returns {boolean} - */ - log(entry) { - validateInstance(entry, LogEntry); - - if (this.loglevel < entry.getLogLevel()) { - return false; - } - - return true; - } - - /** - * set loglevel - * - * @param {integer} loglevel - * @returns {Handler} - * @since 1.5.0 - */ - setLogLevel(loglevel) { - validateInteger(loglevel); - this.loglevel = loglevel; - return this; - } - - /** - * get loglevel - * - * @returns {integer} - * @since 1.5.0 - */ - getLogLevel() { - return this.loglevel; - } - - /** - * Set log level to All - * - * @returns {Handler} - * @since 1.5.0 - */ - setAll() { - this.setLogLevel(ALL); - return this; - } - - /** - * Set log level to Trace - * - * @returns {Handler} - * @since 1.5.0 - */ - setTrace() { - this.setLogLevel(TRACE); - return this; - } - - /** - * Set log level to Debug - * - * @returns {Handler} - * @since 1.5.0 - */ - setDebug() { - this.setLogLevel(DEBUG); - return this; - } - - /** - * Set log level to Info - * - * @returns {Handler} - * @since 1.5.0 - */ - setInfo() { - this.setLogLevel(INFO); - return this; - } - - /** - * Set log level to Warn - * - * @returns {undefined} - * @since 1.5.0 - */ - setWarn() { - this.setLogLevel(WARN); - return this; - } - - /** - * Set log level to Error - * - * @returns {Handler} - * @since 1.5.0 - */ - setError() { - this.setLogLevel(ERROR); - return this; - } - - /** - * Set log level to Fatal - * - * @returns {Handler} - * @since 1.5.0 - */ - setFatal() { - this.setLogLevel(FATAL); - return this; - } - - /** - * Set log level to Off - * - * @returns {Handler} - * @since 1.5.0 - */ - setOff() { - this.setLogLevel(OFF); - return this; - } + constructor() { + super(); + + /** + * Loglevel + * + * @type {integer} + */ + this.loglevel = OFF; + } + + /** + * This is the central log function. this method must be + * overwritten by derived handlers with their own logic. + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * @param {LogEntry} entry + * @returns {boolean} + */ + log(entry) { + validateInstance(entry, LogEntry); + + if (this.loglevel < entry.getLogLevel()) { + return false; + } + + return true; + } + + /** + * set loglevel + * + * @param {integer} loglevel + * @returns {Handler} + * @since 1.5.0 + */ + setLogLevel(loglevel) { + validateInteger(loglevel); + this.loglevel = loglevel; + return this; + } + + /** + * get loglevel + * + * @returns {integer} + * @since 1.5.0 + */ + getLogLevel() { + return this.loglevel; + } + + /** + * Set log level to All + * + * @returns {Handler} + * @since 1.5.0 + */ + setAll() { + this.setLogLevel(ALL); + return this; + } + + /** + * Set log level to Trace + * + * @returns {Handler} + * @since 1.5.0 + */ + setTrace() { + this.setLogLevel(TRACE); + return this; + } + + /** + * Set log level to Debug + * + * @returns {Handler} + * @since 1.5.0 + */ + setDebug() { + this.setLogLevel(DEBUG); + return this; + } + + /** + * Set log level to Info + * + * @returns {Handler} + * @since 1.5.0 + */ + setInfo() { + this.setLogLevel(INFO); + return this; + } + + /** + * Set log level to Warn + * + * @returns {undefined} + * @since 1.5.0 + */ + setWarn() { + this.setLogLevel(WARN); + return this; + } + + /** + * Set log level to Error + * + * @returns {Handler} + * @since 1.5.0 + */ + setError() { + this.setLogLevel(ERROR); + return this; + } + + /** + * Set log level to Fatal + * + * @returns {Handler} + * @since 1.5.0 + */ + setFatal() { + this.setLogLevel(FATAL); + return this; + } + + /** + * Set log level to Off + * + * @returns {Handler} + * @since 1.5.0 + */ + setOff() { + this.setLogLevel(OFF); + return this; + } } diff --git a/source/logging/handler/console.mjs b/source/logging/handler/console.mjs index d63afce6a..934d3a8c6 100644 --- a/source/logging/handler/console.mjs +++ b/source/logging/handler/console.mjs @@ -21,44 +21,44 @@ export { ConsoleHandler }; * @memberOf Monster.Logging.Handler */ class ConsoleHandler extends Handler { - /** - * This is the central log function. this method must be - * overwritten by derived handlers with their own logic. - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * @param {LogEntry} entry - * @returns {boolean} - */ - log(entry) { - if (super.log(entry)) { - let console = getGlobalObject("console"); - if (!console) return false; + /** + * This is the central log function. this method must be + * overwritten by derived handlers with their own logic. + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * @param {LogEntry} entry + * @returns {boolean} + */ + log(entry) { + if (super.log(entry)) { + let console = getGlobalObject("console"); + if (!console) return false; - if (!console.error) console.error = console.log; - if (!console.warn) console.warn = console.log; + if (!console.error) console.error = console.log; + if (!console.warn) console.warn = console.log; - switch (entry.getLogLevel()) { - case TRACE: - case DEBUG: - case INFO: - console.log(entry.toString()); - break; - case FATAL: - case ERROR: - console.error(entry.toString()); - break; - case WARN: - console.warn(entry.toString()); - break; - default: - console.log(entry.toString()); - break; - } + switch (entry.getLogLevel()) { + case TRACE: + case DEBUG: + case INFO: + console.log(entry.toString()); + break; + case FATAL: + case ERROR: + console.error(entry.toString()); + break; + case WARN: + console.warn(entry.toString()); + break; + default: + console.log(entry.toString()); + break; + } - return true; - } + return true; + } - return false; - } + return false; + } } diff --git a/source/logging/logentry.mjs b/source/logging/logentry.mjs index ff6df51f5..6c0f134db 100644 --- a/source/logging/logentry.mjs +++ b/source/logging/logentry.mjs @@ -19,32 +19,32 @@ export { LogEntry }; * @memberOf Monster.Logging */ class LogEntry extends Base { - /** - * - * @param {Integer} loglevel - * @param {...*} args - */ - constructor(loglevel, ...args) { - super(); - validateInteger(loglevel); + /** + * + * @param {Integer} loglevel + * @param {...*} args + */ + constructor(loglevel, ...args) { + super(); + validateInteger(loglevel); - this.loglevel = loglevel; - this.arguments = args; - } + this.loglevel = loglevel; + this.arguments = args; + } - /** - * - * @returns {integerr} - */ - getLogLevel() { - return this.loglevel; - } + /** + * + * @returns {integerr} + */ + getLogLevel() { + return this.loglevel; + } - /** - * - * @returns {array} - */ - getArguments() { - return this.arguments; - } + /** + * + * @returns {array} + */ + getArguments() { + return this.arguments; + } } diff --git a/source/logging/logger.mjs b/source/logging/logger.mjs index 556b33de5..b05ab99ab 100644 --- a/source/logging/logger.mjs +++ b/source/logging/logger.mjs @@ -9,7 +9,11 @@ import { Handler } from "../logging/handler.mjs"; import { LogEntry } from "../logging/logentry.mjs"; import { Base } from "../types/base.mjs"; -import { validateInteger, validateObject, validateString } from "../types/validate.mjs"; +import { + validateInteger, + validateObject, + validateString, +} from "../types/validate.mjs"; export { Logger, ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF }; @@ -71,196 +75,196 @@ const OFF = 0; * @memberOf Monster.Logging */ class Logger extends Base { - /** - * - */ - constructor() { - super(); - this.handler = new Set(); - } + /** + * + */ + constructor() { + super(); + this.handler = new Set(); + } - /** - * - * @param {Handler} handler - * @returns {Logger} - * @throws {Error} the handler must be an instance of Handler - */ - addHandler(handler) { - validateObject(handler); - if (!(handler instanceof Handler)) { - throw new Error("the handler must be an instance of Handler"); - } + /** + * + * @param {Handler} handler + * @returns {Logger} + * @throws {Error} the handler must be an instance of Handler + */ + addHandler(handler) { + validateObject(handler); + if (!(handler instanceof Handler)) { + throw new Error("the handler must be an instance of Handler"); + } - this.handler.add(handler); - return this; - } + this.handler.add(handler); + return this; + } - /** - * - * @param {Handler} handler - * @returns {Logger} - * @throws {Error} the handler must be an instance of Handler - */ - removeHandler(handler) { - validateObject(handler); - if (!(handler instanceof Handler)) { - throw new Error("the handler must be an instance of Handler"); - } + /** + * + * @param {Handler} handler + * @returns {Logger} + * @throws {Error} the handler must be an instance of Handler + */ + removeHandler(handler) { + validateObject(handler); + if (!(handler instanceof Handler)) { + throw new Error("the handler must be an instance of Handler"); + } - this.handler.delete(handler); - return this; - } + this.handler.delete(handler); + return this; + } - /** - * log Trace message - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * @param {*} arguments - * @returns {Logger} - * @since 1.5.0 - */ - logTrace(...args) { - if (typeof args !== "object" || args[0] === null) { - throw new Error("the first argument must be an object"); - } + /** + * log Trace message + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * @param {*} arguments + * @returns {Logger} + * @since 1.5.0 + */ + logTrace(...args) { + if (typeof args !== "object" || args[0] === null) { + throw new Error("the first argument must be an object"); + } - triggerLog.apply(this, [TRACE, ...args]); - return this; - } + triggerLog.apply(this, [TRACE, ...args]); + return this; + } - /** - * log Debug message - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * @param {*} arguments - * @returns {Logger} - * @since 1.5.0 - */ - logDebug(...args) { - if (typeof args !== "object" || args[0] === null) { - throw new Error("the first argument must be an object"); - } + /** + * log Debug message + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * @param {*} arguments + * @returns {Logger} + * @since 1.5.0 + */ + logDebug(...args) { + if (typeof args !== "object" || args[0] === null) { + throw new Error("the first argument must be an object"); + } - triggerLog.apply(this, [DEBUG, ...args]); - return this; - } + triggerLog.apply(this, [DEBUG, ...args]); + return this; + } - /** - * log Info message - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * - * @param {*} arguments - * @returns {Logger} - * @since 1.5.0 - */ - logInfo(...args) { - if (typeof args !== "object" || args[0] === null) { - throw new Error("the first argument must be an object"); - } + /** + * log Info message + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * + * @param {*} arguments + * @returns {Logger} + * @since 1.5.0 + */ + logInfo(...args) { + if (typeof args !== "object" || args[0] === null) { + throw new Error("the first argument must be an object"); + } - triggerLog.apply(this, [INFO, ...args]); - return this; - } + triggerLog.apply(this, [INFO, ...args]); + return this; + } - /** - * log Warn message - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * @param {*} arguments - * @returns {Logger} - * @since 1.5.0 - */ - logWarn(...args) { - if (typeof args !== "object" || args[0] === null) { - throw new Error("the first argument must be an object"); - } + /** + * log Warn message + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * @param {*} arguments + * @returns {Logger} + * @since 1.5.0 + */ + logWarn(...args) { + if (typeof args !== "object" || args[0] === null) { + throw new Error("the first argument must be an object"); + } - triggerLog.apply(this, [WARN, ...args]); - return this; - } + triggerLog.apply(this, [WARN, ...args]); + return this; + } - /** - * log Error message - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * @param {*} arguments - * @returns {Logger} - * @since 1.5.0 - */ - logError(...args) { - if (typeof args !== "object" || args[0] === null) { - throw new Error("the first argument must be an object"); - } + /** + * log Error message + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * @param {*} arguments + * @returns {Logger} + * @since 1.5.0 + */ + logError(...args) { + if (typeof args !== "object" || args[0] === null) { + throw new Error("the first argument must be an object"); + } - triggerLog.apply(this, [ERROR, ...args]); - return this; - } + triggerLog.apply(this, [ERROR, ...args]); + return this; + } - /** - * log Fatal message - * - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; - * - * @param {*} arguments - * @returns {Logger} - * @since 1.5.0 - */ - logFatal(...args) { - if (typeof args !== "object" || args[0] === null) { - throw new Error("the first argument must be an object"); - } + /** + * log Fatal message + * + * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF (ALL = 0xff;OFF = 0x00; + * + * @param {*} arguments + * @returns {Logger} + * @since 1.5.0 + */ + logFatal(...args) { + if (typeof args !== "object" || args[0] === null) { + throw new Error("the first argument must be an object"); + } - triggerLog.apply(this, [FATAL, ...args]); - return this; - } + triggerLog.apply(this, [FATAL, ...args]); + return this; + } - /** - * Labels - * - * @param {integer} level - * @returns {string} - */ - getLabel(level) { - validateInteger(level); + /** + * Labels + * + * @param {integer} level + * @returns {string} + */ + getLabel(level) { + validateInteger(level); - if (level === ALL) return "ALL"; - if (level === TRACE) return "TRACE"; - if (level === DEBUG) return "DEBUG"; - if (level === INFO) return "INFO"; - if (level === WARN) return "WARN"; - if (level === ERROR) return "ERROR"; - if (level === FATAL) return "FATAL"; - if (level === OFF) return "OFF"; + if (level === ALL) return "ALL"; + if (level === TRACE) return "TRACE"; + if (level === DEBUG) return "DEBUG"; + if (level === INFO) return "INFO"; + if (level === WARN) return "WARN"; + if (level === ERROR) return "ERROR"; + if (level === FATAL) return "FATAL"; + if (level === OFF) return "OFF"; - return "unknown"; - } + return "unknown"; + } - /** - * Level - * - * @param {string} label - * @returns {integer} - */ - getLevel(label) { - validateString(label); + /** + * Level + * + * @param {string} label + * @returns {integer} + */ + getLevel(label) { + validateString(label); - if (label === "ALL") return ALL; - if (label === "TRACE") return TRACE; - if (label === "DEBUG") return DEBUG; - if (label === "INFO") return INFO; - if (label === "WARN") return WARN; - if (label === "ERROR") return ERROR; - if (label === "FATAL") return FATAL; - if (label === "OFF") return OFF; + if (label === "ALL") return ALL; + if (label === "TRACE") return TRACE; + if (label === "DEBUG") return DEBUG; + if (label === "INFO") return INFO; + if (label === "WARN") return WARN; + if (label === "ERROR") return ERROR; + if (label === "FATAL") return FATAL; + if (label === "OFF") return OFF; - return 0; - } + return 0; + } } /** @@ -272,11 +276,11 @@ class Logger extends Base { * @private */ function triggerLog(loglevel, ...args) { - var logger = this; + var logger = this; - for (let handler of logger.handler) { - handler.log(new LogEntry(loglevel, args)); - } + for (let handler of logger.handler) { + handler.log(new LogEntry(loglevel, args)); + } - return logger; + return logger; } diff --git a/source/math/random.mjs b/source/math/random.mjs index 163712021..8b3cf0205 100644 --- a/source/math/random.mjs +++ b/source/math/random.mjs @@ -25,18 +25,18 @@ export { random }; * @copyright schukai GmbH */ function random(min, max) { - if (min === undefined) { - min = 0; - } - if (max === undefined) { - max = MAX; - } - - if (max < min) { - throw new Error("max must be greater than min"); - } - - return Math.round(create(min, max)); + if (min === undefined) { + min = 0; + } + if (max === undefined) { + max = MAX; + } + + if (max < min) { + throw new Error("max must be greater than min"); + } + + return Math.round(create(min, max)); } /** @@ -46,10 +46,10 @@ function random(min, max) { var MAX = 1000000000; Math.log2 = - Math.log2 || - function (n) { - return Math.log(n) / Math.log(2); - }; + Math.log2 || + function (n) { + return Math.log(n) / Math.log(2); + }; /** * @@ -62,46 +62,50 @@ Math.log2 = * @throws {Error} the distance is too small to create a random number. */ function create(min, max) { - let crypt; - let globalReference = getGlobal(); - - crypt = globalReference?.["crypto"] || globalReference?.["msCrypto"] || globalReference?.["crypto"] || undefined; - - if (typeof crypt === "undefined") { - throw new Error("missing crypt"); - } - - let rval = 0; - const range = max - min; - if (range < 2) { - throw new Error("the distance is too small to create a random number."); - } - - const bitsNeeded = Math.ceil(Math.log2(range)); - if (bitsNeeded > 53) { - throw new Error("we cannot generate numbers larger than 53 bits."); - } - const bytesNeeded = Math.ceil(bitsNeeded / 8); - const mask = Math.pow(2, bitsNeeded) - 1; - - const byteArray = new Uint8Array(bytesNeeded); - crypt.getRandomValues(byteArray); - - let p = (bytesNeeded - 1) * 8; - for (var i = 0; i < bytesNeeded; i++) { - rval += byteArray[i] * Math.pow(2, p); - p -= 8; - } - - rval = rval & mask; - - if (rval >= range) { - return create(min, max); - } - - if (rval < min) { - rval += min; - } - - return rval; + let crypt; + let globalReference = getGlobal(); + + crypt = + globalReference?.["crypto"] || + globalReference?.["msCrypto"] || + globalReference?.["crypto"] || + undefined; + + if (typeof crypt === "undefined") { + throw new Error("missing crypt"); + } + + let rval = 0; + const range = max - min; + if (range < 2) { + throw new Error("the distance is too small to create a random number."); + } + + const bitsNeeded = Math.ceil(Math.log2(range)); + if (bitsNeeded > 53) { + throw new Error("we cannot generate numbers larger than 53 bits."); + } + const bytesNeeded = Math.ceil(bitsNeeded / 8); + const mask = Math.pow(2, bitsNeeded) - 1; + + const byteArray = new Uint8Array(bytesNeeded); + crypt.getRandomValues(byteArray); + + let p = (bytesNeeded - 1) * 8; + for (var i = 0; i < bytesNeeded; i++) { + rval += byteArray[i] * Math.pow(2, p); + p -= 8; + } + + rval = rval & mask; + + if (rval >= range) { + return create(min, max); + } + + if (rval < min) { + rval += min; + } + + return rval; } diff --git a/source/monster.mjs b/source/monster.mjs index 12e5ec8e3..24cde159f 100644 --- a/source/monster.mjs +++ b/source/monster.mjs @@ -1,4 +1,3 @@ - /** * Copyright schukai GmbH and contributors 2023. All Rights Reserved. * Node module: @schukai/monster @@ -16,107 +15,107 @@ * @author schukai GmbH */ -export * from "./text/formatter.mjs" -export * from "./text/generate-range-comparison-expression.mjs" -export * from "./text/util.mjs" -export * from "./text/bracketed-key-value-hash.mjs" -export * from "./math/random.mjs" -export * from "./util/trimspaces.mjs" -export * from "./util/processing.mjs" -export * from "./util/runtime.mjs" -export * from "./util/deadmansswitch.mjs" -export * from "./util/comparator.mjs" -export * from "./util/freeze.mjs" -export * from "./util/clone.mjs" -export * from "./logging/handler/console.mjs" -export * from "./logging/logger.mjs" -export * from "./logging/handler.mjs" -export * from "./logging/logentry.mjs" -export * from "./net/webconnect.mjs" -export * from "./net/webconnect/message.mjs" -export * from "./constraints/invalid.mjs" -export * from "./constraints/abstractoperator.mjs" -export * from "./constraints/oroperator.mjs" -export * from "./constraints/isobject.mjs" -export * from "./constraints/andoperator.mjs" -export * from "./constraints/isarray.mjs" -export * from "./constraints/abstract.mjs" -export * from "./constraints/valid.mjs" -export * from "./dom/dimension.mjs" -export * from "./dom/resource/link/stylesheet.mjs" -export * from "./dom/resource/link.mjs" -export * from "./dom/resource/script.mjs" -export * from "./dom/resource/data.mjs" -export * from "./dom/util/init-options-from-attributes.mjs" -export * from "./dom/util/set-option-from-attribute.mjs" -export * from "./dom/util/extract-keys.mjs" -export * from "./dom/worker/factory.mjs" -export * from "./dom/updater.mjs" -export * from "./dom/locale.mjs" -export * from "./dom/theme.mjs" -export * from "./dom/customelement.mjs" -export * from "./dom/slotted.mjs" -export * from "./dom/focusmanager.mjs" -export * from "./dom/ready.mjs" -export * from "./dom/util.mjs" -export * from "./dom/attributes.mjs" -export * from "./dom/resource.mjs" -export * from "./dom/resourcemanager.mjs" -export * from "./dom/assembler.mjs" -export * from "./dom/customcontrol.mjs" -export * from "./dom/template.mjs" -export * from "./dom/constants.mjs" -export * from "./dom/events.mjs" -export * from "./data/datasource/dom.mjs" -export * from "./data/datasource/storage/localstorage.mjs" -export * from "./data/datasource/storage/sessionstorage.mjs" -export * from "./data/datasource/storage.mjs" -export * from "./data/datasource/server.mjs" -export * from "./data/datasource/server/restapi/data-fetch-error.mjs" -export * from "./data/datasource/server/restapi/writeerror.mjs" -export * from "./data/datasource/server/webconnect.mjs" -export * from "./data/datasource/server/restapi.mjs" -export * from "./data/datasource.mjs" -export * from "./data/buildmap.mjs" -export * from "./data/transformer.mjs" -export * from "./data/diff.mjs" -export * from "./data/buildtree.mjs" -export * from "./data/pathfinder.mjs" -export * from "./data/pipe.mjs" -export * from "./data/extend.mjs" -export * from "./types/nodelist.mjs" -export * from "./types/base.mjs" -export * from "./types/mediatype.mjs" -export * from "./types/tokenlist.mjs" -export * from "./types/proxyobserver.mjs" -export * from "./types/version.mjs" -export * from "./types/global.mjs" -export * from "./types/observerlist.mjs" -export * from "./types/internal.mjs" -export * from "./types/observablequeue.mjs" -export * from "./types/dataurl.mjs" -export * from "./types/binary.mjs" -export * from "./types/observer.mjs" -export * from "./types/regex.mjs" -export * from "./types/randomid.mjs" -export * from "./types/id.mjs" -export * from "./types/uuid.mjs" -export * from "./types/is.mjs" -export * from "./types/validate.mjs" -export * from "./types/typeof.mjs" -export * from "./types/uniquequeue.mjs" -export * from "./types/stack.mjs" -export * from "./types/basewithoptions.mjs" -export * from "./types/node.mjs" -export * from "./types/queue.mjs" -export * from "./types/noderecursiveiterator.mjs" -export * from "./i18n/formatter.mjs" -export * from "./i18n/locale.mjs" -export * from "./i18n/provider.mjs" -export * from "./i18n/providers/fetch.mjs" -export * from "./i18n/providers/embed.mjs" -export * from "./i18n/translations.mjs" -export * from "./constants.mjs" +export * from "./text/formatter.mjs"; +export * from "./text/generate-range-comparison-expression.mjs"; +export * from "./text/util.mjs"; +export * from "./text/bracketed-key-value-hash.mjs"; +export * from "./math/random.mjs"; +export * from "./util/trimspaces.mjs"; +export * from "./util/processing.mjs"; +export * from "./util/runtime.mjs"; +export * from "./util/deadmansswitch.mjs"; +export * from "./util/comparator.mjs"; +export * from "./util/freeze.mjs"; +export * from "./util/clone.mjs"; +export * from "./logging/handler/console.mjs"; +export * from "./logging/logger.mjs"; +export * from "./logging/handler.mjs"; +export * from "./logging/logentry.mjs"; +export * from "./net/webconnect.mjs"; +export * from "./net/webconnect/message.mjs"; +export * from "./constraints/invalid.mjs"; +export * from "./constraints/abstractoperator.mjs"; +export * from "./constraints/oroperator.mjs"; +export * from "./constraints/isobject.mjs"; +export * from "./constraints/andoperator.mjs"; +export * from "./constraints/isarray.mjs"; +export * from "./constraints/abstract.mjs"; +export * from "./constraints/valid.mjs"; +export * from "./dom/dimension.mjs"; +export * from "./dom/resource/link/stylesheet.mjs"; +export * from "./dom/resource/link.mjs"; +export * from "./dom/resource/script.mjs"; +export * from "./dom/resource/data.mjs"; +export * from "./dom/util/init-options-from-attributes.mjs"; +export * from "./dom/util/set-option-from-attribute.mjs"; +export * from "./dom/util/extract-keys.mjs"; +export * from "./dom/worker/factory.mjs"; +export * from "./dom/updater.mjs"; +export * from "./dom/locale.mjs"; +export * from "./dom/theme.mjs"; +export * from "./dom/customelement.mjs"; +export * from "./dom/slotted.mjs"; +export * from "./dom/focusmanager.mjs"; +export * from "./dom/ready.mjs"; +export * from "./dom/util.mjs"; +export * from "./dom/attributes.mjs"; +export * from "./dom/resource.mjs"; +export * from "./dom/resourcemanager.mjs"; +export * from "./dom/assembler.mjs"; +export * from "./dom/customcontrol.mjs"; +export * from "./dom/template.mjs"; +export * from "./dom/constants.mjs"; +export * from "./dom/events.mjs"; +export * from "./data/datasource/dom.mjs"; +export * from "./data/datasource/storage/localstorage.mjs"; +export * from "./data/datasource/storage/sessionstorage.mjs"; +export * from "./data/datasource/storage.mjs"; +export * from "./data/datasource/server.mjs"; +export * from "./data/datasource/server/restapi/data-fetch-error.mjs"; +export * from "./data/datasource/server/restapi/writeerror.mjs"; +export * from "./data/datasource/server/webconnect.mjs"; +export * from "./data/datasource/server/restapi.mjs"; +export * from "./data/datasource.mjs"; +export * from "./data/buildmap.mjs"; +export * from "./data/transformer.mjs"; +export * from "./data/diff.mjs"; +export * from "./data/buildtree.mjs"; +export * from "./data/pathfinder.mjs"; +export * from "./data/pipe.mjs"; +export * from "./data/extend.mjs"; +export * from "./types/nodelist.mjs"; +export * from "./types/base.mjs"; +export * from "./types/mediatype.mjs"; +export * from "./types/tokenlist.mjs"; +export * from "./types/proxyobserver.mjs"; +export * from "./types/version.mjs"; +export * from "./types/global.mjs"; +export * from "./types/observerlist.mjs"; +export * from "./types/internal.mjs"; +export * from "./types/observablequeue.mjs"; +export * from "./types/dataurl.mjs"; +export * from "./types/binary.mjs"; +export * from "./types/observer.mjs"; +export * from "./types/regex.mjs"; +export * from "./types/randomid.mjs"; +export * from "./types/id.mjs"; +export * from "./types/uuid.mjs"; +export * from "./types/is.mjs"; +export * from "./types/validate.mjs"; +export * from "./types/typeof.mjs"; +export * from "./types/uniquequeue.mjs"; +export * from "./types/stack.mjs"; +export * from "./types/basewithoptions.mjs"; +export * from "./types/node.mjs"; +export * from "./types/queue.mjs"; +export * from "./types/noderecursiveiterator.mjs"; +export * from "./i18n/formatter.mjs"; +export * from "./i18n/locale.mjs"; +export * from "./i18n/provider.mjs"; +export * from "./i18n/providers/fetch.mjs"; +export * from "./i18n/providers/embed.mjs"; +export * from "./i18n/translations.mjs"; +export * from "./constants.mjs"; export { Monster }; @@ -129,5 +128,3 @@ export { Monster }; * @memberOf Monster */ class Monster {} - - diff --git a/source/net/webconnect.mjs b/source/net/webconnect.mjs index 7eb7db338..299f88b50 100644 --- a/source/net/webconnect.mjs +++ b/source/net/webconnect.mjs @@ -44,19 +44,19 @@ const manualCloseSymbol = Symbol("manualClose"); * @type {{"1000": string, "1011": string, "1010": string, "1008": string, "1007": string, "1006": string, "1005": string, "1004": string, "1015": string, "1003": string, "1002": string, "1001": string, "1009": string}} */ const connectionStatusCode = { - 1000: "Normal closure", - 1001: "Going away", - 1002: "Protocol error", - 1003: "Unsupported data", - 1004: "Reserved", - 1005: "No status code", - 1006: "Connection closed abnormally", - 1007: "Invalid frame payload data", - 1008: "Policy violation", - 1009: "Message too big", - 1010: "Mandatory extension", - 1011: "Internal server error", - 1015: "TLS handshake", + 1000: "Normal closure", + 1001: "Going away", + 1002: "Protocol error", + 1003: "Unsupported data", + 1004: "Reserved", + 1005: "No status code", + 1006: "Connection closed abnormally", + 1007: "Invalid frame payload data", + 1008: "Policy violation", + 1009: "Message too big", + 1010: "Mandatory extension", + 1011: "Internal server error", + 1015: "TLS handshake", }; /** @@ -65,89 +65,100 @@ const connectionStatusCode = { * @throws {Error} No url defined for websocket datasource. */ function connectServer(resolve, reject) { - const self = this; - - const url = self.getOption("url"); - if (!url) { - reject("No url defined for webconnect."); - return; - } - - let promiseAllredyResolved = false; - - let connectionTimeout = self.getOption("connection.timeout"); - if (!isInteger(connectionTimeout) || connectionTimeout < 100) { - connectionTimeout = 5000; - } - - setTimeout(() => { - if (promiseAllredyResolved) { - return; - } - reject(new Error("Connection timeout")); - }, connectionTimeout); - - let reconnectTimeout = self.getOption("connection.reconnect.timeout"); - if (!isInteger(reconnectTimeout) || reconnectTimeout < 1000) reconnectTimeout = 1000; - let reconnectAttempts = self.getOption("connection.reconnect.attempts"); - if (!isInteger(reconnectAttempts) || reconnectAttempts < 1) reconnectAttempts = 1; - let reconnectEnabled = self.getOption("connection.reconnect.enabled"); - if (reconnectEnabled !== true) reconnectEnabled = false; - - self[manualCloseSymbol] = false; - self[connectionSymbol].reconnectCounter++; - - if (self[connectionSymbol].socket && self[connectionSymbol].socket.readyState < 2) { - self[connectionSymbol].socket.close(); - } - self[connectionSymbol].socket = null; - self[connectionSymbol].socket = new WebSocket(url); - - self[connectionSymbol].socket.onmessage = function (event) { - if (event.data instanceof Blob) { - const reader = new FileReader(); - reader.addEventListener("loadend", function () { - self[receiveQueueSymbol].add(new Message(reader.result)); - }); - reader.readAsText(new Message(event.data)); - } else { - self[receiveQueueSymbol].add(Message.fromJSON(event.data)); - } - }; - - self[connectionSymbol].socket.onopen = function () { - self[connectionSymbol].reconnectCounter = 0; - if (typeof resolve === "function" && !promiseAllredyResolved) { - promiseAllredyResolved = true; - resolve(); - } - }; - - self[connectionSymbol].socket.close = function (event) { - if (self[manualCloseSymbol]) { - self[manualCloseSymbol] = false; - return; - } - - if (reconnectEnabled && this[connectionSymbol].reconnectCounter < reconnectAttempts) { - setTimeout(() => { - self.connect(); - }, reconnectTimeout * this[connectionSymbol].reconnectCounter); - } - }; - - self[connectionSymbol].socket.onerror = (error) => { - if (reconnectEnabled && self[connectionSymbol].reconnectCounter < reconnectAttempts) { - setTimeout(() => { - self.connect(); - }, reconnectTimeout * this[connectionSymbol].reconnectCounter); - } else { - if (typeof reject === "function" && !promiseAllredyResolved) { - promiseAllredyResolved = true; - reject(error); - } - } - }; + const self = this; + + const url = self.getOption("url"); + if (!url) { + reject("No url defined for webconnect."); + return; + } + + let promiseAllredyResolved = false; + + let connectionTimeout = self.getOption("connection.timeout"); + if (!isInteger(connectionTimeout) || connectionTimeout < 100) { + connectionTimeout = 5000; + } + + setTimeout(() => { + if (promiseAllredyResolved) { + return; + } + reject(new Error("Connection timeout")); + }, connectionTimeout); + + let reconnectTimeout = self.getOption("connection.reconnect.timeout"); + if (!isInteger(reconnectTimeout) || reconnectTimeout < 1000) + reconnectTimeout = 1000; + let reconnectAttempts = self.getOption("connection.reconnect.attempts"); + if (!isInteger(reconnectAttempts) || reconnectAttempts < 1) + reconnectAttempts = 1; + let reconnectEnabled = self.getOption("connection.reconnect.enabled"); + if (reconnectEnabled !== true) reconnectEnabled = false; + + self[manualCloseSymbol] = false; + self[connectionSymbol].reconnectCounter++; + + if ( + self[connectionSymbol].socket && + self[connectionSymbol].socket.readyState < 2 + ) { + self[connectionSymbol].socket.close(); + } + self[connectionSymbol].socket = null; + self[connectionSymbol].socket = new WebSocket(url); + + self[connectionSymbol].socket.onmessage = function (event) { + if (event.data instanceof Blob) { + const reader = new FileReader(); + reader.addEventListener("loadend", function () { + self[receiveQueueSymbol].add(new Message(reader.result)); + }); + reader.readAsText(new Message(event.data)); + } else { + self[receiveQueueSymbol].add(Message.fromJSON(event.data)); + } + }; + + self[connectionSymbol].socket.onopen = function () { + self[connectionSymbol].reconnectCounter = 0; + if (typeof resolve === "function" && !promiseAllredyResolved) { + promiseAllredyResolved = true; + resolve(); + } + }; + + self[connectionSymbol].socket.close = function (event) { + if (self[manualCloseSymbol]) { + self[manualCloseSymbol] = false; + return; + } + + if ( + reconnectEnabled && + this[connectionSymbol].reconnectCounter < reconnectAttempts + ) { + setTimeout(() => { + self.connect(); + }, reconnectTimeout * this[connectionSymbol].reconnectCounter); + } + }; + + self[connectionSymbol].socket.onerror = (error) => { + if ( + reconnectEnabled && + self[connectionSymbol].reconnectCounter < reconnectAttempts + ) { + setTimeout(() => { + self.connect(); + }, reconnectTimeout * this[connectionSymbol].reconnectCounter); + } else { + if (typeof reject === "function" && !promiseAllredyResolved) { + promiseAllredyResolved = true; + reject(error); + } + } + }; } /** @@ -161,175 +172,175 @@ function connectServer(resolve, reject) { * @summary The LocalStorage class encapsulates the access to data objects. */ class WebConnect extends BaseWithOptions { - /** - * - * @param {Object} [options] options contains definitions for the webconnect. - */ - constructor(options) { - if (isString(options)) { - options = { url: options }; - } - - super(options); - - this[receiveQueueSymbol] = new ObservableQueue(); - this[sendQueueSymbol] = new ObservableQueue(); - - this[connectionSymbol] = {}; - this[connectionSymbol].socket = null; - this[connectionSymbol].reconnectCounter = 0; - this[manualCloseSymbol] = false; - } - - /** - * - * @returns {Promise} - */ - connect() { - const self = this; - - return new Promise((resolve, reject) => { - connectServer.call(this, resolve, reject); - }); - } - - /** - * @returns {boolean} - */ - isConnected() { - return this[connectionSymbol]?.socket?.readyState === 1; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/net/webconnect"); - } - - /** - * @property {string} url=undefined Defines the resource that you wish to fetch. - * @property {Object} connection - * @property {Object} connection.timeout=5000 Defines the timeout for the connection. - * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect. - * @property {Number} connection.reconnect.attempts The maximum number of reconnects. - * @property {Bool} connection.reconnect.enabled If the reconnect is enabled. - */ - get defaults() { - return Object.assign({}, super.defaults, { - url: undefined, - connection: { - timeout: 5000, - reconnect: { - timeout: 1000, - attempts: 1, - enabled: false, - }, - }, - }); - } - - /** - * This method closes the connection. - * - * @param {Number} [code=1000] The close code. - * @param {String} [reason=""] The close reason. - * @returns {Promise} - * @see https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 - */ - close(statusCode, reason) { - if (!isInteger(statusCode) || statusCode < 1000 || statusCode > 4999) { - statusCode = 1000; - } - if (!isString(reason)) { - reason = ""; - } - - return new Promise((resolve, reject) => { - try { - this[manualCloseSymbol] = true; - if (this[connectionSymbol].socket) { - this[connectionSymbol].socket.close(statusCode, reason); - } - } catch (error) { - reject(error); - } - resolve(); - }); - } - - /** - * Polls the receive queue for new messages. - * - * @returns {Message} - */ - poll() { - return this[receiveQueueSymbol].poll(); - } - - /** - * Are there any messages in the receive queue? - * - * @returns {boolean} - */ - dataReceived() { - return !this[receiveQueueSymbol].isEmpty(); - } - - /** - * Get Message from the receive queue, but do not remove it. - * - * @returns {Object} - */ - peek() { - return this[receiveQueueSymbol].peek(); - } - - /** - * Attach a new observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - attachObserver(observer) { - this[receiveQueueSymbol].attachObserver(observer); - return this; - } - - /** - * Detach a observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - detachObserver(observer) { - this[receiveQueueSymbol].detachObserver(observer); - return this; - } - - /** - * @param {Observer} observer - * @returns {boolean} - */ - containsObserver(observer) { - return this[receiveQueueSymbol].containsObserver(observer); - } - - /** - * @param {Message|Object} message - * @return {Promise} - */ - send(message) { - const self = this; - - return new Promise((resolve, reject) => { - if (self[connectionSymbol].socket.readyState !== 1) { - reject("the socket is not ready"); - } - - self[connectionSymbol].socket.send(JSON.stringify(message)); - resolve(); - }); - } + /** + * + * @param {Object} [options] options contains definitions for the webconnect. + */ + constructor(options) { + if (isString(options)) { + options = { url: options }; + } + + super(options); + + this[receiveQueueSymbol] = new ObservableQueue(); + this[sendQueueSymbol] = new ObservableQueue(); + + this[connectionSymbol] = {}; + this[connectionSymbol].socket = null; + this[connectionSymbol].reconnectCounter = 0; + this[manualCloseSymbol] = false; + } + + /** + * + * @returns {Promise} + */ + connect() { + const self = this; + + return new Promise((resolve, reject) => { + connectServer.call(this, resolve, reject); + }); + } + + /** + * @returns {boolean} + */ + isConnected() { + return this[connectionSymbol]?.socket?.readyState === 1; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/net/webconnect"); + } + + /** + * @property {string} url=undefined Defines the resource that you wish to fetch. + * @property {Object} connection + * @property {Object} connection.timeout=5000 Defines the timeout for the connection. + * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect. + * @property {Number} connection.reconnect.attempts The maximum number of reconnects. + * @property {Bool} connection.reconnect.enabled If the reconnect is enabled. + */ + get defaults() { + return Object.assign({}, super.defaults, { + url: undefined, + connection: { + timeout: 5000, + reconnect: { + timeout: 1000, + attempts: 1, + enabled: false, + }, + }, + }); + } + + /** + * This method closes the connection. + * + * @param {Number} [code=1000] The close code. + * @param {String} [reason=""] The close reason. + * @returns {Promise} + * @see https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 + */ + close(statusCode, reason) { + if (!isInteger(statusCode) || statusCode < 1000 || statusCode > 4999) { + statusCode = 1000; + } + if (!isString(reason)) { + reason = ""; + } + + return new Promise((resolve, reject) => { + try { + this[manualCloseSymbol] = true; + if (this[connectionSymbol].socket) { + this[connectionSymbol].socket.close(statusCode, reason); + } + } catch (error) { + reject(error); + } + resolve(); + }); + } + + /** + * Polls the receive queue for new messages. + * + * @returns {Message} + */ + poll() { + return this[receiveQueueSymbol].poll(); + } + + /** + * Are there any messages in the receive queue? + * + * @returns {boolean} + */ + dataReceived() { + return !this[receiveQueueSymbol].isEmpty(); + } + + /** + * Get Message from the receive queue, but do not remove it. + * + * @returns {Object} + */ + peek() { + return this[receiveQueueSymbol].peek(); + } + + /** + * Attach a new observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + attachObserver(observer) { + this[receiveQueueSymbol].attachObserver(observer); + return this; + } + + /** + * Detach a observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + detachObserver(observer) { + this[receiveQueueSymbol].detachObserver(observer); + return this; + } + + /** + * @param {Observer} observer + * @returns {boolean} + */ + containsObserver(observer) { + return this[receiveQueueSymbol].containsObserver(observer); + } + + /** + * @param {Message|Object} message + * @return {Promise} + */ + send(message) { + const self = this; + + return new Promise((resolve, reject) => { + if (self[connectionSymbol].socket.readyState !== 1) { + reject("the socket is not ready"); + } + + self[connectionSymbol].socket.send(JSON.stringify(message)); + resolve(); + }); + } } diff --git a/source/net/webconnect/message.mjs b/source/net/webconnect/message.mjs index 09c42a901..a8411bdfa 100644 --- a/source/net/webconnect/message.mjs +++ b/source/net/webconnect/message.mjs @@ -22,38 +22,38 @@ const dataSymbol = Symbol("@@data"); * @summary The Message class encapsulates a WebSocket message. */ class Message extends Base { - /** - * @param {Object} data - * @throws {TypeError} value is not a object - */ - constructor(data) { - super(); - this[dataSymbol] = validateObject(data); - } + /** + * @param {Object} data + * @throws {TypeError} value is not a object + */ + constructor(data) { + super(); + this[dataSymbol] = validateObject(data); + } - /** - * Returns the raw message. - * - * @returns {object} - */ - getData() { - return this[dataSymbol]; - } + /** + * Returns the raw message. + * + * @returns {object} + */ + getData() { + return this[dataSymbol]; + } - /** - * @returns {*} - */ - toJSON() { - return this[dataSymbol]; - } + /** + * @returns {*} + */ + toJSON() { + return this[dataSymbol]; + } - /** - * @param {string} json - * @returns {Message} - * @throws {TypeError} value is not a string - */ - static fromJSON(json) { - validateString(json); - return new Message(JSON.parse(json)); - } + /** + * @param {string} json + * @returns {Message} + * @throws {TypeError} value is not a string + */ + static fromJSON(json) { + validateString(json); + return new Message(JSON.parse(json)); + } } diff --git a/source/text/bracketed-key-value-hash.mjs b/source/text/bracketed-key-value-hash.mjs index 55831dcba..4fe3e50d0 100644 --- a/source/text/bracketed-key-value-hash.mjs +++ b/source/text/bracketed-key-value-hash.mjs @@ -44,152 +44,155 @@ export { parseBracketedKeyValueHash, createBracketedKeyValueHash }; * @returns {Object} - An object representing the parsed result, with keys representing the selectors and values representing the key-value pairs associated with each selector. * - Returns an empty object if there was an error during parsing. */ function parseBracketedKeyValueHash(hashString) { - const selectors = {}; - //const selectorStack = []; - //const keyValueStack = []; - - const trimmedHashString = hashString.trim(); - const cleanedHashString = trimmedHashString.charAt(0) === "#" ? trimmedHashString.slice(1) : trimmedHashString; - - //const selectors = (keyValueStack.length > 0) ? result[selectorStack[selectorStack.length - 1]] : result; - let currentSelector = ""; - - function addToResult(key, value) { - if (currentSelector && key) { - if (!selectors[currentSelector]) { - selectors[currentSelector] = {}; - } - - selectors[currentSelector][key] = value; - } - } - - let currentKey = ""; - let currentValue = ""; - let inKey = true; - let inValue = false; - let inQuotedValue = false; - let inSelector = true; - let escaped = false; - let quotedValueStartChar = ""; - - for (let i = 0; i < cleanedHashString.length; i++) { - const c = cleanedHashString[i]; - const nextChar = cleanedHashString?.[i + 1]; - - if (c === "\\" && !escaped) { - escaped = true; - continue; - } - - if (escaped) { - if (inSelector) { - currentSelector += c; - } else if (inKey) { - currentKey += c; - } else if (inValue) { - currentValue += c; - } - escaped = false; - continue; - } - - if (inQuotedValue && quotedValueStartChar !== c) { - if (inSelector) { - currentSelector += c; - } else if (inKey) { - currentKey += c; - } else if (inValue) { - currentValue += c; - } - - continue; - } - - if (c === ";" && inSelector) { - inSelector = true; - currentSelector = ""; - continue; - } - - if (inSelector === true && c !== "(") { - currentSelector += c; - continue; - } - - if (c === "(" && inSelector) { - inSelector = false; - inKey = true; - - currentKey = ""; - continue; - } - - if (inKey === true && c !== "=") { - currentKey += c; - continue; - } - - if (c === "=" && inKey) { - inKey = false; - inValue = true; - - if (nextChar === '"' || nextChar === "'") { - inQuotedValue = true; - quotedValueStartChar = nextChar; - i++; - continue; - } - - currentValue = ""; - continue; - } - - if (inValue === true) { - if (inQuotedValue) { - if (c === quotedValueStartChar) { - inQuotedValue = false; - continue; - } - - currentValue += c; - continue; - } - - if (c === ",") { - inValue = false; - inKey = true; - const decodedCurrentValue = decodeURIComponent(currentValue); - addToResult(currentKey, decodedCurrentValue); - currentKey = ""; - currentValue = ""; - continue; - } - - if (c === ")") { - inValue = false; - //inKey = true; - inSelector = true; - - const decodedCurrentValue = decodeURIComponent(currentValue); - addToResult(currentKey, decodedCurrentValue); - currentKey = ""; - currentValue = ""; - currentSelector = ""; - continue; - } - - currentValue += c; - - continue; - } - } - - if (inSelector) { - return selectors; - } - - return {}; + const selectors = {}; + //const selectorStack = []; + //const keyValueStack = []; + + const trimmedHashString = hashString.trim(); + const cleanedHashString = + trimmedHashString.charAt(0) === "#" + ? trimmedHashString.slice(1) + : trimmedHashString; + + //const selectors = (keyValueStack.length > 0) ? result[selectorStack[selectorStack.length - 1]] : result; + let currentSelector = ""; + + function addToResult(key, value) { + if (currentSelector && key) { + if (!selectors[currentSelector]) { + selectors[currentSelector] = {}; + } + + selectors[currentSelector][key] = value; + } + } + + let currentKey = ""; + let currentValue = ""; + let inKey = true; + let inValue = false; + let inQuotedValue = false; + let inSelector = true; + let escaped = false; + let quotedValueStartChar = ""; + + for (let i = 0; i < cleanedHashString.length; i++) { + const c = cleanedHashString[i]; + const nextChar = cleanedHashString?.[i + 1]; + + if (c === "\\" && !escaped) { + escaped = true; + continue; + } + + if (escaped) { + if (inSelector) { + currentSelector += c; + } else if (inKey) { + currentKey += c; + } else if (inValue) { + currentValue += c; + } + escaped = false; + continue; + } + + if (inQuotedValue && quotedValueStartChar !== c) { + if (inSelector) { + currentSelector += c; + } else if (inKey) { + currentKey += c; + } else if (inValue) { + currentValue += c; + } + + continue; + } + + if (c === ";" && inSelector) { + inSelector = true; + currentSelector = ""; + continue; + } + + if (inSelector === true && c !== "(") { + currentSelector += c; + continue; + } + + if (c === "(" && inSelector) { + inSelector = false; + inKey = true; + + currentKey = ""; + continue; + } + + if (inKey === true && c !== "=") { + currentKey += c; + continue; + } + + if (c === "=" && inKey) { + inKey = false; + inValue = true; + + if (nextChar === '"' || nextChar === "'") { + inQuotedValue = true; + quotedValueStartChar = nextChar; + i++; + continue; + } + + currentValue = ""; + continue; + } + + if (inValue === true) { + if (inQuotedValue) { + if (c === quotedValueStartChar) { + inQuotedValue = false; + continue; + } + + currentValue += c; + continue; + } + + if (c === ",") { + inValue = false; + inKey = true; + const decodedCurrentValue = decodeURIComponent(currentValue); + addToResult(currentKey, decodedCurrentValue); + currentKey = ""; + currentValue = ""; + continue; + } + + if (c === ")") { + inValue = false; + //inKey = true; + inSelector = true; + + const decodedCurrentValue = decodeURIComponent(currentValue); + addToResult(currentKey, decodedCurrentValue); + currentKey = ""; + currentValue = ""; + currentSelector = ""; + continue; + } + + currentValue += c; + + continue; + } + } + + if (inSelector) { + return selectors; + } + + return {}; } /** @@ -201,37 +204,37 @@ function parseBracketedKeyValueHash(hashString) { * @since 3.37.0 */ function createBracketedKeyValueHash(object, addHashPrefix = true) { - if (!object) { - return addHashPrefix ? "#" : ""; - } - - let hashString = ""; - - function encodeKeyValue(key, value) { - return encodeURIComponent(key) + "=" + encodeURIComponent(value); - } - - for (const selector in object) { - if (object.hasOwnProperty(selector)) { - const keyValuePairs = object[selector]; - let selectorString = selector; - let keyValueString = ""; - - for (const key in keyValuePairs) { - if (keyValuePairs.hasOwnProperty(key)) { - const value = keyValuePairs[key]; - keyValueString += keyValueString.length === 0 ? "" : ","; - keyValueString += encodeKeyValue(key, value); - } - } - - if (keyValueString.length > 0) { - selectorString += "(" + keyValueString + ")"; - hashString += hashString.length === 0 ? "" : ";"; - hashString += selectorString; - } - } - } - - return addHashPrefix ? "#" + hashString : hashString; + if (!object) { + return addHashPrefix ? "#" : ""; + } + + let hashString = ""; + + function encodeKeyValue(key, value) { + return encodeURIComponent(key) + "=" + encodeURIComponent(value); + } + + for (const selector in object) { + if (object.hasOwnProperty(selector)) { + const keyValuePairs = object[selector]; + let selectorString = selector; + let keyValueString = ""; + + for (const key in keyValuePairs) { + if (keyValuePairs.hasOwnProperty(key)) { + const value = keyValuePairs[key]; + keyValueString += keyValueString.length === 0 ? "" : ","; + keyValueString += encodeKeyValue(key, value); + } + } + + if (keyValueString.length > 0) { + selectorString += "(" + keyValueString + ")"; + hashString += hashString.length === 0 ? "" : ";"; + hashString += selectorString; + } + } + } + + return addHashPrefix ? "#" + hashString : hashString; } diff --git a/source/text/formatter.mjs b/source/text/formatter.mjs index 79db10f9c..b238eebb3 100644 --- a/source/text/formatter.mjs +++ b/source/text/formatter.mjs @@ -109,115 +109,117 @@ const workingDataSymbol = Symbol("workingData"); * @memberOf Monster.Text */ class Formatter extends BaseWithOptions { - /** - * Default values for the markers are `${` and `}` - * - * @param {object} object - * @throws {TypeError} value is not a object - */ - constructor(object, options) { - super(options); - this[internalObjectSymbol] = object || {}; - this[markerOpenIndexSymbol] = 0; - this[markerCloseIndexSymbol] = 0; - } - - /** - * @property {object} marker - * @property {array} marker.open=["${"] - * @property {array} marker.close=["${"] - * @property {object} parameter - * @property {string} parameter.delimiter="::" - * @property {string} parameter.assignment="=" - * @property {object} callbacks={} - */ - get defaults() { - return extend({}, super.defaults, { - marker: { - open: ["${"], - close: ["}"], - }, - parameter: { - delimiter: "::", - assignment: "=", - }, - callbacks: {}, - }); - } - - /** - * Set new Parameter Character - * - * Default values for the chars are `::` and `=` - * - * ``` - * formatter.setParameterChars('#'); - * formatter.setParameterChars('[',']'); - * formatter.setParameterChars('i18n{','}'); - * ``` - * - * @param {string} delimiter - * @param {string} assignment - * @return {Formatter} - * @since 1.24.0 - * @throws {TypeError} value is not a string - */ - setParameterChars(delimiter, assignment) { - if (delimiter !== undefined) { - this[internalSymbol]["parameter"]["delimiter"] = validateString(delimiter); - } - - if (assignment !== undefined) { - this[internalSymbol]["parameter"]["assignment"] = validateString(assignment); - } - - return this; - } - - /** - * Set new Marker - * - * Default values for the markers are `${` and `}` - * - * ``` - * formatter.setMarker('#'); // open and close are both # - * formatter.setMarker('[',']'); - * formatter.setMarker('i18n{','}'); - * ``` - * - * @param {array|string} open - * @param {array|string|undefined} close - * @return {Formatter} - * @since 1.12.0 - * @throws {TypeError} value is not a string - */ - setMarker(open, close) { - if (close === undefined) { - close = open; - } - - if (isString(open)) open = [open]; - if (isString(close)) close = [close]; - - this[internalSymbol]["marker"]["open"] = validateArray(open); - this[internalSymbol]["marker"]["close"] = validateArray(close); - return this; - } - - /** - * - * @param {string} text - * @return {string} - * @throws {TypeError} value is not a string - * @throws {Error} too deep nesting - */ - format(text) { - this[watchdogSymbol] = 0; - this[markerOpenIndexSymbol] = 0; - this[markerCloseIndexSymbol] = 0; - this[workingDataSymbol] = {}; - return format.call(this, text); - } + /** + * Default values for the markers are `${` and `}` + * + * @param {object} object + * @throws {TypeError} value is not a object + */ + constructor(object, options) { + super(options); + this[internalObjectSymbol] = object || {}; + this[markerOpenIndexSymbol] = 0; + this[markerCloseIndexSymbol] = 0; + } + + /** + * @property {object} marker + * @property {array} marker.open=["${"] + * @property {array} marker.close=["${"] + * @property {object} parameter + * @property {string} parameter.delimiter="::" + * @property {string} parameter.assignment="=" + * @property {object} callbacks={} + */ + get defaults() { + return extend({}, super.defaults, { + marker: { + open: ["${"], + close: ["}"], + }, + parameter: { + delimiter: "::", + assignment: "=", + }, + callbacks: {}, + }); + } + + /** + * Set new Parameter Character + * + * Default values for the chars are `::` and `=` + * + * ``` + * formatter.setParameterChars('#'); + * formatter.setParameterChars('[',']'); + * formatter.setParameterChars('i18n{','}'); + * ``` + * + * @param {string} delimiter + * @param {string} assignment + * @return {Formatter} + * @since 1.24.0 + * @throws {TypeError} value is not a string + */ + setParameterChars(delimiter, assignment) { + if (delimiter !== undefined) { + this[internalSymbol]["parameter"]["delimiter"] = + validateString(delimiter); + } + + if (assignment !== undefined) { + this[internalSymbol]["parameter"]["assignment"] = + validateString(assignment); + } + + return this; + } + + /** + * Set new Marker + * + * Default values for the markers are `${` and `}` + * + * ``` + * formatter.setMarker('#'); // open and close are both # + * formatter.setMarker('[',']'); + * formatter.setMarker('i18n{','}'); + * ``` + * + * @param {array|string} open + * @param {array|string|undefined} close + * @return {Formatter} + * @since 1.12.0 + * @throws {TypeError} value is not a string + */ + setMarker(open, close) { + if (close === undefined) { + close = open; + } + + if (isString(open)) open = [open]; + if (isString(close)) close = [close]; + + this[internalSymbol]["marker"]["open"] = validateArray(open); + this[internalSymbol]["marker"]["close"] = validateArray(close); + return this; + } + + /** + * + * @param {string} text + * @return {string} + * @throws {TypeError} value is not a string + * @throws {Error} too deep nesting + */ + format(text) { + this[watchdogSymbol] = 0; + this[markerOpenIndexSymbol] = 0; + this[markerCloseIndexSymbol] = 0; + this[workingDataSymbol] = {}; + return format.call(this, text); + } } /** @@ -225,34 +227,45 @@ class Formatter extends BaseWithOptions { * @return {string} */ function format(text) { - const self = this; - - self[watchdogSymbol]++; - if (this[watchdogSymbol] > 20) { - throw new Error("too deep nesting"); - } - - let openMarker = self[internalSymbol]["marker"]["open"]?.[this[markerOpenIndexSymbol]]; - let closeMarker = self[internalSymbol]["marker"]["close"]?.[this[markerCloseIndexSymbol]]; - - // contains no placeholders - if (text.indexOf(openMarker) === -1 || text.indexOf(closeMarker) === -1) { - return text; - } - - let result = tokenize.call(this, validateString(text), openMarker, closeMarker); - - if (self[internalSymbol]["marker"]["open"]?.[this[markerOpenIndexSymbol] + 1]) { - this[markerOpenIndexSymbol]++; - } - - if (self[internalSymbol]["marker"]["close"]?.[this[markerCloseIndexSymbol] + 1]) { - this[markerCloseIndexSymbol]++; - } - - result = format.call(self, result); - - return result; + const self = this; + + self[watchdogSymbol]++; + if (this[watchdogSymbol] > 20) { + throw new Error("too deep nesting"); + } + + let openMarker = + self[internalSymbol]["marker"]["open"]?.[this[markerOpenIndexSymbol]]; + let closeMarker = + self[internalSymbol]["marker"]["close"]?.[this[markerCloseIndexSymbol]]; + + // contains no placeholders + if (text.indexOf(openMarker) === -1 || text.indexOf(closeMarker) === -1) { + return text; + } + + let result = tokenize.call( + this, + validateString(text), + openMarker, + closeMarker, + ); + + if ( + self[internalSymbol]["marker"]["open"]?.[this[markerOpenIndexSymbol] + 1] + ) { + this[markerOpenIndexSymbol]++; + } + + if ( + self[internalSymbol]["marker"]["close"]?.[this[markerCloseIndexSymbol] + 1] + ) { + this[markerCloseIndexSymbol]++; + } + + result = format.call(self, result); + + return result; } /** @@ -263,77 +276,93 @@ function format(text) { * @return {string} */ function tokenize(text, openMarker, closeMarker) { - const self = this; - - let formatted = []; - - const parameterAssignment = self[internalSymbol]["parameter"]["assignment"]; - const parameterDelimiter = self[internalSymbol]["parameter"]["delimiter"]; - const callbacks = self[internalSymbol]["callbacks"]; - - while (true) { - let startIndex = text.indexOf(openMarker); - // no marker - if (startIndex === -1) { - formatted.push(text); - break; - } else if (startIndex > 0) { - formatted.push(text.substring(0, startIndex)); - text = text.substring(startIndex); - } - - let endIndex = text.substring(openMarker.length).indexOf(closeMarker); - if (endIndex !== -1) endIndex += openMarker.length; - let insideStartIndex = text.substring(openMarker.length).indexOf(openMarker); - if (insideStartIndex !== -1) { - insideStartIndex += openMarker.length; - if (insideStartIndex < endIndex) { - let result = tokenize.call(self, text.substring(insideStartIndex), openMarker, closeMarker); - text = text.substring(0, insideStartIndex) + result; - endIndex = text.substring(openMarker.length).indexOf(closeMarker); - if (endIndex !== -1) endIndex += openMarker.length; - } - } - - if (endIndex === -1) { - throw new Error("syntax error in formatter template"); - } - - let key = text.substring(openMarker.length, endIndex); - let parts = key.split(parameterDelimiter); - let currentPipe = parts.shift(); - - self[workingDataSymbol] = extend({}, self[internalObjectSymbol], self[workingDataSymbol]); - - for (const kv of parts) { - const [k, v] = kv.split(parameterAssignment); - self[workingDataSymbol][k] = v; - } - - const t1 = key.split("|").shift().trim(); // pipe symbol - const t2 = t1.split("::").shift().trim(); // key value delimiter - const t3 = t2.split(".").shift().trim(); // path delimiter - let prefix = self[workingDataSymbol]?.[t3] ? "path:" : "static:"; - - let command = ""; - if (prefix && key.indexOf(prefix) !== 0 && key.indexOf("path:") !== 0 && key.indexOf("static:") !== 0) { - command = prefix; - } - - command += currentPipe; - - const pipe = new Pipe(command); - - if (isObject(callbacks)) { - for (const [name, callback] of Object.entries(callbacks)) { - pipe.setCallback(name, callback); - } - } - - formatted.push(validateString(pipe.run(self[workingDataSymbol]))); - - text = text.substring(endIndex + closeMarker.length); - } - - return formatted.join(""); + const self = this; + + let formatted = []; + + const parameterAssignment = self[internalSymbol]["parameter"]["assignment"]; + const parameterDelimiter = self[internalSymbol]["parameter"]["delimiter"]; + const callbacks = self[internalSymbol]["callbacks"]; + + while (true) { + let startIndex = text.indexOf(openMarker); + // no marker + if (startIndex === -1) { + formatted.push(text); + break; + } else if (startIndex > 0) { + formatted.push(text.substring(0, startIndex)); + text = text.substring(startIndex); + } + + let endIndex = text.substring(openMarker.length).indexOf(closeMarker); + if (endIndex !== -1) endIndex += openMarker.length; + let insideStartIndex = text + .substring(openMarker.length) + .indexOf(openMarker); + if (insideStartIndex !== -1) { + insideStartIndex += openMarker.length; + if (insideStartIndex < endIndex) { + let result = tokenize.call( + self, + text.substring(insideStartIndex), + openMarker, + closeMarker, + ); + text = text.substring(0, insideStartIndex) + result; + endIndex = text.substring(openMarker.length).indexOf(closeMarker); + if (endIndex !== -1) endIndex += openMarker.length; + } + } + + if (endIndex === -1) { + throw new Error("syntax error in formatter template"); + } + + let key = text.substring(openMarker.length, endIndex); + let parts = key.split(parameterDelimiter); + let currentPipe = parts.shift(); + + self[workingDataSymbol] = extend( + {}, + self[internalObjectSymbol], + self[workingDataSymbol], + ); + + for (const kv of parts) { + const [k, v] = kv.split(parameterAssignment); + self[workingDataSymbol][k] = v; + } + + const t1 = key.split("|").shift().trim(); // pipe symbol + const t2 = t1.split("::").shift().trim(); // key value delimiter + const t3 = t2.split(".").shift().trim(); // path delimiter + let prefix = self[workingDataSymbol]?.[t3] ? "path:" : "static:"; + + let command = ""; + if ( + prefix && + key.indexOf(prefix) !== 0 && + key.indexOf("path:") !== 0 && + key.indexOf("static:") !== 0 + ) { + command = prefix; + } + + command += currentPipe; + + const pipe = new Pipe(command); + + if (isObject(callbacks)) { + for (const [name, callback] of Object.entries(callbacks)) { + pipe.setCallback(name, callback); + } + } + + formatted.push(validateString(pipe.run(self[workingDataSymbol]))); + + text = text.substring(endIndex + closeMarker.length); + } + + return formatted.join(""); } diff --git a/source/text/generate-range-comparison-expression.mjs b/source/text/generate-range-comparison-expression.mjs index 367516e3c..1d28445bd 100644 --- a/source/text/generate-range-comparison-expression.mjs +++ b/source/text/generate-range-comparison-expression.mjs @@ -56,38 +56,60 @@ export { generateRangeComparisonExpression }; * @memberOf Monster.Text * @summary Generates a comparison expression based on a range of values. */ -function generateRangeComparisonExpression(expression, valueName, options = {}) { - const { urlEncode = false, andOp = "&&", orOp = "||", eqOp = "==", geOp = ">=", leOp = "<=" } = options; - const ranges = expression.split(","); - let comparison = ""; - for (let i = 0; i < ranges.length; i++) { - const range = ranges[i].trim(); - if (range === "") { - throw new Error(`Invalid range '${range}'`); - } else if (range.includes("-")) { - const [start, end] = range.split("-").map((s) => (s === "" ? null : parseFloat(s))); - if ((start !== null && isNaN(start)) || (end !== null && isNaN(end))) { - throw new Error(`Invalid value in range '${range}'`); - } - if (start !== null && end !== null && start > end) { - throw new Error(`Invalid range '${range}'`); - } - const compStart = - start !== null ? `${valueName}${urlEncode ? encodeURIComponent(geOp) : geOp}${start}` : ""; - const compEnd = end !== null ? `${valueName}${urlEncode ? encodeURIComponent(leOp) : leOp}${end}` : ""; - const compRange = `${compStart}${compStart && compEnd ? ` ${andOp} ` : ""}${compEnd}`; - comparison += ranges.length > 1 ? `(${compRange})` : compRange; - } else { - const value = parseFloat(range); - if (isNaN(value)) { - throw new Error(`Invalid value '${range}'`); - } - const compValue = `${valueName}${urlEncode ? encodeURIComponent(eqOp) : eqOp}${value}`; - comparison += ranges.length > 1 ? `(${compValue})` : compValue; - } - if (i < ranges.length - 1) { - comparison += ` ${orOp} `; - } - } - return comparison; +function generateRangeComparisonExpression( + expression, + valueName, + options = {}, +) { + const { + urlEncode = false, + andOp = "&&", + orOp = "||", + eqOp = "==", + geOp = ">=", + leOp = "<=", + } = options; + const ranges = expression.split(","); + let comparison = ""; + for (let i = 0; i < ranges.length; i++) { + const range = ranges[i].trim(); + if (range === "") { + throw new Error(`Invalid range '${range}'`); + } else if (range.includes("-")) { + const [start, end] = range + .split("-") + .map((s) => (s === "" ? null : parseFloat(s))); + if ((start !== null && isNaN(start)) || (end !== null && isNaN(end))) { + throw new Error(`Invalid value in range '${range}'`); + } + if (start !== null && end !== null && start > end) { + throw new Error(`Invalid range '${range}'`); + } + const compStart = + start !== null + ? `${valueName}${urlEncode ? encodeURIComponent(geOp) : geOp}${start}` + : ""; + const compEnd = + end !== null + ? `${valueName}${urlEncode ? encodeURIComponent(leOp) : leOp}${end}` + : ""; + const compRange = `${compStart}${ + compStart && compEnd ? ` ${andOp} ` : "" + }${compEnd}`; + comparison += ranges.length > 1 ? `(${compRange})` : compRange; + } else { + const value = parseFloat(range); + if (isNaN(value)) { + throw new Error(`Invalid value '${range}'`); + } + const compValue = `${valueName}${ + urlEncode ? encodeURIComponent(eqOp) : eqOp + }${value}`; + comparison += ranges.length > 1 ? `(${compValue})` : compValue; + } + if (i < ranges.length - 1) { + comparison += ` ${orOp} `; + } + } + return comparison; } diff --git a/source/types/base.mjs b/source/types/base.mjs index 1e48be078..d7cd4ba7f 100644 --- a/source/types/base.mjs +++ b/source/types/base.mjs @@ -36,50 +36,54 @@ export { Base }; * @memberOf Monster.Types */ class Base extends Object { - /** - * - * @returns {string} - */ - toString() { - return JSON.stringify(this); - } + /** + * + * @returns {string} + */ + toString() { + return JSON.stringify(this); + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/base"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/base"); + } - /** - * This method is called by the `instanceof` operator. - * @param that - * @returns {boolean} - * @since 2.1.0 - */ - static [Symbol.hasInstance](that) { - if (that === undefined || that === null || (typeof that !== "object" && typeof that !== "function")) { - return false; - } + /** + * This method is called by the `instanceof` operator. + * @param that + * @returns {boolean} + * @since 2.1.0 + */ + static [Symbol.hasInstance](that) { + if ( + that === undefined || + that === null || + (typeof that !== "object" && typeof that !== "function") + ) { + return false; + } - const thatClass = Object.getPrototypeOf(that); - if ( - thatClass === undefined || - thatClass === null || - (typeof thatClass !== "object" && typeof thatClass !== "function") - ) { - return false; - } + const thatClass = Object.getPrototypeOf(that); + if ( + thatClass === undefined || + thatClass === null || + (typeof thatClass !== "object" && typeof thatClass !== "function") + ) { + return false; + } - if (checkInstanceSymbol.apply(this, [thatClass]) === true) { - return true; - } + if (checkInstanceSymbol.apply(this, [thatClass]) === true) { + return true; + } - // this call the static method of the super class, if there is one - return super[Symbol.hasInstance](that); - } + // this call the static method of the super class, if there is one + return super[Symbol.hasInstance](that); + } } /** @@ -91,31 +95,35 @@ class Base extends Object { * @since 2.1.0 */ function checkInstanceSymbol(obj) { - if (this.hasOwnProperty(instanceSymbol) === false) { - return false; - } + if (this.hasOwnProperty(instanceSymbol) === false) { + return false; + } - const proto = obj?.constructor; - if (proto === undefined || proto === null || (typeof proto !== "object" && typeof proto !== "function")) { - return false; - } + const proto = obj?.constructor; + if ( + proto === undefined || + proto === null || + (typeof proto !== "object" && typeof proto !== "function") + ) { + return false; + } - if (proto.hasOwnProperty(instanceSymbol) !== true) { - return checkInstanceSymbol.apply(this, [obj.__proto__]); - } + if (proto.hasOwnProperty(instanceSymbol) !== true) { + return checkInstanceSymbol.apply(this, [obj.__proto__]); + } - const symbol = proto[instanceSymbol]; - if (symbol === undefined) { - if (obj.__proto__) { - return checkInstanceSymbol(obj.__proto__); - } else { - return false; - } - } + const symbol = proto[instanceSymbol]; + if (symbol === undefined) { + if (obj.__proto__) { + return checkInstanceSymbol(obj.__proto__); + } else { + return false; + } + } - if (symbol === this[instanceSymbol]) { - return true; - } + if (symbol === this[instanceSymbol]) { + return true; + } - return checkInstanceSymbol.apply(this, [obj.__proto__]); + return checkInstanceSymbol.apply(this, [obj.__proto__]); } diff --git a/source/types/basewithoptions.mjs b/source/types/basewithoptions.mjs index 9ad16e121..793e33bac 100644 --- a/source/types/basewithoptions.mjs +++ b/source/types/basewithoptions.mjs @@ -29,56 +29,56 @@ export { BaseWithOptions }; * @deprecated since 3.15.0 use {@link Monster.Types.Base} with {@link Monster.Types.equipWithInternal} instead. */ class BaseWithOptions extends Base { - /** - * - * @param {object} options - */ - constructor(options) { - super(); + /** + * + * @param {object} options + */ + constructor(options) { + super(); - if (options === undefined) { - options = {}; - } + if (options === undefined) { + options = {}; + } - this[internalSymbol] = extend({}, this.defaults, validateObject(options)); - } + this[internalSymbol] = extend({}, this.defaults, validateObject(options)); + } - /** - * This getter provides the options. Derived classes overwrite - * this getter with their own values. It is good karma to always include - * the values from the parent class. - * - * ```javascript - * get defaults() { - * return Object.assign({}, super.defaults, { - * mykey: true - * }); - * } - * - * ``` - * - * @return {object} - */ - get defaults() { - return {}; - } + /** + * This getter provides the options. Derived classes overwrite + * this getter with their own values. It is good karma to always include + * the values from the parent class. + * + * ```javascript + * get defaults() { + * return Object.assign({}, super.defaults, { + * mykey: true + * }); + * } + * + * ``` + * + * @return {object} + */ + get defaults() { + return {}; + } - /** - * nested options can be specified by path `a.b.c` - * - * @param {string} path - * @param {*} defaultValue - * @return {*} - * @since 1.10.0 - */ - getOption(path, defaultValue) { - let value; + /** + * nested options can be specified by path `a.b.c` + * + * @param {string} path + * @param {*} defaultValue + * @return {*} + * @since 1.10.0 + */ + getOption(path, defaultValue) { + let value; - try { - value = new Pathfinder(this[internalSymbol]).getVia(path); - } catch (e) {} + try { + value = new Pathfinder(this[internalSymbol]).getVia(path); + } catch (e) {} - if (value === undefined) return defaultValue; - return value; - } + if (value === undefined) return defaultValue; + return value; + } } diff --git a/source/types/binary.mjs b/source/types/binary.mjs index 9231a5972..26835add6 100644 --- a/source/types/binary.mjs +++ b/source/types/binary.mjs @@ -20,19 +20,19 @@ export { toBinary, fromBinary }; * @since 1.18.0 */ function toBinary(string) { - const codeUnits = new Uint16Array(validateString(string).length); - for (let i = 0; i < codeUnits.length; i++) { - codeUnits[i] = string.charCodeAt(i); - } + const codeUnits = new Uint16Array(validateString(string).length); + for (let i = 0; i < codeUnits.length; i++) { + codeUnits[i] = string.charCodeAt(i); + } - const charCodes = new Uint8Array(codeUnits.buffer); - let result = ""; + const charCodes = new Uint8Array(codeUnits.buffer); + let result = ""; - for (let i = 0; i < charCodes.byteLength; i++) { - result += String.fromCharCode(charCodes[i]); - } + for (let i = 0; i < charCodes.byteLength; i++) { + result += String.fromCharCode(charCodes[i]); + } - return result; + return result; } /** @@ -46,14 +46,14 @@ function toBinary(string) { * @since 1.18.0 */ function fromBinary(binary) { - const bytes = new Uint8Array(validateString(binary).length); - for (let i = 0; i < bytes.length; i++) { - bytes[i] = binary.charCodeAt(i); - } - const charCodes = new Uint16Array(bytes.buffer); - let result = ""; - for (let i = 0; i < charCodes.length; i++) { - result += String.fromCharCode(charCodes[i]); - } - return result; + const bytes = new Uint8Array(validateString(binary).length); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + const charCodes = new Uint16Array(bytes.buffer); + let result = ""; + for (let i = 0; i < charCodes.length; i++) { + result += String.fromCharCode(charCodes[i]); + } + return result; } diff --git a/source/types/dataurl.mjs b/source/types/dataurl.mjs index b35d0eba9..7d73d5057 100644 --- a/source/types/dataurl.mjs +++ b/source/types/dataurl.mjs @@ -8,7 +8,11 @@ import { Base } from "./base.mjs"; import { isString } from "./is.mjs"; import { MediaType, parseMediaType } from "./mediatype.mjs"; -import { validateBoolean, validateInstance, validateString } from "./validate.mjs"; +import { + validateBoolean, + validateInstance, + validateString, +} from "./validate.mjs"; import { instanceSymbol } from "../constants.mjs"; export { DataUrl, parseDataURL }; @@ -29,59 +33,61 @@ const internal = Symbol("internal"); * @see https://datatracker.ietf.org/doc/html/rfc2397 */ class DataUrl extends Base { - /** - * - * @param {String} content - * @param {String|Monster.Types.MediaType} mediatype - * @param {boolean} base64=true - */ - constructor(content, mediatype, base64) { - super(); - - if (isString(mediatype)) { - mediatype = parseMediaType(mediatype); - } - - this[internal] = { - content: validateString(content), - mediatype: validateInstance(mediatype, MediaType), - base64: validateBoolean(base64 === undefined ? true : base64), - }; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/data-url"); - } - - get content() { - return this[internal].base64 ? atob(this[internal].content) : this[internal].content; - } - - get mediatype() { - return this[internal].mediatype; - } - - /** - * - * @return {string} - * @see https://datatracker.ietf.org/doc/html/rfc2397 - */ - toString() { - let content = this[internal].content; - - if (this[internal].base64 === true) { - content = `;base64,${content}`; - } else { - content = `,${encodeURIComponent(content)}`; - } - - return `data:${this[internal].mediatype.toString()}${content}`; - } + /** + * + * @param {String} content + * @param {String|Monster.Types.MediaType} mediatype + * @param {boolean} base64=true + */ + constructor(content, mediatype, base64) { + super(); + + if (isString(mediatype)) { + mediatype = parseMediaType(mediatype); + } + + this[internal] = { + content: validateString(content), + mediatype: validateInstance(mediatype, MediaType), + base64: validateBoolean(base64 === undefined ? true : base64), + }; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/data-url"); + } + + get content() { + return this[internal].base64 + ? atob(this[internal].content) + : this[internal].content; + } + + get mediatype() { + return this[internal].mediatype; + } + + /** + * + * @return {string} + * @see https://datatracker.ietf.org/doc/html/rfc2397 + */ + toString() { + let content = this[internal].content; + + if (this[internal].base64 === true) { + content = `;base64,${content}`; + } else { + content = `,${encodeURIComponent(content)}`; + } + + return `data:${this[internal].mediatype.toString()}${content}`; + } } /** @@ -105,40 +111,40 @@ class DataUrl extends Base { * @memberOf Monster.Types */ function parseDataURL(dataurl) { - validateString(dataurl); - - dataurl = dataurl.trim(); - - if (dataurl.substring(0, 5) !== "data:") { - throw new TypeError("incorrect or missing data protocol"); - } - - dataurl = dataurl.substring(5); - - let p = dataurl.indexOf(","); - if (p === -1) { - throw new TypeError("malformed data url"); - } - - let content = dataurl.substring(p + 1); - let mediatypeAndBase64 = dataurl.substring(0, p).trim(); - let mediatype = "text/plain;charset=US-ASCII"; - let base64Flag = false; - - if (mediatypeAndBase64 !== "") { - mediatype = mediatypeAndBase64; - if (mediatypeAndBase64.endsWith("base64")) { - let i = mediatypeAndBase64.lastIndexOf(";"); - mediatype = mediatypeAndBase64.substring(0, i); - base64Flag = true; - } else { - content = decodeURIComponent(content); - } - - mediatype = parseMediaType(mediatype); - } else { - content = decodeURIComponent(content); - } - - return new DataUrl(content, mediatype, base64Flag); + validateString(dataurl); + + dataurl = dataurl.trim(); + + if (dataurl.substring(0, 5) !== "data:") { + throw new TypeError("incorrect or missing data protocol"); + } + + dataurl = dataurl.substring(5); + + let p = dataurl.indexOf(","); + if (p === -1) { + throw new TypeError("malformed data url"); + } + + let content = dataurl.substring(p + 1); + let mediatypeAndBase64 = dataurl.substring(0, p).trim(); + let mediatype = "text/plain;charset=US-ASCII"; + let base64Flag = false; + + if (mediatypeAndBase64 !== "") { + mediatype = mediatypeAndBase64; + if (mediatypeAndBase64.endsWith("base64")) { + let i = mediatypeAndBase64.lastIndexOf(";"); + mediatype = mediatypeAndBase64.substring(0, i); + base64Flag = true; + } else { + content = decodeURIComponent(content); + } + + mediatype = parseMediaType(mediatype); + } else { + content = decodeURIComponent(content); + } + + return new DataUrl(content, mediatype, base64Flag); } diff --git a/source/types/global.mjs b/source/types/global.mjs index 7cf2e4beb..7933dc7ea 100644 --- a/source/types/global.mjs +++ b/source/types/global.mjs @@ -5,7 +5,11 @@ * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html */ -import { validateFunction, validateObject, validateString } from "./validate.mjs"; +import { + validateFunction, + validateObject, + validateString, +} from "./validate.mjs"; export { getGlobal, getGlobalObject, getGlobalFunction }; @@ -20,39 +24,39 @@ let globalReference; * @throws {Error} unsupported environment. */ (function () { - if (typeof globalThis === "object") { - globalReference = globalThis; - return; - } + if (typeof globalThis === "object") { + globalReference = globalThis; + return; + } - if (typeof self !== "undefined") { - globalReference = self; - return; - } else if (typeof window !== "undefined") { - globalReference = window; - return; - } + if (typeof self !== "undefined") { + globalReference = self; + return; + } else if (typeof window !== "undefined") { + globalReference = window; + return; + } - Object.defineProperty(Object.prototype, "__monster__", { - get: function () { - return this; - }, - configurable: true, - }); + Object.defineProperty(Object.prototype, "__monster__", { + get: function () { + return this; + }, + configurable: true, + }); - if (typeof __monster__ === "object") { - __monster__.globalThis = __monster__; - delete Object.prototype.__monster__; + if (typeof __monster__ === "object") { + __monster__.globalThis = __monster__; + delete Object.prototype.__monster__; - globalReference = globalThis; - return; - } + globalReference = globalThis; + return; + } - try { - globalReference = Function("return this")(); - } catch (e) {} + try { + globalReference = Function("return this")(); + } catch (e) {} - throw new Error("unsupported environment."); + throw new Error("unsupported environment."); })(); /** @@ -66,7 +70,7 @@ let globalReference; * @returns {objec} globalThis */ function getGlobal() { - return globalReference; + return globalReference; } /** @@ -102,11 +106,12 @@ function getGlobal() { * @throws {TypeError} value is not a string */ function getGlobalObject(name) { - validateString(name); - let o = globalReference?.[name]; - if (typeof o === "undefined") throw new Error(`the object ${name} is not defined`); - validateObject(o); - return o; + validateString(name); + let o = globalReference?.[name]; + if (typeof o === "undefined") + throw new Error(`the object ${name} is not defined`); + validateObject(o); + return o; } /** @@ -140,9 +145,10 @@ function getGlobalObject(name) { * @throws {TypeError} value is not a string */ function getGlobalFunction(name) { - validateString(name); - let f = globalReference?.[name]; - if (typeof f === "undefined") throw new Error(`the function ${name} is not defined`); - validateFunction(f); - return f; + validateString(name); + let f = globalReference?.[name]; + if (typeof f === "undefined") + throw new Error(`the function ${name} is not defined`); + validateFunction(f); + return f; } diff --git a/source/types/id.mjs b/source/types/id.mjs index aefedbaed..0d45221fa 100644 --- a/source/types/id.mjs +++ b/source/types/id.mjs @@ -32,34 +32,34 @@ let internalCounter = new Map(); * @summary Automatic generation of ids */ class ID extends Base { - /** - * create new id with prefix - * - * @param {string} prefix - */ - constructor(prefix) { - super(); + /** + * create new id with prefix + * + * @param {string} prefix + */ + constructor(prefix) { + super(); - if (prefix === undefined) { - prefix = "id"; - } + if (prefix === undefined) { + prefix = "id"; + } - validateString(prefix); + validateString(prefix); - if (!internalCounter.has(prefix)) { - internalCounter.set(prefix, 1); - } + if (!internalCounter.has(prefix)) { + internalCounter.set(prefix, 1); + } - let count = internalCounter.get(prefix); - this.id = prefix + count; + let count = internalCounter.get(prefix); + this.id = prefix + count; - internalCounter.set(prefix, ++count); - } + internalCounter.set(prefix, ++count); + } - /** - * @return {string} - */ - toString() { - return this.id; - } + /** + * @return {string} + */ + toString() { + return this.id; + } } diff --git a/source/types/internal.mjs b/source/types/internal.mjs index 323b4068e..f8840d069 100644 --- a/source/types/internal.mjs +++ b/source/types/internal.mjs @@ -38,97 +38,99 @@ const propertyName = "internalDefaults"; * @memberOf Monster.Types */ function equipWithInternal() { - const self = this; - validateObject(self); - - if (!hasGetter(self, propertyName)) { - Object.defineProperty(self, propertyName, { - get: function () { - return {}; - }, - }); - } - - const defaults = extend({}, self[propertyName] || {}); - self[internalSymbol] = new ProxyObserver(defaults); - - /** - * Attach a new observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - self["attachInternalObserver"] = (observer) => { - self[internalSymbol].attachObserver(observer); - return self; - }; - - /** - * Detach a observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - self["detachInternalObserver"] = (observer) => { - self[internalSymbol].detachObserver(observer); - return self; - }; - - /** - * Check if a observer is attached - * - * @param {Observer} observer - * @returns {boolean} - */ - self["containsInternalObserver"] = (observer) => { - return self[internalSymbol].containsObserver(observer); - }; - - /** - * Set an internal value, nested internals can be specified by path `a.b.c` - * - * @param {string} path - * @param {*} value - * @return {Datasource} - */ - self["setInternal"] = (path, value) => { - new Pathfinder(self[internalSymbol].getSubject()).setVia(path, value); - return self; - }; - - /** - * set multiple internals at once - * - * @param {string|object} options - * @return {Datasource} - * @throws {Error} the options does not contain a valid json definition - */ - self["setInternals"] = (options) => { - if (isString(options)) { - options = parseOptionsJSON(options); - } - - extend(self[internalSymbol].getSubject(), defaults, options); - return self; - }; - - /** - * nested internals can be specified by path `a.b.c` - * - * @param {string} path - * @param {*} defaultValue - * @return {*} - */ - self["getInternal"] = (path, defaultValue) => { - let value; - - try { - value = new Pathfinder(self[internalSymbol].getRealSubject()).getVia(path); - } catch (e) {} - - if (value === undefined) return defaultValue; - return value; - }; + const self = this; + validateObject(self); + + if (!hasGetter(self, propertyName)) { + Object.defineProperty(self, propertyName, { + get: function () { + return {}; + }, + }); + } + + const defaults = extend({}, self[propertyName] || {}); + self[internalSymbol] = new ProxyObserver(defaults); + + /** + * Attach a new observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + self["attachInternalObserver"] = (observer) => { + self[internalSymbol].attachObserver(observer); + return self; + }; + + /** + * Detach a observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + self["detachInternalObserver"] = (observer) => { + self[internalSymbol].detachObserver(observer); + return self; + }; + + /** + * Check if a observer is attached + * + * @param {Observer} observer + * @returns {boolean} + */ + self["containsInternalObserver"] = (observer) => { + return self[internalSymbol].containsObserver(observer); + }; + + /** + * Set an internal value, nested internals can be specified by path `a.b.c` + * + * @param {string} path + * @param {*} value + * @return {Datasource} + */ + self["setInternal"] = (path, value) => { + new Pathfinder(self[internalSymbol].getSubject()).setVia(path, value); + return self; + }; + + /** + * set multiple internals at once + * + * @param {string|object} options + * @return {Datasource} + * @throws {Error} the options does not contain a valid json definition + */ + self["setInternals"] = (options) => { + if (isString(options)) { + options = parseOptionsJSON(options); + } + + extend(self[internalSymbol].getSubject(), defaults, options); + return self; + }; + + /** + * nested internals can be specified by path `a.b.c` + * + * @param {string} path + * @param {*} defaultValue + * @return {*} + */ + self["getInternal"] = (path, defaultValue) => { + let value; + + try { + value = new Pathfinder(self[internalSymbol].getRealSubject()).getVia( + path, + ); + } catch (e) {} + + if (value === undefined) return defaultValue; + return value; + }; } /** @@ -138,14 +140,14 @@ function equipWithInternal() { * @return {boolean} */ function hasGetter(obj, prop) { - while (isObject(obj)) { - if (Object.getOwnPropertyDescriptor(obj, prop)?.["get"]) { - return true; - } - obj = Object.getPrototypeOf(obj); - } - - return false; + while (isObject(obj)) { + if (Object.getOwnPropertyDescriptor(obj, prop)?.["get"]) { + return true; + } + obj = Object.getPrototypeOf(obj); + } + + return false; } /** @@ -154,23 +156,23 @@ function hasGetter(obj, prop) { * @return {Object} */ function parseOptionsJSON(data) { - let obj = {}; + let obj = {}; - if (!isString(data)) { - return obj; - } + if (!isString(data)) { + return obj; + } - // the configuration can be specified as a data url. - try { - let dataUrl = parseDataURL(data); - data = dataUrl.content; - } catch (e) {} + // the configuration can be specified as a data url. + try { + let dataUrl = parseDataURL(data); + data = dataUrl.content; + } catch (e) {} - try { - obj = JSON.parse(data); - } catch (e) { - throw e; - } + try { + obj = JSON.parse(data); + } catch (e) { + throw e; + } - return validateObject(obj); + return validateObject(obj); } diff --git a/source/types/is.mjs b/source/types/is.mjs index 7a22e28e1..7e183130c 100644 --- a/source/types/is.mjs +++ b/source/types/is.mjs @@ -5,7 +5,18 @@ * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html */ -export { isIterable, isPrimitive, isSymbol, isBoolean, isString, isObject, isInstance, isArray, isFunction, isInteger }; +export { + isIterable, + isPrimitive, + isSymbol, + isBoolean, + isString, + isObject, + isInstance, + isArray, + isFunction, + isInteger, +}; /** * With this function you can check if a value is iterable. @@ -23,9 +34,9 @@ export { isIterable, isPrimitive, isSymbol, isBoolean, isString, isObject, isIns * @memberOf Monster.Types */ function isIterable(value) { - if (value === undefined) return false; - if (value === null) return false; - return typeof value?.[Symbol.iterator] === "function"; + if (value === undefined) return false; + if (value === null) return false; + return typeof value?.[Symbol.iterator] === "function"; } /** @@ -42,19 +53,24 @@ function isIterable(value) { * @memberOf Monster.Types */ function isPrimitive(value) { - var type; + var type; - if (value === undefined || value === null) { - return true; - } + if (value === undefined || value === null) { + return true; + } - type = typeof value; + type = typeof value; - if (type === "string" || type === "number" || type === "boolean" || type === "symbol") { - return true; - } + if ( + type === "string" || + type === "number" || + type === "boolean" || + type === "symbol" + ) { + return true; + } - return false; + return false; } /** @@ -71,7 +87,7 @@ function isPrimitive(value) { * @memberOf Monster.Types */ function isSymbol(value) { - return "symbol" === typeof value ? true : false; + return "symbol" === typeof value ? true : false; } /** @@ -88,11 +104,11 @@ function isSymbol(value) { * @memberOf Monster.Types */ function isBoolean(value) { - if (value === true || value === false) { - return true; - } + if (value === true || value === false) { + return true; + } - return false; + return false; } /** @@ -109,10 +125,10 @@ function isBoolean(value) { * @memberOf Monster.Types */ function isString(value) { - if (value === undefined || typeof value !== "string") { - return false; - } - return true; + if (value === undefined || typeof value !== "string") { + return false; + } + return true; } /** @@ -129,14 +145,14 @@ function isString(value) { * @memberOf Monster.Types */ function isObject(value) { - if (isArray(value)) return false; - if (isPrimitive(value)) return false; + if (isArray(value)) return false; + if (isPrimitive(value)) return false; - if (typeof value === "object") { - return true; - } + if (typeof value === "object") { + return true; + } - return false; + return false; } /** @@ -154,10 +170,10 @@ function isObject(value) { * @memberOf Monster.Types */ function isInstance(value, instance) { - if (!isObject(value)) return false; - if (!isFunction(instance)) return false; - if (!instance.hasOwnProperty("prototype")) return false; - return value instanceof instance ? true : false; + if (!isObject(value)) return false; + if (!isFunction(instance)) return false; + if (!instance.hasOwnProperty("prototype")) return false; + return value instanceof instance ? true : false; } /** @@ -175,7 +191,7 @@ function isInstance(value, instance) { * @see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray */ function isArray(value) { - return Array.isArray(value); + return Array.isArray(value); } /** @@ -192,14 +208,14 @@ function isArray(value) { * @memberOf Monster.Types */ function isFunction(value) { - if (isArray(value)) return false; - if (isPrimitive(value)) return false; + if (isArray(value)) return false; + if (isPrimitive(value)) return false; - if (typeof value === "function") { - return true; - } + if (typeof value === "function") { + return true; + } - return false; + return false; } /** @@ -216,5 +232,5 @@ function isFunction(value) { * @memberOf Monster.Types */ function isInteger(value) { - return Number.isInteger(value); + return Number.isInteger(value); } diff --git a/source/types/mediatype.mjs b/source/types/mediatype.mjs index efde823e2..e4f855454 100644 --- a/source/types/mediatype.mjs +++ b/source/types/mediatype.mjs @@ -33,92 +33,92 @@ const internal = Symbol("internal"); * @memberOf Monster.Types */ class MediaType extends Base { - /** - * - * @param {String} type - * @param {String} subtype - * @param {Monster.Types.Parameter[]} parameter - */ - constructor(type, subtype, parameter) { - super(); - - this[internal] = { - type: validateString(type).toLowerCase(), - subtype: validateString(subtype).toLowerCase(), - parameter: [], - }; - - if (parameter !== undefined) { - this[internal]["parameter"] = validateArray(parameter); - } - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/media-type"); - } - - /** - * @return {String} - */ - get type() { - return this[internal].type; - } - - /** - * @return {String} - */ - get subtype() { - return this[internal].subtype; - } - - /** - * @return {Monster.Types.Parameter[]} - */ - get parameter() { - return this[internal].parameter; - } - - /** - * - * - * @return {Map} - */ - get parameter() { - const result = new Map(); - - this[internal]["parameter"].forEach((p) => { - let value = p.value; - - // internally special values are partly stored with quotes, this function removes them. - if (value.startsWith('"') && value.endsWith('"')) { - value = value.substring(1, value.length - 1); - } - - result.set(p.key, value); - }); - - return result; - } - - /** - * - * @return {string} - */ - toString() { - let parameter = []; - for (let a of this[internal].parameter) { - parameter.push(`${a.key}=${a.value}`); - } - - return `${this[internal].type}/${this[internal].subtype}${ - parameter.length > 0 ? `;${parameter.join(";")}` : "" - }`; - } + /** + * + * @param {String} type + * @param {String} subtype + * @param {Monster.Types.Parameter[]} parameter + */ + constructor(type, subtype, parameter) { + super(); + + this[internal] = { + type: validateString(type).toLowerCase(), + subtype: validateString(subtype).toLowerCase(), + parameter: [], + }; + + if (parameter !== undefined) { + this[internal]["parameter"] = validateArray(parameter); + } + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/media-type"); + } + + /** + * @return {String} + */ + get type() { + return this[internal].type; + } + + /** + * @return {String} + */ + get subtype() { + return this[internal].subtype; + } + + /** + * @return {Monster.Types.Parameter[]} + */ + get parameter() { + return this[internal].parameter; + } + + /** + * + * + * @return {Map} + */ + get parameter() { + const result = new Map(); + + this[internal]["parameter"].forEach((p) => { + let value = p.value; + + // internally special values are partly stored with quotes, this function removes them. + if (value.startsWith('"') && value.endsWith('"')) { + value = value.substring(1, value.length - 1); + } + + result.set(p.key, value); + }); + + return result; + } + + /** + * + * @return {string} + */ + toString() { + let parameter = []; + for (let a of this[internal].parameter) { + parameter.push(`${a.key}=${a.value}`); + } + + return `${this[internal].type}/${this[internal].subtype}${ + parameter.length > 0 ? `;${parameter.join(";")}` : "" + }`; + } } /** @@ -158,24 +158,24 @@ class MediaType extends Base { * @memberOf Monster.Types */ function parseMediaType(mediatype) { - const regex = - /(?<type>[A-Za-z]+|\*)\/(?<subtype>([a-zA-Z0-9.\+_\-]+)|\*|)(?<parameter>\s*;\s*([a-zA-Z0-9]+)\s*(=\s*("?[A-Za-z0-9_\-]+"?))?)*/g; - const result = regex.exec(validateString(mediatype)); + const regex = + /(?<type>[A-Za-z]+|\*)\/(?<subtype>([a-zA-Z0-9.\+_\-]+)|\*|)(?<parameter>\s*;\s*([a-zA-Z0-9]+)\s*(=\s*("?[A-Za-z0-9_\-]+"?))?)*/g; + const result = regex.exec(validateString(mediatype)); - const groups = result?.["groups"]; - if (groups === undefined) { - throw new TypeError("the mimetype can not be parsed"); - } + const groups = result?.["groups"]; + if (groups === undefined) { + throw new TypeError("the mimetype can not be parsed"); + } - const type = groups?.["type"]; - const subtype = groups?.["subtype"]; - const parameter = groups?.["parameter"]; + const type = groups?.["type"]; + const subtype = groups?.["subtype"]; + const parameter = groups?.["parameter"]; - if (subtype === "" || type === "") { - throw new TypeError("blank value is not allowed"); - } + if (subtype === "" || type === "") { + throw new TypeError("blank value is not allowed"); + } - return new MediaType(type, subtype, parseParameter(parameter)); + return new MediaType(type, subtype, parseParameter(parameter)); } /** @@ -187,29 +187,29 @@ function parseMediaType(mediatype) { * @memberOf Monster.Types */ function parseParameter(parameter) { - if (!isString(parameter)) { - return undefined; - } + if (!isString(parameter)) { + return undefined; + } - let result = []; + let result = []; - parameter.split(";").forEach((entry) => { - entry = entry.trim(); - if (entry === "") { - return; - } + parameter.split(";").forEach((entry) => { + entry = entry.trim(); + if (entry === "") { + return; + } - const kv = entry.split("="); + const kv = entry.split("="); - let key = validateString(kv?.[0]).trim(); - let value = validateString(kv?.[1]).trim(); + let key = validateString(kv?.[0]).trim(); + let value = validateString(kv?.[1]).trim(); - // if values are quoted, they remain so internally - result.push({ - key: key, - value: value, - }); - }); + // if values are quoted, they remain so internally + result.push({ + key: key, + value: value, + }); + }); - return result; + return result; } diff --git a/source/types/node.mjs b/source/types/node.mjs index 7a90e892f..ebc2be2b2 100644 --- a/source/types/node.mjs +++ b/source/types/node.mjs @@ -36,144 +36,153 @@ const treeStructureSymbol = Symbol("treeStructure"); * @see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Iteration_protocols */ class Node extends Base { - /** - * @param {*} [value] - */ - constructor(value) { - super(); - this[internalValueSymbol] = value; - - this[treeStructureSymbol] = { - parent: null, - childNodes: new NodeList(), - level: 0, - }; - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/node"); - } - - /** - * @property {*} - */ - get value() { - return this[internalValueSymbol]; - } - - /** - * @property {*} - */ - set value(value) { - this[internalValueSymbol] = value; - } - - /** - * @property {Monster.Types.Node|null} - */ - get parent() { - return this[treeStructureSymbol].parent; - } - - /** - * @property {integer} - */ - get level() { - return this[treeStructureSymbol].level; - } - - /** - * - * @property {NodeList} - */ - get childNodes() { - return this[treeStructureSymbol].childNodes; - } - - /** - * - * @property {NodeList} - */ - set childNodes(childNodes) { - this[treeStructureSymbol].childNodes = validateInstance(childNodes, NodeList); - setChildLevelAndParent.call(this, this, 1); - } - - /** - * @return {Monster.Types.Node} - * @param {Node} node - */ - appendChild(node) { - this[treeStructureSymbol].childNodes.add(validateInstance(node, Node)); - node[treeStructureSymbol].parent = this; - - node[treeStructureSymbol].level = this.level + 1; - setChildLevelAndParent.call(this, node, 1); - return this; - } - - /** - * @return {Monster.Types.Node} - * @param {Node} node - */ - removeChild(node) { - this[treeStructureSymbol].childNodes.remove(validateInstance(node, Node)); - node[treeStructureSymbol].parent = null; - - node[treeStructureSymbol].level = 0; - setChildLevelAndParent.call(this, node, -1); - return this; - } - - /** - * - * @return {boolean} - */ - hasChildNodes() { - return this[treeStructureSymbol].childNodes.length > 0; - } - - /** - * @return {Monster.Types.Node} - * @param {Node} node - */ - hasChild(node) { - return this[treeStructureSymbol].childNodes.has(validateInstance(node, Node)); - } - - /** - * @since 1.28.0 - * @return {string} - */ - toString() { - let parts = []; - if (this[internalValueSymbol]) { - let label = this[internalValueSymbol]; - if (!isPrimitive(label)) label = JSON.stringify(this[internalValueSymbol]); - - parts.push(label); - } - - if (!this.hasChildNodes()) { - return parts.join("\n"); - } - - let count = this.childNodes.length; - let counter = 0; - - for (const node of this.childNodes) { - counter++; - const prefix = (count === counter ? "└" : "├").padStart(2 * node.level, " |"); - parts.push(prefix + node.toString()); - } - - return parts.join("\n"); - } + /** + * @param {*} [value] + */ + constructor(value) { + super(); + this[internalValueSymbol] = value; + + this[treeStructureSymbol] = { + parent: null, + childNodes: new NodeList(), + level: 0, + }; + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/node"); + } + + /** + * @property {*} + */ + get value() { + return this[internalValueSymbol]; + } + + /** + * @property {*} + */ + set value(value) { + this[internalValueSymbol] = value; + } + + /** + * @property {Monster.Types.Node|null} + */ + get parent() { + return this[treeStructureSymbol].parent; + } + + /** + * @property {integer} + */ + get level() { + return this[treeStructureSymbol].level; + } + + /** + * + * @property {NodeList} + */ + get childNodes() { + return this[treeStructureSymbol].childNodes; + } + + /** + * + * @property {NodeList} + */ + set childNodes(childNodes) { + this[treeStructureSymbol].childNodes = validateInstance( + childNodes, + NodeList, + ); + setChildLevelAndParent.call(this, this, 1); + } + + /** + * @return {Monster.Types.Node} + * @param {Node} node + */ + appendChild(node) { + this[treeStructureSymbol].childNodes.add(validateInstance(node, Node)); + node[treeStructureSymbol].parent = this; + + node[treeStructureSymbol].level = this.level + 1; + setChildLevelAndParent.call(this, node, 1); + return this; + } + + /** + * @return {Monster.Types.Node} + * @param {Node} node + */ + removeChild(node) { + this[treeStructureSymbol].childNodes.remove(validateInstance(node, Node)); + node[treeStructureSymbol].parent = null; + + node[treeStructureSymbol].level = 0; + setChildLevelAndParent.call(this, node, -1); + return this; + } + + /** + * + * @return {boolean} + */ + hasChildNodes() { + return this[treeStructureSymbol].childNodes.length > 0; + } + + /** + * @return {Monster.Types.Node} + * @param {Node} node + */ + hasChild(node) { + return this[treeStructureSymbol].childNodes.has( + validateInstance(node, Node), + ); + } + + /** + * @since 1.28.0 + * @return {string} + */ + toString() { + let parts = []; + if (this[internalValueSymbol]) { + let label = this[internalValueSymbol]; + if (!isPrimitive(label)) + label = JSON.stringify(this[internalValueSymbol]); + + parts.push(label); + } + + if (!this.hasChildNodes()) { + return parts.join("\n"); + } + + let count = this.childNodes.length; + let counter = 0; + + for (const node of this.childNodes) { + counter++; + const prefix = (count === counter ? "└" : "├").padStart( + 2 * node.level, + " |", + ); + parts.push(prefix + node.toString()); + } + + return parts.join("\n"); + } } /** @@ -183,16 +192,17 @@ class Node extends Base { * @return {setChildLevelAndParent} */ function setChildLevelAndParent(node, operand) { - const self = this; - - if (node !== this) { - node[treeStructureSymbol].parent = this; - } - - node[treeStructureSymbol].childNodes.forEach(function (child) { - child[treeStructureSymbol].parent = node; - child[treeStructureSymbol].level = node[treeStructureSymbol].level + operand; - setChildLevelAndParent.call(self, child, operand); - }); - return this; + const self = this; + + if (node !== this) { + node[treeStructureSymbol].parent = this; + } + + node[treeStructureSymbol].childNodes.forEach(function (child) { + child[treeStructureSymbol].parent = node; + child[treeStructureSymbol].level = + node[treeStructureSymbol].level + operand; + setChildLevelAndParent.call(self, child, operand); + }); + return this; } diff --git a/source/types/nodelist.mjs b/source/types/nodelist.mjs index 131ee5620..5d7a8635c 100644 --- a/source/types/nodelist.mjs +++ b/source/types/nodelist.mjs @@ -21,100 +21,100 @@ export { NodeList }; * @summary A NodeList class */ class NodeList extends Set { - /** - * @throws {Error} invalid value type - * @param {NodeList|Node|Array<Node>}values - */ - constructor(values) { - super(); - - const self = this; - - if (values === undefined) return; - - if (isArray(values)) { - values.forEach((value) => self.add(value)); - } else if (isInstance(values, NodeList)) { - values.forEach((value) => self.add(value)); - } else if (isInstance(values, Node)) { - self.add(values); - } else { - throw new Error("invalid value type"); - } - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/node-list"); - } - - /** - * - * @param {Node} node - * @return {Monster.Types.NodeList} - */ - add(node) { - super.add(validateInstance(node, Node)); - return this; - } - - /** - * @param {Node} node - * @returns {NodeList} - */ - remove(node) { - super.delete(validateInstance(node, Node)); - return this; - } - - /** - * @param {Node} node - * @returns {boolean} - */ - has(node) { - return super.has(validateInstance(node, Node)); - } - - /** - * @returns {NodeList} - */ - clear() { - super.clear(); - return this; - } - - /** - * @returns {NodeList} - */ - toArray() { - return Array.from(this); - } - - /** - * @returns {NodeList} - */ - toJSON() { - return this.toArray(); - } - - /** - * @returns {NodeList} - */ - toString() { - let parts = []; - - for (const node of this.toArray()) { - parts.push(node.toString()); - } - - return parts.join("\n"); - } - - get length() { - return super.size; - } + /** + * @throws {Error} invalid value type + * @param {NodeList|Node|Array<Node>}values + */ + constructor(values) { + super(); + + const self = this; + + if (values === undefined) return; + + if (isArray(values)) { + values.forEach((value) => self.add(value)); + } else if (isInstance(values, NodeList)) { + values.forEach((value) => self.add(value)); + } else if (isInstance(values, Node)) { + self.add(values); + } else { + throw new Error("invalid value type"); + } + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/node-list"); + } + + /** + * + * @param {Node} node + * @return {Monster.Types.NodeList} + */ + add(node) { + super.add(validateInstance(node, Node)); + return this; + } + + /** + * @param {Node} node + * @returns {NodeList} + */ + remove(node) { + super.delete(validateInstance(node, Node)); + return this; + } + + /** + * @param {Node} node + * @returns {boolean} + */ + has(node) { + return super.has(validateInstance(node, Node)); + } + + /** + * @returns {NodeList} + */ + clear() { + super.clear(); + return this; + } + + /** + * @returns {NodeList} + */ + toArray() { + return Array.from(this); + } + + /** + * @returns {NodeList} + */ + toJSON() { + return this.toArray(); + } + + /** + * @returns {NodeList} + */ + toString() { + let parts = []; + + for (const node of this.toArray()) { + parts.push(node.toString()); + } + + return parts.join("\n"); + } + + get length() { + return super.size; + } } diff --git a/source/types/noderecursiveiterator.mjs b/source/types/noderecursiveiterator.mjs index 900f01e49..f1673f30f 100644 --- a/source/types/noderecursiveiterator.mjs +++ b/source/types/noderecursiveiterator.mjs @@ -32,63 +32,63 @@ const isNodeListSymbol = Symbol("isNodeList"); * @summary An iterator to run recursively through a tree of nodes */ class NodeRecursiveIterator extends Base { - /** - * @param {Node} [data] - */ - constructor(node) { - super(); + /** + * @param {Node} [data] + */ + constructor(node) { + super(); - this[isNodeListSymbol] = false; + this[isNodeListSymbol] = false; - // iterator is a nodelist - if (isInstance(node, NodeList)) { - let children = node; - node = new Node(); - node.childNodes = children; - this[isNodeListSymbol] = true; - } + // iterator is a nodelist + if (isInstance(node, NodeList)) { + let children = node; + node = new Node(); + node.childNodes = children; + this[isNodeListSymbol] = true; + } - this[internalSymbol] = validateInstance(node, Node); - } + this[internalSymbol] = validateInstance(node, Node); + } - /** - * @private - * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield - */ - [Symbol.iterator] = function* () { - /** - * The end of the generator function is reached. In this case, execution of the generator - * ends and an IteratorResult is returned to the caller in which the value is undefined and done is true. - * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield - */ - if (this[internalSymbol] === undefined) { - return; - } + /** + * @private + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield + */ + [Symbol.iterator] = function* () { + /** + * The end of the generator function is reached. In this case, execution of the generator + * ends and an IteratorResult is returned to the caller in which the value is undefined and done is true. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield + */ + if (this[internalSymbol] === undefined) { + return; + } - // iterator is a nodelist and the main node is only a placeholder - if (this[isNodeListSymbol] !== true) { - yield this[internalSymbol]; - } + // iterator is a nodelist and the main node is only a placeholder + if (this[isNodeListSymbol] !== true) { + yield this[internalSymbol]; + } - if (this[internalSymbol].hasChildNodes()) { - let childNodes = this[internalSymbol].childNodes; + if (this[internalSymbol].hasChildNodes()) { + let childNodes = this[internalSymbol].childNodes; - for (let node of childNodes) { - yield* new NodeRecursiveIterator(node); - } - } + for (let node of childNodes) { + yield* new NodeRecursiveIterator(node); + } + } - return; - }; + return; + }; - /** - * @param {function} callback - * @return {Monster.Types.NodeRecursiveIterator} - */ - forEach(callback) { - for (const node of this) { - callback(node); - } - return this; - } + /** + * @param {function} callback + * @return {Monster.Types.NodeRecursiveIterator} + */ + forEach(callback) { + for (const node of this) { + callback(node); + } + return this; + } } diff --git a/source/types/observablequeue.mjs b/source/types/observablequeue.mjs index 19b2d99a9..93e2151d9 100644 --- a/source/types/observablequeue.mjs +++ b/source/types/observablequeue.mjs @@ -24,84 +24,84 @@ export { ObservableQueue }; * @summary An observable Queue (Fifo) */ class ObservableQueue extends Queue { - /** - * - */ - constructor() { - super(); - this[internalSymbol] = { - observers: new ObserverList(), - }; - } + /** + * + */ + constructor() { + super(); + this[internalSymbol] = { + observers: new ObserverList(), + }; + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/observablequeue"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/observablequeue"); + } - /** - * Add a new element to the end of the queue. - * - * @param {*} value - * @returns {Queue} - */ - add(value) { - super.add(value); - this.notifyObservers(); - return this; - } + /** + * Add a new element to the end of the queue. + * + * @param {*} value + * @returns {Queue} + */ + add(value) { + super.add(value); + this.notifyObservers(); + return this; + } - /** - * remove all entries - * - * @returns {Queue} - */ - clear() { - super.clear(); - this.notifyObservers(); - return this; - } + /** + * remove all entries + * + * @returns {Queue} + */ + clear() { + super.clear(); + this.notifyObservers(); + return this; + } - /** - * Attach a new observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - attachObserver(observer) { - this[internalSymbol].observers.attach(observer); - return this; - } + /** + * Attach a new observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + attachObserver(observer) { + this[internalSymbol].observers.attach(observer); + return this; + } - /** - * Detach a observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - detachObserver(observer) { - this[internalSymbol].observers.detach(observer); - return this; - } + /** + * Detach a observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + detachObserver(observer) { + this[internalSymbol].observers.detach(observer); + return this; + } - /** - * Notify all observer - * - * @returns {Promise} - */ - notifyObservers() { - return this[internalSymbol].observers.notify(this); - } + /** + * Notify all observer + * + * @returns {Promise} + */ + notifyObservers() { + return this[internalSymbol].observers.notify(this); + } - /** - * @param {Observer} observer - * @returns {boolean} - */ - containsObserver(observer) { - return this[internalSymbol].observers.contains(observer); - } + /** + * @param {Observer} observer + * @returns {boolean} + */ + containsObserver(observer) { + return this[internalSymbol].observers.contains(observer); + } } diff --git a/source/types/observer.mjs b/source/types/observer.mjs index 6c9cd78c8..ab1ccee08 100644 --- a/source/types/observer.mjs +++ b/source/types/observer.mjs @@ -45,108 +45,108 @@ export { Observer }; * @memberOf Monster.Types */ class Observer extends Base { - /** - * - * @param {function} callback - * @param {*} args - */ - constructor(callback, ...args) { - super(); - - if (typeof callback !== "function") { - throw new Error("observer callback must be a function"); - } - - this.callback = callback; - this.arguments = args; - this.tags = new TokenList(); - this.queue = new UniqueQueue(); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/observer"); - } - - /** - * - * @param {string} tag - * @returns {Observer} - */ - addTag(tag) { - this.tags.add(tag); - return this; - } - - /** - * - * @param {string} tag - * @returns {Observer} - */ - removeTag(tag) { - this.tags.remove(tag); - return this; - } - - /** - * - * @returns {Array} - */ - getTags() { - return this.tags.entries(); - } - - /** - * - * @param {string} tag - * @returns {boolean} - */ - hasTag(tag) { - return this.tags.contains(tag); - } - - /** - * - * @param {object} subject - * @returns {Promise} - */ - update(subject) { - let self = this; - - return new Promise(function (resolve, reject) { - if (!isObject(subject)) { - reject("subject must be an object"); - return; - } - - self.queue.add(subject); - - setTimeout(() => { - try { - // the queue and the settimeout ensure that an object is not - // informed of the same change more than once. - if (self.queue.isEmpty()) { - resolve(); - return; - } - - let s = self.queue.poll(); - let result = self.callback.apply(s, self.arguments); - - if (isObject(result) && result instanceof Promise) { - result.then(resolve).catch(reject); - return; - } - - resolve(result); - } catch (e) { - reject(e); - } - }, 0); - }); - } + /** + * + * @param {function} callback + * @param {*} args + */ + constructor(callback, ...args) { + super(); + + if (typeof callback !== "function") { + throw new Error("observer callback must be a function"); + } + + this.callback = callback; + this.arguments = args; + this.tags = new TokenList(); + this.queue = new UniqueQueue(); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/observer"); + } + + /** + * + * @param {string} tag + * @returns {Observer} + */ + addTag(tag) { + this.tags.add(tag); + return this; + } + + /** + * + * @param {string} tag + * @returns {Observer} + */ + removeTag(tag) { + this.tags.remove(tag); + return this; + } + + /** + * + * @returns {Array} + */ + getTags() { + return this.tags.entries(); + } + + /** + * + * @param {string} tag + * @returns {boolean} + */ + hasTag(tag) { + return this.tags.contains(tag); + } + + /** + * + * @param {object} subject + * @returns {Promise} + */ + update(subject) { + let self = this; + + return new Promise(function (resolve, reject) { + if (!isObject(subject)) { + reject("subject must be an object"); + return; + } + + self.queue.add(subject); + + setTimeout(() => { + try { + // the queue and the settimeout ensure that an object is not + // informed of the same change more than once. + if (self.queue.isEmpty()) { + resolve(); + return; + } + + let s = self.queue.poll(); + let result = self.callback.apply(s, self.arguments); + + if (isObject(result) && result instanceof Promise) { + result.then(resolve).catch(reject); + return; + } + + resolve(result); + } catch (e) { + reject(e); + } + }, 0); + }); + } } diff --git a/source/types/observerlist.mjs b/source/types/observerlist.mjs index 92befe4ee..bcc4a4890 100644 --- a/source/types/observerlist.mjs +++ b/source/types/observerlist.mjs @@ -20,79 +20,79 @@ export { ObserverList }; * @memberOf Monster.Types */ class ObserverList extends Base { - /** - * - */ - constructor() { - super(); - this.observers = []; - } + /** + * + */ + constructor() { + super(); + this.observers = []; + } - /** - * - * @param {Observer} observer - * @return {ObserverList} - * @throws {TypeError} value is not an instance of Observer - */ - attach(observer) { - validateInstance(observer, Observer); + /** + * + * @param {Observer} observer + * @return {ObserverList} + * @throws {TypeError} value is not an instance of Observer + */ + attach(observer) { + validateInstance(observer, Observer); - this.observers.push(observer); - return this; - } + this.observers.push(observer); + return this; + } - /** - * - * @param {Observer} observer - * @return {ObserverList} - * @throws {TypeError} value is not an instance of Observer - */ - detach(observer) { - validateInstance(observer, Observer); + /** + * + * @param {Observer} observer + * @return {ObserverList} + * @throws {TypeError} value is not an instance of Observer + */ + detach(observer) { + validateInstance(observer, Observer); - var i = 0; - var l = this.observers.length; - for (; i < l; i++) { - if (this.observers[i] === observer) { - this.observers.splice(i, 1); - } - } + var i = 0; + var l = this.observers.length; + for (; i < l; i++) { + if (this.observers[i] === observer) { + this.observers.splice(i, 1); + } + } - return this; - } + return this; + } - /** - * - * @param {Observer} observer - * @return {boolean} - * @throws {TypeError} value is not an instance of Observer - */ - contains(observer) { - validateInstance(observer, Observer); - var i = 0; - var l = this.observers.length; - for (; i < l; i++) { - if (this.observers[i] === observer) { - return true; - } - } - return false; - } + /** + * + * @param {Observer} observer + * @return {boolean} + * @throws {TypeError} value is not an instance of Observer + */ + contains(observer) { + validateInstance(observer, Observer); + var i = 0; + var l = this.observers.length; + for (; i < l; i++) { + if (this.observers[i] === observer) { + return true; + } + } + return false; + } - /** - * - * @param subject - * @return {Promise} - */ - notify(subject) { - let pomises = []; + /** + * + * @param subject + * @return {Promise} + */ + notify(subject) { + let pomises = []; - let i = 0; - let l = this.observers.length; - for (; i < l; i++) { - pomises.push(this.observers[i].update(subject)); - } + let i = 0; + let l = this.observers.length; + for (; i < l; i++) { + pomises.push(this.observers[i].update(subject)); + } - return Promise.all(pomises); - } + return Promise.all(pomises); + } } diff --git a/source/types/proxyobserver.mjs b/source/types/proxyobserver.mjs index 8043db6a8..42c19d9e8 100644 --- a/source/types/proxyobserver.mjs +++ b/source/types/proxyobserver.mjs @@ -29,107 +29,107 @@ export { ProxyObserver }; * @memberOf Monster.Types */ class ProxyObserver extends Base { - /** - * - * @param {object} object - * @throws {TypeError} value is not a object - */ - constructor(object) { - super(); - - this.realSubject = validateObject(object); - this.subject = new Proxy(object, getHandler.call(this)); - - this.objectMap = new WeakMap(); - this.objectMap.set(this.realSubject, this.subject); - - this.proxyMap = new WeakMap(); - this.proxyMap.set(this.subject, this.realSubject); - - this.observers = new ObserverList(); - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/proxy-observer"); - } - - /** - * @returns {object} - */ - getSubject() { - return this.subject; - } - - /** - * @since 1.24.0 - * @param {Object} obj - * @return {Monster.Types.ProxyObserver} - */ - setSubject(obj) { - let i; - let k = Object.keys(this.subject); - for (i = 0; i < k.length; i++) { - delete this.subject[k[i]]; - } - - this.subject = extend(this.subject, obj); - return this; - } - - /** - * Get the real object - * - * Changes to this object are not noticed by the observers, so you can make a large number of changes and inform the observers later. - * - * @returns {object} - */ - getRealSubject() { - return this.realSubject; - } - - /** - * attach a new observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - attachObserver(observer) { - this.observers.attach(observer); - return this; - } - - /** - * detach a observer - * - * @param {Observer} observer - * @returns {ProxyObserver} - */ - detachObserver(observer) { - this.observers.detach(observer); - return this; - } - - /** - * notify all observer - * - * @returns {Promise} - */ - notifyObservers() { - return this.observers.notify(this); - } - - /** - * @param {Observer} observer - * @returns {boolean} - */ - containsObserver(observer) { - return this.observers.contains(observer); - } + /** + * + * @param {object} object + * @throws {TypeError} value is not a object + */ + constructor(object) { + super(); + + this.realSubject = validateObject(object); + this.subject = new Proxy(object, getHandler.call(this)); + + this.objectMap = new WeakMap(); + this.objectMap.set(this.realSubject, this.subject); + + this.proxyMap = new WeakMap(); + this.proxyMap.set(this.subject, this.realSubject); + + this.observers = new ObserverList(); + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/proxy-observer"); + } + + /** + * @returns {object} + */ + getSubject() { + return this.subject; + } + + /** + * @since 1.24.0 + * @param {Object} obj + * @return {Monster.Types.ProxyObserver} + */ + setSubject(obj) { + let i; + let k = Object.keys(this.subject); + for (i = 0; i < k.length; i++) { + delete this.subject[k[i]]; + } + + this.subject = extend(this.subject, obj); + return this; + } + + /** + * Get the real object + * + * Changes to this object are not noticed by the observers, so you can make a large number of changes and inform the observers later. + * + * @returns {object} + */ + getRealSubject() { + return this.realSubject; + } + + /** + * attach a new observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + attachObserver(observer) { + this.observers.attach(observer); + return this; + } + + /** + * detach a observer + * + * @param {Observer} observer + * @returns {ProxyObserver} + */ + detachObserver(observer) { + this.observers.detach(observer); + return this; + } + + /** + * notify all observer + * + * @returns {Promise} + */ + notifyObservers() { + return this.observers.notify(this); + } + + /** + * @param {Observer} observer + * @returns {boolean} + */ + containsObserver(observer) { + return this.observers.contains(observer); + } } /** @@ -139,113 +139,113 @@ class ProxyObserver extends Base { * @see {@link https://gitlab.schukai.com/-/snippets/49} */ function getHandler() { - const proxy = this; - - // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots - const handler = { - // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver - get: function (target, key, receiver) { - const value = Reflect.get(target, key, receiver); - - if (typeof key === "symbol") { - return value; - } - - if (isPrimitive(value)) { - return value; - } - - // set value as proxy if object or array - if (isArray(value) || isObject(value)) { - if (proxy.objectMap.has(value)) { - return proxy.objectMap.get(value); - } else if (proxy.proxyMap.has(value)) { - return value; - } else { - let p = new Proxy(value, handler); - proxy.objectMap.set(value, p); - proxy.proxyMap.set(p, value); - return p; - } - } - - return value; - }, - - // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver - set: function (target, key, value, receiver) { - if (proxy.proxyMap.has(value)) { - value = proxy.proxyMap.get(value); - } - - if (proxy.proxyMap.has(target)) { - target = proxy.proxyMap.get(target); - } - - let current = Reflect.get(target, key, receiver); - if (proxy.proxyMap.has(current)) { - current = proxy.proxyMap.get(current); - } - - if (current === value) { - return true; - } - - let result; - let descriptor = Reflect.getOwnPropertyDescriptor(target, key); - - if (descriptor === undefined) { - descriptor = { - writable: true, - enumerable: true, - configurable: true, - }; - } - - descriptor["value"] = value; - result = Reflect.defineProperty(target, key, descriptor); - - if (typeof key !== "symbol") { - proxy.observers.notify(proxy); - } - - return result; - }, - - // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-delete-p - deleteProperty: function (target, key) { - if (key in target) { - delete target[key]; - - if (typeof key !== "symbol") { - proxy.observers.notify(proxy); - } - - return true; - } - return false; - }, - - // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc - defineProperty: function (target, key, descriptor) { - let result = Reflect.defineProperty(target, key, descriptor); - if (typeof key !== "symbol") { - proxy.observers.notify(proxy); - } - return result; - }, - - // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v - setPrototypeOf: function (target, key) { - let result = Reflect.setPrototypeOf(object1, key); - - if (typeof key !== "symbol") { - proxy.observers.notify(proxy); - } - - return result; - }, - }; - - return handler; + const proxy = this; + + // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots + const handler = { + // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver + get: function (target, key, receiver) { + const value = Reflect.get(target, key, receiver); + + if (typeof key === "symbol") { + return value; + } + + if (isPrimitive(value)) { + return value; + } + + // set value as proxy if object or array + if (isArray(value) || isObject(value)) { + if (proxy.objectMap.has(value)) { + return proxy.objectMap.get(value); + } else if (proxy.proxyMap.has(value)) { + return value; + } else { + let p = new Proxy(value, handler); + proxy.objectMap.set(value, p); + proxy.proxyMap.set(p, value); + return p; + } + } + + return value; + }, + + // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver + set: function (target, key, value, receiver) { + if (proxy.proxyMap.has(value)) { + value = proxy.proxyMap.get(value); + } + + if (proxy.proxyMap.has(target)) { + target = proxy.proxyMap.get(target); + } + + let current = Reflect.get(target, key, receiver); + if (proxy.proxyMap.has(current)) { + current = proxy.proxyMap.get(current); + } + + if (current === value) { + return true; + } + + let result; + let descriptor = Reflect.getOwnPropertyDescriptor(target, key); + + if (descriptor === undefined) { + descriptor = { + writable: true, + enumerable: true, + configurable: true, + }; + } + + descriptor["value"] = value; + result = Reflect.defineProperty(target, key, descriptor); + + if (typeof key !== "symbol") { + proxy.observers.notify(proxy); + } + + return result; + }, + + // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-delete-p + deleteProperty: function (target, key) { + if (key in target) { + delete target[key]; + + if (typeof key !== "symbol") { + proxy.observers.notify(proxy); + } + + return true; + } + return false; + }, + + // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + defineProperty: function (target, key, descriptor) { + let result = Reflect.defineProperty(target, key, descriptor); + if (typeof key !== "symbol") { + proxy.observers.notify(proxy); + } + return result; + }, + + // https://262.ecma-international.org/9.0/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v + setPrototypeOf: function (target, key) { + let result = Reflect.setPrototypeOf(object1, key); + + if (typeof key !== "symbol") { + proxy.observers.notify(proxy); + } + + return result; + }, + }; + + return handler; } diff --git a/source/types/queue.mjs b/source/types/queue.mjs index 259122768..ffdeaf0b6 100644 --- a/source/types/queue.mjs +++ b/source/types/queue.mjs @@ -27,74 +27,74 @@ export { Queue }; * @summary A Queue (Fifo) */ class Queue extends Base { - /** - * - */ - constructor() { - super(); - this.data = []; - } + /** + * + */ + constructor() { + super(); + this.data = []; + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/queue"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/queue"); + } - /** - * @return {boolean} - */ - isEmpty() { - return this.data.length === 0; - } + /** + * @return {boolean} + */ + isEmpty() { + return this.data.length === 0; + } - /** - * Read the element at the front of the queue without removing it. - * - * @return {*} - */ - peek() { - if (this.isEmpty()) { - return undefined; - } + /** + * Read the element at the front of the queue without removing it. + * + * @return {*} + */ + peek() { + if (this.isEmpty()) { + return undefined; + } - return this.data[0]; - } + return this.data[0]; + } - /** - * Add a new element to the end of the queue. - * - * @param {*} value - * @returns {Queue} - */ - add(value) { - this.data.push(value); - return this; - } + /** + * Add a new element to the end of the queue. + * + * @param {*} value + * @returns {Queue} + */ + add(value) { + this.data.push(value); + return this; + } - /** - * remove all entries - * - * @returns {Queue} - */ - clear() { - this.data = []; - return this; - } + /** + * remove all entries + * + * @returns {Queue} + */ + clear() { + this.data = []; + return this; + } - /** - * Remove the element at the front of the queue - * If the queue is empty, return undefined. - * - * @return {*} - */ - poll() { - if (this.isEmpty()) { - return undefined; - } - return this.data.shift(); - } + /** + * Remove the element at the front of the queue + * If the queue is empty, return undefined. + * + * @return {*} + */ + poll() { + if (this.isEmpty()) { + return undefined; + } + return this.data.shift(); + } } diff --git a/source/types/randomid.mjs b/source/types/randomid.mjs index 075f1ad6b..b033f12aa 100644 --- a/source/types/randomid.mjs +++ b/source/types/randomid.mjs @@ -27,19 +27,19 @@ let internalCounter = 0; * @summary class to generate random numbers */ class RandomID extends ID { - /** - * create new object - */ - constructor() { - super(); + /** + * create new object + */ + constructor() { + super(); - internalCounter += 1; + internalCounter += 1; - this.id = - getGlobal() - .btoa(random(1, 10000)) - .replace(/=/g, "") - /** No numbers at the beginning of the ID, because of possible problems with DOM */ - .replace(/^[0-9]+/, "X") + internalCounter; - } + this.id = + getGlobal() + .btoa(random(1, 10000)) + .replace(/=/g, "") + /** No numbers at the beginning of the ID, because of possible problems with DOM */ + .replace(/^[0-9]+/, "X") + internalCounter; + } } diff --git a/source/types/regex.mjs b/source/types/regex.mjs index ea10692f5..90d4b74c1 100644 --- a/source/types/regex.mjs +++ b/source/types/regex.mjs @@ -21,5 +21,7 @@ export { escapeString }; * @throws {TypeError} value is not a string */ function escapeString(value) { - return validateString(value).replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); + return validateString(value) + .replace(/[|\\{}()[\]^$+*?.]/g, "\\$&") + .replace(/-/g, "\\x2d"); } diff --git a/source/types/stack.mjs b/source/types/stack.mjs index 6d5a7179c..8958f433a 100644 --- a/source/types/stack.mjs +++ b/source/types/stack.mjs @@ -18,75 +18,75 @@ export { Stack }; * @memberOf Monster.Types */ class Stack extends Base { - /** - * - */ - constructor() { - super(); - this.data = []; - } + /** + * + */ + constructor() { + super(); + this.data = []; + } - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/stack"); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/stack"); + } - /** - * @return {boolean} - */ - isEmpty() { - return this.data.length === 0; - } + /** + * @return {boolean} + */ + isEmpty() { + return this.data.length === 0; + } - /** - * looks at the object at the top of this stack without removing it from the stack. - * - * @return {*} - */ - peek() { - if (this.isEmpty()) { - return undefined; - } + /** + * looks at the object at the top of this stack without removing it from the stack. + * + * @return {*} + */ + peek() { + if (this.isEmpty()) { + return undefined; + } - return this.data?.[this.data.length - 1]; - } + return this.data?.[this.data.length - 1]; + } - /** - * pushes an item onto the top of this stack. - * - * @param {*} value - * @returns {Queue} - */ - push(value) { - this.data.push(value); - return this; - } + /** + * pushes an item onto the top of this stack. + * + * @param {*} value + * @returns {Queue} + */ + push(value) { + this.data.push(value); + return this; + } - /** - * remove all entries - * - * @returns {Queue} - */ - clear() { - this.data = []; - return this; - } + /** + * remove all entries + * + * @returns {Queue} + */ + clear() { + this.data = []; + return this; + } - /** - * removes the object at the top of this stack and returns - * that object as the value of this function. is the stack empty - * the return value is undefined. - * - * @return {*} - */ - pop() { - if (this.isEmpty()) { - return undefined; - } - return this.data.pop(); - } + /** + * removes the object at the top of this stack and returns + * that object as the value of this function. is the stack empty + * the return value is undefined. + * + * @return {*} + */ + pop() { + if (this.isEmpty()) { + return undefined; + } + return this.data.pop(); + } } diff --git a/source/types/tokenlist.mjs b/source/types/tokenlist.mjs index b93c25cfc..0e6a7f194 100644 --- a/source/types/tokenlist.mjs +++ b/source/types/tokenlist.mjs @@ -25,223 +25,223 @@ export { TokenList }; * @memberOf Monster.Types */ class TokenList extends Base { - /** - * - * @param {array|string|iteratable} init - */ - constructor(init) { - super(); - this.tokens = new Set(); + /** + * + * @param {array|string|iteratable} init + */ + constructor(init) { + super(); + this.tokens = new Set(); - if (typeof init !== "undefined") { - this.add(init); - } - } + if (typeof init !== "undefined") { + this.add(init); + } + } - /** - * Iterator protocol - * - * @returns {Symbol.iterator} - */ - getIterator() { - return this[Symbol.iterator](); - } + /** + * Iterator protocol + * + * @returns {Symbol.iterator} + */ + getIterator() { + return this[Symbol.iterator](); + } - /** - * Iterator - * - * @returns {{next: ((function(): ({value: *, done: boolean}))|*)}} - */ - [Symbol.iterator]() { - // Use a new index for each iterator. This makes multiple - // iterations over the iterable safe for non-trivial cases, - // such as use of break or nested looping over the same iterable. - let index = 0; - let entries = this.entries(); + /** + * Iterator + * + * @returns {{next: ((function(): ({value: *, done: boolean}))|*)}} + */ + [Symbol.iterator]() { + // Use a new index for each iterator. This makes multiple + // iterations over the iterable safe for non-trivial cases, + // such as use of break or nested looping over the same iterable. + let index = 0; + let entries = this.entries(); - return { - next: () => { - if (index < entries.length) { - return { value: entries?.[index++], done: false }; - } else { - return { done: true }; - } - }, - }; - } + return { + next: () => { + if (index < entries.length) { + return { value: entries?.[index++], done: false }; + } else { + return { done: true }; + } + }, + }; + } - /** - * Returns true if it contains token, otherwise false - * - * @externalExample ../../example/types/tokenlist-2.mjs - * @param {array|string|iteratable} value - * @returns {boolean} - */ - contains(value) { - if (isString(value)) { - value = value.trim(); - let counter = 0; - value.split(" ").forEach((token) => { - if (this.tokens.has(token.trim()) === false) return false; - counter++; - }); - return counter > 0 ? true : false; - } + /** + * Returns true if it contains token, otherwise false + * + * @externalExample ../../example/types/tokenlist-2.mjs + * @param {array|string|iteratable} value + * @returns {boolean} + */ + contains(value) { + if (isString(value)) { + value = value.trim(); + let counter = 0; + value.split(" ").forEach((token) => { + if (this.tokens.has(token.trim()) === false) return false; + counter++; + }); + return counter > 0 ? true : false; + } - if (isIterable(value)) { - let counter = 0; - for (let token of value) { - validateString(token); - if (this.tokens.has(token.trim()) === false) return false; - counter++; - } - return counter > 0 ? true : false; - } + if (isIterable(value)) { + let counter = 0; + for (let token of value) { + validateString(token); + if (this.tokens.has(token.trim()) === false) return false; + counter++; + } + return counter > 0 ? true : false; + } - return false; - } + return false; + } - /** - * Add tokens - * - * @externalExample ../../example/types/tokenlist-3.mjs - * @param {array|string|iteratable} value - * @returns {TokenList} - * @throws {TypeError} unsupported value - */ - add(value) { - if (isString(value)) { - value.split(" ").forEach((token) => { - this.tokens.add(token.trim()); - }); - } else if (isIterable(value)) { - for (let token of value) { - validateString(token); - this.tokens.add(token.trim()); - } - } else if (typeof value !== "undefined") { - throw new TypeError("unsupported value"); - } + /** + * Add tokens + * + * @externalExample ../../example/types/tokenlist-3.mjs + * @param {array|string|iteratable} value + * @returns {TokenList} + * @throws {TypeError} unsupported value + */ + add(value) { + if (isString(value)) { + value.split(" ").forEach((token) => { + this.tokens.add(token.trim()); + }); + } else if (isIterable(value)) { + for (let token of value) { + validateString(token); + this.tokens.add(token.trim()); + } + } else if (typeof value !== "undefined") { + throw new TypeError("unsupported value"); + } - return this; - } + return this; + } - /** - * remove all tokens - * - * @returns {TokenList} - */ - clear() { - this.tokens.clear(); - return this; - } + /** + * remove all tokens + * + * @returns {TokenList} + */ + clear() { + this.tokens.clear(); + return this; + } - /** - * Removes token - * - * @externalExample ../../example/types/tokenlist-4.mjs - * @param {array|string|iteratable} value - * @returns {TokenList} - * @throws {TypeError} unsupported value - */ - remove(value) { - if (isString(value)) { - value.split(" ").forEach((token) => { - this.tokens.delete(token.trim()); - }); - } else if (isIterable(value)) { - for (let token of value) { - validateString(token); - this.tokens.delete(token.trim()); - } - } else if (typeof value !== "undefined") { - throw new TypeError("unsupported value", "types/tokenlist.mjs"); - } + /** + * Removes token + * + * @externalExample ../../example/types/tokenlist-4.mjs + * @param {array|string|iteratable} value + * @returns {TokenList} + * @throws {TypeError} unsupported value + */ + remove(value) { + if (isString(value)) { + value.split(" ").forEach((token) => { + this.tokens.delete(token.trim()); + }); + } else if (isIterable(value)) { + for (let token of value) { + validateString(token); + this.tokens.delete(token.trim()); + } + } else if (typeof value !== "undefined") { + throw new TypeError("unsupported value", "types/tokenlist.mjs"); + } - return this; - } + return this; + } - /** - * this method replaces a token with a new token. - * - * if the passed token exists, it is replaced with newToken and TokenList is returned. - * if the token does not exist, newToken is not set and TokenList is returned. - * - * @param {string} token - * @param {string} newToken - * @returns {TokenList} - */ - replace(token, newToken) { - validateString(token); - validateString(newToken); - if (!this.contains(token)) { - return this; - } + /** + * this method replaces a token with a new token. + * + * if the passed token exists, it is replaced with newToken and TokenList is returned. + * if the token does not exist, newToken is not set and TokenList is returned. + * + * @param {string} token + * @param {string} newToken + * @returns {TokenList} + */ + replace(token, newToken) { + validateString(token); + validateString(newToken); + if (!this.contains(token)) { + return this; + } - let a = Array.from(this.tokens); - let i = a.indexOf(token); - if (i === -1) return this; + let a = Array.from(this.tokens); + let i = a.indexOf(token); + if (i === -1) return this; - a.splice(i, 1, newToken); - this.tokens = new Set(); - this.add(a); + a.splice(i, 1, newToken); + this.tokens = new Set(); + this.add(a); - return this; - } + return this; + } - /** - * Removes token from string. If token doesn't exist it's added. - * - * @externalExample ../../example/types/tokenlist-5.mjs - * @param {array|string|iteratable} value - * @returns {boolean} - * @throws {TypeError} unsupported value - */ - toggle(value) { - if (isString(value)) { - value.split(" ").forEach((token) => { - toggleValue.call(this, token); - }); - } else if (isIterable(value)) { - for (let token of value) { - toggleValue.call(this, token); - } - } else if (typeof value !== "undefined") { - throw new TypeError("unsupported value", "types/tokenlist.mjs"); - } + /** + * Removes token from string. If token doesn't exist it's added. + * + * @externalExample ../../example/types/tokenlist-5.mjs + * @param {array|string|iteratable} value + * @returns {boolean} + * @throws {TypeError} unsupported value + */ + toggle(value) { + if (isString(value)) { + value.split(" ").forEach((token) => { + toggleValue.call(this, token); + }); + } else if (isIterable(value)) { + for (let token of value) { + toggleValue.call(this, token); + } + } else if (typeof value !== "undefined") { + throw new TypeError("unsupported value", "types/tokenlist.mjs"); + } - return this; - } + return this; + } - /** - * returns an array with all tokens - * - * @returns {array} - */ - entries() { - return Array.from(this.tokens); - } + /** + * returns an array with all tokens + * + * @returns {array} + */ + entries() { + return Array.from(this.tokens); + } - /** - * executes the provided function with each value of the set - * - * @param {function} callback - * @returns {TokenList} - */ - forEach(callback) { - validateFunction(callback); - this.tokens.forEach(callback); - return this; - } + /** + * executes the provided function with each value of the set + * + * @param {function} callback + * @returns {TokenList} + */ + forEach(callback) { + validateFunction(callback); + this.tokens.forEach(callback); + return this; + } - /** - * returns the individual tokens separated by a blank character - * - * @returns {string} - */ - toString() { - return this.entries().join(" "); - } + /** + * returns the individual tokens separated by a blank character + * + * @returns {string} + */ + toString() { + return this.entries().join(" "); + } } /** @@ -251,13 +251,14 @@ class TokenList extends Base { * @throws {Error} must be called with TokenList.call */ function toggleValue(token) { - if (!(this instanceof TokenList)) throw Error("must be called with TokenList.call"); - validateString(token); - token = token.trim(); - if (this.contains(token)) { - this.remove(token); - return this; - } - this.add(token); - return this; + if (!(this instanceof TokenList)) + throw Error("must be called with TokenList.call"); + validateString(token); + token = token.trim(); + if (this.contains(token)) { + this.remove(token); + return this; + } + this.add(token); + return this; } diff --git a/source/types/typeof.mjs b/source/types/typeof.mjs index 0ec8d1c94..651c17c6e 100644 --- a/source/types/typeof.mjs +++ b/source/types/typeof.mjs @@ -20,16 +20,18 @@ export { typeOf }; * @throws {TypeError} value is not a primitive */ function typeOf(value) { - let type = {}.toString.call(value).match(/\s([a-zA-Z]+)/)[1]; - if ("Object" === type) { - const name = value.constructor.name; - if (name) { - return name.toLowerCase(); - } + let type = {}.toString.call(value).match(/\s([a-zA-Z]+)/)[1]; + if ("Object" === type) { + const name = value.constructor.name; + if (name) { + return name.toLowerCase(); + } - const results = /^(class|function)\s+(\w+)/.exec(value.constructor.toString()); - type = results && results.length > 2 ? results[2] : ""; - } + const results = /^(class|function)\s+(\w+)/.exec( + value.constructor.toString(), + ); + type = results && results.length > 2 ? results[2] : ""; + } - return type.toLowerCase(); + return type.toLowerCase(); } diff --git a/source/types/uniquequeue.mjs b/source/types/uniquequeue.mjs index ccad0b974..856920c2b 100644 --- a/source/types/uniquequeue.mjs +++ b/source/types/uniquequeue.mjs @@ -21,57 +21,57 @@ export { UniqueQueue }; * @summary A queue for unique values */ class UniqueQueue extends Queue { - /** - * - */ - constructor() { - super(); - this[internalSymbol] = { - unique: new WeakSet(), - }; - } + /** + * + */ + constructor() { + super(); + this[internalSymbol] = { + unique: new WeakSet(), + }; + } - /** - * Add a new element to the end of the queue. - * - * @param {object} value - * @returns {Queue} - * @throws {TypeError} value is not a object - */ - add(value) { - validateObject(value); + /** + * Add a new element to the end of the queue. + * + * @param {object} value + * @returns {Queue} + * @throws {TypeError} value is not a object + */ + add(value) { + validateObject(value); - if (!this[internalSymbol].unique.has(value)) { - this[internalSymbol].unique.add(value); - super.add(value); - } + if (!this[internalSymbol].unique.has(value)) { + this[internalSymbol].unique.add(value); + super.add(value); + } - return this; - } + return this; + } - /** - * remove all entries - * - * @returns {Queue} - */ - clear() { - super.clear(); - this[internalSymbol].unique = new WeakSet(); - return this; - } + /** + * remove all entries + * + * @returns {Queue} + */ + clear() { + super.clear(); + this[internalSymbol].unique = new WeakSet(); + return this; + } - /** - * Remove the element at the front of the queue - * If the queue is empty, return undefined. - * - * @return {object} - */ - poll() { - if (this.isEmpty()) { - return undefined; - } - let value = this.data.shift(); - this[internalSymbol].unique.delete(value); - return value; - } + /** + * Remove the element at the front of the queue + * If the queue is empty, return undefined. + * + * @return {object} + */ + poll() { + if (this.isEmpty()) { + return undefined; + } + let value = this.data.shift(); + this[internalSymbol].unique.delete(value); + return value; + } } diff --git a/source/types/uuid.mjs b/source/types/uuid.mjs index bc832eb78..e7994368b 100644 --- a/source/types/uuid.mjs +++ b/source/types/uuid.mjs @@ -23,34 +23,34 @@ export { UUID }; * @throws {Error} unsupported */ class UUID extends Base { - /** - * - */ - constructor() { - super(); + /** + * + */ + constructor() { + super(); - let uuid = createWithCrypto(); + let uuid = createWithCrypto(); - if (uuid === undefined) { - uuid = createWithRandom(); - } + if (uuid === undefined) { + uuid = createWithRandom(); + } - if (uuid === undefined) { - throw new Error("unsupported"); - } + if (uuid === undefined) { + throw new Error("unsupported"); + } - this[internalSymbol] = { - value: uuid, - }; - } + this[internalSymbol] = { + value: uuid, + }; + } - /** - * - * @return {string} - */ - toString() { - return this[internalSymbol]["value"]; - } + /** + * + * @return {string} + */ + toString() { + return this[internalSymbol]["value"]; + } } /** @@ -58,11 +58,11 @@ class UUID extends Base { * @return {string|undefined} */ function createWithRandom() { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { - const r = (random(0, 65000) * 16) | 0; - const v = c === "x" ? r : (r & 0x3) | 0x8; - return v.toString(16)[0]; - }); + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { + const r = (random(0, 65000) * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + return v.toString(16)[0]; + }); } /** @@ -70,8 +70,8 @@ function createWithRandom() { * @return {string|undefined} */ function createWithCrypto() { - const crypt = getGlobalObject("crypto"); - if (!isObject(crypt)) return; - if (typeof crypt?.["randomUUID"]) return; - return crypt.randomUUID(); + const crypt = getGlobalObject("crypto"); + if (!isObject(crypt)) return; + if (typeof crypt?.["randomUUID"]) return; + return crypt.randomUUID(); } diff --git a/source/types/validate.mjs b/source/types/validate.mjs index e97c4c6ff..f7aa7f5f2 100644 --- a/source/types/validate.mjs +++ b/source/types/validate.mjs @@ -6,29 +6,29 @@ */ import { - isArray, - isBoolean, - isFunction, - isInstance, - isInteger, - isIterable, - isObject, - isPrimitive, - isString, - isSymbol, + isArray, + isBoolean, + isFunction, + isInstance, + isInteger, + isIterable, + isObject, + isPrimitive, + isString, + isSymbol, } from "./is.mjs"; export { - validateIterable, - validatePrimitive, - validateBoolean, - validateString, - validateObject, - validateInstance, - validateArray, - validateSymbol, - validateFunction, - validateInteger, + validateIterable, + validatePrimitive, + validateBoolean, + validateString, + validateObject, + validateInstance, + validateArray, + validateSymbol, + validateFunction, + validateInteger, }; /** @@ -54,10 +54,10 @@ export { * @see {@link Monster.Types#isPrimitive} */ function validateIterable(value) { - if (!isIterable(value)) { - throw new TypeError("value is not iterable"); - } - return value; + if (!isIterable(value)) { + throw new TypeError("value is not iterable"); + } + return value; } /** @@ -83,10 +83,10 @@ function validateIterable(value) { * @see {@link Monster.Types#isPrimitive} */ function validatePrimitive(value) { - if (!isPrimitive(value)) { - throw new TypeError("value is not a primitive"); - } - return value; + if (!isPrimitive(value)) { + throw new TypeError("value is not a primitive"); + } + return value; } /** @@ -111,10 +111,10 @@ function validatePrimitive(value) { * @throws {TypeError} value is not primitive */ function validateBoolean(value) { - if (!isBoolean(value)) { - throw new TypeError("value is not a boolean"); - } - return value; + if (!isBoolean(value)) { + throw new TypeError("value is not a boolean"); + } + return value; } /** @@ -137,10 +137,10 @@ function validateBoolean(value) { * @throws {TypeError} value is not a string */ function validateString(value) { - if (!isString(value)) { - throw new TypeError("value is not a string"); - } - return value; + if (!isString(value)) { + throw new TypeError("value is not a string"); + } + return value; } /** @@ -164,10 +164,10 @@ function validateString(value) { * @throws {TypeError} value is not a object */ function validateObject(value) { - if (!isObject(value)) { - throw new TypeError("value is not a object"); - } - return value; + if (!isObject(value)) { + throw new TypeError("value is not a object"); + } + return value; } /** @@ -191,19 +191,19 @@ function validateObject(value) { * @throws {TypeError} value is not an instance of */ function validateInstance(value, instance) { - if (!isInstance(value, instance)) { - let n = ""; - if (isObject(instance) || isFunction(instance)) { - n = instance?.["name"]; - } + if (!isInstance(value, instance)) { + let n = ""; + if (isObject(instance) || isFunction(instance)) { + n = instance?.["name"]; + } - if (n) { - n = ` ${n}`; - } + if (n) { + n = ` ${n}`; + } - throw new TypeError(`value is not an instance of${n}`); - } - return value; + throw new TypeError(`value is not an instance of${n}`); + } + return value; } /** @@ -226,10 +226,10 @@ function validateInstance(value, instance) { * @throws {TypeError} value is not an array */ function validateArray(value) { - if (!isArray(value)) { - throw new TypeError("value is not an array"); - } - return value; + if (!isArray(value)) { + throw new TypeError("value is not an array"); + } + return value; } /** @@ -252,10 +252,10 @@ function validateArray(value) { * @throws {TypeError} value is not an symbol */ function validateSymbol(value) { - if (!isSymbol(value)) { - throw new TypeError("value is not an symbol"); - } - return value; + if (!isSymbol(value)) { + throw new TypeError("value is not an symbol"); + } + return value; } /** @@ -279,10 +279,10 @@ function validateSymbol(value) { * @throws {TypeError} value is not a function */ function validateFunction(value) { - if (!isFunction(value)) { - throw new TypeError("value is not a function"); - } - return value; + if (!isFunction(value)) { + throw new TypeError("value is not a function"); + } + return value; } /** @@ -306,8 +306,8 @@ function validateFunction(value) { * @throws {TypeError} value is not an integer */ function validateInteger(value) { - if (!isInteger(value)) { - throw new TypeError("value is not an integer"); - } - return value; + if (!isInteger(value)) { + throw new TypeError("value is not an integer"); + } + return value; } diff --git a/source/types/version.mjs b/source/types/version.mjs index 16d054108..6d12667df 100644 --- a/source/types/version.mjs +++ b/source/types/version.mjs @@ -22,105 +22,115 @@ export { Version, getMonsterVersion }; * @summary The version object contains a sematic version number */ class Version extends Base { - /** - * - * @param major - * @param minor - * @param patch - * @throws {Error} major is not a number - * @throws {Error} minor is not a number - * @throws {Error} patch is not a number - */ - constructor(major, minor, patch) { - super(); - - if (typeof major === "string" && minor === undefined && patch === undefined) { - let parts = major.toString().split("."); - major = parseInt(parts[0] || 0); - minor = parseInt(parts[1] || 0); - patch = parseInt(parts[2] || 0); - } - - if (major === undefined) { - throw new Error("major version is undefined"); - } - - if (minor === undefined) { - minor = 0; - } - - if (patch === undefined) { - patch = 0; - } - - this.major = parseInt(major); - this.minor = parseInt(minor); - this.patch = parseInt(patch); - - if (isNaN(this.major)) { - throw new Error("major is not a number"); - } - - if (isNaN(this.minor)) { - throw new Error("minor is not a number"); - } - - if (isNaN(this.patch)) { - throw new Error("patch is not a number"); - } - } - - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/types/version"); - } - - /** - * - * @returns {string} - */ - toString() { - return `${this.major}.${this.minor}.${this.patch}`; - } - - /** - * returns 0 if equal, -1 if the object version is less and 1 if greater - * than the compared version - * - * @param {string|Version} version Version to compare - * @returns {number} - */ - compareTo(version) { - if (version instanceof Version) { - version = version.toString(); - } - - if (typeof version !== "string") { - throw new Error("type exception"); - } - - if (version === this.toString()) { - return 0; - } - - let a = [this.major, this.minor, this.patch]; - let b = version.split("."); - let len = Math.max(a.length, b.length); - - for (let i = 0; i < len; i += 1) { - if ((a[i] && !b[i] && parseInt(a[i]) > 0) || parseInt(a[i]) > parseInt(b[i])) { - return 1; - } else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || parseInt(a[i]) < parseInt(b[i])) { - return -1; - } - } - - return 0; - } + /** + * + * @param major + * @param minor + * @param patch + * @throws {Error} major is not a number + * @throws {Error} minor is not a number + * @throws {Error} patch is not a number + */ + constructor(major, minor, patch) { + super(); + + if ( + typeof major === "string" && + minor === undefined && + patch === undefined + ) { + let parts = major.toString().split("."); + major = parseInt(parts[0] || 0); + minor = parseInt(parts[1] || 0); + patch = parseInt(parts[2] || 0); + } + + if (major === undefined) { + throw new Error("major version is undefined"); + } + + if (minor === undefined) { + minor = 0; + } + + if (patch === undefined) { + patch = 0; + } + + this.major = parseInt(major); + this.minor = parseInt(minor); + this.patch = parseInt(patch); + + if (isNaN(this.major)) { + throw new Error("major is not a number"); + } + + if (isNaN(this.minor)) { + throw new Error("minor is not a number"); + } + + if (isNaN(this.patch)) { + throw new Error("patch is not a number"); + } + } + + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/types/version"); + } + + /** + * + * @returns {string} + */ + toString() { + return `${this.major}.${this.minor}.${this.patch}`; + } + + /** + * returns 0 if equal, -1 if the object version is less and 1 if greater + * than the compared version + * + * @param {string|Version} version Version to compare + * @returns {number} + */ + compareTo(version) { + if (version instanceof Version) { + version = version.toString(); + } + + if (typeof version !== "string") { + throw new Error("type exception"); + } + + if (version === this.toString()) { + return 0; + } + + let a = [this.major, this.minor, this.patch]; + let b = version.split("."); + let len = Math.max(a.length, b.length); + + for (let i = 0; i < len; i += 1) { + if ( + (a[i] && !b[i] && parseInt(a[i]) > 0) || + parseInt(a[i]) > parseInt(b[i]) + ) { + return 1; + } else if ( + (b[i] && !a[i] && parseInt(b[i]) > 0) || + parseInt(a[i]) < parseInt(b[i]) + ) { + return -1; + } + } + + return 0; + } } let monsterVersion; @@ -137,12 +147,12 @@ let monsterVersion; * @memberOf Monster */ function getMonsterVersion() { - if (monsterVersion instanceof Version) { - return monsterVersion; - } + if (monsterVersion instanceof Version) { + return monsterVersion; + } - /** don't touch, replaced by make with package.json version */ - monsterVersion = new Version("3.51.5"); + /** don't touch, replaced by make with package.json version */ + monsterVersion = new Version("3.51.5"); - return monsterVersion; + return monsterVersion; } diff --git a/source/util/clone.mjs b/source/util/clone.mjs index 5df58a329..3482a1dfb 100644 --- a/source/util/clone.mjs +++ b/source/util/clone.mjs @@ -30,64 +30,70 @@ export { clone }; * @throws {Error} unable to clone obj! its type isn't supported. */ function clone(obj) { - // typeof null results in 'object'. https://2ality.com/2013/10/typeof-null.html - if (null === obj) { - return obj; - } - - // Handle the two simple types, null and undefined - if (isPrimitive(obj)) { - return obj; - } - - // Handle the two simple types, null and undefined - if (isFunction(obj)) { - return obj; - } - - // Handle Array - if (isArray(obj)) { - let copy = []; - for (var i = 0, len = obj.length; i < len; i++) { - copy[i] = clone(obj[i]); - } - - return copy; - } - - if (isObject(obj)) { - // Handle Date - if (obj instanceof Date) { - let copy = new Date(); - copy.setTime(obj.getTime()); - return copy; - } - - /** Do not clone DOM nodes */ - if (typeof Element !== "undefined" && obj instanceof Element) return obj; - if (typeof HTMLDocument !== "undefined" && obj instanceof HTMLDocument) return obj; - if (typeof DocumentFragment !== "undefined" && obj instanceof DocumentFragment) return obj; - - /** Do not clone global objects */ - if (obj === getGlobal()) return obj; - if (typeof globalContext !== "undefined" && obj === globalContext) return obj; - if (typeof window !== "undefined" && obj === window) return obj; - if (typeof document !== "undefined" && obj === document) return obj; - if (typeof navigator !== "undefined" && obj === navigator) return obj; - if (typeof JSON !== "undefined" && obj === JSON) return obj; - - // Handle Proxy-Object - try { - // try/catch because possible: TypeError: Function has non-object prototype 'undefined' in instanceof check - if (obj instanceof Proxy) { - return obj; - } - } catch (e) {} - - return cloneObject(obj); - } - - throw new Error("unable to clone obj! its type isn't supported."); + // typeof null results in 'object'. https://2ality.com/2013/10/typeof-null.html + if (null === obj) { + return obj; + } + + // Handle the two simple types, null and undefined + if (isPrimitive(obj)) { + return obj; + } + + // Handle the two simple types, null and undefined + if (isFunction(obj)) { + return obj; + } + + // Handle Array + if (isArray(obj)) { + let copy = []; + for (var i = 0, len = obj.length; i < len; i++) { + copy[i] = clone(obj[i]); + } + + return copy; + } + + if (isObject(obj)) { + // Handle Date + if (obj instanceof Date) { + let copy = new Date(); + copy.setTime(obj.getTime()); + return copy; + } + + /** Do not clone DOM nodes */ + if (typeof Element !== "undefined" && obj instanceof Element) return obj; + if (typeof HTMLDocument !== "undefined" && obj instanceof HTMLDocument) + return obj; + if ( + typeof DocumentFragment !== "undefined" && + obj instanceof DocumentFragment + ) + return obj; + + /** Do not clone global objects */ + if (obj === getGlobal()) return obj; + if (typeof globalContext !== "undefined" && obj === globalContext) + return obj; + if (typeof window !== "undefined" && obj === window) return obj; + if (typeof document !== "undefined" && obj === document) return obj; + if (typeof navigator !== "undefined" && obj === navigator) return obj; + if (typeof JSON !== "undefined" && obj === JSON) return obj; + + // Handle Proxy-Object + try { + // try/catch because possible: TypeError: Function has non-object prototype 'undefined' in instanceof check + if (obj instanceof Proxy) { + return obj; + } + } catch (e) {} + + return cloneObject(obj); + } + + throw new Error("unable to clone obj! its type isn't supported."); } /** @@ -97,37 +103,43 @@ function clone(obj) { * @private */ function cloneObject(obj) { - validateObject(obj); - - const fkt = obj?.["constructor"]; - - /** Object has clone method */ - if (typeOf(fkt) === "function") { - const prototype = fkt?.prototype; - if (typeof prototype === "object") { - if (prototype.hasOwnProperty("getClone") && typeOf(obj.getClone) === "function") { - return obj.getClone(); - } - } - } - - let copy = {}; - if (typeof obj.constructor === "function" && typeof obj.constructor.call === "function") { - copy = new obj.constructor(); - } - - for (let key in obj) { - if (!obj.hasOwnProperty(key)) { - continue; - } - - if (isPrimitive(obj[key])) { - copy[key] = obj[key]; - continue; - } - - copy[key] = clone(obj[key]); - } - - return copy; + validateObject(obj); + + const fkt = obj?.["constructor"]; + + /** Object has clone method */ + if (typeOf(fkt) === "function") { + const prototype = fkt?.prototype; + if (typeof prototype === "object") { + if ( + prototype.hasOwnProperty("getClone") && + typeOf(obj.getClone) === "function" + ) { + return obj.getClone(); + } + } + } + + let copy = {}; + if ( + typeof obj.constructor === "function" && + typeof obj.constructor.call === "function" + ) { + copy = new obj.constructor(); + } + + for (let key in obj) { + if (!obj.hasOwnProperty(key)) { + continue; + } + + if (isPrimitive(obj[key])) { + copy[key] = obj[key]; + continue; + } + + copy[key] = clone(obj[key]); + } + + return copy; } diff --git a/source/util/comparator.mjs b/source/util/comparator.mjs index b681f7f1e..89342da7b 100644 --- a/source/util/comparator.mjs +++ b/source/util/comparator.mjs @@ -37,112 +37,112 @@ export { Comparator }; * @memberOf Monster.Util */ class Comparator extends Base { - /** - * create new comparator - * - * @param {Monster.Util~exampleCallback} [callback] Comparator callback - * @throw {TypeError} unsupported type - * @throw {TypeError} impractical comparison - */ - constructor(callback) { - super(); + /** + * create new comparator + * + * @param {Monster.Util~exampleCallback} [callback] Comparator callback + * @throw {TypeError} unsupported type + * @throw {TypeError} impractical comparison + */ + constructor(callback) { + super(); - if (isFunction(callback)) { - this.compare = callback; - } else if (callback !== undefined) { - throw new TypeError("unsupported type"); - } else { - // default compare function + if (isFunction(callback)) { + this.compare = callback; + } else if (callback !== undefined) { + throw new TypeError("unsupported type"); + } else { + // default compare function - /** - * - * @param {*} a - * @param {*} b - * @return {integer} -1, 0 or 1 - */ - this.compare = function (a, b) { - if (typeof a !== typeof b) { - throw new TypeError("impractical comparison", "types/comparator.mjs"); - } + /** + * + * @param {*} a + * @param {*} b + * @return {integer} -1, 0 or 1 + */ + this.compare = function (a, b) { + if (typeof a !== typeof b) { + throw new TypeError("impractical comparison", "types/comparator.mjs"); + } - if (a === b) { - return 0; - } - return a < b ? -1 : 1; - }; - } - } + if (a === b) { + return 0; + } + return a < b ? -1 : 1; + }; + } + } - /** - * changes the order of the operators - * - * @return {Comparator} - */ - reverse() { - const original = this.compare; - this.compare = (a, b) => original(b, a); - return this; - } + /** + * changes the order of the operators + * + * @return {Comparator} + */ + reverse() { + const original = this.compare; + this.compare = (a, b) => original(b, a); + return this; + } - /** - * Checks if two variables are equal. - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - equal(a, b) { - return this.compare(a, b) === 0; - } + /** + * Checks if two variables are equal. + * + * @param {*} a + * @param {*} b + * + * @return {boolean} + */ + equal(a, b) { + return this.compare(a, b) === 0; + } - /** - * Checks if variable `a` is greater than `b` - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - greaterThan(a, b) { - return this.compare(a, b) > 0; - } + /** + * Checks if variable `a` is greater than `b` + * + * @param {*} a + * @param {*} b + * + * @return {boolean} + */ + greaterThan(a, b) { + return this.compare(a, b) > 0; + } - /** - * Checks if variable `a` is greater than or equal to `b` - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - greaterThanOrEqual(a, b) { - return this.greaterThan(a, b) || this.equal(a, b); - } + /** + * Checks if variable `a` is greater than or equal to `b` + * + * @param {*} a + * @param {*} b + * + * @return {boolean} + */ + greaterThanOrEqual(a, b) { + return this.greaterThan(a, b) || this.equal(a, b); + } - /** - * Checks if variable `a` is less than or equal to `b` - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - lessThanOrEqual(a, b) { - return this.lessThan(a, b) || this.equal(a, b); - } + /** + * Checks if variable `a` is less than or equal to `b` + * + * @param {*} a + * @param {*} b + * + * @return {boolean} + */ + lessThanOrEqual(a, b) { + return this.lessThan(a, b) || this.equal(a, b); + } - /** - * Checks if variable a is less than b - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - lessThan(a, b) { - return this.compare(a, b) < 0; - } + /** + * Checks if variable a is less than b + * + * @param {*} a + * @param {*} b + * + * @return {boolean} + */ + lessThan(a, b) { + return this.compare(a, b) < 0; + } } /** diff --git a/source/util/deadmansswitch.mjs b/source/util/deadmansswitch.mjs index 32fe83afb..04b028094 100644 --- a/source/util/deadmansswitch.mjs +++ b/source/util/deadmansswitch.mjs @@ -24,53 +24,53 @@ export { DeadMansSwitch }; * @summary Class to be able to execute function chains */ class DeadMansSwitch extends Base { - /** - * Create new dead man's switch - * - * @param {Integer} delay - * @param {function} callback - * @throw {TypeError} the arguments must be either integer or functions - * @throws {TypeError} value is not an integer - */ - constructor(delay, callback) { - super(); + /** + * Create new dead man's switch + * + * @param {Integer} delay + * @param {function} callback + * @throw {TypeError} the arguments must be either integer or functions + * @throws {TypeError} value is not an integer + */ + constructor(delay, callback) { + super(); - init.call(this, validateInteger(delay), validateFunction(callback)); - } + init.call(this, validateInteger(delay), validateFunction(callback)); + } - /** - * - * @param {Integer|undefined} [delay] - */ - touch(delay) { - if (this[internalSymbol]["isAlreadyRun"] === true) { - throw new Error("has already run"); - } + /** + * + * @param {Integer|undefined} [delay] + */ + touch(delay) { + if (this[internalSymbol]["isAlreadyRun"] === true) { + throw new Error("has already run"); + } - if (isInteger(delay)) { - this[internalSymbol]["delay"] = delay; - } else if (delay !== undefined) { - throw new Error("unsupported argument"); - } + if (isInteger(delay)) { + this[internalSymbol]["delay"] = delay; + } else if (delay !== undefined) { + throw new Error("unsupported argument"); + } - clearTimeout(this[internalSymbol]["timer"]); + clearTimeout(this[internalSymbol]["timer"]); - initCallback.call(this); + initCallback.call(this); - return this; - } + return this; + } } /** * @private */ function initCallback() { - const self = this; + const self = this; - self[internalSymbol]["timer"] = setTimeout(() => { - self[internalSymbol]["isAlreadyRun"] = true; - self[internalSymbol]["callback"](); - }, self[internalSymbol]["delay"]); + self[internalSymbol]["timer"] = setTimeout(() => { + self[internalSymbol]["isAlreadyRun"] = true; + self[internalSymbol]["callback"](); + }, self[internalSymbol]["delay"]); } /** @@ -79,14 +79,14 @@ function initCallback() { * @param {function} callback */ function init(delay, callback) { - const self = this; + const self = this; - self[internalSymbol] = { - callback, - delay, - isAlreadyRun: false, - timer: undefined, - }; + self[internalSymbol] = { + callback, + delay, + isAlreadyRun: false, + timer: undefined, + }; - initCallback.call(self); + initCallback.call(self); } diff --git a/source/util/freeze.mjs b/source/util/freeze.mjs index b17e54848..fbb20e5f8 100644 --- a/source/util/freeze.mjs +++ b/source/util/freeze.mjs @@ -21,17 +21,18 @@ export { deepFreeze }; * @throws {TypeError} value is not a object */ function deepFreeze(object) { - validateObject(object); + validateObject(object); - // Retrieve the defined property names of the object - var propNames = Object.getOwnPropertyNames(object); + // Retrieve the defined property names of the object + var propNames = Object.getOwnPropertyNames(object); - // Freeze properties before freezing yourself - for (let name of propNames) { - let value = object[name]; + // Freeze properties before freezing yourself + for (let name of propNames) { + let value = object[name]; - object[name] = value && typeof value === "object" ? deepFreeze(value) : value; - } + object[name] = + value && typeof value === "object" ? deepFreeze(value) : value; + } - return Object.freeze(object); + return Object.freeze(object); } diff --git a/source/util/processing.mjs b/source/util/processing.mjs index 2f9ece33b..b0b1ef1f6 100644 --- a/source/util/processing.mjs +++ b/source/util/processing.mjs @@ -5,51 +5,51 @@ * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html */ -import {internalSymbol} from "../constants.mjs"; -import {Base} from "../types/base.mjs"; -import {getGlobalFunction} from "../types/global.mjs"; -import {isFunction, isInteger} from "../types/is.mjs"; -import {Queue} from "../types/queue.mjs"; -import {validateFunction, validateInteger} from "../types/validate.mjs"; +import { internalSymbol } from "../constants.mjs"; +import { Base } from "../types/base.mjs"; +import { getGlobalFunction } from "../types/global.mjs"; +import { isFunction, isInteger } from "../types/is.mjs"; +import { Queue } from "../types/queue.mjs"; +import { validateFunction, validateInteger } from "../types/validate.mjs"; -export {Processing}; +export { Processing }; /** * @private */ class Callback { - /** - * - * @param {function} callback - * @param {int|undefined} time - * @throws {TypeError} value is not a function - * @throws {TypeError} value is not an integer - * @private - */ - constructor(callback, time) { - this[internalSymbol] = { - callback: validateFunction(callback), - time: validateInteger(time ?? 0), - }; - } + /** + * + * @param {function} callback + * @param {int|undefined} time + * @throws {TypeError} value is not a function + * @throws {TypeError} value is not an integer + * @private + */ + constructor(callback, time) { + this[internalSymbol] = { + callback: validateFunction(callback), + time: validateInteger(time ?? 0), + }; + } - /** - * @private - * @param {*} data - * @return {Promise} - */ - run(data) { - const self = this; - return new Promise((resolve, reject) => { - getGlobalFunction("setTimeout")(() => { - try { - resolve(self[internalSymbol].callback(data)); - } catch (e) { - reject(e); - } - }, self[internalSymbol].time); - }); - } + /** + * @private + * @param {*} data + * @return {Promise} + */ + run(data) { + const self = this; + return new Promise((resolve, reject) => { + getGlobalFunction("setTimeout")(() => { + try { + resolve(self[internalSymbol].callback(data)); + } catch (e) { + reject(e); + } + }, self[internalSymbol].time); + }); + } } /** @@ -74,80 +74,81 @@ class Callback { * @summary Class to be able to execute function chains */ class Processing extends Base { - /** - * Create new Processing - * - * Functions and timeouts can be passed. If a timeout is passed, it applies to all further functions. - * In the example - * - * `timeout1, function1, function2, function3, timeout2, function4` - * - * the timeout1 is valid for the functions 1, 2 and 3 and the timeout2 for the function4. - * - * So the execution time is timeout1+timeout1+timeout1+timeout2 - * - * @throw {TypeError} the arguments must be either integer or functions - * @param {...(int|function)} args - */ - constructor(...args) { - super(); + /** + * Create new Processing + * + * Functions and timeouts can be passed. If a timeout is passed, it applies to all further functions. + * In the example + * + * `timeout1, function1, function2, function3, timeout2, function4` + * + * the timeout1 is valid for the functions 1, 2 and 3 and the timeout2 for the function4. + * + * So the execution time is timeout1+timeout1+timeout1+timeout2 + * + * @throw {TypeError} the arguments must be either integer or functions + * @param {...(int|function)} args + */ + constructor(...args) { + super(); - this[internalSymbol] = { - queue: new Queue(), - }; + this[internalSymbol] = { + queue: new Queue(), + }; - let time = 0; + let time = 0; - if (typeof args !== "object" || args[0] === null) { - throw new TypeError("the arguments must be either integer or functions"); - } + if (typeof args !== "object" || args[0] === null) { + throw new TypeError("the arguments must be either integer or functions"); + } - for (const [, arg] of Object.entries(args)) { - if (isInteger(arg) && arg >= 0) { - time = arg; - } else if (isFunction(arg)) { - this[internalSymbol].queue.add(new Callback(arg, time)); - } else { - throw new TypeError("the arguments must be either integer or functions"); - } - } - } + for (const [, arg] of Object.entries(args)) { + if (isInteger(arg) && arg >= 0) { + time = arg; + } else if (isFunction(arg)) { + this[internalSymbol].queue.add(new Callback(arg, time)); + } else { + throw new TypeError( + "the arguments must be either integer or functions", + ); + } + } + } - /** - * Adds a function with the desired timeout - * If no timeout is specified, the timeout of the previous function is used. - * - * @param {function} callback - * @param {int|undefined} time - * @throws {TypeError} value is not a function - * @throws {TypeError} value is not an integer - */ - add(callback, time) { - this[internalSymbol].queue.add(new Callback(callback, time)); - return this; - } + /** + * Adds a function with the desired timeout + * If no timeout is specified, the timeout of the previous function is used. + * + * @param {function} callback + * @param {int|undefined} time + * @throws {TypeError} value is not a function + * @throws {TypeError} value is not an integer + */ + add(callback, time) { + this[internalSymbol].queue.add(new Callback(callback, time)); + return this; + } - /** - * Executes the defined functions in order. - * - * @param {*} data - * @return {Promise} - */ - run(data) { - const self = this; - if (self[internalSymbol].queue.isEmpty()) { - return Promise.resolve(data); - } + /** + * Executes the defined functions in order. + * + * @param {*} data + * @return {Promise} + */ + run(data) { + const self = this; + if (self[internalSymbol].queue.isEmpty()) { + return Promise.resolve(data); + } - const callback = self[internalSymbol].queue.poll(); + const callback = self[internalSymbol].queue.poll(); - if (callback === null || callback === undefined) { - return Promise.resolve(data); - } + if (callback === null || callback === undefined) { + return Promise.resolve(data); + } - return callback.run(data) - .then((result) => { - return self.run(result); - }); - } + return callback.run(data).then((result) => { + return self.run(result); + }); + } } diff --git a/source/util/runtime.mjs b/source/util/runtime.mjs index 41803e3a1..1c0803e9e 100644 --- a/source/util/runtime.mjs +++ b/source/util/runtime.mjs @@ -64,56 +64,68 @@ const ENV_UNKNOWN = "unknown"; * @returns {string} The detected runtime environment. Possible values are: */ function detectRuntimeEnvironment() { - // AWS Lambda environment - if (typeof process !== "undefined" && process.env != null && process.env.AWS_LAMBDA_FUNCTION_NAME) { - return ENV_AWS_LAMBDA; - } + // AWS Lambda environment + if ( + typeof process !== "undefined" && + process.env != null && + process.env.AWS_LAMBDA_FUNCTION_NAME + ) { + return ENV_AWS_LAMBDA; + } - // Google Cloud Functions environment - if (typeof process !== "undefined" && process.env != null && process.env.FUNCTION_NAME) { - return ENV_GOOGLE_FUNCTIONS; - } + // Google Cloud Functions environment + if ( + typeof process !== "undefined" && + process.env != null && + process.env.FUNCTION_NAME + ) { + return ENV_GOOGLE_FUNCTIONS; + } - // Node.js environment - if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) { - // Electron environment - if (process.versions.electron != null) { - return ENV_ELECTRON; - } - return ENV_NODE; - } + // Node.js environment + if ( + typeof process !== "undefined" && + process.versions != null && + process.versions.node != null + ) { + // Electron environment + if (process.versions.electron != null) { + return ENV_ELECTRON; + } + return ENV_NODE; + } - // Browser environment - if ( - typeof window !== "undefined" && - typeof window.document !== "undefined" && - typeof navigator !== "undefined" && - typeof navigator.userAgent === "string" - ) { - // Web Worker environment - if (typeof self === "object" && typeof importScripts === "function") { - return ENV_WEB_WORKER; - } - return ENV_BROWSER; - } + // Browser environment + if ( + typeof window !== "undefined" && + typeof window.document !== "undefined" && + typeof navigator !== "undefined" && + typeof navigator.userAgent === "string" + ) { + // Web Worker environment + if (typeof self === "object" && typeof importScripts === "function") { + return ENV_WEB_WORKER; + } + return ENV_BROWSER; + } - // Deno environment - if (typeof Deno !== "undefined") { - return ENV_DENO; - } + // Deno environment + if (typeof Deno !== "undefined") { + return ENV_DENO; + } - // Unknown environment - return ENV_UNKNOWN; + // Unknown environment + return ENV_UNKNOWN; } export { - ENV_AWS_LAMBDA, - ENV_GOOGLE_FUNCTIONS, - ENV_ELECTRON, - ENV_NODE, - ENV_BROWSER, - ENV_WEB_WORKER, - ENV_DENO, - ENV_UNKNOWN, - detectRuntimeEnvironment, + ENV_AWS_LAMBDA, + ENV_GOOGLE_FUNCTIONS, + ENV_ELECTRON, + ENV_NODE, + ENV_BROWSER, + ENV_WEB_WORKER, + ENV_DENO, + ENV_UNKNOWN, + detectRuntimeEnvironment, }; diff --git a/source/util/trimspaces.mjs b/source/util/trimspaces.mjs index 9967daa47..9bfadc017 100644 --- a/source/util/trimspaces.mjs +++ b/source/util/trimspaces.mjs @@ -37,35 +37,35 @@ export { trimSpaces }; * @throws {TypeError} value is not a string */ function trimSpaces(value) { - validateString(value); + validateString(value); - let placeholder = new Map(); - const regex = /((?<pattern>\\(?<char>.)){1})/gim; + let placeholder = new Map(); + const regex = /((?<pattern>\\(?<char>.)){1})/gim; - // The separator for args must be escaped - // undefined string which should not occur normally and is also not a regex - let result = value.matchAll(regex); + // The separator for args must be escaped + // undefined string which should not occur normally and is also not a regex + let result = value.matchAll(regex); - for (let m of result) { - let g = m?.["groups"]; - if (!isObject(g)) { - continue; - } + for (let m of result) { + let g = m?.["groups"]; + if (!isObject(g)) { + continue; + } - let p = g?.["pattern"]; - let c = g?.["char"]; + let p = g?.["pattern"]; + let c = g?.["char"]; - if (p && c) { - let r = `__${new ID().toString()}__`; - placeholder.set(r, c); - value = value.replace(p, r); - } - } + if (p && c) { + let r = `__${new ID().toString()}__`; + placeholder.set(r, c); + value = value.replace(p, r); + } + } - value = value.trim(); - placeholder.forEach((v, k) => { - value = value.replace(k, `\\${v}`); - }); + value = value.trim(); + placeholder.forEach((v, k) => { + value = value.replace(k, `\\${v}`); + }); - return value; + return value; } -- GitLab